rulesync 36.0.2 → 37.0.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-BPSwSahy.js} +329 -111
- package/dist/{import-Tya9qZRK.js.map → import-BPSwSahy.js.map} +1 -1
- package/dist/{import-DVcemXlR.cjs → import-S70XttcD.cjs} +345 -127
- 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
|
|
@@ -12857,6 +12857,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12857
12857
|
kind: "replace-owned-keys",
|
|
12858
12858
|
ownedKeys: ["disableAllHooks"]
|
|
12859
12859
|
},
|
|
12860
|
+
mcp: {
|
|
12861
|
+
kind: "replace-owned-keys",
|
|
12862
|
+
ownedKeys: ["disabledMcpServers"]
|
|
12863
|
+
},
|
|
12860
12864
|
permissions: {
|
|
12861
12865
|
kind: "replace-owned-keys",
|
|
12862
12866
|
ownedKeys: ["deniedUrls"]
|
|
@@ -12871,6 +12875,10 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12871
12875
|
kind: "replace-owned-keys",
|
|
12872
12876
|
ownedKeys: ["disableAllHooks"]
|
|
12873
12877
|
},
|
|
12878
|
+
mcp: {
|
|
12879
|
+
kind: "replace-owned-keys",
|
|
12880
|
+
ownedKeys: ["disabledMcpServers"]
|
|
12881
|
+
},
|
|
12874
12882
|
permissions: {
|
|
12875
12883
|
kind: "replace-owned-keys",
|
|
12876
12884
|
ownedKeys: ["allowedUrls", "deniedUrls"]
|
|
@@ -22135,7 +22143,7 @@ function getOpencodeCommandEntries({ config, layout = OPENCODE_LAYOUT }) {
|
|
|
22135
22143
|
*/
|
|
22136
22144
|
function getOpencodeSkillPaths({ config, layout = OPENCODE_LAYOUT }) {
|
|
22137
22145
|
if (Array.isArray(config.skills)) {
|
|
22138
|
-
if (!layout.readsV2ConfigSpellings || !isStringArray$
|
|
22146
|
+
if (!layout.readsV2ConfigSpellings || !isStringArray$1(config.skills)) return [];
|
|
22139
22147
|
return config.skills.filter((value) => !/^https?:\/\//i.test(value));
|
|
22140
22148
|
}
|
|
22141
22149
|
const skills = asOpencodeEntries(config.skills);
|
|
@@ -26381,7 +26389,7 @@ const isBooleanValue = ({ value }) => typeof value === "boolean";
|
|
|
26381
26389
|
const isFiniteNumber = ({ value }) => Number.isFinite(value);
|
|
26382
26390
|
const isNonEmptyString = (value) => typeof value === "string" && value !== "";
|
|
26383
26391
|
const isEmittableString = ({ value }) => isNonEmptyString(value);
|
|
26384
|
-
const isEmittableArray = ({ value }) => isStringArray
|
|
26392
|
+
const isEmittableArray = ({ value }) => isStringArray(value);
|
|
26385
26393
|
const isEmittableRecord = ({ value }) => isSafeStringRecord(value);
|
|
26386
26394
|
const isImportableArray = ({ value }) => isSafeStringArray(value);
|
|
26387
26395
|
const isEmittableObject = ({ value }) => isPlainObject$1(value);
|
|
@@ -26823,7 +26831,7 @@ function isStringRecord(value) {
|
|
|
26823
26831
|
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
26824
26832
|
return Object.values(value).every((v) => typeof v === "string");
|
|
26825
26833
|
}
|
|
26826
|
-
function isStringArray
|
|
26834
|
+
function isStringArray(value) {
|
|
26827
26835
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
26828
26836
|
}
|
|
26829
26837
|
/** Compare object values without letting key order decide the answer. */
|
|
@@ -26868,7 +26876,7 @@ function isSafeEnvEntry(entry) {
|
|
|
26868
26876
|
* skipped wholesale when that read fails.
|
|
26869
26877
|
*/
|
|
26870
26878
|
function isSafeStringArray(value) {
|
|
26871
|
-
return isStringArray
|
|
26879
|
+
return isStringArray(value) && value.every((entry) => !CONTROL_CHARS.some((char) => entry.includes(char)));
|
|
26872
26880
|
}
|
|
26873
26881
|
/**
|
|
26874
26882
|
* A raw string kept only if the canonical field it would land in accepts it,
|
|
@@ -26910,7 +26918,7 @@ function importTypePayloadFields({ h, hookType, warn }) {
|
|
|
26910
26918
|
return {
|
|
26911
26919
|
...url !== void 0 && { url },
|
|
26912
26920
|
...isStringRecord(headers) && { headers },
|
|
26913
|
-
...isStringArray
|
|
26921
|
+
...isStringArray(h.allowedEnvVars) && { allowedEnvVars: h.allowedEnvVars }
|
|
26914
26922
|
};
|
|
26915
26923
|
}
|
|
26916
26924
|
if (hookType === "mcp_tool") {
|
|
@@ -28071,7 +28079,8 @@ function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned,
|
|
|
28071
28079
|
logger
|
|
28072
28080
|
})).map((handler) => cloneHandler(handler));
|
|
28073
28081
|
if (preserved.length === 0) continue;
|
|
28074
|
-
const
|
|
28082
|
+
const attributes = groupAttributesKey(group);
|
|
28083
|
+
const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher && groupAttributesKey(candidate) === attributes);
|
|
28075
28084
|
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
28076
28085
|
else merged.push({
|
|
28077
28086
|
...omitPrototypePollutionKeys(group),
|
|
@@ -28192,6 +28201,11 @@ function stableStringify$1(value) {
|
|
|
28192
28201
|
function matcherKey(matcher) {
|
|
28193
28202
|
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
28194
28203
|
}
|
|
28204
|
+
/** The stable form of a matcher group's keys other than `matcher` and `hooks`. */
|
|
28205
|
+
function groupAttributesKey(group) {
|
|
28206
|
+
const { matcher: _matcher, hooks: _hooks, ...attributes } = group;
|
|
28207
|
+
return stableStringify$1(attributes);
|
|
28208
|
+
}
|
|
28195
28209
|
function warnSkip({ logger, event, expected }) {
|
|
28196
28210
|
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
28197
28211
|
}
|
|
@@ -28687,7 +28701,7 @@ const MANAGED_EVENT_NAMES = new Set(Object.values(CANONICAL_TO_CLINE_EVENT_NAMES
|
|
|
28687
28701
|
function parseManifest(fileContent) {
|
|
28688
28702
|
try {
|
|
28689
28703
|
const parsed = JSON.parse(fileContent);
|
|
28690
|
-
if (!isRecord$1(parsed) || !isStringArray$
|
|
28704
|
+
if (!isRecord$1(parsed) || !isStringArray$1(parsed.events)) return null;
|
|
28691
28705
|
return {
|
|
28692
28706
|
generatedBy: "rulesync",
|
|
28693
28707
|
events: parsed.events.filter((event) => MANAGED_EVENT_NAMES.has(event))
|
|
@@ -30225,7 +30239,7 @@ function copilotCliHooksToCanonical(rawHooks, logger) {
|
|
|
30225
30239
|
* see the CLI config dir reference:
|
|
30226
30240
|
* https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
30227
30241
|
*/
|
|
30228
|
-
function copilotCliSettingsPaths({ global }) {
|
|
30242
|
+
function copilotCliSettingsPaths$1({ global }) {
|
|
30229
30243
|
return global ? {
|
|
30230
30244
|
relativeDirPath: COPILOT_DIR,
|
|
30231
30245
|
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
@@ -30240,7 +30254,7 @@ function copilotCliSettingsPaths({ global }) {
|
|
|
30240
30254
|
* subject, and the permissions feature reports a broken settings file itself.
|
|
30241
30255
|
*/
|
|
30242
30256
|
async function readDisableAllHooks({ outputRoot, global }) {
|
|
30243
|
-
const paths = copilotCliSettingsPaths({ global });
|
|
30257
|
+
const paths = copilotCliSettingsPaths$1({ global });
|
|
30244
30258
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath));
|
|
30245
30259
|
if (fileContent === null) return;
|
|
30246
30260
|
try {
|
|
@@ -30270,7 +30284,7 @@ var CopilotcliHooksSettings = class CopilotcliHooksSettings extends ToolFile {
|
|
|
30270
30284
|
return false;
|
|
30271
30285
|
}
|
|
30272
30286
|
static async fromDisableAllHooks({ outputRoot = process.cwd(), global = false, disableAllHooks }) {
|
|
30273
|
-
const paths = copilotCliSettingsPaths({ global });
|
|
30287
|
+
const paths = copilotCliSettingsPaths$1({ global });
|
|
30274
30288
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
30275
30289
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
30276
30290
|
return new CopilotcliHooksSettings({
|
|
@@ -30300,7 +30314,7 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
30300
30314
|
this.disableAllHooks = disableAllHooks;
|
|
30301
30315
|
}
|
|
30302
30316
|
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
30303
|
-
return [copilotCliSettingsPaths({ global })];
|
|
30317
|
+
return [copilotCliSettingsPaths$1({ global })];
|
|
30304
30318
|
}
|
|
30305
30319
|
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
30306
30320
|
return toolHooks instanceof CopilotcliHooks && toolHooks.settingsFile ? [toolHooks.settingsFile] : [];
|
|
@@ -34744,12 +34758,21 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
34744
34758
|
validate
|
|
34745
34759
|
});
|
|
34746
34760
|
}
|
|
34747
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
34761
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
34748
34762
|
const paths = QwencodeHooks.getSettablePaths({ global });
|
|
34749
34763
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
34750
34764
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
34751
34765
|
const config = rulesyncHooks.getJson();
|
|
34752
|
-
const
|
|
34766
|
+
const previouslyOwned = preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0;
|
|
34767
|
+
const merged = mergeGeneratedHookLists({
|
|
34768
|
+
existingContent,
|
|
34769
|
+
generatedHooks: canonicalToQwencodeHooks(config, logger),
|
|
34770
|
+
shape: "matcher-groups",
|
|
34771
|
+
preserveUnowned,
|
|
34772
|
+
previouslyOwned,
|
|
34773
|
+
logger
|
|
34774
|
+
});
|
|
34775
|
+
const patch = { hooks: merged.hooks };
|
|
34753
34776
|
const disableAllHooks = config.qwencode?.disableAllHooks;
|
|
34754
34777
|
if (typeof disableAllHooks === "boolean") patch.disableAllHooks = disableAllHooks;
|
|
34755
34778
|
const fileContent = applySharedConfigPatch({
|
|
@@ -34764,9 +34787,16 @@ var QwencodeHooks = class QwencodeHooks extends ToolHooks {
|
|
|
34764
34787
|
relativeDirPath: paths.relativeDirPath,
|
|
34765
34788
|
relativeFilePath: paths.relativeFilePath,
|
|
34766
34789
|
fileContent,
|
|
34790
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
34767
34791
|
validate
|
|
34768
34792
|
});
|
|
34769
34793
|
}
|
|
34794
|
+
static supportsPreserveUnowned() {
|
|
34795
|
+
return true;
|
|
34796
|
+
}
|
|
34797
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
34798
|
+
return toolHooks instanceof QwencodeHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
34799
|
+
}
|
|
34770
34800
|
toRulesyncHooks() {
|
|
34771
34801
|
let settings;
|
|
34772
34802
|
try {
|
|
@@ -41166,7 +41196,7 @@ function isCodexStdioServer(config) {
|
|
|
41166
41196
|
* @see https://learn.chatgpt.com/docs/extend/mcp
|
|
41167
41197
|
*/
|
|
41168
41198
|
function isValidRenamedArray(key, value) {
|
|
41169
|
-
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$
|
|
41199
|
+
return key === "env_vars" || key === "envVars" ? isEnvVarEntryArray(value) : isStringArray$1(value);
|
|
41170
41200
|
}
|
|
41171
41201
|
/**
|
|
41172
41202
|
* The keys of Codex's `McpServerOAuthConfig`. Anything else in a server's
|
|
@@ -42385,18 +42415,6 @@ function renameEnabledToolsToTools({ server, serverName, logger }) {
|
|
|
42385
42415
|
};
|
|
42386
42416
|
}
|
|
42387
42417
|
/**
|
|
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
42418
|
* Resolves and sets the transport type for each MCP server config.
|
|
42401
42419
|
* GitHub Copilot CLI requires the "type" field for each server. A server it
|
|
42402
42420
|
* cannot express — one that names no transport, or names one it carries no way
|
|
@@ -42438,7 +42456,7 @@ function addTypeField(mcpServers, logger) {
|
|
|
42438
42456
|
});
|
|
42439
42457
|
continue;
|
|
42440
42458
|
}
|
|
42441
|
-
const { httpUrl: _httpUrl, ...rest } = parsed;
|
|
42459
|
+
const { httpUrl: _httpUrl, disabled: _disabled, ...rest } = parsed;
|
|
42442
42460
|
result[name] = {
|
|
42443
42461
|
...renameEnabledToolsToTools({
|
|
42444
42462
|
server: rest,
|
|
@@ -42450,7 +42468,7 @@ function addTypeField(mcpServers, logger) {
|
|
|
42450
42468
|
};
|
|
42451
42469
|
continue;
|
|
42452
42470
|
}
|
|
42453
|
-
const { httpUrl: _localHttpUrl, ...local } = parsed;
|
|
42471
|
+
const { httpUrl: _localHttpUrl, disabled: _localDisabled, ...local } = parsed;
|
|
42454
42472
|
const [head, ...tail] = resolveLocalMcpCommand(parsed);
|
|
42455
42473
|
if (head === void 0) {
|
|
42456
42474
|
warnAndSkipMcpServer({
|
|
@@ -42481,7 +42499,7 @@ function removeTypeField(config) {
|
|
|
42481
42499
|
const result = {};
|
|
42482
42500
|
for (const [name, server] of Object.entries(config.mcpServers ?? {})) {
|
|
42483
42501
|
const { tools, ...withoutTools } = server;
|
|
42484
|
-
const restored = isStringArray(tools) ? {
|
|
42502
|
+
const restored = isStringArray$1(tools) ? {
|
|
42485
42503
|
...withoutTools,
|
|
42486
42504
|
enabledTools: tools
|
|
42487
42505
|
} : {
|
|
@@ -42497,6 +42515,99 @@ function removeTypeField(config) {
|
|
|
42497
42515
|
}
|
|
42498
42516
|
return result;
|
|
42499
42517
|
}
|
|
42518
|
+
/**
|
|
42519
|
+
* The Copilot CLI settings file carrying `disabledMcpServers`:
|
|
42520
|
+
* `.github/copilot/settings.json` (project) / `~/.copilot/settings.json`
|
|
42521
|
+
* (global). Both scopes document the key ("MCP server names to disable. Listed
|
|
42522
|
+
* servers are configured but not started"); at repository scope it is
|
|
42523
|
+
* union-merged with the user value, so a repository can add names but never
|
|
42524
|
+
* remove them.
|
|
42525
|
+
*
|
|
42526
|
+
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
42527
|
+
*/
|
|
42528
|
+
function copilotCliSettingsPaths({ global }) {
|
|
42529
|
+
return global ? {
|
|
42530
|
+
relativeDirPath: COPILOT_DIR,
|
|
42531
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
42532
|
+
} : {
|
|
42533
|
+
relativeDirPath: COPILOTCLI_PROJECT_SETTINGS_DIR_PATH,
|
|
42534
|
+
relativeFilePath: COPILOTCLI_SETTINGS_FILE_NAME
|
|
42535
|
+
};
|
|
42536
|
+
}
|
|
42537
|
+
async function readCopilotCliSettings({ outputRoot, global }) {
|
|
42538
|
+
const paths = copilotCliSettingsPaths({ global });
|
|
42539
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
42540
|
+
try {
|
|
42541
|
+
return {
|
|
42542
|
+
paths,
|
|
42543
|
+
existingContent,
|
|
42544
|
+
document: parseSharedConfig({
|
|
42545
|
+
format: "json",
|
|
42546
|
+
fileContent: existingContent,
|
|
42547
|
+
filePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath),
|
|
42548
|
+
invalidRootPolicy: "error"
|
|
42549
|
+
})
|
|
42550
|
+
};
|
|
42551
|
+
} catch (error) {
|
|
42552
|
+
return {
|
|
42553
|
+
paths,
|
|
42554
|
+
existingContent,
|
|
42555
|
+
document: null,
|
|
42556
|
+
error
|
|
42557
|
+
};
|
|
42558
|
+
}
|
|
42559
|
+
}
|
|
42560
|
+
/**
|
|
42561
|
+
* The names of the servers rulesync actually writes for this target: one
|
|
42562
|
+
* `addTypeField` skips (no transport, WebSocket, missing url or command) is
|
|
42563
|
+
* not managed, so its name neither gains a toggle nor strips a same-named
|
|
42564
|
+
* user-authored entry from the settings file.
|
|
42565
|
+
*/
|
|
42566
|
+
function managedServerNames(mcpServers) {
|
|
42567
|
+
return Object.keys(addTypeField(mcpServers) ?? {});
|
|
42568
|
+
}
|
|
42569
|
+
/**
|
|
42570
|
+
* Overlays `disabled: true` on the servers the scope's settings file lists in
|
|
42571
|
+
* `disabledMcpServers`, so import round-trips the toggle into the canonical
|
|
42572
|
+
* config. A settings file that exists but cannot be parsed throws
|
|
42573
|
+
* (fail-closed): importing past it would re-enable every disabled server.
|
|
42574
|
+
*/
|
|
42575
|
+
async function overlayDisabledServers({ mcpServers, outputRoot, global }) {
|
|
42576
|
+
const settings = await readCopilotCliSettings({
|
|
42577
|
+
outputRoot,
|
|
42578
|
+
global
|
|
42579
|
+
});
|
|
42580
|
+
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 });
|
|
42581
|
+
const disabled = settings.document.disabledMcpServers;
|
|
42582
|
+
if (!isStringArray$1(disabled) || disabled.length === 0) return mcpServers;
|
|
42583
|
+
const servers = { ...mcpServers };
|
|
42584
|
+
for (const name of disabled) {
|
|
42585
|
+
if (!Object.hasOwn(servers, name)) continue;
|
|
42586
|
+
const server = servers[name];
|
|
42587
|
+
if (isPlainObject$1(server)) servers[name] = {
|
|
42588
|
+
...server,
|
|
42589
|
+
disabled: true
|
|
42590
|
+
};
|
|
42591
|
+
}
|
|
42592
|
+
return servers;
|
|
42593
|
+
}
|
|
42594
|
+
/**
|
|
42595
|
+
* Auxiliary writer for `disabledMcpServers` in the Copilot CLI settings file.
|
|
42596
|
+
* Only that key is owned (through the shared-config gateway), so `deniedUrls`,
|
|
42597
|
+
* `disableAllHooks`, `model` and every other setting are preserved. It is a
|
|
42598
|
+
* shared, user-owned file and is never deleted.
|
|
42599
|
+
*/
|
|
42600
|
+
var CopilotcliMcpSettings = class extends ToolFile {
|
|
42601
|
+
validate() {
|
|
42602
|
+
return {
|
|
42603
|
+
success: true,
|
|
42604
|
+
error: null
|
|
42605
|
+
};
|
|
42606
|
+
}
|
|
42607
|
+
isDeletable() {
|
|
42608
|
+
return false;
|
|
42609
|
+
}
|
|
42610
|
+
};
|
|
42500
42611
|
var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
42501
42612
|
json;
|
|
42502
42613
|
constructor(params) {
|
|
@@ -42539,7 +42650,11 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42539
42650
|
const json = JSON.parse(fileContent);
|
|
42540
42651
|
const newJson = {
|
|
42541
42652
|
...json,
|
|
42542
|
-
mcpServers:
|
|
42653
|
+
mcpServers: await overlayDisabledServers({
|
|
42654
|
+
mcpServers: json.mcpServers ?? {},
|
|
42655
|
+
outputRoot,
|
|
42656
|
+
global
|
|
42657
|
+
})
|
|
42543
42658
|
};
|
|
42544
42659
|
return new CopilotcliMcp({
|
|
42545
42660
|
outputRoot,
|
|
@@ -42554,7 +42669,15 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42554
42669
|
const paths = this.getSettablePaths({ global });
|
|
42555
42670
|
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? JSON.stringify({ mcpServers: {} }, null, 2);
|
|
42556
42671
|
const json = JSON.parse(fileContent);
|
|
42557
|
-
const
|
|
42672
|
+
const settings = await readCopilotCliSettings({
|
|
42673
|
+
outputRoot,
|
|
42674
|
+
global
|
|
42675
|
+
});
|
|
42676
|
+
const copilotCliMcpServers = addTypeField(Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).filter(([name, server]) => {
|
|
42677
|
+
if (server.disabled !== true || settings.document !== null) return true;
|
|
42678
|
+
logger?.warn(`[CopilotcliMcp] Skipping disabled MCP server "${name}": ${(0, node_path.join)(settings.paths.relativeDirPath, settings.paths.relativeFilePath)} cannot be parsed, so disabledMcpServers cannot be written to switch it off.`);
|
|
42679
|
+
return false;
|
|
42680
|
+
})), logger);
|
|
42558
42681
|
const mcpJson = {
|
|
42559
42682
|
...json,
|
|
42560
42683
|
mcpServers: copilotCliMcpServers
|
|
@@ -42568,6 +42691,61 @@ var CopilotcliMcp = class CopilotcliMcp extends ToolMcp {
|
|
|
42568
42691
|
global
|
|
42569
42692
|
});
|
|
42570
42693
|
}
|
|
42694
|
+
/**
|
|
42695
|
+
* `disabledMcpServers` lives in the Copilot CLI settings file the hooks and
|
|
42696
|
+
* permissions features also write. Declared here so the write-order
|
|
42697
|
+
* derivation sees this feature as one of that file's writers — it is not a
|
|
42698
|
+
* settable path, since the servers themselves live in the MCP file.
|
|
42699
|
+
*/
|
|
42700
|
+
static getExtraSharedWritePaths({ global = false } = {}) {
|
|
42701
|
+
return [copilotCliSettingsPaths({ global })];
|
|
42702
|
+
}
|
|
42703
|
+
/**
|
|
42704
|
+
* Routes the canonical `disabled: true` to `disabledMcpServers`, the key
|
|
42705
|
+
* Copilot CLI actually consults: an MCP file entry has no `disabled` field.
|
|
42706
|
+
* rulesync owns the toggle for the servers it writes; names it does not
|
|
42707
|
+
* manage keep their existing state. The file is not created or touched when
|
|
42708
|
+
* there is nothing to disable and nothing to retract.
|
|
42709
|
+
*/
|
|
42710
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), global = false, rulesyncMcp, logger }) {
|
|
42711
|
+
const servers = rulesyncMcp.forTarget({
|
|
42712
|
+
toolTarget: "copilotcli",
|
|
42713
|
+
global
|
|
42714
|
+
}).getMcpServers();
|
|
42715
|
+
const managedNames = managedServerNames(servers);
|
|
42716
|
+
const disabledNames = managedNames.filter((name) => servers[name]?.disabled === true);
|
|
42717
|
+
const settings = await readCopilotCliSettings({
|
|
42718
|
+
outputRoot,
|
|
42719
|
+
global
|
|
42720
|
+
});
|
|
42721
|
+
if (settings.document === null) {
|
|
42722
|
+
logger?.warn(`Skipping the Copilot CLI disabledMcpServers update: ${formatError(settings.error)}`);
|
|
42723
|
+
return [];
|
|
42724
|
+
}
|
|
42725
|
+
const existing = settings.document.disabledMcpServers;
|
|
42726
|
+
const existingDisabled = isStringArray$1(existing) ? existing : [];
|
|
42727
|
+
const managedNameSet = new Set(managedNames);
|
|
42728
|
+
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
42729
|
+
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.`);
|
|
42730
|
+
const mergedDisabled = [...existingDisabled.filter((name) => !managedNameSet.has(name)), ...disabledNames].toSorted();
|
|
42731
|
+
if (mergedDisabled.length === 0 && existing === void 0) return [];
|
|
42732
|
+
if (isStringArray$1(existing) && existing.length === mergedDisabled.length && existing.toSorted().every((name, index) => name === mergedDisabled[index])) return [];
|
|
42733
|
+
const { paths } = settings;
|
|
42734
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
42735
|
+
return [new CopilotcliMcpSettings({
|
|
42736
|
+
outputRoot,
|
|
42737
|
+
relativeDirPath: paths.relativeDirPath,
|
|
42738
|
+
relativeFilePath: paths.relativeFilePath,
|
|
42739
|
+
fileContent: applySharedConfigPatch({
|
|
42740
|
+
fileKey: sharedConfigFileKey(paths),
|
|
42741
|
+
feature: "mcp",
|
|
42742
|
+
existingContent: settings.existingContent,
|
|
42743
|
+
patch: { disabledMcpServers: mergedDisabled.length > 0 ? mergedDisabled : void 0 },
|
|
42744
|
+
filePath
|
|
42745
|
+
}),
|
|
42746
|
+
global
|
|
42747
|
+
})];
|
|
42748
|
+
}
|
|
42571
42749
|
toRulesyncMcp() {
|
|
42572
42750
|
const mcpServers = removeTypeField(this.json);
|
|
42573
42751
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
@@ -42999,10 +43177,10 @@ function convertFromCrushFormat(crushServers) {
|
|
|
42999
43177
|
if (value === "http" || value === "sse") converted.type = value;
|
|
43000
43178
|
break;
|
|
43001
43179
|
case "disabled_tools":
|
|
43002
|
-
if (isStringArray$
|
|
43180
|
+
if (isStringArray$1(value)) converted.disabledTools = value;
|
|
43003
43181
|
break;
|
|
43004
43182
|
case "enabled_tools":
|
|
43005
|
-
if (isStringArray$
|
|
43183
|
+
if (isStringArray$1(value)) converted.enabledTools = value;
|
|
43006
43184
|
break;
|
|
43007
43185
|
case "oauth_token": break;
|
|
43008
43186
|
default: {
|
|
@@ -43989,7 +44167,7 @@ function convertRowsFromDsh(rows, logger) {
|
|
|
43989
44167
|
} else {
|
|
43990
44168
|
server.type = "stdio";
|
|
43991
44169
|
if (typeof config.command === "string") server.command = config.command;
|
|
43992
|
-
if (isStringArray$
|
|
44170
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
43993
44171
|
const env = copyStringRecord(config.env);
|
|
43994
44172
|
if (env !== void 0) server.env = env;
|
|
43995
44173
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -44420,11 +44598,11 @@ function applyGooseStdioFields(ext, config) {
|
|
|
44420
44598
|
if (Array.isArray(command)) {
|
|
44421
44599
|
if (typeof command[0] === "string") ext.cmd = command[0];
|
|
44422
44600
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
44423
|
-
const args = isStringArray$
|
|
44601
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
44424
44602
|
if (rest.length > 0 || args.length > 0) ext.args = [...rest, ...args];
|
|
44425
44603
|
} else if (typeof command === "string") {
|
|
44426
44604
|
ext.cmd = command;
|
|
44427
|
-
if (isStringArray$
|
|
44605
|
+
if (isStringArray$1(config.args)) ext.args = config.args;
|
|
44428
44606
|
}
|
|
44429
44607
|
if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
|
|
44430
44608
|
if (typeof config.cwd === "string") ext.cwd = config.cwd;
|
|
@@ -44527,7 +44705,7 @@ function convertFromGooseFormat(extensions) {
|
|
|
44527
44705
|
else if (type === "streamable_http") server.type = "http";
|
|
44528
44706
|
else server.type = "stdio";
|
|
44529
44707
|
if (typeof ext.cmd === "string") server.command = ext.cmd;
|
|
44530
|
-
if (isStringArray$
|
|
44708
|
+
if (isStringArray$1(ext.args)) server.args = ext.args;
|
|
44531
44709
|
if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
|
|
44532
44710
|
if (typeof ext.cwd === "string") server.cwd = ext.cwd;
|
|
44533
44711
|
if (typeof ext.uri === "string") server.url = ext.uri;
|
|
@@ -44549,11 +44727,11 @@ function buildGoosePluginStdioServer(config) {
|
|
|
44549
44727
|
if (Array.isArray(command)) {
|
|
44550
44728
|
if (typeof command[0] === "string") server.command = command[0];
|
|
44551
44729
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
44552
|
-
const args = isStringArray$
|
|
44730
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
44553
44731
|
if (rest.length > 0 || args.length > 0) server.args = [...rest, ...args];
|
|
44554
44732
|
} else if (typeof command === "string") {
|
|
44555
44733
|
server.command = command;
|
|
44556
|
-
if (isStringArray$
|
|
44734
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
44557
44735
|
}
|
|
44558
44736
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
44559
44737
|
if (typeof config.cwd === "string") server.cwd = config.cwd;
|
|
@@ -44961,7 +45139,7 @@ function copyHermesOauth(source) {
|
|
|
44961
45139
|
if (!isRecord$1(source)) return;
|
|
44962
45140
|
const oauth = {};
|
|
44963
45141
|
for (const key of HERMES_OAUTH_STRING_KEYS) if (typeof source[key] === "string") oauth[key] = source[key];
|
|
44964
|
-
if (typeof oauth.scope !== "string" && isStringArray$
|
|
45142
|
+
if (typeof oauth.scope !== "string" && isStringArray$1(source.scopes)) oauth.scope = source.scopes.join(" ");
|
|
44965
45143
|
for (const key of ["redirect_port", "timeout"]) if (typeof source[key] === "number") oauth[key] = source[key];
|
|
44966
45144
|
if (typeof source.cimd === "boolean") oauth.cimd = source.cimd;
|
|
44967
45145
|
return Object.keys(oauth).length > 0 ? oauth : void 0;
|
|
@@ -44988,7 +45166,7 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
44988
45166
|
target.auth = source.auth;
|
|
44989
45167
|
copied = true;
|
|
44990
45168
|
}
|
|
44991
|
-
if (typeof source.client_cert === "string" || isStringArray$
|
|
45169
|
+
if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) {
|
|
44992
45170
|
target.client_cert = source.client_cert;
|
|
44993
45171
|
copied = true;
|
|
44994
45172
|
}
|
|
@@ -45060,8 +45238,8 @@ function copyHermesAdvancedFields(source, target) {
|
|
|
45060
45238
|
*/
|
|
45061
45239
|
function buildHermesToolsBlock(config) {
|
|
45062
45240
|
const tools = {};
|
|
45063
|
-
if (isStringArray$
|
|
45064
|
-
if (isStringArray$
|
|
45241
|
+
if (isStringArray$1(config.enabledTools)) tools.include = config.enabledTools;
|
|
45242
|
+
if (isStringArray$1(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
45065
45243
|
if (typeof config.promptsEnabled === "boolean") tools.prompts = config.promptsEnabled;
|
|
45066
45244
|
if (typeof config.resourcesEnabled === "boolean") tools.resources = config.resourcesEnabled;
|
|
45067
45245
|
return tools;
|
|
@@ -45073,8 +45251,8 @@ function buildHermesToolsBlock(config) {
|
|
|
45073
45251
|
* `promptsEnabled`/`resourcesEnabled` top-level toggles.
|
|
45074
45252
|
*/
|
|
45075
45253
|
function applyHermesToolsBlock(hermesTools, server) {
|
|
45076
|
-
if (isStringArray$
|
|
45077
|
-
if (isStringArray$
|
|
45254
|
+
if (isStringArray$1(hermesTools.include)) server.enabledTools = hermesTools.include;
|
|
45255
|
+
if (isStringArray$1(hermesTools.exclude)) server.disabledTools = hermesTools.exclude;
|
|
45078
45256
|
if (typeof hermesTools.prompts === "boolean") server.promptsEnabled = hermesTools.prompts;
|
|
45079
45257
|
if (typeof hermesTools.resources === "boolean") server.resourcesEnabled = hermesTools.resources;
|
|
45080
45258
|
}
|
|
@@ -45100,11 +45278,11 @@ function convertServerToHermes(config) {
|
|
|
45100
45278
|
if (Array.isArray(command)) {
|
|
45101
45279
|
if (typeof command[0] === "string") out.command = command[0];
|
|
45102
45280
|
const rest = command.slice(1).filter((c) => typeof c === "string");
|
|
45103
|
-
const args = isStringArray$
|
|
45281
|
+
const args = isStringArray$1(config.args) ? config.args : [];
|
|
45104
45282
|
if (rest.length > 0 || args.length > 0) out.args = [...rest, ...args];
|
|
45105
45283
|
} else if (typeof command === "string") {
|
|
45106
45284
|
out.command = command;
|
|
45107
|
-
if (isStringArray$
|
|
45285
|
+
if (isStringArray$1(config.args)) out.args = config.args;
|
|
45108
45286
|
}
|
|
45109
45287
|
if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
|
|
45110
45288
|
} else if (url !== void 0) {
|
|
@@ -45154,7 +45332,7 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
45154
45332
|
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
45155
45333
|
const server = {};
|
|
45156
45334
|
if (typeof config.command === "string") server.command = config.command;
|
|
45157
|
-
if (isStringArray$
|
|
45335
|
+
if (isStringArray$1(config.args)) server.args = config.args;
|
|
45158
45336
|
if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
|
|
45159
45337
|
if (typeof config.url === "string") server.url = config.url;
|
|
45160
45338
|
if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
@@ -46346,7 +46524,7 @@ function mergeToolLists(...lists) {
|
|
|
46346
46524
|
function toKiroMcpServers(servers) {
|
|
46347
46525
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
46348
46526
|
const { kiroAutoApprove, kiroAutoBlock, disabledTools, ...rest } = server;
|
|
46349
|
-
const autoApprove = mergeToolLists(isStringArray$
|
|
46527
|
+
const autoApprove = mergeToolLists(isStringArray$1(rest.autoApprove) ? rest.autoApprove : void 0, kiroAutoApprove);
|
|
46350
46528
|
const disabled = mergeToolLists(disabledTools, kiroAutoBlock);
|
|
46351
46529
|
return [name, {
|
|
46352
46530
|
...rest,
|
|
@@ -46370,7 +46548,7 @@ function fromKiroMcpServers(servers) {
|
|
|
46370
46548
|
return Object.fromEntries(Object.entries(servers).map(([name, server]) => {
|
|
46371
46549
|
if (server === null || typeof server !== "object" || Array.isArray(server)) return [name, server];
|
|
46372
46550
|
const { autoApprove, ...rest } = server;
|
|
46373
|
-
if (!isStringArray$
|
|
46551
|
+
if (!isStringArray$1(autoApprove)) return [name, server];
|
|
46374
46552
|
return [name, {
|
|
46375
46553
|
...rest,
|
|
46376
46554
|
kiroAutoApprove: autoApprove
|
|
@@ -47799,7 +47977,7 @@ function headersRecordToPoolList(headers) {
|
|
|
47799
47977
|
return Object.entries(headers).map(([name, value]) => `${name}: ${value}`);
|
|
47800
47978
|
}
|
|
47801
47979
|
function poolHeadersListToRecord(serverName, headers) {
|
|
47802
|
-
if (!isStringArray$
|
|
47980
|
+
if (!isStringArray$1(headers)) return void 0;
|
|
47803
47981
|
const result = {};
|
|
47804
47982
|
for (const entry of headers) {
|
|
47805
47983
|
const separator = entry.indexOf(":");
|
|
@@ -47825,7 +48003,7 @@ const GLOB_METACHARACTERS = /[*?[]/;
|
|
|
47825
48003
|
/** Patterns named in the glob warning before the rest collapse to `(+N more)`. */
|
|
47826
48004
|
const MAX_LISTED_GLOB_PATTERNS = 10;
|
|
47827
48005
|
function importPoolToolList({ serverName, poolKey, value, warnOnGlobs }) {
|
|
47828
|
-
if (!isStringArray$
|
|
48006
|
+
if (!isStringArray$1(value)) {
|
|
47829
48007
|
warnWithFallback(void 0, `Ignored malformed value for ${poolKey} in Pool MCP server ${quoteValueForWarning(serverName)}: expected a list of strings`);
|
|
47830
48008
|
return;
|
|
47831
48009
|
}
|
|
@@ -48035,7 +48213,7 @@ var PoolMcp = class PoolMcp extends ToolMcp {
|
|
|
48035
48213
|
const poolAllow = isRecord$1(rawServer) ? rawServer.poolAllow : void 0;
|
|
48036
48214
|
return [serverName, {
|
|
48037
48215
|
...serverConfig,
|
|
48038
|
-
...isStringArray$
|
|
48216
|
+
...isStringArray$1(poolAllow) && { poolAllow }
|
|
48039
48217
|
}];
|
|
48040
48218
|
})), logger);
|
|
48041
48219
|
return new PoolMcp({
|
|
@@ -48266,7 +48444,7 @@ function pickServerLists(source) {
|
|
|
48266
48444
|
if (!isPlainObject$1(source)) return {};
|
|
48267
48445
|
return Object.fromEntries(QWENCODE_MCP_SERVER_LIST_KEYS.flatMap((key) => {
|
|
48268
48446
|
const value = source[key];
|
|
48269
|
-
return isStringArray$
|
|
48447
|
+
return isStringArray$1(value) ? [[key, value]] : [];
|
|
48270
48448
|
}));
|
|
48271
48449
|
}
|
|
48272
48450
|
/**
|
|
@@ -48959,7 +49137,7 @@ async function resolveRovodevMcpImportPath({ outputRoot, global, config, logger
|
|
|
48959
49137
|
}
|
|
48960
49138
|
function disabledNamesOf(config) {
|
|
48961
49139
|
const mcpBlock = config && isRecord$1(config.mcp) ? config.mcp : {};
|
|
48962
|
-
return isStringArray$
|
|
49140
|
+
return isStringArray$1(mcpBlock.disabledMcpServers) ? mcpBlock.disabledMcpServers : [];
|
|
48963
49141
|
}
|
|
48964
49142
|
/**
|
|
48965
49143
|
* The value `mcp.mcpConfigPath` needs so Rovo Dev reads the `mcp.json` rulesync
|
|
@@ -49375,7 +49553,7 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
49375
49553
|
return [];
|
|
49376
49554
|
}
|
|
49377
49555
|
const existingMcp = isRecord$1(existingParsed.mcp) ? { ...existingParsed.mcp } : {};
|
|
49378
|
-
const existingDisabled = isStringArray$
|
|
49556
|
+
const existingDisabled = isStringArray$1(existingMcp.disabledMcpServers) ? existingMcp.disabledMcpServers : [];
|
|
49379
49557
|
const managedNameSet = new Set(managedNames);
|
|
49380
49558
|
const reEnabled = existingDisabled.filter((name) => managedNameSet.has(name) && !disabledNames.includes(name));
|
|
49381
49559
|
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 +56712,9 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
56534
56712
|
//#endregion
|
|
56535
56713
|
//#region src/features/permissions/cline-permissions.ts
|
|
56536
56714
|
/**
|
|
56537
|
-
* Cline CLI
|
|
56538
|
-
*
|
|
56715
|
+
* The legacy Cline CLI loaded command permissions from the `CLINE_COMMAND_PERMISSIONS`
|
|
56716
|
+
* environment variable. It supported only `allow` and `deny` (no `ask`), and only for
|
|
56717
|
+
* shell commands.
|
|
56539
56718
|
* Schema:
|
|
56540
56719
|
* ```json
|
|
56541
56720
|
* { "allow": ["pattern1", ...], "deny": ["pattern2", ...], "allowRedirects": false }
|
|
@@ -56543,7 +56722,14 @@ function convertClaudeToRulesyncPermissions(params) {
|
|
|
56543
56722
|
*
|
|
56544
56723
|
* Rulesync writes the JSON to `.cline/command-permissions.json` so users can do:
|
|
56545
56724
|
* `export CLINE_COMMAND_PERMISSIONS=$(cat .cline/command-permissions.json)`
|
|
56725
|
+
*
|
|
56726
|
+
* Deprecated: no Cline CLI 3 release reads `CLINE_COMMAND_PERMISSIONS`. Upstream removed
|
|
56727
|
+
* it from the docs in cline/cline@6354d209, and the CLI reference now says the CLI has no
|
|
56728
|
+
* allow or deny list for shell commands and recommends a `PreToolUse` hook instead
|
|
56729
|
+
* (https://docs.cline.bot/cli/cli-reference). The output is still generated for
|
|
56730
|
+
* compatibility, with a deprecation warning on every generation.
|
|
56546
56731
|
*/
|
|
56732
|
+
const CLINE_PERMISSIONS_DEPRECATION_WARNING = "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. See https://docs.cline.bot/cli/cli-reference.";
|
|
56547
56733
|
const ClineCommandPermissionsSchema = zod_mini.z.looseObject({
|
|
56548
56734
|
allow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
56549
56735
|
deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -56661,6 +56847,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
56661
56847
|
});
|
|
56662
56848
|
}
|
|
56663
56849
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false, logger }) {
|
|
56850
|
+
logger?.warn(CLINE_PERMISSIONS_DEPRECATION_WARNING);
|
|
56664
56851
|
const paths = ClinePermissions.getSettablePaths({ global });
|
|
56665
56852
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
56666
56853
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
@@ -56690,7 +56877,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
56690
56877
|
const mergedDeny = (0, es_toolkit.uniq)([...existing.deny ?? [], ...dedupedDeny]).toSorted();
|
|
56691
56878
|
const denySet = new Set(mergedDeny);
|
|
56692
56879
|
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
|
|
56880
|
+
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
56881
|
const next = {
|
|
56695
56882
|
...existing,
|
|
56696
56883
|
allow: dedupedAllow,
|
|
@@ -59973,9 +60160,9 @@ function managedContinueToolNames(config) {
|
|
|
59973
60160
|
*/
|
|
59974
60161
|
function parseContinuePermissionLists(parsed) {
|
|
59975
60162
|
return {
|
|
59976
|
-
exclude: isStringArray$
|
|
59977
|
-
ask: isStringArray$
|
|
59978
|
-
allow: isStringArray$
|
|
60163
|
+
exclude: isStringArray$1(parsed.exclude) ? parsed.exclude : [],
|
|
60164
|
+
ask: isStringArray$1(parsed.ask) ? parsed.ask : [],
|
|
60165
|
+
allow: isStringArray$1(parsed.allow) ? parsed.allow : []
|
|
59979
60166
|
};
|
|
59980
60167
|
}
|
|
59981
60168
|
/**
|
|
@@ -60502,8 +60689,8 @@ var CrushPermissions = class CrushPermissions extends ToolPermissions {
|
|
|
60502
60689
|
logger
|
|
60503
60690
|
});
|
|
60504
60691
|
const managedToolNames = managedCrushToolNames(config);
|
|
60505
|
-
const preservedAllowed = (isStringArray$
|
|
60506
|
-
const preservedDisabled = (isStringArray$
|
|
60692
|
+
const preservedAllowed = (isStringArray$1(existingPermissions?.["allowed_tools"]) ? existingPermissions[CRUSH_ALLOWED_TOOLS_KEY] : []).filter((entry) => entry.includes(":") || !managedToolNames.has(entry));
|
|
60693
|
+
const preservedDisabled = (isStringArray$1(existingOptions?.["disabled_tools"]) ? existingOptions[CRUSH_DISABLED_TOOLS_KEY] : []).filter((entry) => !managedToolNames.has(entry));
|
|
60507
60694
|
const allowedList = uniq$11([...preservedAllowed, ...generated.allowed]);
|
|
60508
60695
|
const disabledList = uniq$11([...preservedDisabled, ...generated.disabled]);
|
|
60509
60696
|
const patch = {};
|
|
@@ -60529,8 +60716,8 @@ var CrushPermissions = class CrushPermissions extends ToolPermissions {
|
|
|
60529
60716
|
const permissions = isRecord$1(this.json["permissions"]) ? this.json[CRUSH_PERMISSIONS_KEY] : {};
|
|
60530
60717
|
const options = isRecord$1(this.json["options"]) ? this.json[CRUSH_OPTIONS_KEY] : {};
|
|
60531
60718
|
const rulesyncConfig = convertCrushListsToRulesync({
|
|
60532
|
-
allowed: isStringArray$
|
|
60533
|
-
disabled: isStringArray$
|
|
60719
|
+
allowed: isStringArray$1(permissions["allowed_tools"]) ? permissions[CRUSH_ALLOWED_TOOLS_KEY] : [],
|
|
60720
|
+
disabled: isStringArray$1(options["disabled_tools"]) ? options[CRUSH_DISABLED_TOOLS_KEY] : []
|
|
60534
60721
|
});
|
|
60535
60722
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
60536
60723
|
}
|
|
@@ -62705,7 +62892,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62705
62892
|
function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
62706
62893
|
const permission = {};
|
|
62707
62894
|
for (const key of GOOSE_PERMISSION_LIST_KEYS) {
|
|
62708
|
-
const toolNames = isStringArray$
|
|
62895
|
+
const toolNames = isStringArray$1(userPermission[key]) ? userPermission[key] : [];
|
|
62709
62896
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62710
62897
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62711
62898
|
permission[category] ??= {};
|
|
@@ -63053,7 +63240,7 @@ const ACTION_RANK$3 = {
|
|
|
63053
63240
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
63054
63241
|
*/
|
|
63055
63242
|
function unmanagedEntries(existingPermission, key) {
|
|
63056
|
-
return (isStringArray$
|
|
63243
|
+
return (isStringArray$1(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
63057
63244
|
}
|
|
63058
63245
|
/**
|
|
63059
63246
|
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
@@ -63110,7 +63297,7 @@ function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger })
|
|
|
63110
63297
|
const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63111
63298
|
for (const action of ["deny", "ask"]) {
|
|
63112
63299
|
const entries = existingPermission[action];
|
|
63113
|
-
if (!isStringArray$
|
|
63300
|
+
if (!isStringArray$1(entries)) continue;
|
|
63114
63301
|
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63115
63302
|
if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
|
|
63116
63303
|
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 +63328,9 @@ function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
|
63141
63328
|
* described more than once resolves to the strictest action.
|
|
63142
63329
|
*/
|
|
63143
63330
|
function parseGrokPermissionArrays(permission) {
|
|
63144
|
-
const allow = isStringArray$
|
|
63145
|
-
const deny = isStringArray$
|
|
63146
|
-
const ask = isStringArray$
|
|
63331
|
+
const allow = isStringArray$1(permission.allow) ? permission.allow : void 0;
|
|
63332
|
+
const deny = isStringArray$1(permission.deny) ? permission.deny : void 0;
|
|
63333
|
+
const ask = isStringArray$1(permission.ask) ? permission.ask : void 0;
|
|
63147
63334
|
const rules = Array.isArray(permission[GROKCLI_PERMISSION_RULES_KEY]) ? permission[GROKCLI_PERMISSION_RULES_KEY] : void 0;
|
|
63148
63335
|
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
|
|
63149
63336
|
const result = {};
|
|
@@ -63351,20 +63538,20 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
63351
63538
|
const permission = clonePermissionBlock(provenance.permission);
|
|
63352
63539
|
reconcileCommandAllowlist({
|
|
63353
63540
|
permission,
|
|
63354
|
-
commandAllowlist: isStringArray$
|
|
63541
|
+
commandAllowlist: isStringArray$1(config.command_allowlist) ? config.command_allowlist : []
|
|
63355
63542
|
});
|
|
63356
63543
|
const approvals = isRecord$1(config.approvals) ? config.approvals : {};
|
|
63357
63544
|
reconcileNativeDenies({
|
|
63358
63545
|
permission,
|
|
63359
63546
|
category: SHELL_PERMISSION_CATEGORY,
|
|
63360
|
-
patterns: isStringArray$
|
|
63547
|
+
patterns: isStringArray$1(approvals.deny) ? approvals.deny : []
|
|
63361
63548
|
});
|
|
63362
63549
|
const security = isRecord$1(config.security) ? config.security : {};
|
|
63363
63550
|
const websiteBlocklist = isRecord$1(security.website_blocklist) ? security.website_blocklist : {};
|
|
63364
63551
|
reconcileNativeDenies({
|
|
63365
63552
|
permission,
|
|
63366
63553
|
category: WEBFETCH_PERMISSION_CATEGORY,
|
|
63367
|
-
patterns: websiteBlocklist.enabled === true && isStringArray$
|
|
63554
|
+
patterns: websiteBlocklist.enabled === true && isStringArray$1(websiteBlocklist.domains) ? websiteBlocklist.domains : []
|
|
63368
63555
|
});
|
|
63369
63556
|
removeEmptyCategories(permission);
|
|
63370
63557
|
const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
|
|
@@ -64758,13 +64945,13 @@ function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
|
64758
64945
|
* how many entries rulesync does not generate.
|
|
64759
64946
|
*/
|
|
64760
64947
|
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$
|
|
64948
|
+
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
64949
|
rule,
|
|
64763
64950
|
unrelatedEntries: 0
|
|
64764
64951
|
};
|
|
64765
64952
|
const capability = rule.capability;
|
|
64766
64953
|
const isCatchAll = rule.match === void 0;
|
|
64767
|
-
const patterns = isStringArray$
|
|
64954
|
+
const patterns = isStringArray$1(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
|
|
64768
64955
|
if (patterns.length === 0) return {
|
|
64769
64956
|
rule,
|
|
64770
64957
|
unrelatedEntries: 0
|
|
@@ -64948,7 +65135,7 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
|
|
|
64948
65135
|
for (const rule of rules) {
|
|
64949
65136
|
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
|
|
64950
65137
|
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
64951
|
-
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$
|
|
65138
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$1(rule.match) ? rule.match : [];
|
|
64952
65139
|
const categoryRules = permission[category] ??= {};
|
|
64953
65140
|
for (const pattern of patterns) {
|
|
64954
65141
|
const previous = categoryRules[pattern];
|
|
@@ -66666,8 +66853,8 @@ function importPoolTool({ toolConfig, bucket }) {
|
|
|
66666
66853
|
bucket[CATCH_ALL_PATTERN$3] = "deny";
|
|
66667
66854
|
return;
|
|
66668
66855
|
}
|
|
66669
|
-
const denied = isStringArray$
|
|
66670
|
-
const allowed = isStringArray$
|
|
66856
|
+
const denied = isStringArray$1(toolConfig[POOL_DENY_KEY]) ? toolConfig[POOL_DENY_KEY] : [];
|
|
66857
|
+
const allowed = isStringArray$1(toolConfig[POOL_ALLOW_KEY]) ? toolConfig[POOL_ALLOW_KEY] : [];
|
|
66671
66858
|
for (const pattern of denied.filter(isUsablePattern)) bucket[pattern] = "deny";
|
|
66672
66859
|
for (const pattern of allowed.filter(isUsablePattern)) bucket[pattern] ??= "allow";
|
|
66673
66860
|
}
|
|
@@ -68872,7 +69059,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
|
|
|
68872
69059
|
permission[category] ??= {};
|
|
68873
69060
|
permission[category][CATCH_ALL_PATTERN$1] = levels.reduce((strictest, level) => strictestAction(strictest, level ?? implicitLevel), permission[category][CATCH_ALL_PATTERN$1]);
|
|
68874
69061
|
}
|
|
68875
|
-
if (isStringArray$
|
|
69062
|
+
if (isStringArray$1(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
|
|
68876
69063
|
permission.read ??= {};
|
|
68877
69064
|
permission.read[path] ??= "allow";
|
|
68878
69065
|
}
|
|
@@ -69128,7 +69315,7 @@ function stripRefusedOverridePaths(override) {
|
|
|
69128
69315
|
*/
|
|
69129
69316
|
function collectTrustAffectingOverrideEntries({ tools, general }) {
|
|
69130
69317
|
const entries = [];
|
|
69131
|
-
const allowed = isStringArray$
|
|
69318
|
+
const allowed = isStringArray$1(tools[ALLOWED_KEY]) ? tools[ALLOWED_KEY] : [];
|
|
69132
69319
|
if (allowed.length > 0) entries.push({
|
|
69133
69320
|
label: `tools.allowed (${allowed.map(quoteValueForWarning).join(", ")})`,
|
|
69134
69321
|
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 +69466,7 @@ function withholdDeniedOverrideAllowed({ overrideVerbatimAllowed, exclude, logge
|
|
|
69279
69466
|
* hand-written entry went.
|
|
69280
69467
|
*/
|
|
69281
69468
|
function reportDroppedManagedEntries({ existingTools, allowedList, excludeList, logger }) {
|
|
69282
|
-
const dropped = (key, list) => (0, es_toolkit.uniq)(isStringArray$
|
|
69469
|
+
const dropped = (key, list) => (0, es_toolkit.uniq)(isStringArray$1(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
|
|
69283
69470
|
if (list.includes(entry)) return false;
|
|
69284
69471
|
const toolName = parseTabnineEntry(entry)?.toolName;
|
|
69285
69472
|
return toolName === void 0 || !list.includes(toolName);
|
|
@@ -69361,7 +69548,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69361
69548
|
}
|
|
69362
69549
|
const overrideList = (key) => {
|
|
69363
69550
|
const value = overrideTools[key];
|
|
69364
|
-
if (value === void 0 || isStringArray$
|
|
69551
|
+
if (value === void 0 || isStringArray$1(value)) return value ?? [];
|
|
69365
69552
|
warnWithFallback(logger, `Tabnine CLI permissions: ignored tools.${key} of the tabnine override (${quoteValueForWarning(value)}); it must be a list of tool names.`);
|
|
69366
69553
|
return [];
|
|
69367
69554
|
};
|
|
@@ -69399,7 +69586,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69399
69586
|
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
69587
|
const existingTools = this.existingToolsGroup(existingContent);
|
|
69401
69588
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => category === "bash" || category === "*" ? SHELL_TOOL_NAME : toTabnineToolName(category)));
|
|
69402
|
-
const preservedEntries = (key) => (isStringArray$
|
|
69589
|
+
const preservedEntries = (key) => (isStringArray$1(existingTools?.[key]) ? existingTools[key] : []).filter((entry) => {
|
|
69403
69590
|
const toolName = parseTabnineEntry(entry)?.toolName;
|
|
69404
69591
|
return toolName === void 0 || !managedToolNames.has(toolName);
|
|
69405
69592
|
});
|
|
@@ -69477,7 +69664,7 @@ var TabninePermissions = class TabninePermissions extends ToolPermissions {
|
|
|
69477
69664
|
if (rules !== void 0 && !Object.hasOwn(rules, pattern)) rules[pattern] = action;
|
|
69478
69665
|
};
|
|
69479
69666
|
for (const [key, action] of [[EXCLUDE_KEY, "deny"], [ALLOWED_KEY, "allow"]]) {
|
|
69480
|
-
const entries = isStringArray$
|
|
69667
|
+
const entries = isStringArray$1(tools[key]) ? tools[key] : [];
|
|
69481
69668
|
for (const entry of entries) {
|
|
69482
69669
|
const parsed = parseTabnineEntry(entry);
|
|
69483
69670
|
if (parsed === void 0) {
|
|
@@ -70345,12 +70532,12 @@ const WIDE_CHARACTERS_PATTERN = /\p{Emoji_Presentation}|\p{Emoji_Modifier_Base}|
|
|
|
70345
70532
|
* The ranges are the `East_Asian_Width=A` property of Unicode 17.0, taken from
|
|
70346
70533
|
* the table `get-east-asian-width` 1.6.0 carries and merged where two are
|
|
70347
70534
|
* 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
|
|
70535
|
+
* that the one lookup adds no runtime dependency of its own, and the drift test
|
|
70536
|
+
* in `display-width.test.ts` is what keeps it honest: it loads the package,
|
|
70537
|
+
* pinned as a dev dependency, walks every code point, and fails when this
|
|
70538
|
+
* table and the property disagree in either direction. When that pin moves to
|
|
70539
|
+
* a newer Unicode, that test is what fails, and the table is regenerated from
|
|
70540
|
+
* the new property — its ranges, marks aside,
|
|
70354
70541
|
* merged where adjacent and written as escapes — with the version named here.
|
|
70355
70542
|
*
|
|
70356
70543
|
* Three stretches of the property are left out because they never reach this
|
|
@@ -72099,8 +72286,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
72099
72286
|
const executionProfiles = isRecord$1(agents["execution_profiles"]) ? agents[EXECUTION_PROFILES_KEY] : void 0;
|
|
72100
72287
|
const defaultProfile = executionProfiles && isRecord$1(executionProfiles["default"]) ? executionProfiles[DEFAULT_PROFILE_KEY] : void 0;
|
|
72101
72288
|
const config = convertWarpToRulesyncPermissions({
|
|
72102
|
-
allow: defaultProfile ? isStringArray$
|
|
72103
|
-
deny: defaultProfile ? isStringArray$
|
|
72289
|
+
allow: defaultProfile ? isStringArray$1(defaultProfile["command_allowlist"]) ? defaultProfile[PROFILE_ALLOWLIST_KEY] : [] : isStringArray$1(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
72290
|
+
deny: defaultProfile ? isStringArray$1(defaultProfile["command_denylist"]) ? defaultProfile[PROFILE_DENYLIST_KEY] : [] : isStringArray$1(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
72104
72291
|
});
|
|
72105
72292
|
const warpOverride = {};
|
|
72106
72293
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
@@ -72630,8 +72817,8 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
72630
72817
|
const allowlist = defaultProfile[PROFILE_ALLOWLIST_KEY];
|
|
72631
72818
|
const denylist = defaultProfile[PROFILE_DENYLIST_KEY];
|
|
72632
72819
|
const config = convertWarpToRulesyncPermissions({
|
|
72633
|
-
allow: isStringArray$
|
|
72634
|
-
deny: isStringArray$
|
|
72820
|
+
allow: isStringArray$1(allowlist) ? allowlist : [],
|
|
72821
|
+
deny: isStringArray$1(denylist) ? denylist : []
|
|
72635
72822
|
});
|
|
72636
72823
|
const executionProfileOverride = liftExecutionProfileOverride(defaultProfile);
|
|
72637
72824
|
const warpcliOverride = {};
|
|
@@ -84646,7 +84833,7 @@ var BobSubagent = class extends RooSubagent {
|
|
|
84646
84833
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
84647
84834
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
84648
84835
|
const bobSection = isRecord$1(frontmatter.bob) ? frontmatter.bob : {};
|
|
84649
|
-
if (isStringArray$
|
|
84836
|
+
if (isStringArray$1(bobSection.allowedSubagents)) mode.allowedSubagents = bobSection.allowedSubagents;
|
|
84650
84837
|
return mode;
|
|
84651
84838
|
}
|
|
84652
84839
|
};
|
|
@@ -90101,7 +90288,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
90101
90288
|
const mode = super.toRooMode(rulesyncSubagent);
|
|
90102
90289
|
const frontmatter = rulesyncSubagent.getFrontmatter();
|
|
90103
90290
|
const zoocodeSection = isRecord$1(frontmatter.zoocode) ? frontmatter.zoocode : {};
|
|
90104
|
-
if (isStringArray$
|
|
90291
|
+
if (isStringArray$1(zoocodeSection.allowedMcpServers)) mode.allowedMcpServers = zoocodeSection.allowedMcpServers;
|
|
90105
90292
|
return mode;
|
|
90106
90293
|
}
|
|
90107
90294
|
toRulesyncSubagents() {
|
|
@@ -90113,7 +90300,7 @@ var ZoocodeSubagent = class extends RooSubagent {
|
|
|
90113
90300
|
targets: ["zoocode"],
|
|
90114
90301
|
roo: restRooSection
|
|
90115
90302
|
};
|
|
90116
|
-
if (isStringArray$
|
|
90303
|
+
if (isStringArray$1(allowedMcpServers)) rebuilt.zoocode = { allowedMcpServers };
|
|
90117
90304
|
return new RulesyncSubagent({
|
|
90118
90305
|
outputRoot: ".",
|
|
90119
90306
|
frontmatter: rebuilt,
|
|
@@ -94054,7 +94241,8 @@ var NestedAgentsmdRule = class extends ToolRule {
|
|
|
94054
94241
|
* `<subprojectPath>/AGENTS.md` (project scope only); every other non-root rule
|
|
94055
94242
|
* folds into the root `AGENTS.md`, since Codex has no modular rules directory.
|
|
94056
94243
|
* The project files on the root-to-cwd chain share one `project_doc_max_bytes`
|
|
94057
|
-
* budget (32 KiB by default
|
|
94244
|
+
* budget (32 KiB by default, or the value set in the project's
|
|
94245
|
+
* `.codex/config.toml`), which the RulesProcessor warns about.
|
|
94058
94246
|
*
|
|
94059
94247
|
* @see https://learn.chatgpt.com/docs/agent-configuration/agents-md
|
|
94060
94248
|
*/
|
|
@@ -94066,6 +94254,29 @@ var CodexcliRule = class extends NestedAgentsmdRule {
|
|
|
94066
94254
|
};
|
|
94067
94255
|
}
|
|
94068
94256
|
};
|
|
94257
|
+
/**
|
|
94258
|
+
* Read the `project_doc_max_bytes` value set in the project's
|
|
94259
|
+
* `.codex/config.toml`, which Codex loads for trusted projects and which
|
|
94260
|
+
* overrides the 32 KiB default budget for project `AGENTS.md` files.
|
|
94261
|
+
*
|
|
94262
|
+
* Answers `undefined` when the file is missing, is not valid TOML, or does not
|
|
94263
|
+
* set the key to a positive integer, so the caller falls back to the default.
|
|
94264
|
+
*
|
|
94265
|
+
* @see https://learn.chatgpt.com/docs/config-file/config-reference
|
|
94266
|
+
*/
|
|
94267
|
+
async function readCodexcliProjectDocMaxBytes(outputRoot) {
|
|
94268
|
+
const content = await readFileContentOrNull((0, node_path.join)(outputRoot, CODEXCLI_DIR, CODEXCLI_MCP_FILE_NAME));
|
|
94269
|
+
if (content === null) return;
|
|
94270
|
+
let parsed;
|
|
94271
|
+
try {
|
|
94272
|
+
parsed = smol_toml.parse(content);
|
|
94273
|
+
} catch {
|
|
94274
|
+
return;
|
|
94275
|
+
}
|
|
94276
|
+
if (!isPlainObject$1(parsed)) return;
|
|
94277
|
+
const value = parsed.project_doc_max_bytes;
|
|
94278
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
94279
|
+
}
|
|
94069
94280
|
//#endregion
|
|
94070
94281
|
//#region src/features/rules/commandcode-rule.ts
|
|
94071
94282
|
var CommandcodeRule = class CommandcodeRule extends ToolRule {
|
|
@@ -99440,7 +99651,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
99440
99651
|
collisionPolicy: "fold",
|
|
99441
99652
|
projectInstructionBudget: {
|
|
99442
99653
|
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
|
|
99654
|
+
remedy: "Codex CLI stops reading project `AGENTS.md` files once their combined size on the root-to-working-directory chain reaches `project_doc_max_bytes` (32 KiB by default) and silently drops the rest. Move directory-scoped rules into nested `AGENTS.md` files with `agentsmd.subprojectPath` (or `deriveSubprojectPathFromGlobs: true` in rulesync.jsonc), trim the rules, or raise `project_doc_max_bytes` in `.codex/config.toml`.",
|
|
99655
|
+
readConfiguredBytes: readCodexcliProjectDocMaxBytes,
|
|
99656
|
+
configuredSource: "`project_doc_max_bytes` in `.codex/config.toml`"
|
|
99444
99657
|
}
|
|
99445
99658
|
}
|
|
99446
99659
|
}],
|
|
@@ -99985,7 +100198,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
99985
100198
|
factory
|
|
99986
100199
|
});
|
|
99987
100200
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
99988
|
-
this.warnForInstructionBudget({
|
|
100201
|
+
await this.warnForInstructionBudget({
|
|
99989
100202
|
toolRules,
|
|
99990
100203
|
meta
|
|
99991
100204
|
});
|
|
@@ -100006,14 +100219,19 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100006
100219
|
* once, at its shallowest directory. With several output roots the warning
|
|
100007
100220
|
* names the root it refers to. An output root below the git root is measured
|
|
100008
100221
|
* without the parent directories' files Codex would also load, so the
|
|
100009
|
-
* estimate can come out low there. The budget checked is the
|
|
100010
|
-
*
|
|
100222
|
+
* estimate can come out low there. The budget checked is the value set in the
|
|
100223
|
+
* output root's tool config when the tool supports one (Codex's
|
|
100224
|
+
* `project_doc_max_bytes` in `.codex/config.toml`), else the tool's default;
|
|
100225
|
+
* the warning names which one it used. Global scope is not
|
|
100011
100226
|
* checked because Codex loads the personal `~/.codex/AGENTS.md` outside the
|
|
100012
100227
|
* project budget.
|
|
100013
100228
|
*/
|
|
100014
|
-
warnForInstructionBudget({ toolRules, meta }) {
|
|
100229
|
+
async warnForInstructionBudget({ toolRules, meta }) {
|
|
100015
100230
|
const budget = meta.projectInstructionBudget;
|
|
100016
100231
|
if (budget === void 0 || this.global) return;
|
|
100232
|
+
const configuredBytes = await budget.readConfiguredBytes?.(this.outputRoot);
|
|
100233
|
+
const limitBytes = configuredBytes ?? budget.bytes;
|
|
100234
|
+
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"}`;
|
|
100017
100235
|
const files = toolRules.map((rule) => ({
|
|
100018
100236
|
dir: normalizeRelativeDir(rule.getRelativeDirPath()),
|
|
100019
100237
|
path: toPosixPath((0, node_path.join)(rule.getRelativeDirPath(), rule.getRelativeFilePath())),
|
|
@@ -100023,8 +100241,8 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100023
100241
|
const location = this.outputRoot === process.cwd() ? "" : ` under '${this.outputRoot}'`;
|
|
100024
100242
|
const rootFiles = files.filter(({ dir }) => dir === ".");
|
|
100025
100243
|
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 ${
|
|
100244
|
+
if (rootBytes > limitBytes) {
|
|
100245
|
+
this.logger.warn(`The generated ${this.toolTarget} root instruction file${location} (${rootFiles.map(({ path }) => path).join(", ")}) is ${rootBytes} bytes, over the ${limitLabel}. ${budget.remedy}`);
|
|
100028
100246
|
return;
|
|
100029
100247
|
}
|
|
100030
100248
|
const reportedDirs = [];
|
|
@@ -100033,9 +100251,9 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
100033
100251
|
if (reportedDirs.some((dir) => isAncestorOrSelf(dir, file.dir))) continue;
|
|
100034
100252
|
const chain = files.filter(({ dir }) => isAncestorOrSelf(dir, file.dir));
|
|
100035
100253
|
const chainBytes = chain.reduce((sum, { bytes }) => sum + bytes, 0);
|
|
100036
|
-
if (chainBytes >
|
|
100254
|
+
if (chainBytes > limitBytes) {
|
|
100037
100255
|
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 ${
|
|
100256
|
+
this.logger.warn(`The generated ${this.toolTarget} instruction files${location} loaded while working in '${file.dir}' (${chain.map(({ path }) => path).join(", ")}) total ${chainBytes} bytes, over the ${limitLabel} (as is every directory below it). ${budget.remedy}`);
|
|
100039
100257
|
}
|
|
100040
100258
|
}
|
|
100041
100259
|
}
|