rulesync 34.0.1 → 36.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/README.md +3 -3
- package/dist/cli/index.cjs +435 -26
- package/dist/cli/index.js +435 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-Bx0zNGBx.cjs → import-DVcemXlR.cjs} +1850 -305
- package/dist/{import-DFwX4TTE.js → import-Tya9qZRK.js} +1819 -280
- package/dist/{import-DFwX4TTE.js.map → import-Tya9qZRK.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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$422 } = 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$422(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$422(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
473
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
475
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
476
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
477
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
478
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
479
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
480
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
481
|
+
join$422(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
482
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$422(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
483
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$422(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$422(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$422(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
489
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$422(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";
|
|
@@ -605,6 +605,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
605
605
|
"reasonix",
|
|
606
606
|
"roo",
|
|
607
607
|
"tabnine",
|
|
608
|
+
"trae",
|
|
608
609
|
"devin",
|
|
609
610
|
"vibe",
|
|
610
611
|
"warp",
|
|
@@ -698,6 +699,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
698
699
|
"hermesagent",
|
|
699
700
|
"junie",
|
|
700
701
|
"kilo",
|
|
702
|
+
"kimi-code",
|
|
701
703
|
"kimi-code-plugin",
|
|
702
704
|
"kiro",
|
|
703
705
|
"kiro-cli",
|
|
@@ -714,6 +716,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
714
716
|
"rovodev",
|
|
715
717
|
"tabnine",
|
|
716
718
|
"takt",
|
|
719
|
+
"trae",
|
|
717
720
|
"devin",
|
|
718
721
|
"warp",
|
|
719
722
|
"zcode",
|
|
@@ -1405,20 +1408,22 @@ async function fileExists(filepath) {
|
|
|
1405
1408
|
return false;
|
|
1406
1409
|
}
|
|
1407
1410
|
}
|
|
1408
|
-
|
|
1411
|
+
/**
|
|
1412
|
+
* The size of a file and whether it carries any execute bit, read with a single
|
|
1413
|
+
* `stat`. Windows reports no execute bits, so a file there is never seen as
|
|
1414
|
+
* executable.
|
|
1415
|
+
*/
|
|
1416
|
+
async function getFileSizeAndExecutableBit(filepath) {
|
|
1409
1417
|
try {
|
|
1410
|
-
|
|
1418
|
+
const stats = await (0, node_fs_promises.stat)(filepath);
|
|
1419
|
+
return {
|
|
1420
|
+
size: stats.size,
|
|
1421
|
+
executable: (stats.mode & 73) !== 0
|
|
1422
|
+
};
|
|
1411
1423
|
} catch (error) {
|
|
1412
|
-
throw new Error(`Failed to
|
|
1424
|
+
throw new Error(`Failed to stat "${filepath}": ${formatError(error)}`, { cause: error });
|
|
1413
1425
|
}
|
|
1414
1426
|
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Whether a file carries any execute bit. Windows reports none, so a file
|
|
1417
|
-
* there is never seen as executable.
|
|
1418
|
-
*/
|
|
1419
|
-
async function hasExecutableBit(filepath) {
|
|
1420
|
-
return ((await (0, node_fs_promises.stat)(filepath)).mode & 73) !== 0;
|
|
1421
|
-
}
|
|
1422
1427
|
async function isSymlink(filepath) {
|
|
1423
1428
|
try {
|
|
1424
1429
|
return (await (0, node_fs_promises.lstat)(filepath)).isSymbolicLink();
|
|
@@ -5289,7 +5294,7 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
5289
5294
|
* `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
|
|
5290
5295
|
* hooks are supported.
|
|
5291
5296
|
* @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
|
|
5292
|
-
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/
|
|
5297
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/v9.28.0/packages/core/hooks/src/schema.ts
|
|
5293
5298
|
*/
|
|
5294
5299
|
const GITLABDUO_HOOK_EVENTS = [
|
|
5295
5300
|
"sessionStart",
|
|
@@ -5643,6 +5648,7 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
5643
5648
|
*
|
|
5644
5649
|
* @see https://www.codebuddy.ai/docs/cli/hooks
|
|
5645
5650
|
* @see https://www.codebuddy.ai/docs/cli/plugins-reference
|
|
5651
|
+
* @see https://www.npmjs.com/package/@tencent-ai/codebuddy-code/v/2.167.0
|
|
5646
5652
|
*/
|
|
5647
5653
|
const CODEBUDDY_HOOK_EVENTS = [
|
|
5648
5654
|
"sessionStart",
|
|
@@ -8168,7 +8174,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8168
8174
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
8169
8175
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
8170
8176
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8171
|
-
* `todoWrite`, `
|
|
8177
|
+
* `todoWrite`, `codeModeOnly`, `freeform`, `workflowsEnabled`, `workflowNameOnly`,
|
|
8178
|
+
* `executionSandbox`) and
|
|
8172
8179
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8173
8180
|
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8174
8181
|
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
@@ -8187,7 +8194,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8187
8194
|
* changes what the file said is reported in either scope, naming what that key
|
|
8188
8195
|
* decides there — the autonomy and containment
|
|
8189
8196
|
* controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
|
|
8190
|
-
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8197
|
+
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8198
|
+
* `codeModeOnly`, `freeform`, and `eager`, which
|
|
8191
8199
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8192
8200
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8193
8201
|
* classifier config, the skill kill-switches, and, because these groups are
|
|
@@ -10880,13 +10888,15 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
10880
10888
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
10881
10889
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10882
10890
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10883
|
-
metadata: zod_mini.z.optional(zod_mini.z.unknown())
|
|
10891
|
+
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10892
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
10884
10893
|
})),
|
|
10885
10894
|
mimocode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10886
10895
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
10887
10896
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10888
10897
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10889
|
-
metadata: zod_mini.z.optional(zod_mini.z.unknown())
|
|
10898
|
+
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
10899
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
10890
10900
|
})),
|
|
10891
10901
|
kilo: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10892
10902
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -11339,7 +11349,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11339
11349
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11340
11350
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11341
11351
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11342
|
-
* commandcode, lettacode). Each tool's own section may override that
|
|
11352
|
+
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11343
11353
|
* default with a per-target value. A defined section value (including `false`)
|
|
11344
11354
|
* always wins over the root default.
|
|
11345
11355
|
*
|
|
@@ -13538,8 +13548,8 @@ function diffSharedConfigOwnedKeys({ fileKey, existingContent, newContent }) {
|
|
|
13538
13548
|
return operations;
|
|
13539
13549
|
}
|
|
13540
13550
|
const READ_TOOL_NAME = "Read";
|
|
13541
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${
|
|
13542
|
-
const buildReadDenyEntry = (pattern) => `${
|
|
13551
|
+
const isReadDenyEntry = (entry, readToolName = READ_TOOL_NAME) => entry.startsWith(`${readToolName}(`) && entry.endsWith(")");
|
|
13552
|
+
const buildReadDenyEntry = (pattern, readToolName = READ_TOOL_NAME) => `${readToolName}(${pattern})`;
|
|
13543
13553
|
const parsePermissionsBlock = (settings) => {
|
|
13544
13554
|
const permissions = settings.permissions ?? {};
|
|
13545
13555
|
return {
|
|
@@ -13563,9 +13573,9 @@ const withPermissions = (settings, next) => {
|
|
|
13563
13573
|
};
|
|
13564
13574
|
};
|
|
13565
13575
|
const applyIgnoreReadDenies = (params) => {
|
|
13566
|
-
const { settings, readDenies } = params;
|
|
13576
|
+
const { settings, readDenies, readToolName = READ_TOOL_NAME } = params;
|
|
13567
13577
|
const current = parsePermissionsBlock(settings);
|
|
13568
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
13578
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry, readToolName) || readDenies.includes(entry));
|
|
13569
13579
|
return withPermissions(settings, {
|
|
13570
13580
|
allow: current.allow,
|
|
13571
13581
|
ask: current.ask,
|
|
@@ -17892,6 +17902,7 @@ const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
|
17892
17902
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
17893
17903
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
17894
17904
|
const ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME = "plugin.json";
|
|
17905
|
+
const ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS = [".antigravity-plugin", ".gemini-plugin"];
|
|
17895
17906
|
const AUGMENTCODE_PLUGIN_RULES_DIR = "rules";
|
|
17896
17907
|
const AUGMENTCODE_PLUGIN_COMMANDS_DIR = "commands";
|
|
17897
17908
|
const AUGMENTCODE_PLUGIN_AGENTS_DIR = "agents";
|
|
@@ -19538,9 +19549,36 @@ function commandSlug(relativeFilePath) {
|
|
|
19538
19549
|
* skill. Once the command is removed from every source tree's `commands/`
|
|
19539
19550
|
* directory, the directory stops matching and the skills feature cleans
|
|
19540
19551
|
* it up as a regular orphan.
|
|
19552
|
+
*
|
|
19553
|
+
* When `toolTarget` is given, only commands whose `targets` include it (or
|
|
19554
|
+
* `*`) count: a command aimed at other tools never reaches this tool's skills
|
|
19555
|
+
* tree, so it must not shadow a same-named skill there. Commands that cannot
|
|
19556
|
+
* be parsed are skipped, as the commands feature reports them itself.
|
|
19541
19557
|
*/
|
|
19542
|
-
async function rulesyncCommandSlugExists({ inputRoots, dirName }) {
|
|
19543
|
-
|
|
19558
|
+
async function rulesyncCommandSlugExists({ inputRoots, dirName, toolTarget }) {
|
|
19559
|
+
const matching = (await Promise.all(inputRoots.map(async (root) => {
|
|
19560
|
+
const commandsDirPath = (0, node_path.join)(root, COMMANDS_FEATURE_SUBDIR);
|
|
19561
|
+
return (await findFilesByGlobs("**/*.md", { cwd: commandsDirPath })).map((filePath) => ({
|
|
19562
|
+
root,
|
|
19563
|
+
commandsDirPath,
|
|
19564
|
+
filePath
|
|
19565
|
+
}));
|
|
19566
|
+
}))).flat().filter(({ filePath }) => commandSlug((0, node_path.basename)(filePath)) === dirName);
|
|
19567
|
+
if (toolTarget === void 0) return matching.length > 0;
|
|
19568
|
+
for (const { root, commandsDirPath, filePath } of matching) {
|
|
19569
|
+
let targets;
|
|
19570
|
+
try {
|
|
19571
|
+
targets = (await RulesyncCommand.fromFile({
|
|
19572
|
+
outputRoot: root,
|
|
19573
|
+
relativeDirPath: COMMANDS_FEATURE_SUBDIR,
|
|
19574
|
+
relativeFilePath: (0, node_path.relative)(commandsDirPath, filePath)
|
|
19575
|
+
})).getFrontmatter().targets;
|
|
19576
|
+
} catch {
|
|
19577
|
+
continue;
|
|
19578
|
+
}
|
|
19579
|
+
if (targets.includes("*") || targets.includes(toolTarget)) return true;
|
|
19580
|
+
}
|
|
19581
|
+
return false;
|
|
19544
19582
|
}
|
|
19545
19583
|
//#endregion
|
|
19546
19584
|
//#region src/features/commands/devin-command.ts
|
|
@@ -19558,7 +19596,7 @@ const DEVIN_COMMAND_SECTION_KEYS = [
|
|
|
19558
19596
|
"permissions",
|
|
19559
19597
|
"triggers"
|
|
19560
19598
|
];
|
|
19561
|
-
function commandSkillContent$
|
|
19599
|
+
function commandSkillContent$2(rulesyncCommand) {
|
|
19562
19600
|
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
19563
19601
|
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
19564
19602
|
const description = frontmatter.description ?? `${slug} command`;
|
|
@@ -19632,7 +19670,7 @@ var DevinCommand = class DevinCommand extends ToolCommand {
|
|
|
19632
19670
|
relativeDirPath: paths.relativeDirPath,
|
|
19633
19671
|
relativeFilePath: SKILL_FILE_NAME,
|
|
19634
19672
|
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
19635
|
-
fileContent: commandSkillContent$
|
|
19673
|
+
fileContent: commandSkillContent$2(rulesyncCommand)
|
|
19636
19674
|
});
|
|
19637
19675
|
}
|
|
19638
19676
|
getFileContent() {
|
|
@@ -21452,6 +21490,180 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
21452
21490
|
}
|
|
21453
21491
|
};
|
|
21454
21492
|
//#endregion
|
|
21493
|
+
//#region src/constants/kimi-code-paths.ts
|
|
21494
|
+
const KIMI_CODE_DIR = ".kimi-code";
|
|
21495
|
+
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
21496
|
+
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
21497
|
+
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
21498
|
+
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
21499
|
+
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
21500
|
+
const KIMI_CODE_SKILLS_DIR_PATH = (0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
21501
|
+
(0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
21502
|
+
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
21503
|
+
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = (0, node_path.join)(".agents", "agents");
|
|
21504
|
+
//#endregion
|
|
21505
|
+
//#region src/utils/kimi-code.ts
|
|
21506
|
+
function getKimiCodeHome() {
|
|
21507
|
+
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
21508
|
+
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
21509
|
+
}
|
|
21510
|
+
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
21511
|
+
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
21512
|
+
}
|
|
21513
|
+
/**
|
|
21514
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
21515
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
21516
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
21517
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
21518
|
+
*/
|
|
21519
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
21520
|
+
return [{
|
|
21521
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
21522
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21523
|
+
}, {
|
|
21524
|
+
relativeDirPath: ".",
|
|
21525
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21526
|
+
}];
|
|
21527
|
+
}
|
|
21528
|
+
/**
|
|
21529
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
21530
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
21531
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
21532
|
+
*/
|
|
21533
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
21534
|
+
return sharedConfigFileKey({
|
|
21535
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
21536
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21537
|
+
});
|
|
21538
|
+
}
|
|
21539
|
+
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
21540
|
+
return getToolRulesyncOutputRoot({
|
|
21541
|
+
nativeOutputRoot,
|
|
21542
|
+
global,
|
|
21543
|
+
toolHome: getKimiCodeHome
|
|
21544
|
+
});
|
|
21545
|
+
}
|
|
21546
|
+
//#endregion
|
|
21547
|
+
//#region src/features/commands/kimi-code-command.ts
|
|
21548
|
+
/**
|
|
21549
|
+
* Kimi Code SKILL.md frontmatter keys a command may author through its
|
|
21550
|
+
* `kimi-code:` section, each with the alias spellings Kimi also accepts. An
|
|
21551
|
+
* alias is normalized to the canonical key; the canonical spelling wins when
|
|
21552
|
+
* both are present.
|
|
21553
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21554
|
+
*/
|
|
21555
|
+
const KIMI_CODE_COMMAND_SECTION_KEYS = [
|
|
21556
|
+
{
|
|
21557
|
+
key: "type",
|
|
21558
|
+
aliases: []
|
|
21559
|
+
},
|
|
21560
|
+
{
|
|
21561
|
+
key: "whenToUse",
|
|
21562
|
+
aliases: ["when-to-use", "when_to_use"]
|
|
21563
|
+
},
|
|
21564
|
+
{
|
|
21565
|
+
key: "disableModelInvocation",
|
|
21566
|
+
aliases: ["disable-model-invocation", "disable_model_invocation"]
|
|
21567
|
+
},
|
|
21568
|
+
{
|
|
21569
|
+
key: "arguments",
|
|
21570
|
+
aliases: []
|
|
21571
|
+
}
|
|
21572
|
+
];
|
|
21573
|
+
function pickKimiCodeSectionKeys(section) {
|
|
21574
|
+
const picked = {};
|
|
21575
|
+
for (const { key, aliases } of KIMI_CODE_COMMAND_SECTION_KEYS) {
|
|
21576
|
+
const spelling = [key, ...aliases].find((candidate) => section[candidate] !== void 0);
|
|
21577
|
+
if (spelling !== void 0) picked[key] = section[spelling];
|
|
21578
|
+
}
|
|
21579
|
+
return picked;
|
|
21580
|
+
}
|
|
21581
|
+
function commandSkillContent$1(rulesyncCommand) {
|
|
21582
|
+
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
21583
|
+
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
21584
|
+
const description = frontmatter.description?.trim() || `${slug} command`;
|
|
21585
|
+
return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
|
|
21586
|
+
name: slug,
|
|
21587
|
+
description,
|
|
21588
|
+
disableModelInvocation: true,
|
|
21589
|
+
...pickKimiCodeSectionKeys(frontmatter["kimi-code"] ?? {})
|
|
21590
|
+
});
|
|
21591
|
+
}
|
|
21592
|
+
/**
|
|
21593
|
+
* Represents a Kimi Code slash command, emitted as a Kimi Code Skill.
|
|
21594
|
+
*
|
|
21595
|
+
* Kimi Code has no standalone custom-command directory outside plugins: every
|
|
21596
|
+
* external Skill is registered as a slash command (`/skill:<name>`, with a
|
|
21597
|
+
* `/<name>` shorthand when the bare name is free), and the Skill body supports
|
|
21598
|
+
* `$ARGUMENTS`, positional `$0` / `$1`, and named `$<name>` placeholders.
|
|
21599
|
+
* Commands are therefore emitted onto the native skills surface, one
|
|
21600
|
+
* `SKILL.md` per command: `.kimi-code/skills/<slug>/SKILL.md` (project) and
|
|
21601
|
+
* `~/.kimi-code/skills/<slug>/SKILL.md` (global; `$KIMI_CODE_HOME/skills/`
|
|
21602
|
+
* when `KIMI_CODE_HOME` is set). Directory-form `SKILL.md` requires both
|
|
21603
|
+
* `name` and `description`, so both are always written.
|
|
21604
|
+
*
|
|
21605
|
+
* Import and deletion are intentionally no-ops for this target: the skills
|
|
21606
|
+
* feature owns the `.kimi-code/skills/` tree, so importing it as commands
|
|
21607
|
+
* would double-import every skill (mirrors the Devin, Warp and Hermes Agent
|
|
21608
|
+
* commands targets).
|
|
21609
|
+
*
|
|
21610
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21611
|
+
* @see https://moonshotai.github.io/kimi-code/en/reference/slash-commands.html
|
|
21612
|
+
*/
|
|
21613
|
+
var KimiCodeCommand = class KimiCodeCommand extends ToolCommand {
|
|
21614
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
21615
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
21616
|
+
rulesyncCommand,
|
|
21617
|
+
toolTarget: "kimi-code"
|
|
21618
|
+
});
|
|
21619
|
+
}
|
|
21620
|
+
static getSettablePaths({ global = false } = {}) {
|
|
21621
|
+
return { relativeDirPath: getKimiCodeRelativeDirPath({
|
|
21622
|
+
global,
|
|
21623
|
+
relativeDirPath: KIMI_CODE_SKILLS_DIR_NAME
|
|
21624
|
+
}) };
|
|
21625
|
+
}
|
|
21626
|
+
constructor({ slug, ...params }) {
|
|
21627
|
+
super({
|
|
21628
|
+
...params,
|
|
21629
|
+
...slug !== void 0 && {
|
|
21630
|
+
relativeDirPath: (0, node_path.join)(params.relativeDirPath, slug),
|
|
21631
|
+
relativeFilePath: "SKILL.md"
|
|
21632
|
+
}
|
|
21633
|
+
});
|
|
21634
|
+
}
|
|
21635
|
+
validate() {
|
|
21636
|
+
return {
|
|
21637
|
+
success: true,
|
|
21638
|
+
error: null
|
|
21639
|
+
};
|
|
21640
|
+
}
|
|
21641
|
+
toRulesyncCommand() {
|
|
21642
|
+
const slug = (0, node_path.basename)((0, node_path.dirname)(this.getRelativePathFromCwd()));
|
|
21643
|
+
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
21644
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
21645
|
+
return new RulesyncCommand({
|
|
21646
|
+
relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
|
|
21647
|
+
relativeFilePath: `${slug}.md`,
|
|
21648
|
+
frontmatter: { description },
|
|
21649
|
+
body: body.trimStart()
|
|
21650
|
+
});
|
|
21651
|
+
}
|
|
21652
|
+
static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
|
|
21653
|
+
const paths = KimiCodeCommand.getSettablePaths({ global });
|
|
21654
|
+
return new KimiCodeCommand({
|
|
21655
|
+
outputRoot,
|
|
21656
|
+
relativeDirPath: paths.relativeDirPath,
|
|
21657
|
+
relativeFilePath: SKILL_FILE_NAME,
|
|
21658
|
+
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
21659
|
+
fileContent: commandSkillContent$1(rulesyncCommand)
|
|
21660
|
+
});
|
|
21661
|
+
}
|
|
21662
|
+
getFileContent() {
|
|
21663
|
+
return this.fileContent;
|
|
21664
|
+
}
|
|
21665
|
+
};
|
|
21666
|
+
//#endregion
|
|
21455
21667
|
//#region src/features/commands/kimi-code-plugin-command.ts
|
|
21456
21668
|
/**
|
|
21457
21669
|
* Kimi Code reads only `name` and `description` from a plugin command. The
|
|
@@ -21606,6 +21818,14 @@ const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
|
|
|
21606
21818
|
const KIRO_MCP_FILE_NAME = "mcp.json";
|
|
21607
21819
|
const KIRO_IGNORE_FILE_NAME = ".kiroignore";
|
|
21608
21820
|
const KIRO_GLOBAL_IGNORE_FILE_NAME = "kiroignore";
|
|
21821
|
+
/**
|
|
21822
|
+
* Kiro's capability-based permission rules (IDE 1.0, CLI V3). The user-level
|
|
21823
|
+
* file lives at `~/.kiro/settings/permissions.yaml`; the workspace-level file
|
|
21824
|
+
* sits under a hash-derived `~/.kiro/workspace-roots/<hash>/` directory outside
|
|
21825
|
+
* the repository, so only the user-level file is generated.
|
|
21826
|
+
* @see https://kiro.dev/docs/permissions/
|
|
21827
|
+
*/
|
|
21828
|
+
const KIRO_PERMISSIONS_FILE_NAME = "permissions.yaml";
|
|
21609
21829
|
//#endregion
|
|
21610
21830
|
//#region src/features/commands/kiro-command.ts
|
|
21611
21831
|
var KiroCommand = class KiroCommand extends ToolCommand {
|
|
@@ -23618,6 +23838,193 @@ var TaktCommand = class TaktCommand extends ToolCommand {
|
|
|
23618
23838
|
}
|
|
23619
23839
|
};
|
|
23620
23840
|
//#endregion
|
|
23841
|
+
//#region src/constants/trae-paths.ts
|
|
23842
|
+
/**
|
|
23843
|
+
* Trae (ByteDance's AI IDE, "TraeCode" in its current docs) keeps project
|
|
23844
|
+
* configuration under `.trae/` and user configuration under `~/.trae/`.
|
|
23845
|
+
*
|
|
23846
|
+
* @see https://docs.trae.ai/ide/rules?_lang=en
|
|
23847
|
+
* @see https://docs.trae.ai/ide/skills?_lang=en
|
|
23848
|
+
* @see https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
23849
|
+
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23850
|
+
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
23851
|
+
*/
|
|
23852
|
+
const TRAE_DIR = ".trae";
|
|
23853
|
+
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
23854
|
+
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
23855
|
+
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
23856
|
+
const TRAE_SKILLS_DIR_PATH = (0, node_path.join)(TRAE_DIR, "skills");
|
|
23857
|
+
/**
|
|
23858
|
+
* Custom commands: Markdown files under `.trae/commands/` (project, up to three nested
|
|
23859
|
+
* directory levels) and `~/.trae/commands/` (global).
|
|
23860
|
+
*/
|
|
23861
|
+
const TRAE_COMMANDS_DIR_PATH = (0, node_path.join)(TRAE_DIR, "commands");
|
|
23862
|
+
/** Indexing ignore list (gitignore syntax), `.trae/.ignore`, project scope only. */
|
|
23863
|
+
const TRAE_IGNORE_FILE_NAME = ".ignore";
|
|
23864
|
+
//#endregion
|
|
23865
|
+
//#region src/features/commands/trae-command.ts
|
|
23866
|
+
const TraeCommandFrontmatterSchema = zod_mini.z.looseObject({
|
|
23867
|
+
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
23868
|
+
name: zod_mini.z.optional(zod_mini.z.string())
|
|
23869
|
+
});
|
|
23870
|
+
/**
|
|
23871
|
+
* Trae reads project commands only up to three directory levels below
|
|
23872
|
+
* `.trae/commands/`; a command file nested deeper is never picked up.
|
|
23873
|
+
*/
|
|
23874
|
+
const TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH = 3;
|
|
23875
|
+
/**
|
|
23876
|
+
* Custom command for Trae.
|
|
23877
|
+
*
|
|
23878
|
+
* Trae reads Markdown command files under `.trae/commands/` (project, up to
|
|
23879
|
+
* three levels of nested directories) and `~/.trae/commands/` (global). The
|
|
23880
|
+
* YAML frontmatter carries `name` and `description`, and the body below the
|
|
23881
|
+
* closing `---` holds the instructions. rulesync writes the rulesync
|
|
23882
|
+
* `description`, a `name` equal to the file name stem (as Trae's editor does),
|
|
23883
|
+
* plus any fields of the `trae` section (which win over the derived ones), and
|
|
23884
|
+
* keeps nested command paths.
|
|
23885
|
+
*
|
|
23886
|
+
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23887
|
+
*/
|
|
23888
|
+
var TraeCommand = class TraeCommand extends ToolCommand {
|
|
23889
|
+
frontmatter;
|
|
23890
|
+
body;
|
|
23891
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
23892
|
+
if (rest.validate) {
|
|
23893
|
+
const result = TraeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
23894
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
23895
|
+
}
|
|
23896
|
+
super({
|
|
23897
|
+
...rest,
|
|
23898
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
23899
|
+
});
|
|
23900
|
+
this.frontmatter = frontmatter;
|
|
23901
|
+
this.body = body;
|
|
23902
|
+
}
|
|
23903
|
+
static getSettablePaths(_options = {}) {
|
|
23904
|
+
return { relativeDirPath: TRAE_COMMANDS_DIR_PATH };
|
|
23905
|
+
}
|
|
23906
|
+
getBody() {
|
|
23907
|
+
return this.body;
|
|
23908
|
+
}
|
|
23909
|
+
getFrontmatter() {
|
|
23910
|
+
return this.frontmatter;
|
|
23911
|
+
}
|
|
23912
|
+
toRulesyncCommand() {
|
|
23913
|
+
const { description, name, ...otherFields } = this.frontmatter;
|
|
23914
|
+
const restFields = name === void 0 || name === (0, node_path.basename)(this.relativeFilePath, ".md") ? otherFields : {
|
|
23915
|
+
name,
|
|
23916
|
+
...otherFields
|
|
23917
|
+
};
|
|
23918
|
+
const rulesyncFrontmatter = {
|
|
23919
|
+
targets: ["*"],
|
|
23920
|
+
description,
|
|
23921
|
+
...Object.keys(restFields).length > 0 && { trae: restFields }
|
|
23922
|
+
};
|
|
23923
|
+
return new RulesyncCommand({
|
|
23924
|
+
outputRoot: ".",
|
|
23925
|
+
frontmatter: rulesyncFrontmatter,
|
|
23926
|
+
body: this.body,
|
|
23927
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
23928
|
+
relativeFilePath: this.relativeFilePath,
|
|
23929
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
23930
|
+
validate: true
|
|
23931
|
+
});
|
|
23932
|
+
}
|
|
23933
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
23934
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
23935
|
+
const { name: _name, ...traeFields } = rulesyncFrontmatter.trae ?? {};
|
|
23936
|
+
const traeFrontmatter = {
|
|
23937
|
+
name: this.deriveName(rulesyncCommand),
|
|
23938
|
+
description: rulesyncFrontmatter.description,
|
|
23939
|
+
...traeFields
|
|
23940
|
+
};
|
|
23941
|
+
return new TraeCommand({
|
|
23942
|
+
outputRoot,
|
|
23943
|
+
frontmatter: traeFrontmatter,
|
|
23944
|
+
body: rulesyncCommand.getBody(),
|
|
23945
|
+
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
23946
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
23947
|
+
validate
|
|
23948
|
+
});
|
|
23949
|
+
}
|
|
23950
|
+
validate() {
|
|
23951
|
+
if (!this.frontmatter) return {
|
|
23952
|
+
success: true,
|
|
23953
|
+
error: null
|
|
23954
|
+
};
|
|
23955
|
+
const result = TraeCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
23956
|
+
if (result.success) return {
|
|
23957
|
+
success: true,
|
|
23958
|
+
error: null
|
|
23959
|
+
};
|
|
23960
|
+
return {
|
|
23961
|
+
success: false,
|
|
23962
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${(0, node_path.join)(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
23963
|
+
};
|
|
23964
|
+
}
|
|
23965
|
+
/**
|
|
23966
|
+
* Warn about commands Trae will not load as written: Trae documents `name`
|
|
23967
|
+
* as the command's unique identifier, so two commands deriving the same
|
|
23968
|
+
* `name` (e.g. `a/review.md` and `b/review.md`) collide, and it reads
|
|
23969
|
+
* project commands only up to three nested directory levels. Both files are
|
|
23970
|
+
* still written, so this warns rather than failing the run.
|
|
23971
|
+
*/
|
|
23972
|
+
static validateRulesyncCommands({ rulesyncCommands, logger, global = false }) {
|
|
23973
|
+
const pathsByName = /* @__PURE__ */ new Map();
|
|
23974
|
+
for (const rulesyncCommand of rulesyncCommands) {
|
|
23975
|
+
if (!this.isTargetedByRulesyncCommand(rulesyncCommand)) continue;
|
|
23976
|
+
const relativeFilePath = toPosixPath(rulesyncCommand.getRelativeFilePath());
|
|
23977
|
+
const name = this.deriveName(rulesyncCommand);
|
|
23978
|
+
if (typeof name === "string") pathsByName.set(name, [...pathsByName.get(name) ?? [], relativeFilePath]);
|
|
23979
|
+
const dirDepth = relativeFilePath.split("/").length - 1;
|
|
23980
|
+
if (!global && dirDepth > TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH) logger.warn(`Trae reads project commands only up to ${TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH} nested directory levels, so ${quoteForLog(relativeFilePath)} is written but never loaded. Move it to a shallower directory.`);
|
|
23981
|
+
}
|
|
23982
|
+
for (const [name, paths] of pathsByName) {
|
|
23983
|
+
if (paths.length < 2) continue;
|
|
23984
|
+
logger.warn(`Trae command name ${quoteForLog(name)} is shared by ${paths.map(quoteForLog).join(", ")}, but Trae treats \`name\` as a unique identifier. Rename a file or set \`trae.name\` to tell them apart.`);
|
|
23985
|
+
}
|
|
23986
|
+
}
|
|
23987
|
+
/**
|
|
23988
|
+
* The `name` Trae sees for a command: the `trae` section's `name` when set,
|
|
23989
|
+
* otherwise the file name stem. A non-string override is returned as is so
|
|
23990
|
+
* frontmatter validation still rejects it.
|
|
23991
|
+
*/
|
|
23992
|
+
static deriveName(rulesyncCommand) {
|
|
23993
|
+
return rulesyncCommand.getFrontmatter().trae?.name ?? (0, node_path.basename)(rulesyncCommand.getRelativeFilePath(), ".md");
|
|
23994
|
+
}
|
|
23995
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
23996
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
23997
|
+
rulesyncCommand,
|
|
23998
|
+
toolTarget: "trae"
|
|
23999
|
+
});
|
|
24000
|
+
}
|
|
24001
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
24002
|
+
const paths = this.getSettablePaths({ global });
|
|
24003
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
24004
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
24005
|
+
const result = TraeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
24006
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
24007
|
+
return new TraeCommand({
|
|
24008
|
+
outputRoot,
|
|
24009
|
+
relativeDirPath: paths.relativeDirPath,
|
|
24010
|
+
relativeFilePath,
|
|
24011
|
+
frontmatter: result.data,
|
|
24012
|
+
body: body.trim(),
|
|
24013
|
+
validate
|
|
24014
|
+
});
|
|
24015
|
+
}
|
|
24016
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
24017
|
+
return new TraeCommand({
|
|
24018
|
+
outputRoot,
|
|
24019
|
+
relativeDirPath,
|
|
24020
|
+
relativeFilePath,
|
|
24021
|
+
frontmatter: { description: "" },
|
|
24022
|
+
body: "",
|
|
24023
|
+
validate: false
|
|
24024
|
+
});
|
|
24025
|
+
}
|
|
24026
|
+
};
|
|
24027
|
+
//#endregion
|
|
23621
24028
|
//#region src/constants/warp-paths.ts
|
|
23622
24029
|
const WARP_DIR = ".warp";
|
|
23623
24030
|
const WARP_SKILLS_DIR_PATH = (0, node_path.join)(WARP_DIR, "skills");
|
|
@@ -24168,6 +24575,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24168
24575
|
supportsSubdirectory: false
|
|
24169
24576
|
}
|
|
24170
24577
|
}],
|
|
24578
|
+
["kimi-code", {
|
|
24579
|
+
class: KimiCodeCommand,
|
|
24580
|
+
meta: {
|
|
24581
|
+
extension: "md",
|
|
24582
|
+
supportsProject: true,
|
|
24583
|
+
supportsGlobal: true,
|
|
24584
|
+
isSimulated: false,
|
|
24585
|
+
supportsSubdirectory: false,
|
|
24586
|
+
skipToolFileScan: true
|
|
24587
|
+
}
|
|
24588
|
+
}],
|
|
24171
24589
|
["kimi-code-plugin", {
|
|
24172
24590
|
class: KimiCodePluginCommand,
|
|
24173
24591
|
meta: {
|
|
@@ -24328,6 +24746,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24328
24746
|
supportsSubdirectory: false
|
|
24329
24747
|
}
|
|
24330
24748
|
}],
|
|
24749
|
+
["trae", {
|
|
24750
|
+
class: TraeCommand,
|
|
24751
|
+
meta: {
|
|
24752
|
+
extension: "md",
|
|
24753
|
+
supportsProject: true,
|
|
24754
|
+
supportsGlobal: true,
|
|
24755
|
+
isSimulated: false,
|
|
24756
|
+
supportsSubdirectory: true
|
|
24757
|
+
}
|
|
24758
|
+
}],
|
|
24331
24759
|
["devin", {
|
|
24332
24760
|
class: DevinCommand,
|
|
24333
24761
|
meta: {
|
|
@@ -24411,7 +24839,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
24411
24839
|
await factory.class.validateRulesyncCommands?.({
|
|
24412
24840
|
inputRoots: this.inputRoots,
|
|
24413
24841
|
rulesyncCommands,
|
|
24414
|
-
logger: this.logger
|
|
24842
|
+
logger: this.logger,
|
|
24843
|
+
global: this.global
|
|
24415
24844
|
});
|
|
24416
24845
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
24417
24846
|
const toolCommands = [];
|
|
@@ -26091,8 +26520,8 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
26091
26520
|
* side either way, so the value is dropped rather than carried into a config
|
|
26092
26521
|
* the tool would ignore or refuse.
|
|
26093
26522
|
*/
|
|
26094
|
-
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
26095
|
-
const { canonical, tool, commandOnly, events } = spec;
|
|
26523
|
+
function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction, warn }) {
|
|
26524
|
+
const { canonical, tool, commandOnly, hookTypes, events, ignoredEvents, ignoredWhenFlags } = spec;
|
|
26096
26525
|
const field = direction === "generated" ? canonical : tool;
|
|
26097
26526
|
if (!isFieldApplicable({
|
|
26098
26527
|
commandOnly,
|
|
@@ -26101,10 +26530,23 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }
|
|
|
26101
26530
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
26102
26531
|
return false;
|
|
26103
26532
|
}
|
|
26533
|
+
if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
|
|
26534
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${[...hookTypes].map((type) => `"${type}"`).join(", ")} hooks only, so it is not ${direction}.`);
|
|
26535
|
+
return false;
|
|
26536
|
+
}
|
|
26104
26537
|
if (events !== void 0 && !events.has(eventName)) {
|
|
26105
26538
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${[...events].map((event) => `"${event}"`).join(", ")} only, so it is not ${direction}.`);
|
|
26106
26539
|
return false;
|
|
26107
26540
|
}
|
|
26541
|
+
if (ignoredEvents?.has(eventName)) {
|
|
26542
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on "${eventName}", so it is not ${direction}.`);
|
|
26543
|
+
return false;
|
|
26544
|
+
}
|
|
26545
|
+
const setFlag = hookType === "command" ? ignoredWhenFlags?.find((flag) => flags[flag] === true) : void 0;
|
|
26546
|
+
if (setFlag !== void 0) {
|
|
26547
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on a hook that sets "${setFlag}", so it is not ${direction}.`);
|
|
26548
|
+
return false;
|
|
26549
|
+
}
|
|
26108
26550
|
return true;
|
|
26109
26551
|
}
|
|
26110
26552
|
/**
|
|
@@ -26121,6 +26563,7 @@ function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
|
26121
26563
|
spec,
|
|
26122
26564
|
hookType,
|
|
26123
26565
|
eventName,
|
|
26566
|
+
flags: def,
|
|
26124
26567
|
direction: "generated",
|
|
26125
26568
|
warn
|
|
26126
26569
|
})) emitted[spec.tool] = spec.trueValue;
|
|
@@ -26142,6 +26585,7 @@ function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
|
26142
26585
|
spec,
|
|
26143
26586
|
hookType,
|
|
26144
26587
|
eventName,
|
|
26588
|
+
flags: h,
|
|
26145
26589
|
direction: "imported",
|
|
26146
26590
|
warn
|
|
26147
26591
|
})) continue;
|
|
@@ -28034,6 +28478,20 @@ const CLAUDE_SPEC = {
|
|
|
28034
28478
|
tool: "continueOnBlock"
|
|
28035
28479
|
}
|
|
28036
28480
|
],
|
|
28481
|
+
keywordPassthroughFields: [{
|
|
28482
|
+
canonical: "failClosed",
|
|
28483
|
+
tool: "onFailure",
|
|
28484
|
+
trueValue: "block",
|
|
28485
|
+
falseValue: "continue",
|
|
28486
|
+
hookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
28487
|
+
ignoredEvents: /* @__PURE__ */ new Set([
|
|
28488
|
+
"stop",
|
|
28489
|
+
"subagentStop",
|
|
28490
|
+
"taskCompleted",
|
|
28491
|
+
"teammateIdle"
|
|
28492
|
+
]),
|
|
28493
|
+
ignoredWhenFlags: ["async", "asyncRewake"]
|
|
28494
|
+
}],
|
|
28037
28495
|
arrayPassthroughFields: [{
|
|
28038
28496
|
canonical: "args",
|
|
28039
28497
|
tool: "args",
|
|
@@ -28756,18 +29214,20 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28756
29214
|
canonicalToToolEventNames: CANONICAL_TO_CODEXCLI_EVENT_NAMES,
|
|
28757
29215
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
28758
29216
|
projectDirVar: "",
|
|
28759
|
-
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
29217
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command", "mcp_tool"]),
|
|
28760
29218
|
passthroughFields: ["name", "description"],
|
|
28761
29219
|
stringPassthroughFields: [{
|
|
28762
29220
|
canonical: "commandWindows",
|
|
28763
|
-
tool: "commandWindows"
|
|
29221
|
+
tool: "commandWindows",
|
|
29222
|
+
commandOnly: true
|
|
28764
29223
|
}, {
|
|
28765
29224
|
canonical: "statusMessage",
|
|
28766
29225
|
tool: "statusMessage"
|
|
28767
29226
|
}],
|
|
28768
29227
|
numberPassthroughFields: [{
|
|
28769
29228
|
canonical: "additionalContextLimit",
|
|
28770
|
-
tool: "additionalContextLimit"
|
|
29229
|
+
tool: "additionalContextLimit",
|
|
29230
|
+
commandOnly: true
|
|
28771
29231
|
}],
|
|
28772
29232
|
booleanPassthroughFields: [{
|
|
28773
29233
|
canonical: "async",
|
|
@@ -28776,6 +29236,74 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28776
29236
|
}]
|
|
28777
29237
|
};
|
|
28778
29238
|
/**
|
|
29239
|
+
* Whether a JSON value holds a `null` anywhere. Codex rejects an `mcp_tool`
|
|
29240
|
+
* hook `input` that cannot be represented as TOML (it hashes the handler as
|
|
29241
|
+
* TOML for trust review), and `null` is the one JSON value TOML lacks.
|
|
29242
|
+
*/
|
|
29243
|
+
function containsNull(value) {
|
|
29244
|
+
if (value === null) return true;
|
|
29245
|
+
if (Array.isArray(value)) return value.some(containsNull);
|
|
29246
|
+
if (typeof value === "object") return Object.values(value).some(containsNull);
|
|
29247
|
+
return false;
|
|
29248
|
+
}
|
|
29249
|
+
const WHOLE_FILE_FAILURE = "Codex fails to load the whole hooks file on such a handler";
|
|
29250
|
+
/**
|
|
29251
|
+
* Why this generated handler must not be written, or `undefined` when Codex
|
|
29252
|
+
* can load and run it. Codex deserializes `timeout` as `Option<u64>` on every
|
|
29253
|
+
* handler type, and `server` / `tool` as required strings plus `input` as a
|
|
29254
|
+
* TOML-representable map on `mcp_tool`; a handler that fails that stops the
|
|
29255
|
+
* whole hooks file from loading rather than just that one hook. An empty
|
|
29256
|
+
* `server` / `tool` string still loads but can never resolve to a tool.
|
|
29257
|
+
* https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs
|
|
29258
|
+
*/
|
|
29259
|
+
function describeRejectedHandler(handler) {
|
|
29260
|
+
const { timeout } = handler;
|
|
29261
|
+
if (timeout !== void 0 && (typeof timeout !== "number" || !Number.isSafeInteger(timeout) || timeout < 0)) return `its "timeout" ${JSON.stringify(timeout) ?? String(timeout)} is not a non-negative integer, and ${WHOLE_FILE_FAILURE}`;
|
|
29262
|
+
if (handler.type !== "mcp_tool") return;
|
|
29263
|
+
for (const field of ["server", "tool"]) {
|
|
29264
|
+
const value = handler[field];
|
|
29265
|
+
if (typeof value !== "string") return `it has no "${field}", and ${WHOLE_FILE_FAILURE}`;
|
|
29266
|
+
if (value === "") return `its "${field}" is empty, so it can never resolve to an MCP tool`;
|
|
29267
|
+
}
|
|
29268
|
+
if (containsNull(handler.input)) return `its "input" holds a null, which Codex cannot represent as TOML, and ${WHOLE_FILE_FAILURE}`;
|
|
29269
|
+
}
|
|
29270
|
+
/**
|
|
29271
|
+
* Drop the generated handlers Codex would refuse to load (or could never
|
|
29272
|
+
* run), and warn about the `mcp_tool` ones Codex loads but skips (`SessionEnd`
|
|
29273
|
+
* MCP hooks are skipped with a startup warning). Matcher groups left without
|
|
29274
|
+
* handlers are removed. https://learn.chatgpt.com/docs/hooks
|
|
29275
|
+
*/
|
|
29276
|
+
function filterCodexHandlers({ hooks, logger }) {
|
|
29277
|
+
const result = {};
|
|
29278
|
+
const skippedSessionEndTools = [];
|
|
29279
|
+
for (const [eventName, groups] of Object.entries(hooks)) {
|
|
29280
|
+
const kept = [];
|
|
29281
|
+
for (const group of groups) {
|
|
29282
|
+
const entry = group;
|
|
29283
|
+
if (!Array.isArray(entry.hooks)) {
|
|
29284
|
+
kept.push(group);
|
|
29285
|
+
continue;
|
|
29286
|
+
}
|
|
29287
|
+
const handlers = entry.hooks.filter((handler) => {
|
|
29288
|
+
const reason = describeRejectedHandler(handler);
|
|
29289
|
+
if (reason !== void 0) {
|
|
29290
|
+
logger?.warn(`Skipping a "${String(handler.type)}" hook on Codex CLI "${eventName}": ${reason}.`);
|
|
29291
|
+
return false;
|
|
29292
|
+
}
|
|
29293
|
+
if (eventName === "SessionEnd" && handler.type === "mcp_tool") skippedSessionEndTools.push(`${String(handler.server)}/${String(handler.tool)}`);
|
|
29294
|
+
return true;
|
|
29295
|
+
});
|
|
29296
|
+
if (handlers.length > 0) kept.push({
|
|
29297
|
+
...entry,
|
|
29298
|
+
hooks: handlers
|
|
29299
|
+
});
|
|
29300
|
+
}
|
|
29301
|
+
if (kept.length > 0) result[eventName] = kept;
|
|
29302
|
+
}
|
|
29303
|
+
if (skippedSessionEndTools.length > 0) logger?.warn(`Codex CLI skips "mcp_tool" hooks on "SessionEnd" with a startup warning, so these are written but will not run: ${skippedSessionEndTools.join(", ")}.`);
|
|
29304
|
+
return result;
|
|
29305
|
+
}
|
|
29306
|
+
/**
|
|
28779
29307
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
28780
29308
|
* Reads the existing file (if any), parses TOML, and returns the content without writing to
|
|
28781
29309
|
* disk. The caller is responsible for writing via the normal write phase.
|
|
@@ -28868,10 +29396,13 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
28868
29396
|
const config = rulesyncHooks.getJson();
|
|
28869
29397
|
const merged = mergeGeneratedHookLists({
|
|
28870
29398
|
existingContent,
|
|
28871
|
-
generatedHooks:
|
|
28872
|
-
|
|
28873
|
-
|
|
28874
|
-
|
|
29399
|
+
generatedHooks: filterCodexHandlers({
|
|
29400
|
+
hooks: canonicalToToolHooks({
|
|
29401
|
+
config,
|
|
29402
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
29403
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
29404
|
+
logger
|
|
29405
|
+
}),
|
|
28875
29406
|
logger
|
|
28876
29407
|
}),
|
|
28877
29408
|
shape: "matcher-groups",
|
|
@@ -32050,60 +32581,6 @@ var KiloHooks = class KiloHooks extends ToolHooks {
|
|
|
32050
32581
|
}
|
|
32051
32582
|
};
|
|
32052
32583
|
//#endregion
|
|
32053
|
-
//#region src/constants/kimi-code-paths.ts
|
|
32054
|
-
const KIMI_CODE_DIR = ".kimi-code";
|
|
32055
|
-
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
32056
|
-
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
32057
|
-
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
32058
|
-
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
32059
|
-
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
32060
|
-
const KIMI_CODE_SKILLS_DIR_PATH = (0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
32061
|
-
(0, node_path.join)(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
32062
|
-
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
|
|
32063
|
-
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = (0, node_path.join)(".agents", "agents");
|
|
32064
|
-
//#endregion
|
|
32065
|
-
//#region src/utils/kimi-code.ts
|
|
32066
|
-
function getKimiCodeHome() {
|
|
32067
|
-
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
32068
|
-
return configuredHome ? (0, node_path.resolve)(configuredHome) : void 0;
|
|
32069
|
-
}
|
|
32070
|
-
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
32071
|
-
return global && getKimiCodeHome() ? relativeDirPath : (0, node_path.join)(KIMI_CODE_DIR, relativeDirPath);
|
|
32072
|
-
}
|
|
32073
|
-
/**
|
|
32074
|
-
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
32075
|
-
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
32076
|
-
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
32077
|
-
* guards checked against them — do not depend on the ambient environment.
|
|
32078
|
-
*/
|
|
32079
|
-
function getKimiCodeSharedConfigWritePaths() {
|
|
32080
|
-
return [{
|
|
32081
|
-
relativeDirPath: KIMI_CODE_DIR,
|
|
32082
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32083
|
-
}, {
|
|
32084
|
-
relativeDirPath: ".",
|
|
32085
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32086
|
-
}];
|
|
32087
|
-
}
|
|
32088
|
-
/**
|
|
32089
|
-
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
32090
|
-
* Both spellings carry the same declaration, but passing the key of the file
|
|
32091
|
-
* being written keeps the write path and the drift guards on the same entry.
|
|
32092
|
-
*/
|
|
32093
|
-
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
32094
|
-
return sharedConfigFileKey({
|
|
32095
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
32096
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32097
|
-
});
|
|
32098
|
-
}
|
|
32099
|
-
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
32100
|
-
return getToolRulesyncOutputRoot({
|
|
32101
|
-
nativeOutputRoot,
|
|
32102
|
-
global,
|
|
32103
|
-
toolHome: getKimiCodeHome
|
|
32104
|
-
});
|
|
32105
|
-
}
|
|
32106
|
-
//#endregion
|
|
32107
32584
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
32108
32585
|
function runFromTrustedDirectory({ command, trustedDirectory }) {
|
|
32109
32586
|
if (process.platform === "win32") return `set "RULESYNC_KIMI_HOOK_CWD=1" && cd /d "${trustedDirectory.replaceAll("%", "%%").replaceAll("\"", "\"\"")}" && ${command}`;
|
|
@@ -35560,7 +36037,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
35560
36037
|
supportsImport: true
|
|
35561
36038
|
},
|
|
35562
36039
|
supportedEvents: CODEXCLI_HOOK_EVENTS,
|
|
35563
|
-
supportedHookTypes: ["command"],
|
|
36040
|
+
supportedHookTypes: ["command", "mcp_tool"],
|
|
35564
36041
|
supportsMatcher: true
|
|
35565
36042
|
}],
|
|
35566
36043
|
["commandcode", {
|
|
@@ -37822,6 +38299,23 @@ function toReasonixTable(value) {
|
|
|
37822
38299
|
if (!isPlainObject$1(value)) return {};
|
|
37823
38300
|
return { ...value };
|
|
37824
38301
|
}
|
|
38302
|
+
/**
|
|
38303
|
+
* The tool name a Reasonix `[permissions]` rule must use to reach file reads.
|
|
38304
|
+
* Reasonix matches a rule's tool exactly against its registered tools
|
|
38305
|
+
* (`read_file`, `write_file`, `grep`, ...); only `Bash`, `Edit`, `Browser` and
|
|
38306
|
+
* `Computer` are Claude-Code-style aliases (`canonicalRuleTool` in
|
|
38307
|
+
* `internal/safety/permission/permission.go`), so a `Read(...)` rule matches
|
|
38308
|
+
* nothing. Shared by the `ignore` and `permissions` adapters, which both write
|
|
38309
|
+
* read denies into the same table.
|
|
38310
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
38311
|
+
*/
|
|
38312
|
+
const REASONIX_READ_TOOL_NAME = "read_file";
|
|
38313
|
+
/**
|
|
38314
|
+
* The spelling rulesync wrote for read rules before it followed the Reasonix
|
|
38315
|
+
* tool vocabulary. Still accepted on import, and an entry spelled this way is
|
|
38316
|
+
* treated as rulesync-owned on generate so the dormant copy is replaced.
|
|
38317
|
+
*/
|
|
38318
|
+
const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
|
|
37825
38319
|
//#endregion
|
|
37826
38320
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
37827
38321
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -37840,10 +38334,15 @@ const asClaudeStyleSettings = (document) => {
|
|
|
37840
38334
|
...table,
|
|
37841
38335
|
allow: toReasonixStringArray(table.allow),
|
|
37842
38336
|
ask: toReasonixStringArray(table.ask),
|
|
37843
|
-
deny: toReasonixStringArray(table.deny)
|
|
38337
|
+
deny: toReasonixStringArray(table.deny).filter((entry) => !isReadDenyEntry(entry, REASONIX_LEGACY_READ_TOOL_NAME))
|
|
37844
38338
|
}
|
|
37845
38339
|
};
|
|
37846
38340
|
};
|
|
38341
|
+
/** The pattern of a `read_file(...)` (or legacy `Read(...)`) deny, else null. */
|
|
38342
|
+
const readDenyPatternOf = (entry) => {
|
|
38343
|
+
for (const toolName of [REASONIX_READ_TOOL_NAME, REASONIX_LEGACY_READ_TOOL_NAME]) if (isReadDenyEntry(entry, toolName)) return entry.slice(toolName.length + 1, -1);
|
|
38344
|
+
return null;
|
|
38345
|
+
};
|
|
37847
38346
|
/**
|
|
37848
38347
|
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
37849
38348
|
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
@@ -37856,13 +38355,16 @@ const withoutEmptyPermissions = (settings) => {
|
|
|
37856
38355
|
return document;
|
|
37857
38356
|
};
|
|
37858
38357
|
/**
|
|
37859
|
-
* Writes `.rulesyncignore` patterns as `
|
|
38358
|
+
* Writes `.rulesyncignore` patterns as `read_file(<pattern>)` entries in the
|
|
37860
38359
|
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
37861
38360
|
* `~/.reasonix/config.toml` (global).
|
|
37862
38361
|
*
|
|
37863
38362
|
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
37864
38363
|
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
37865
38364
|
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
38365
|
+
* The tool is spelled `read_file` because Reasonix matches rule tools against
|
|
38366
|
+
* its registered tool names (see {@link REASONIX_READ_TOOL_NAME}); a legacy
|
|
38367
|
+
* `Read(...)` deny is still read back on import.
|
|
37866
38368
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
37867
38369
|
*/
|
|
37868
38370
|
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
@@ -37889,7 +38391,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37889
38391
|
return false;
|
|
37890
38392
|
}
|
|
37891
38393
|
toRulesyncIgnore() {
|
|
37892
|
-
const rulesyncPatterns = this.patterns.
|
|
38394
|
+
const rulesyncPatterns = (0, es_toolkit.uniq)(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
|
|
37893
38395
|
return new RulesyncIgnore({
|
|
37894
38396
|
outputRoot: this.outputRoot,
|
|
37895
38397
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -37898,7 +38400,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37898
38400
|
});
|
|
37899
38401
|
}
|
|
37900
38402
|
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
37901
|
-
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
38403
|
+
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern, REASONIX_READ_TOOL_NAME));
|
|
37902
38404
|
const paths = this.getSettablePaths({ global });
|
|
37903
38405
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
37904
38406
|
const existingDocument = parseSharedConfig({
|
|
@@ -37908,7 +38410,8 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37908
38410
|
});
|
|
37909
38411
|
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
37910
38412
|
settings: asClaudeStyleSettings(existingDocument),
|
|
37911
|
-
readDenies
|
|
38413
|
+
readDenies,
|
|
38414
|
+
readToolName: REASONIX_READ_TOOL_NAME
|
|
37912
38415
|
}));
|
|
37913
38416
|
return new ReasonixIgnore({
|
|
37914
38417
|
outputRoot,
|
|
@@ -38053,6 +38556,61 @@ var TabnineIgnore = class TabnineIgnore extends ToolIgnore {
|
|
|
38053
38556
|
}
|
|
38054
38557
|
};
|
|
38055
38558
|
//#endregion
|
|
38559
|
+
//#region src/features/ignore/trae-ignore.ts
|
|
38560
|
+
/**
|
|
38561
|
+
* Trae ignore file implementation.
|
|
38562
|
+
*
|
|
38563
|
+
* Trae reads `.trae/.ignore` (gitignore syntax) as a supplement to
|
|
38564
|
+
* `.gitignore` when it indexes the codebase; ignored files are also left out
|
|
38565
|
+
* of `#Workspace` / `#Folder` context. There is no user-level ignore file, so
|
|
38566
|
+
* only project scope is supported.
|
|
38567
|
+
*
|
|
38568
|
+
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
38569
|
+
*/
|
|
38570
|
+
var TraeIgnore = class TraeIgnore extends ToolIgnore {
|
|
38571
|
+
static getSettablePaths(_params = {}) {
|
|
38572
|
+
return {
|
|
38573
|
+
relativeDirPath: TRAE_DIR,
|
|
38574
|
+
relativeFilePath: TRAE_IGNORE_FILE_NAME
|
|
38575
|
+
};
|
|
38576
|
+
}
|
|
38577
|
+
toRulesyncIgnore() {
|
|
38578
|
+
return this.toRulesyncIgnoreDefault();
|
|
38579
|
+
}
|
|
38580
|
+
static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
38581
|
+
const paths = this.getSettablePaths({ global });
|
|
38582
|
+
return new TraeIgnore({
|
|
38583
|
+
outputRoot,
|
|
38584
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38585
|
+
relativeFilePath: paths.relativeFilePath,
|
|
38586
|
+
fileContent: rulesyncIgnore.getFileContent(),
|
|
38587
|
+
global
|
|
38588
|
+
});
|
|
38589
|
+
}
|
|
38590
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
38591
|
+
const { relativeDirPath, relativeFilePath } = this.getSettablePaths({ global });
|
|
38592
|
+
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
38593
|
+
return new TraeIgnore({
|
|
38594
|
+
outputRoot,
|
|
38595
|
+
relativeDirPath,
|
|
38596
|
+
relativeFilePath,
|
|
38597
|
+
fileContent,
|
|
38598
|
+
validate,
|
|
38599
|
+
global
|
|
38600
|
+
});
|
|
38601
|
+
}
|
|
38602
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
38603
|
+
return new TraeIgnore({
|
|
38604
|
+
outputRoot,
|
|
38605
|
+
relativeDirPath,
|
|
38606
|
+
relativeFilePath,
|
|
38607
|
+
fileContent: "",
|
|
38608
|
+
validate: false,
|
|
38609
|
+
global
|
|
38610
|
+
});
|
|
38611
|
+
}
|
|
38612
|
+
};
|
|
38613
|
+
//#endregion
|
|
38056
38614
|
//#region src/features/ignore/vibe-ignore.ts
|
|
38057
38615
|
var VibeIgnore = class VibeIgnore extends ToolIgnore {
|
|
38058
38616
|
static getSettablePaths() {
|
|
@@ -38281,6 +38839,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
38281
38839
|
["zoocode", { class: RooIgnore }],
|
|
38282
38840
|
["devin", { class: DevinIgnore }],
|
|
38283
38841
|
["tabnine", { class: TabnineIgnore }],
|
|
38842
|
+
["trae", { class: TraeIgnore }],
|
|
38284
38843
|
["vibe", { class: VibeIgnore }],
|
|
38285
38844
|
["warp", { class: WarpIgnore }],
|
|
38286
38845
|
["zed", { class: ZedIgnore }]
|
|
@@ -43633,12 +44192,14 @@ function toGitlabduoTimeout({ serverName, server, logger }) {
|
|
|
43633
44192
|
/**
|
|
43634
44193
|
* Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
|
|
43635
44194
|
* (milliseconds). A server that already carries a `timeout` is left as is, so
|
|
43636
|
-
* both keys round-trip and `timeoutMs` keeps winning on the next generate.
|
|
44195
|
+
* both keys round-trip and `timeoutMs` keeps winning on the next generate. A
|
|
44196
|
+
* non-number `timeoutMs` is not copied, since the canonical `timeout` must be a
|
|
44197
|
+
* number; it is kept verbatim as a passthrough key instead.
|
|
43637
44198
|
*/
|
|
43638
44199
|
function fromGitlabduoTimeout(server) {
|
|
43639
44200
|
if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
|
|
43640
44201
|
const { timeoutMs, ...rest } = server;
|
|
43641
|
-
if (timeoutMs
|
|
44202
|
+
if (typeof timeoutMs !== "number" || rest.timeout !== void 0) return server;
|
|
43642
44203
|
return {
|
|
43643
44204
|
...rest,
|
|
43644
44205
|
timeout: timeoutMs
|
|
@@ -45353,7 +45914,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
45353
45914
|
feature: "mcp",
|
|
45354
45915
|
existingContent: fileContent ?? "",
|
|
45355
45916
|
patch: {
|
|
45356
|
-
mcp: convertedMcp,
|
|
45917
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
45357
45918
|
...buildKiloMcpPermissionPatch({
|
|
45358
45919
|
existing: existingDocument,
|
|
45359
45920
|
managedServerNames: (0, es_toolkit.uniq)([...Object.keys(mcpServers), ...Object.keys(isRecord$1(existingDocument.mcp) ? existingDocument.mcp : {})]),
|
|
@@ -46213,7 +46774,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
46213
46774
|
feature: "mcp",
|
|
46214
46775
|
existingContent: fileContent ?? "",
|
|
46215
46776
|
patch: {
|
|
46216
|
-
mcp: convertedMcp,
|
|
46777
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
46217
46778
|
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
46218
46779
|
},
|
|
46219
46780
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath),
|
|
@@ -46561,9 +47122,12 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46561
47122
|
/**
|
|
46562
47123
|
* Meta Muse Code MCP servers.
|
|
46563
47124
|
*
|
|
46564
|
-
* Muse Code
|
|
46565
|
-
* block of the GLOBAL user settings file
|
|
46566
|
-
*
|
|
47125
|
+
* rulesync writes Muse Code MCP servers only to the `mcp_servers` (or
|
|
47126
|
+
* `mcpServers`) block of the GLOBAL user settings file
|
|
47127
|
+
* `~/.config/muse/settings.json`, the location extending.md documents. Muse
|
|
47128
|
+
* Code 1.4.0+ also loads a trusted project `.mcp.json` (changelog only), which
|
|
47129
|
+
* is not emitted here because the `claudecode` target already writes that
|
|
47130
|
+
* file. The settings file must carry
|
|
46567
47131
|
* `"schema_version": 1` — a file that omits that key fails every command at
|
|
46568
47132
|
* startup with `malformed settings file` — so the key is bootstrapped when the
|
|
46569
47133
|
* file is created and preserved when it already exists. Other settings keys are
|
|
@@ -46571,6 +47135,7 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46571
47135
|
*
|
|
46572
47136
|
* @see https://dev.meta.ai/docs/muse-code/configuration.md
|
|
46573
47137
|
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
47138
|
+
* @see https://dev.meta.ai/docs/muse-code/changelog
|
|
46574
47139
|
*/
|
|
46575
47140
|
var MusecodeMcp = class MusecodeMcp extends ToolMcp {
|
|
46576
47141
|
json;
|
|
@@ -47111,8 +47676,8 @@ function convertFromPiFormat(mcpServers) {
|
|
|
47111
47676
|
* existing file (`autoEnableCodemode`, ...) are preserved. Pi expands `${VAR}`
|
|
47112
47677
|
* in `env` and `headers` values itself, so those are written unchanged.
|
|
47113
47678
|
*
|
|
47114
|
-
* @see https://github.com/earendil-works/pi/blob/
|
|
47115
|
-
* @see https://github.com/earendil-works/pi/blob/
|
|
47679
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
|
|
47680
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/core/mcp-servers.ts
|
|
47116
47681
|
*/
|
|
47117
47682
|
var PiMcp = class PiMcp extends ToolMcp {
|
|
47118
47683
|
json;
|
|
@@ -47803,7 +48368,7 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
47803
48368
|
feature: "mcp",
|
|
47804
48369
|
existingContent,
|
|
47805
48370
|
patch: {
|
|
47806
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()),
|
|
48371
|
+
mcpServers: undefinedWhenEmpty(convertToQwencodeFormat(rulesyncMcp.getMcpServers())),
|
|
47807
48372
|
...mcpPatch !== void 0 && { mcp: mcpPatch }
|
|
47808
48373
|
},
|
|
47809
48374
|
filePath
|
|
@@ -49226,21 +49791,6 @@ function deriveTransportAllowlist(servers) {
|
|
|
49226
49791
|
return allowlist;
|
|
49227
49792
|
}
|
|
49228
49793
|
//#endregion
|
|
49229
|
-
//#region src/constants/trae-paths.ts
|
|
49230
|
-
/**
|
|
49231
|
-
* Trae (ByteDance's AI IDE, "TraeCode" in its current docs) keeps project
|
|
49232
|
-
* configuration under `.trae/` and user configuration under `~/.trae/`.
|
|
49233
|
-
*
|
|
49234
|
-
* @see https://docs.trae.ai/ide/rules?_lang=en
|
|
49235
|
-
* @see https://docs.trae.ai/ide/skills?_lang=en
|
|
49236
|
-
* @see https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
49237
|
-
*/
|
|
49238
|
-
const TRAE_DIR = ".trae";
|
|
49239
|
-
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
49240
|
-
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
49241
|
-
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
49242
|
-
const TRAE_SKILLS_DIR_PATH = (0, node_path.join)(TRAE_DIR, "skills");
|
|
49243
|
-
//#endregion
|
|
49244
49794
|
//#region src/features/mcp/trae-mcp.ts
|
|
49245
49795
|
/** A `${...}` reference Trae does not expand: anything but `${workspaceFolder}`. */
|
|
49246
49796
|
const UNEXPANDED_VARIABLE_REGEX = /\$\{(?!workspaceFolder\})[^}]+\}/;
|
|
@@ -51850,6 +52400,11 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
51850
52400
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
51851
52401
|
*/
|
|
51852
52402
|
const REGEX_PREFIX = "regex:";
|
|
52403
|
+
/**
|
|
52404
|
+
* The Antigravity actions whose target is a command: `command`, and
|
|
52405
|
+
* `unsandboxed`, which takes the same word-by-word prefix or `regex:` target.
|
|
52406
|
+
*/
|
|
52407
|
+
const ANTIGRAVITY_COMMAND_ACTIONS = /* @__PURE__ */ new Set(["command", "unsandboxed"]);
|
|
51853
52408
|
const TRAILING_ANCHOR = /(?<!\\)\$$/;
|
|
51854
52409
|
const PLAIN_TARGET_GLOB_CHARACTERS = /[*?[{\\]/;
|
|
51855
52410
|
const REGEX_METACHARACTERS$1 = /[\\^$.|?*+()[\]{}]/;
|
|
@@ -52227,8 +52782,8 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
52227
52782
|
}
|
|
52228
52783
|
/**
|
|
52229
52784
|
* Read one regex word back as a glob, or `undefined` when it uses anything a
|
|
52230
|
-
* glob cannot say. Antigravity anchors each word anyway, so
|
|
52231
|
-
* optional. Only escaped characters and `.*` translate; anything else keeps
|
|
52785
|
+
* glob cannot say. Antigravity anchors each word anyway (`^(?:pattern)$`), so
|
|
52786
|
+
* `^` and `$` are optional. Only escaped characters and `.*` translate; anything else keeps
|
|
52232
52787
|
* the rule a regex.
|
|
52233
52788
|
*/
|
|
52234
52789
|
function regexWordToGlob(word) {
|
|
@@ -52254,8 +52809,10 @@ function regexWordToGlob(word) {
|
|
|
52254
52809
|
}
|
|
52255
52810
|
/** Read a per-word regex back as a glob, or `undefined` when it has no glob spelling. */
|
|
52256
52811
|
function regexToGlob(source) {
|
|
52812
|
+
const words = splitWords(source);
|
|
52813
|
+
if (words.length === 0) return;
|
|
52257
52814
|
const globs = [];
|
|
52258
|
-
for (const word of
|
|
52815
|
+
for (const word of words) {
|
|
52259
52816
|
const glob = regexWordToGlob(word);
|
|
52260
52817
|
if (glob === void 0) return;
|
|
52261
52818
|
globs.push(glob);
|
|
@@ -52280,36 +52837,23 @@ function fromAntigravityCommandTarget(target) {
|
|
|
52280
52837
|
return `${target} *`;
|
|
52281
52838
|
}
|
|
52282
52839
|
/**
|
|
52283
|
-
* Whether a regex word means the same anchored or not: each end is either
|
|
52284
|
-
* anchored (`^`, an unescaped `$`) or open-ended (`.*`).
|
|
52285
|
-
*/
|
|
52286
|
-
function isAnchoredWord(word) {
|
|
52287
|
-
const startAnchored = word.startsWith("^") || word.startsWith(".*");
|
|
52288
|
-
const endAnchored = TRAILING_ANCHOR.test(word) || word.endsWith(".*");
|
|
52289
|
-
return startAnchored && endAnchored;
|
|
52290
|
-
}
|
|
52291
|
-
/**
|
|
52292
52840
|
* Explain what importing an Antigravity `command` target may lose, or return
|
|
52293
52841
|
* `undefined` when the import is faithful.
|
|
52294
52842
|
*
|
|
52295
|
-
*
|
|
52296
|
-
*
|
|
52297
|
-
*
|
|
52298
|
-
*
|
|
52299
|
-
*
|
|
52300
|
-
*
|
|
52301
|
-
*
|
|
52843
|
+
* A plain target holding characters that are special in a glob is most
|
|
52844
|
+
* likely the verbatim output of an older rulesync (`command(rm -rf *)`).
|
|
52845
|
+
* Antigravity matches it literally, so it imports as a regex that matches a
|
|
52846
|
+
* literal `*` word and is inert in the other tools it is exported to.
|
|
52847
|
+
*
|
|
52848
|
+
* A `regex:` word without `^...$` (`regex:rm`) is read as anchored, which is
|
|
52849
|
+
* faithful: Antigravity documents that each token is evaluated as the
|
|
52850
|
+
* anchored regex `^(?:pattern)$`.
|
|
52851
|
+
*
|
|
52852
|
+
* @see https://antigravity.google/docs/permissions
|
|
52302
52853
|
*/
|
|
52303
52854
|
function describeAntigravityCommandImportLoss(target) {
|
|
52304
|
-
if (target.startsWith(REGEX_PREFIX))
|
|
52305
|
-
|
|
52306
|
-
if (splitWords(source).some((word) => !isAnchoredWord(word)) && regexToGlob(source) !== void 0) return {
|
|
52307
|
-
reason: "it has a regex word without `^...$` anchors and is imported as a glob that assumes Antigravity anchors every word, so the imported rule may be narrower than the original",
|
|
52308
|
-
denyConsequence: "The imported deny may block less than it does in Antigravity."
|
|
52309
|
-
};
|
|
52310
|
-
return;
|
|
52311
|
-
}
|
|
52312
|
-
if (target !== "*" && PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
52855
|
+
if (target.startsWith(REGEX_PREFIX) || target === "*") return;
|
|
52856
|
+
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
52313
52857
|
reason: "it holds characters that are special in a glob (`*`, `?`, `[`, `{` or `\\`), which Antigravity matches literally (likely output of an older rulesync), so it is imported as a literal-match regex that no other tool enforces",
|
|
52314
52858
|
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
52315
52859
|
};
|
|
@@ -52341,17 +52885,20 @@ function importAntigravityCommandTarget({ target, entry, decision, logger, toolL
|
|
|
52341
52885
|
* every file.
|
|
52342
52886
|
* - `read_url` takes a domain and covers its subdomains. The URL path is
|
|
52343
52887
|
* ignored, and `*` covers every domain.
|
|
52344
|
-
* - `
|
|
52345
|
-
*
|
|
52888
|
+
* - `execute_url` takes a domain, like `read_url`.
|
|
52889
|
+
* - `command` and `unsandboxed` take literal words or a per-word `regex:`,
|
|
52890
|
+
* matched word by word (see `antigravity-command-patterns.ts`).
|
|
52346
52891
|
*
|
|
52347
52892
|
* A glob copied verbatim (`read_file(**\/*.env)`) is a literal path that
|
|
52348
52893
|
* matches nothing, so a deny written that way blocks nothing. A pattern with
|
|
52349
|
-
* no Antigravity spelling is skipped with a warning instead, except a
|
|
52350
|
-
*
|
|
52894
|
+
* no Antigravity spelling is skipped with a warning instead, except a deny
|
|
52895
|
+
* with a literal lead (the first words of a command, the leading directories
|
|
52896
|
+
* of a path), which is widened to it.
|
|
52351
52897
|
*
|
|
52352
52898
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
52353
52899
|
*/
|
|
52354
52900
|
const PATH_ACTIONS = /* @__PURE__ */ new Set(["read_file", "write_file"]);
|
|
52901
|
+
const URL_ACTIONS = /* @__PURE__ */ new Set(["read_url", "execute_url"]);
|
|
52355
52902
|
const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
|
|
52356
52903
|
"*",
|
|
52357
52904
|
"**",
|
|
@@ -52363,7 +52910,8 @@ const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
|
|
|
52363
52910
|
const DENY_HINTS = {
|
|
52364
52911
|
read_file: "deny a directory or file path instead",
|
|
52365
52912
|
write_file: "deny a directory or file path instead",
|
|
52366
|
-
command: "deny the first words of the command instead"
|
|
52913
|
+
command: "deny the first words of the command instead",
|
|
52914
|
+
unsandboxed: "deny the first words of the command instead"
|
|
52367
52915
|
};
|
|
52368
52916
|
function hasGlob(pattern) {
|
|
52369
52917
|
return BRACE_ALTERNATIVES.test(pattern) || parseGlobPattern(pattern).steps.some((step) => step.kind !== "literal");
|
|
@@ -52414,37 +52962,88 @@ function toUrlTarget(pattern) {
|
|
|
52414
52962
|
};
|
|
52415
52963
|
}
|
|
52416
52964
|
function toTarget(action, pattern) {
|
|
52417
|
-
if (action
|
|
52965
|
+
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandTarget(pattern);
|
|
52418
52966
|
if (PATH_ACTIONS.has(action)) return toPathTarget(pattern);
|
|
52419
|
-
if (action
|
|
52967
|
+
if (URL_ACTIONS.has(action)) return toUrlTarget(pattern);
|
|
52420
52968
|
return { target: pattern };
|
|
52421
52969
|
}
|
|
52422
52970
|
/**
|
|
52971
|
+
* The leading directories of a path glob (`src` for `src/*.ts`), or
|
|
52972
|
+
* `undefined` when it has none. A directory target covers everything inside
|
|
52973
|
+
* it, so it matches at least as much as the glob. The filesystem root alone
|
|
52974
|
+
* (`/*`) is not a lead: a deny of it would block every file. Neither is a lead
|
|
52975
|
+
* with `..` in it.
|
|
52976
|
+
*/
|
|
52977
|
+
function toPathLiteralPrefix(pattern) {
|
|
52978
|
+
const path = pattern.startsWith("./") ? pattern.slice(2) : pattern;
|
|
52979
|
+
const prefix = [];
|
|
52980
|
+
for (const segment of path.split("/").slice(0, -1)) {
|
|
52981
|
+
if (hasGlob(segment) || segment.includes("{") || segment.includes("}")) break;
|
|
52982
|
+
prefix.push(segment);
|
|
52983
|
+
}
|
|
52984
|
+
if (prefix.includes("..") || prefix.every((segment) => segment === "" || segment === ".")) return;
|
|
52985
|
+
return prefix.join("/");
|
|
52986
|
+
}
|
|
52987
|
+
/** The widened target of a deny that has no exact Antigravity spelling. */
|
|
52988
|
+
function toWidenedDenyTarget(action, pattern) {
|
|
52989
|
+
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandLiteralPrefix(pattern);
|
|
52990
|
+
if (PATH_ACTIONS.has(action)) return toPathLiteralPrefix(pattern);
|
|
52991
|
+
}
|
|
52992
|
+
/**
|
|
52993
|
+
* Warn about a path target that is a home or variable reference. Antigravity
|
|
52994
|
+
* documents absolute and workspace-relative paths only, so such a reference
|
|
52995
|
+
* may be matched literally and never apply. The entry is still written, since
|
|
52996
|
+
* dropping a deny would lose it where Antigravity does expand the reference.
|
|
52997
|
+
*/
|
|
52998
|
+
function warnHomeOrVariablePath({ action, target, rule, decision, logger, toolLabel }) {
|
|
52999
|
+
if (!PATH_ACTIONS.has(action) || !HOME_OR_VARIABLE_PATH.test(target)) return;
|
|
53000
|
+
const consequence = decision === "deny" ? "this deny may block nothing" : decision === "ask" ? `${toolLabel} may fall back to its default, so it may run without a prompt when ${toolLabel} is set to always proceed` : `${toolLabel} may fall back to its default`;
|
|
53001
|
+
logger.warn(`${toolLabel} permissions: ${rule}: \`~\` and environment variables may not be expanded in Antigravity path targets, so ${action}(${target}) may match nothing and ${consequence}; use an absolute path instead.`);
|
|
53002
|
+
}
|
|
53003
|
+
/**
|
|
52423
53004
|
* Build the entry for one canonical rule, or return `undefined` when
|
|
52424
|
-
* Antigravity cannot express it. A
|
|
52425
|
-
*
|
|
52426
|
-
*
|
|
52427
|
-
*
|
|
52428
|
-
* deny is warned about loudly, because
|
|
53005
|
+
* Antigravity cannot express it. A deny is widened to its literal lead rather
|
|
53006
|
+
* than skipped (`git push * --force` → `command(git push)`, `src/*.ts` →
|
|
53007
|
+
* `read_file(src)`): Antigravity applies deny over ask and allow, so an
|
|
53008
|
+
* overlapping allow that is translated (`git push *`) cannot let through what
|
|
53009
|
+
* the deny was meant to block. A skipped deny is warned about loudly, because
|
|
53010
|
+
* nothing enforces it, and a widened one is warned about too, because it
|
|
53011
|
+
* blocks more than it says.
|
|
52429
53012
|
*/
|
|
52430
53013
|
function buildAntigravityPermissionEntry({ action, category, pattern, decision, logger, toolLabel }) {
|
|
52431
53014
|
const result = pattern === "*" ? { target: "*" } : toTarget(action, pattern);
|
|
52432
53015
|
const rule = `${category}: { "${pattern}": "${decision}" }`;
|
|
52433
53016
|
if ("skipReason" in result) {
|
|
52434
|
-
const prefix =
|
|
53017
|
+
const prefix = decision === "deny" ? toWidenedDenyTarget(action, pattern) : void 0;
|
|
52435
53018
|
if (prefix !== void 0) {
|
|
52436
|
-
|
|
53019
|
+
const scope = PATH_ACTIONS.has(action) ? `every path under \`${prefix}\`` : `every command that starts with \`${prefix}\``;
|
|
53020
|
+
logger.warn(`${toolLabel} permissions: widening ${rule} to ${action}(${prefix}). ${result.skipReason}. The deny now blocks ${scope}, overriding any allow or ask for them.`);
|
|
53021
|
+
warnHomeOrVariablePath({
|
|
53022
|
+
action,
|
|
53023
|
+
target: prefix,
|
|
53024
|
+
rule,
|
|
53025
|
+
decision,
|
|
53026
|
+
logger,
|
|
53027
|
+
toolLabel
|
|
53028
|
+
});
|
|
52437
53029
|
return `${action}(${prefix})`;
|
|
52438
53030
|
}
|
|
52439
53031
|
const consequence = decision === "deny" ? `This deny is NOT enforced in ${toolLabel}; ${DENY_HINTS[action] ?? "write it in a form Antigravity can match"}` : decision === "ask" ? `${toolLabel} falls back to its default for it, so it runs without a prompt when ${toolLabel} is set to always proceed` : `${toolLabel} falls back to its default for it`;
|
|
52440
53032
|
logger.warn(`${toolLabel} permissions: skipping ${rule}. ${result.skipReason}. ${consequence}.`);
|
|
52441
53033
|
return;
|
|
52442
53034
|
}
|
|
52443
|
-
if (result.note !== void 0 && decision !== "deny"
|
|
52444
|
-
|
|
52445
|
-
|
|
52446
|
-
logger.warn(`${toolLabel} permissions: ${rule}: \`~\` and environment variables may not be expanded in Antigravity path targets, so ${action}(${result.target}) may match nothing and ${consequence}; use an absolute path instead.`);
|
|
53035
|
+
if (result.note !== void 0 && (decision !== "deny" || URL_ACTIONS.has(action))) {
|
|
53036
|
+
const consequence = decision === "deny" ? ", so the deny also overrides any allow or ask there" : "";
|
|
53037
|
+
logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}${consequence}.`);
|
|
52447
53038
|
}
|
|
53039
|
+
warnHomeOrVariablePath({
|
|
53040
|
+
action,
|
|
53041
|
+
target: result.target,
|
|
53042
|
+
rule,
|
|
53043
|
+
decision,
|
|
53044
|
+
logger,
|
|
53045
|
+
toolLabel
|
|
53046
|
+
});
|
|
52448
53047
|
return result.target === "*" ? action : `${action}(${result.target})`;
|
|
52449
53048
|
}
|
|
52450
53049
|
//#endregion
|
|
@@ -52778,7 +53377,8 @@ function pickDocumentedValue({ key, value, documented }) {
|
|
|
52778
53377
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
52779
53378
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
52780
53379
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
52781
|
-
* `unsandboxed` which have no canonical equivalent and
|
|
53380
|
+
* `unsandboxed` which have no canonical equivalent and keep their names; their
|
|
53381
|
+
* targets are translated like `read_url` / `command` ones). `edit` and
|
|
52782
53382
|
* `write` both map to `write_file`, and `webfetch` / `websearch` both map to
|
|
52783
53383
|
* `read_url`; on import those collapse to the canonical `write` / `webfetch`
|
|
52784
53384
|
* form (a documented, lossy normalization). This shares the action vocabulary of
|
|
@@ -53025,7 +53625,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
53025
53625
|
for (const entry of entries) {
|
|
53026
53626
|
const { toolName, pattern: target } = parsePermissionEntry$1(entry);
|
|
53027
53627
|
const canonical = toCanonicalToolName$9(toolName);
|
|
53028
|
-
const pattern = toolName
|
|
53628
|
+
const pattern = ANTIGRAVITY_COMMAND_ACTIONS.has(toolName) ? importAntigravityCommandTarget({
|
|
53029
53629
|
target,
|
|
53030
53630
|
entry,
|
|
53031
53631
|
decision: action,
|
|
@@ -53047,7 +53647,8 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
53047
53647
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
53048
53648
|
* Antigravity IDE action vocabulary (`read_file`, `write_file`, `read_url`,
|
|
53049
53649
|
* `command`, `mcp`, plus `execute_url` / `unsandboxed` which have no canonical
|
|
53050
|
-
* equivalent and
|
|
53650
|
+
* equivalent and keep their names; their targets are translated like
|
|
53651
|
+
* `read_url` / `command` ones). `edit` and `write` both map to `write_file`, and
|
|
53051
53652
|
* `webfetch` / `websearch` both map to `read_url`; on import those collapse to
|
|
53052
53653
|
* the canonical `write` / `webfetch` form (a documented, lossy normalization).
|
|
53053
53654
|
*/
|
|
@@ -53249,7 +53850,7 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
53249
53850
|
for (const entry of entries) {
|
|
53250
53851
|
const { action: ideAction, pattern: target } = parsePermissionEntry(entry);
|
|
53251
53852
|
const canonical = toCanonicalCategory$2(ideAction);
|
|
53252
|
-
const pattern = ideAction
|
|
53853
|
+
const pattern = ANTIGRAVITY_COMMAND_ACTIONS.has(ideAction) ? importAntigravityCommandTarget({
|
|
53253
53854
|
target,
|
|
53254
53855
|
entry,
|
|
53255
53856
|
decision: action,
|
|
@@ -55179,6 +55780,7 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55179
55780
|
"dialogExpiry",
|
|
55180
55781
|
"feedbackDrafts",
|
|
55181
55782
|
"footerLinksRegexes",
|
|
55783
|
+
"forceLoginGatewayUrl",
|
|
55182
55784
|
"modelPicker",
|
|
55183
55785
|
"pluginConfigs",
|
|
55184
55786
|
"prependPlugins",
|
|
@@ -55192,6 +55794,39 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55192
55794
|
"vimInsertModeRemaps"
|
|
55193
55795
|
]);
|
|
55194
55796
|
/**
|
|
55797
|
+
* Nested settings paths under an otherwise project-honored top-level key that
|
|
55798
|
+
* Claude Code reads only from user settings — the nested counterpart of
|
|
55799
|
+
* {@link CLAUDECODE_USER_SCOPE_ONLY_KEYS}. `worktree` is `Any file`, but
|
|
55800
|
+
* `worktree.location` (where Desktop SSH sessions create worktrees on the
|
|
55801
|
+
* remote machine) is documented with a `User` scope, so it is dropped from a
|
|
55802
|
+
* project `.claude/settings.json` and written only under `--global`, while its
|
|
55803
|
+
* siblings are kept.
|
|
55804
|
+
*
|
|
55805
|
+
* @see https://code.claude.com/docs/en/settings-reference#worktree-location
|
|
55806
|
+
*/
|
|
55807
|
+
const CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS = [["worktree", "location"]];
|
|
55808
|
+
/**
|
|
55809
|
+
* Drops {@link CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS} from the top-level
|
|
55810
|
+
* passthrough at project scope, in place, warning once per dropped path. The
|
|
55811
|
+
* authored subtree is copied before the deletion so the caller's config is not
|
|
55812
|
+
* mutated, and a parent the removal emptied is dropped with it.
|
|
55813
|
+
*/
|
|
55814
|
+
function stripUserScopeOnlyNestedPaths({ overrides, global, relativeFilePath, logger }) {
|
|
55815
|
+
if (global) return;
|
|
55816
|
+
for (const path of CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS) {
|
|
55817
|
+
const [topLevelKey] = path;
|
|
55818
|
+
if (topLevelKey === void 0 || !isRecord$1(overrides[topLevelKey])) continue;
|
|
55819
|
+
const copy = { [topLevelKey]: structuredClone(overrides[topLevelKey]) };
|
|
55820
|
+
if (!deleteSandboxPath({
|
|
55821
|
+
target: copy,
|
|
55822
|
+
path
|
|
55823
|
+
})) continue;
|
|
55824
|
+
if (copy[topLevelKey] === void 0) delete overrides[topLevelKey];
|
|
55825
|
+
else overrides[topLevelKey] = copy[topLevelKey];
|
|
55826
|
+
logger?.warn(`Claude Code permissions: '${path.map((segment) => displayKey(segment)).join(".")}' is not honored in the project-scoped ${relativeFilePath}, so it is not written there — Claude Code reads it from user settings only. Author it in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
55827
|
+
}
|
|
55828
|
+
}
|
|
55829
|
+
/**
|
|
55195
55830
|
* Top-level settings keys neither file rulesync writes can honor, with the file
|
|
55196
55831
|
* that does. `Managed` keys are read only from the settings file an organization
|
|
55197
55832
|
* deploys, and `Global config` keys only from `~/.claude.json` — rulesync writes
|
|
@@ -55229,7 +55864,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
55229
55864
|
disableMobileSimulatorTools: "managed settings",
|
|
55230
55865
|
disableSideloadFlags: "managed settings",
|
|
55231
55866
|
externalEditorContext: "~/.claude.json",
|
|
55232
|
-
forceLoginGatewayUrl: "managed settings",
|
|
55233
55867
|
forceRemoteSettingsRefresh: "managed settings",
|
|
55234
55868
|
gatewayInternalNetworks: "managed settings",
|
|
55235
55869
|
leftArrowOpensAgents: "~/.claude.json",
|
|
@@ -55388,8 +56022,9 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55388
56022
|
/**
|
|
55389
56023
|
* `env` variables a project or local settings file cannot set, because a
|
|
55390
56024
|
* checked-out repository should not control them: where Claude Code stores its
|
|
55391
|
-
* own files, what session content it exports, where telemetry goes,
|
|
55392
|
-
* starts or syncs
|
|
56025
|
+
* own files, what session content it exports, where telemetry goes, how it
|
|
56026
|
+
* starts or syncs, the timers on an unanswered dialog, and whether it processes
|
|
56027
|
+
* attachments or expands `@` file mentions. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
55393
56028
|
*
|
|
55394
56029
|
* Only the names the settings reference lists are covered; its directory and
|
|
55395
56030
|
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
@@ -55400,6 +56035,10 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55400
56035
|
*/
|
|
55401
56036
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
55402
56037
|
"BETA_TRACING_ENDPOINT",
|
|
56038
|
+
"CLAUDE_AFK_COUNTDOWN_MS",
|
|
56039
|
+
"CLAUDE_AFK_TIMEOUT_MS",
|
|
56040
|
+
"CLAUDE_CODE_DISABLE_ATTACHMENTS",
|
|
56041
|
+
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
55403
56042
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
55404
56043
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
55405
56044
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
@@ -55408,6 +56047,7 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
55408
56047
|
"CLAUDE_CODE_SYNC_PLUGINS",
|
|
55409
56048
|
"CLAUDE_CODE_SYNC_SKILLS",
|
|
55410
56049
|
"CLAUDE_CODE_TMPDIR",
|
|
56050
|
+
"CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS",
|
|
55411
56051
|
"CLAUDE_CONFIG_DIR",
|
|
55412
56052
|
"ENABLE_BETA_TRACING_DETAILED",
|
|
55413
56053
|
"ENABLE_ENHANCED_TELEMETRY_BETA",
|
|
@@ -55453,6 +56093,13 @@ const CLAUDECODE_ENV_CONTENT_OFF_VALUES = /* @__PURE__ */ new Set([
|
|
|
55453
56093
|
* writing a value Claude Code ignores is the old behavior, while dropping an
|
|
55454
56094
|
* opt-out it honors would remove a privacy setting. `OTEL_LOG_ASSISTANT_RESPONSES`
|
|
55455
56095
|
* has no such exception and sits in the plain name table above.
|
|
56096
|
+
*
|
|
56097
|
+
* `CLAUDE_CODE_ENABLE_CFC` follows the same shape for a different reason: a
|
|
56098
|
+
* project cannot turn Claude in Chrome on, since a checked-out repository must
|
|
56099
|
+
* not be able to connect Claude to the user's browser, so only an off value is
|
|
56100
|
+
* kept at project scope.
|
|
56101
|
+
*
|
|
56102
|
+
* @see https://code.claude.com/docs/en/chrome#project-settings-cant-turn-on-chrome
|
|
55456
56103
|
*/
|
|
55457
56104
|
const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
55458
56105
|
OTEL_LOGS_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
@@ -55460,7 +56107,8 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
55460
56107
|
OTEL_TRACES_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
55461
56108
|
OTEL_LOG_USER_PROMPTS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55462
56109
|
OTEL_LOG_TOOL_CONTENT: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55463
|
-
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
56110
|
+
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
56111
|
+
CLAUDE_CODE_ENABLE_CFC: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
55464
56112
|
};
|
|
55465
56113
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
55466
56114
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_")) return true;
|
|
@@ -55497,7 +56145,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
55497
56145
|
kept[name] = value;
|
|
55498
56146
|
}
|
|
55499
56147
|
if (ignoredEverywhere.length > 0) logger?.warn(`Claude Code permissions: Claude Code ignores ${ignoredEverywhere.map((name) => `'env.${name}'`).join(", ")} in every settings file, so ${ignoredEverywhere.length === 1 ? "it is" : "they are"} not written to ${relativeFilePath}. Set ${ignoredEverywhere.length === 1 ? "it" : "them"} in the environment Claude Code starts from instead, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`);
|
|
55500
|
-
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry,
|
|
56148
|
+
if (ignoredAtProjectScope.length > 0) logger?.warn(`Claude Code permissions: ${ignoredAtProjectScope.map((name) => `'env.${name}'`).join(", ")} ${ignoredAtProjectScope.length === 1 ? "is" : "are"} not honored in the project-scoped ${relativeFilePath}, so ${ignoredAtProjectScope.length === 1 ? "it is" : "they are"} not written there — Claude Code ignores variables there that choose where it writes its files, export session content or telemetry, change how it starts or syncs, set dialog timers, turn attachments or file mentions off, or turn Claude in Chrome on (a value that turns telemetry or Chrome off, such as "none" or "0", is still written). Author ${ignoredAtProjectScope.length === 1 ? "it" : "them"} in the global scope instead, and check that file for a stale value an earlier generate may have left there.`);
|
|
55501
56149
|
return kept;
|
|
55502
56150
|
}
|
|
55503
56151
|
/**
|
|
@@ -55728,6 +56376,12 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
55728
56376
|
relativeFilePath: paths.relativeFilePath,
|
|
55729
56377
|
logger
|
|
55730
56378
|
});
|
|
56379
|
+
stripUserScopeOnlyNestedPaths({
|
|
56380
|
+
overrides: overrideTopLevel,
|
|
56381
|
+
global,
|
|
56382
|
+
relativeFilePath: paths.relativeFilePath,
|
|
56383
|
+
logger
|
|
56384
|
+
});
|
|
55731
56385
|
const { filtered: scopedTopLevel, trustAffecting: trustAffectingTopLevel } = stripUnhonoredTopLevelKeys({
|
|
55732
56386
|
overrides: overrideTopLevel,
|
|
55733
56387
|
global,
|
|
@@ -56145,7 +56799,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
56145
56799
|
"ask",
|
|
56146
56800
|
"deny"
|
|
56147
56801
|
];
|
|
56148
|
-
const CATCH_ALL_PATTERN$
|
|
56802
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
56149
56803
|
const MCP_CATEGORY = "mcp";
|
|
56150
56804
|
const MCP_RULE_PREFIX = "mcp__";
|
|
56151
56805
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -56218,7 +56872,7 @@ function categoryOfRule$2(rule) {
|
|
|
56218
56872
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
56219
56873
|
*/
|
|
56220
56874
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
56221
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
56875
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$12;
|
|
56222
56876
|
if (category === MCP_CATEGORY) {
|
|
56223
56877
|
if (catchAll && action === "allow") {
|
|
56224
56878
|
logger?.warn(`CodeBuddy permissions: skipping the 'mcp' allow rule '*' — CodeBuddy honors the all-MCP wildcard 'mcp__*' in deny and ask only. Allow servers by name instead (for example 'mcp__github').`);
|
|
@@ -56227,7 +56881,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
56227
56881
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
56228
56882
|
}
|
|
56229
56883
|
if (category === "*") {
|
|
56230
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
56884
|
+
if (catchAll) return CATCH_ALL_PATTERN$12;
|
|
56231
56885
|
logger?.warn(`CodeBuddy permissions: skipping the all-tools '*' rule '${pattern}' — CodeBuddy's standalone '*' rule takes no specifier. Write it under the category it is meant for (for example 'bash' or 'read').`);
|
|
56232
56886
|
return null;
|
|
56233
56887
|
}
|
|
@@ -56245,7 +56899,7 @@ function parseCodebuddyRule(rule) {
|
|
|
56245
56899
|
const { specifier } = splitCodebuddyRule(rule);
|
|
56246
56900
|
if (specifier === void 0) return {
|
|
56247
56901
|
category,
|
|
56248
|
-
pattern: CATCH_ALL_PATTERN$
|
|
56902
|
+
pattern: CATCH_ALL_PATTERN$12
|
|
56249
56903
|
};
|
|
56250
56904
|
const trimmed = specifier.trim();
|
|
56251
56905
|
return trimmed === "" ? null : {
|
|
@@ -58496,7 +59150,7 @@ function mapBashActionToDecision(action) {
|
|
|
58496
59150
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
58497
59151
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
58498
59152
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
58499
|
-
const CATCH_ALL_PATTERN$
|
|
59153
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
58500
59154
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
58501
59155
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
58502
59156
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -58521,14 +59175,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
|
58521
59175
|
web_fetch: "webfetch",
|
|
58522
59176
|
web_search: "websearch"
|
|
58523
59177
|
};
|
|
58524
|
-
const ACTION_RANK$
|
|
59178
|
+
const ACTION_RANK$5 = {
|
|
58525
59179
|
allow: 0,
|
|
58526
59180
|
ask: 1,
|
|
58527
59181
|
deny: 2
|
|
58528
59182
|
};
|
|
58529
59183
|
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
58530
59184
|
function isStricterAction({ action, existing }) {
|
|
58531
|
-
return existing === void 0 || ACTION_RANK$
|
|
59185
|
+
return existing === void 0 || ACTION_RANK$5[action] > ACTION_RANK$5[existing];
|
|
58532
59186
|
}
|
|
58533
59187
|
/**
|
|
58534
59188
|
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
@@ -58551,10 +59205,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
58551
59205
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
58552
59206
|
*/
|
|
58553
59207
|
function buildCommandcodeRule(category, pattern) {
|
|
58554
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
59208
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$11 || pattern === "";
|
|
58555
59209
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
58556
59210
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
58557
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
59211
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$11 : null;
|
|
58558
59212
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
58559
59213
|
if (tool === void 0) return null;
|
|
58560
59214
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -58590,7 +59244,7 @@ function splitCommandcodeRule(rule) {
|
|
|
58590
59244
|
}
|
|
58591
59245
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
58592
59246
|
function isCatchAllSpecifier(inner) {
|
|
58593
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
59247
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$11;
|
|
58594
59248
|
}
|
|
58595
59249
|
/**
|
|
58596
59250
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -58624,34 +59278,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
58624
59278
|
if (server.length === 0) return null;
|
|
58625
59279
|
const everyServer = {
|
|
58626
59280
|
category: "mcp",
|
|
58627
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59281
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58628
59282
|
notInAllow: true
|
|
58629
59283
|
};
|
|
58630
59284
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
58631
|
-
if (server === CATCH_ALL_PATTERN$
|
|
58632
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
59285
|
+
if (server === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59286
|
+
if (server.includes(CATCH_ALL_PATTERN$11)) return null;
|
|
58633
59287
|
return {
|
|
58634
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
58635
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59288
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$11 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
|
|
59289
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58636
59290
|
notInAllow: false
|
|
58637
59291
|
};
|
|
58638
59292
|
}
|
|
58639
59293
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
58640
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
59294
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$11);
|
|
58641
59295
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
58642
59296
|
category,
|
|
58643
59297
|
pattern: inner,
|
|
58644
59298
|
notInAllow: true
|
|
58645
59299
|
} : null;
|
|
58646
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
58647
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
59300
|
+
if (remainder === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59301
|
+
if (rest === CATCH_ALL_PATTERN$11 && !server.includes(CATCH_ALL_PATTERN$11)) return {
|
|
58648
59302
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
58649
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59303
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58650
59304
|
notInAllow: true
|
|
58651
59305
|
};
|
|
58652
59306
|
return namesTool ? {
|
|
58653
59307
|
category,
|
|
58654
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59308
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58655
59309
|
notInAllow: false
|
|
58656
59310
|
} : null;
|
|
58657
59311
|
}
|
|
@@ -58675,14 +59329,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58675
59329
|
if (category === "bash") {
|
|
58676
59330
|
if (isCatchAllSpecifier(inner)) return {
|
|
58677
59331
|
category,
|
|
58678
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59332
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58679
59333
|
notInAllow: false
|
|
58680
59334
|
};
|
|
58681
59335
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
58682
59336
|
if (normalized === "" || normalized === ":*") return null;
|
|
58683
59337
|
return isCatchAllSpecifier(normalized) ? {
|
|
58684
59338
|
category,
|
|
58685
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59339
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58686
59340
|
notInAllow: true
|
|
58687
59341
|
} : {
|
|
58688
59342
|
category,
|
|
@@ -58692,7 +59346,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58692
59346
|
}
|
|
58693
59347
|
if (isCatchAllSpecifier(inner)) return {
|
|
58694
59348
|
category,
|
|
58695
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59349
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58696
59350
|
notInAllow: false
|
|
58697
59351
|
};
|
|
58698
59352
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -58711,9 +59365,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58711
59365
|
*/
|
|
58712
59366
|
function parseCommandcodeRule(rule) {
|
|
58713
59367
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
58714
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
59368
|
+
if (tool === CATCH_ALL_PATTERN$11) return isCatchAllSpecifier(inner) ? {
|
|
58715
59369
|
category: "*",
|
|
58716
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59370
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58717
59371
|
notInAllow: true
|
|
58718
59372
|
} : null;
|
|
58719
59373
|
const lowered = tool.toLowerCase();
|
|
@@ -58783,7 +59437,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
58783
59437
|
}).filter((rule) => {
|
|
58784
59438
|
const parsed = parseCommandcodeRule(rule);
|
|
58785
59439
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
58786
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
59440
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$11})`);
|
|
58787
59441
|
if (key !== "allow" && isStricterAction({
|
|
58788
59442
|
action: key,
|
|
58789
59443
|
existing: replacement
|
|
@@ -58840,7 +59494,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
58840
59494
|
const ranked = /* @__PURE__ */ new Map();
|
|
58841
59495
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
58842
59496
|
for (const [category, rules] of Object.entries(permission)) {
|
|
58843
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
59497
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$11);
|
|
58844
59498
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
58845
59499
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
58846
59500
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -59111,7 +59765,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
59111
59765
|
//#endregion
|
|
59112
59766
|
//#region src/features/permissions/continue-permissions.ts
|
|
59113
59767
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
59114
|
-
const CATCH_ALL_PATTERN$
|
|
59768
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
59115
59769
|
/**
|
|
59116
59770
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
59117
59771
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -59119,7 +59773,7 @@ const CATCH_ALL_PATTERN$9 = "*";
|
|
|
59119
59773
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
59120
59774
|
*/
|
|
59121
59775
|
function normalizeCatchAllPattern$1(pattern) {
|
|
59122
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
59776
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
|
|
59123
59777
|
}
|
|
59124
59778
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
59125
59779
|
bash: "Bash",
|
|
@@ -59156,7 +59810,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
59156
59810
|
* `Tool(pattern)`.
|
|
59157
59811
|
*/
|
|
59158
59812
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
59159
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
59813
|
+
return pattern === CATCH_ALL_PATTERN$10 ? toolName : `${toolName}(${pattern})`;
|
|
59160
59814
|
}
|
|
59161
59815
|
/**
|
|
59162
59816
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -59170,7 +59824,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
59170
59824
|
if (toolName === "") return;
|
|
59171
59825
|
return {
|
|
59172
59826
|
toolName,
|
|
59173
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
59827
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$10 : pattern
|
|
59174
59828
|
};
|
|
59175
59829
|
}
|
|
59176
59830
|
/**
|
|
@@ -59344,7 +59998,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
59344
59998
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59345
59999
|
continue;
|
|
59346
60000
|
}
|
|
59347
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
60001
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$10) {
|
|
59348
60002
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59349
60003
|
continue;
|
|
59350
60004
|
}
|
|
@@ -59712,14 +60366,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
59712
60366
|
};
|
|
59713
60367
|
//#endregion
|
|
59714
60368
|
//#region src/features/permissions/crush-permissions.ts
|
|
59715
|
-
const CATCH_ALL_PATTERN$
|
|
60369
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
59716
60370
|
/**
|
|
59717
60371
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
59718
60372
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
59719
60373
|
* still be written.
|
|
59720
60374
|
*/
|
|
59721
60375
|
function normalizeCatchAllPattern(pattern) {
|
|
59722
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
60376
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
|
|
59723
60377
|
}
|
|
59724
60378
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
59725
60379
|
bash: "bash",
|
|
@@ -59918,7 +60572,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
59918
60572
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
59919
60573
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
59920
60574
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
59921
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
60575
|
+
if (pattern === CATCH_ALL_PATTERN$9) {
|
|
59922
60576
|
catchAllAction = action;
|
|
59923
60577
|
continue;
|
|
59924
60578
|
}
|
|
@@ -59991,11 +60645,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
59991
60645
|
};
|
|
59992
60646
|
for (const entry of disabled) {
|
|
59993
60647
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
59994
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60648
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] = "deny";
|
|
59995
60649
|
}
|
|
59996
60650
|
for (const entry of allowed) {
|
|
59997
60651
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
59998
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60652
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] ??= "allow";
|
|
59999
60653
|
}
|
|
60000
60654
|
return { permission };
|
|
60001
60655
|
}
|
|
@@ -61860,7 +62514,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
61860
62514
|
//#region src/features/permissions/goose-permissions.ts
|
|
61861
62515
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
61862
62516
|
const GOOSE_USER_KEY = "user";
|
|
61863
|
-
const CATCH_ALL_PATTERN$
|
|
62517
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
61864
62518
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
61865
62519
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
61866
62520
|
shell: ["bash"],
|
|
@@ -61872,7 +62526,7 @@ const ACTION_TO_GOOSE_LIST = {
|
|
|
61872
62526
|
ask: "ask_before",
|
|
61873
62527
|
deny: "never_allow"
|
|
61874
62528
|
};
|
|
61875
|
-
const ACTION_RANK$
|
|
62529
|
+
const ACTION_RANK$4 = {
|
|
61876
62530
|
allow: 0,
|
|
61877
62531
|
ask: 1,
|
|
61878
62532
|
deny: 2
|
|
@@ -62024,14 +62678,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62024
62678
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
62025
62679
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
62026
62680
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
62027
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
62681
|
+
if (pattern !== CATCH_ALL_PATTERN$8) {
|
|
62028
62682
|
logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
|
|
62029
62683
|
continue;
|
|
62030
62684
|
}
|
|
62031
62685
|
const previous = assigned.get(toolName);
|
|
62032
62686
|
if (previous === action) continue;
|
|
62033
62687
|
if (previous !== void 0) {
|
|
62034
|
-
const stricter = ACTION_RANK$
|
|
62688
|
+
const stricter = ACTION_RANK$4[action] > ACTION_RANK$4[previous] ? action : previous;
|
|
62035
62689
|
logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
|
|
62036
62690
|
if (stricter === previous) continue;
|
|
62037
62691
|
const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
|
|
@@ -62055,7 +62709,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
62055
62709
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62056
62710
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62057
62711
|
permission[category] ??= {};
|
|
62058
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
62712
|
+
permission[category][CATCH_ALL_PATTERN$8] = action;
|
|
62059
62713
|
}
|
|
62060
62714
|
}
|
|
62061
62715
|
return { permission };
|
|
@@ -62066,7 +62720,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
62066
62720
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
62067
62721
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
62068
62722
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
62069
|
-
const CATCH_ALL_PATTERN$
|
|
62723
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
62070
62724
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
62071
62725
|
const CATEGORY_TO_GROK_TOOL = {
|
|
62072
62726
|
bash: "Bash",
|
|
@@ -62091,6 +62745,8 @@ const GROK_TOOL_TO_CATEGORY = {
|
|
|
62091
62745
|
...Object.fromEntries(["SendSubagentMessage", "SendAgentMessage"].map((tool) => [tool, "agent"]))
|
|
62092
62746
|
};
|
|
62093
62747
|
const GROK_MCP_TOOL = "MCPTool";
|
|
62748
|
+
const GROK_ALL_TOOLS_ENTRY = "*";
|
|
62749
|
+
const GROK_VERBOSE_ANY_TOOL = "any";
|
|
62094
62750
|
const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
62095
62751
|
...Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category])),
|
|
62096
62752
|
agent_message: "agent"
|
|
@@ -62105,22 +62761,26 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
62105
62761
|
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
62106
62762
|
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
62107
62763
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
62108
|
-
* concrete pattern emits `Tool(pattern)`.
|
|
62764
|
+
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
62765
|
+
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
62766
|
+
* under it has no Grok spelling and returns `null`.
|
|
62109
62767
|
*/
|
|
62110
62768
|
function buildGrokEntry(category, pattern) {
|
|
62769
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$7 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
62111
62770
|
if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
|
|
62112
62771
|
const remainder = category.slice(5);
|
|
62113
62772
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
62114
62773
|
}
|
|
62115
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
62774
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
62116
62775
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
62117
62776
|
if (tool === void 0) return null;
|
|
62118
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
62777
|
+
return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
62119
62778
|
}
|
|
62120
62779
|
/**
|
|
62121
62780
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
62122
|
-
* pattern. Entries without parentheses are treated as catch-all (`*`)
|
|
62123
|
-
*
|
|
62781
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`), and the
|
|
62782
|
+
* bare `*` rule becomes the all-tools `*` category. Returns `null` for tool
|
|
62783
|
+
* prefixes with no canonical equivalent.
|
|
62124
62784
|
*/
|
|
62125
62785
|
function parseGrokEntry(entry) {
|
|
62126
62786
|
const trimmed = entry.trim();
|
|
@@ -62134,19 +62794,23 @@ function parseGrokEntry(entry) {
|
|
|
62134
62794
|
tool = trimmed.slice(0, parenIndex);
|
|
62135
62795
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
62136
62796
|
}
|
|
62797
|
+
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
62798
|
+
category: "*",
|
|
62799
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62800
|
+
};
|
|
62137
62801
|
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
|
|
62138
62802
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
62139
62803
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
62140
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62804
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62141
62805
|
} : {
|
|
62142
62806
|
category: "mcp",
|
|
62143
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62807
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62144
62808
|
};
|
|
62145
62809
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
62146
62810
|
if (category === void 0) return null;
|
|
62147
62811
|
return {
|
|
62148
62812
|
category,
|
|
62149
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
62813
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$7
|
|
62150
62814
|
};
|
|
62151
62815
|
}
|
|
62152
62816
|
/**
|
|
@@ -62163,14 +62827,14 @@ function parseGrokEntry(entry) {
|
|
|
62163
62827
|
function parseClaudeStyleMcpEntry(entry) {
|
|
62164
62828
|
const rest = entry.slice(5);
|
|
62165
62829
|
if (rest.length === 0) return null;
|
|
62166
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
62830
|
+
if (rest === CATCH_ALL_PATTERN$7) return {
|
|
62167
62831
|
category: "mcp",
|
|
62168
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62832
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62169
62833
|
};
|
|
62170
62834
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
62171
62835
|
return {
|
|
62172
62836
|
category: `${MCP_CANONICAL_PREFIX$3}${address}`,
|
|
62173
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62837
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62174
62838
|
};
|
|
62175
62839
|
}
|
|
62176
62840
|
/**
|
|
@@ -62191,15 +62855,20 @@ function parseGrokRule(rule) {
|
|
|
62191
62855
|
if (typeof tool !== "string") return null;
|
|
62192
62856
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
62193
62857
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
62194
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
62858
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$7;
|
|
62195
62859
|
const lowerTool = tool.trim().toLowerCase();
|
|
62196
|
-
if (
|
|
62860
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62861
|
+
category: "*",
|
|
62862
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62863
|
+
action
|
|
62864
|
+
} : null;
|
|
62865
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62197
62866
|
category: "mcp",
|
|
62198
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62867
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62199
62868
|
action
|
|
62200
62869
|
} : {
|
|
62201
62870
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
62202
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62871
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62203
62872
|
action
|
|
62204
62873
|
};
|
|
62205
62874
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -62229,7 +62898,10 @@ function parseGrokRule(rule) {
|
|
|
62229
62898
|
* subagent rather than its launch, and whose pattern is a subagent id
|
|
62230
62899
|
* rather than a subagent type); `mcp__*` maps to `MCPTool(...)` (a scoped
|
|
62231
62900
|
* MCP category folds its address into the parentheses, so a non-`*`
|
|
62232
|
-
* argument pattern on it is not represented)
|
|
62901
|
+
* argument pattern on it is not represented); the all-tools `*` category
|
|
62902
|
+
* maps to Grok's bare `*` rule (which matches every tool) for its
|
|
62903
|
+
* catch-all pattern, while a narrower `*` pattern is skipped with a
|
|
62904
|
+
* warning. The one category with no Grok tool (`notebookedit`) is
|
|
62233
62905
|
* skipped (with a warning when it carries a `deny` rule, to surface the
|
|
62234
62906
|
* gap). When two canonical rules collapse onto the same Grok
|
|
62235
62907
|
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
@@ -62240,7 +62912,9 @@ function parseGrokRule(rule) {
|
|
|
62240
62912
|
* verbose `[[permission.rules]]` tables
|
|
62241
62913
|
* (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
|
|
62242
62914
|
* field is matched case-insensitively against the same tool table, plus
|
|
62243
|
-
* the documented `mcp` alias for the compact form's `MCPTool`.
|
|
62915
|
+
* the documented `mcp` alias for the compact form's `MCPTool`. A bare `*`
|
|
62916
|
+
* entry and a tool-wide verbose `any` rule both import as the all-tools
|
|
62917
|
+
* `*` category. Rules
|
|
62244
62918
|
* from the two forms are merged with the same `deny > ask > allow`
|
|
62245
62919
|
* precedence. When neither form carries a rule (older configs), the coarse
|
|
62246
62920
|
* `[ui] permission_mode` is used as a fallback. Generate always emits the
|
|
@@ -62265,7 +62939,7 @@ function parseGrokRule(rule) {
|
|
|
62265
62939
|
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
62266
62940
|
* every other key
|
|
62267
62941
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
62268
|
-
* entries for tool prefixes rulesync cannot model (e.g. `
|
|
62942
|
+
* entries for tool prefixes rulesync cannot model (e.g. a pattern-only entry such as `src/**`) are preserved.
|
|
62269
62943
|
* The file is never deleted.
|
|
62270
62944
|
*/
|
|
62271
62945
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -62347,7 +63021,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62347
63021
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
62348
63022
|
}
|
|
62349
63023
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
62350
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
63024
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$7]: legacyModeAction(parsed) } } };
|
|
62351
63025
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
62352
63026
|
}
|
|
62353
63027
|
validate() {
|
|
@@ -62367,14 +63041,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62367
63041
|
});
|
|
62368
63042
|
}
|
|
62369
63043
|
};
|
|
62370
|
-
const ACTION_RANK$
|
|
63044
|
+
const ACTION_RANK$3 = {
|
|
62371
63045
|
allow: 0,
|
|
62372
63046
|
ask: 1,
|
|
62373
63047
|
deny: 2
|
|
62374
63048
|
};
|
|
62375
63049
|
/**
|
|
62376
63050
|
* Collect user-authored entries from an existing `[permission]` array whose
|
|
62377
|
-
* tool prefix rulesync cannot model (e.g. `
|
|
63051
|
+
* tool prefix rulesync cannot model (e.g. a pattern-only entry such as `src/**`). Such entries are
|
|
62378
63052
|
* preserved verbatim so replacing the arrays does not silently drop them
|
|
62379
63053
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
62380
63054
|
*/
|
|
@@ -62393,13 +63067,26 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62393
63067
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) for (const [pattern, action] of Object.entries(rules)) {
|
|
62394
63068
|
const entry = buildGrokEntry(category, pattern);
|
|
62395
63069
|
if (entry === null) {
|
|
63070
|
+
if (category === "*") {
|
|
63071
|
+
logger?.warn(`Grok CLI permissions: skipping the all-tools '*' rule '${pattern}' — Grok's bare '*' rule takes no pattern. Write it under the categories it is meant for (for example 'bash' or 'read').`);
|
|
63072
|
+
continue;
|
|
63073
|
+
}
|
|
62396
63074
|
if (action === "deny" && logger) logger.warn(`Grok CLI has no permission tool for the '${category}' category; its 'deny' rule could not be represented and was skipped.`);
|
|
62397
63075
|
continue;
|
|
62398
63076
|
}
|
|
62399
63077
|
const existing = ranked.get(entry);
|
|
62400
63078
|
if (existing !== void 0 && existing !== action) logger?.warn(`Grok permission entry '${entry}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
|
|
62401
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63079
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) ranked.set(entry, action);
|
|
62402
63080
|
}
|
|
63081
|
+
warnRemovedAllToolsRestrictions({
|
|
63082
|
+
existingPermission,
|
|
63083
|
+
ranked,
|
|
63084
|
+
logger
|
|
63085
|
+
});
|
|
63086
|
+
warnEntriesShadowedByAllTools({
|
|
63087
|
+
ranked,
|
|
63088
|
+
logger
|
|
63089
|
+
});
|
|
62403
63090
|
const allow = unmanagedEntries(existingPermission, "allow");
|
|
62404
63091
|
const deny = unmanagedEntries(existingPermission, "deny");
|
|
62405
63092
|
const ask = unmanagedEntries(existingPermission, "ask");
|
|
@@ -62413,6 +63100,38 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62413
63100
|
};
|
|
62414
63101
|
}
|
|
62415
63102
|
/**
|
|
63103
|
+
* Warn when generate drops an existing bare `*` `deny`/`ask` entry without
|
|
63104
|
+
* writing an equally strict one back. Such an entry is Rulesync-owned (it maps
|
|
63105
|
+
* to the all-tools `*` category), so a config without a matching canonical
|
|
63106
|
+
* rule removes it — which silently lifts a restriction on every tool.
|
|
63107
|
+
*/
|
|
63108
|
+
function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger }) {
|
|
63109
|
+
if (!logger) return;
|
|
63110
|
+
const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63111
|
+
for (const action of ["deny", "ask"]) {
|
|
63112
|
+
const entries = existingPermission[action];
|
|
63113
|
+
if (!isStringArray$2(entries)) continue;
|
|
63114
|
+
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63115
|
+
if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
|
|
63116
|
+
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.`);
|
|
63117
|
+
}
|
|
63118
|
+
}
|
|
63119
|
+
/**
|
|
63120
|
+
* Warn about entries that a bare `*` `deny`/`ask` makes ineffective: Grok
|
|
63121
|
+
* evaluates by severity (`deny > ask > allow`), so a specific `allow` (and,
|
|
63122
|
+
* under a `*` deny, a specific `ask`) never takes effect next to it. Shell
|
|
63123
|
+
* rules are already folded by `honorAllToolsOnBash`, so this covers the other
|
|
63124
|
+
* tools.
|
|
63125
|
+
*/
|
|
63126
|
+
function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
63127
|
+
if (!logger) return;
|
|
63128
|
+
const allTools = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63129
|
+
if (allTools === void 0 || allTools === "allow") return;
|
|
63130
|
+
const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$3[action] < ACTION_RANK$3[allTools]).map(([entry]) => entry).toSorted();
|
|
63131
|
+
if (shadowed.length === 0) return;
|
|
63132
|
+
logger.warn(`Grok CLI permissions: the bare '*' '${allTools}' rule applies to every tool, and Grok evaluates deny > ask > allow, so these less strict entries never take effect: ${shadowed.join(", ")}.`);
|
|
63133
|
+
}
|
|
63134
|
+
/**
|
|
62416
63135
|
* Parse Grok's `[permission]` section back into a canonical permission map,
|
|
62417
63136
|
* reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
|
|
62418
63137
|
* Claude-style entries and the verbose `[[permission.rules]]` tables
|
|
@@ -62431,7 +63150,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
62431
63150
|
const record = ({ category, pattern, action }) => {
|
|
62432
63151
|
const bucket = result[category] ??= {};
|
|
62433
63152
|
const existing = bucket[pattern];
|
|
62434
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63153
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) bucket[pattern] = action;
|
|
62435
63154
|
};
|
|
62436
63155
|
const applyEntries = (entries, action) => {
|
|
62437
63156
|
for (const entry of entries ?? []) {
|
|
@@ -62835,7 +63554,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
62835
63554
|
mcpTools: "mcp",
|
|
62836
63555
|
readOutsideProject: "read"
|
|
62837
63556
|
};
|
|
62838
|
-
function isPermissionAction$
|
|
63557
|
+
function isPermissionAction$2(value) {
|
|
62839
63558
|
return PermissionActionSchema.safeParse(value).success;
|
|
62840
63559
|
}
|
|
62841
63560
|
/**
|
|
@@ -63021,7 +63740,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
|
63021
63740
|
for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
|
|
63022
63741
|
if (!rule || typeof rule !== "object") continue;
|
|
63023
63742
|
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
63024
|
-
if (pattern === void 0 || !isPermissionAction$
|
|
63743
|
+
if (pattern === void 0 || !isPermissionAction$2(rule.action)) continue;
|
|
63025
63744
|
(permission[category] ??= {})[pattern] = rule.action;
|
|
63026
63745
|
}
|
|
63027
63746
|
}
|
|
@@ -63980,6 +64699,367 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
63980
64699
|
return rules;
|
|
63981
64700
|
}
|
|
63982
64701
|
//#endregion
|
|
64702
|
+
//#region src/features/permissions/kiro-capability-permissions.ts
|
|
64703
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
64704
|
+
const CANONICAL_TO_KIRO_CAPABILITY = {
|
|
64705
|
+
bash: "shell",
|
|
64706
|
+
read: "fs_read",
|
|
64707
|
+
edit: "fs_write",
|
|
64708
|
+
write: "fs_write",
|
|
64709
|
+
webfetch: "web_fetch",
|
|
64710
|
+
websearch: "web_search"
|
|
64711
|
+
};
|
|
64712
|
+
const KIRO_CAPABILITY_TO_CANONICAL = {
|
|
64713
|
+
shell: "bash",
|
|
64714
|
+
fs_read: "read",
|
|
64715
|
+
fs_write: "write",
|
|
64716
|
+
web_fetch: "webfetch",
|
|
64717
|
+
web_search: "websearch"
|
|
64718
|
+
};
|
|
64719
|
+
const MANAGED_CAPABILITIES = new Set(Object.keys(KIRO_CAPABILITY_TO_CANONICAL));
|
|
64720
|
+
const ACTION_RANK$2 = {
|
|
64721
|
+
allow: 0,
|
|
64722
|
+
ask: 1,
|
|
64723
|
+
deny: 2
|
|
64724
|
+
};
|
|
64725
|
+
function isPermissionAction$1(value) {
|
|
64726
|
+
return value === "allow" || value === "ask" || value === "deny";
|
|
64727
|
+
}
|
|
64728
|
+
/**
|
|
64729
|
+
* Whether an existing `permissions.yaml` rule can be read back into the
|
|
64730
|
+
* canonical model: a rule for a capability rulesync maps, with no `exclude`
|
|
64731
|
+
* list (the canonical model has no exclusion). Used on import only.
|
|
64732
|
+
*/
|
|
64733
|
+
function isImportableRule(rule) {
|
|
64734
|
+
return isRecord$1(rule) && typeof rule.capability === "string" && MANAGED_CAPABILITIES.has(rule.capability) && rule.exclude === void 0;
|
|
64735
|
+
}
|
|
64736
|
+
/**
|
|
64737
|
+
* Key identifying one (capability, pattern) pair. The catch-all `*` stands for
|
|
64738
|
+
* a match-less rule.
|
|
64739
|
+
*/
|
|
64740
|
+
function rulePatternKey(capability, pattern) {
|
|
64741
|
+
return `${capability}\u0000${pattern}`;
|
|
64742
|
+
}
|
|
64743
|
+
/**
|
|
64744
|
+
* Classify one existing (capability, pattern, effect) entry against the
|
|
64745
|
+
* generated rules: an exact duplicate (same effect) is replaced by the
|
|
64746
|
+
* generated rule; a different effect is a conflict and is kept; anything
|
|
64747
|
+
* rulesync does not emit is unrelated and kept.
|
|
64748
|
+
*/
|
|
64749
|
+
function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
64750
|
+
const effects = emitted.get(rulePatternKey(capability, pattern));
|
|
64751
|
+
if (effects === void 0) return "unrelated";
|
|
64752
|
+
return isPermissionAction$1(effect) && effects.has(effect) ? "duplicate" : "conflict";
|
|
64753
|
+
}
|
|
64754
|
+
/**
|
|
64755
|
+
* Merge one existing rule against the generated entries (see
|
|
64756
|
+
* {@link mergeKiroCapabilityRules}). Returns the rule to keep (possibly with a
|
|
64757
|
+
* narrowed `match`), or `undefined` when every entry is an exact duplicate, and
|
|
64758
|
+
* how many entries rulesync does not generate.
|
|
64759
|
+
*/
|
|
64760
|
+
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$2(rule.match)) return {
|
|
64762
|
+
rule,
|
|
64763
|
+
unrelatedEntries: 0
|
|
64764
|
+
};
|
|
64765
|
+
const capability = rule.capability;
|
|
64766
|
+
const isCatchAll = rule.match === void 0;
|
|
64767
|
+
const patterns = isStringArray$2(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
|
|
64768
|
+
if (patterns.length === 0) return {
|
|
64769
|
+
rule,
|
|
64770
|
+
unrelatedEntries: 0
|
|
64771
|
+
};
|
|
64772
|
+
const remaining = [];
|
|
64773
|
+
let unrelatedEntries = 0;
|
|
64774
|
+
for (const pattern of patterns) {
|
|
64775
|
+
const outcome = classifyExistingEntry({
|
|
64776
|
+
emitted,
|
|
64777
|
+
capability,
|
|
64778
|
+
pattern,
|
|
64779
|
+
effect: rule.effect
|
|
64780
|
+
});
|
|
64781
|
+
if (outcome === "duplicate") continue;
|
|
64782
|
+
remaining.push(pattern);
|
|
64783
|
+
if (outcome === "unrelated") {
|
|
64784
|
+
unrelatedEntries++;
|
|
64785
|
+
continue;
|
|
64786
|
+
}
|
|
64787
|
+
logger?.warn(`Kiro permissions.yaml already has a ${capability} rule for "${pattern}" with effect "${String(rule.effect)}"; keeping it alongside the generated rule. Kiro applies the stricter effect (deny > ask > allow).`);
|
|
64788
|
+
}
|
|
64789
|
+
if (remaining.length === 0) return {
|
|
64790
|
+
rule: void 0,
|
|
64791
|
+
unrelatedEntries
|
|
64792
|
+
};
|
|
64793
|
+
if (remaining.length === patterns.length || isCatchAll) return {
|
|
64794
|
+
rule,
|
|
64795
|
+
unrelatedEntries
|
|
64796
|
+
};
|
|
64797
|
+
return {
|
|
64798
|
+
rule: {
|
|
64799
|
+
...rule,
|
|
64800
|
+
match: remaining
|
|
64801
|
+
},
|
|
64802
|
+
unrelatedEntries
|
|
64803
|
+
};
|
|
64804
|
+
}
|
|
64805
|
+
/**
|
|
64806
|
+
* Merge the existing rules with the generated ones. Rulesync keeps no
|
|
64807
|
+
* ownership record in the file, so it only removes what it is about to write
|
|
64808
|
+
* again: an existing rule loses the `match` entries that exactly duplicate a
|
|
64809
|
+
* generated (capability, pattern, effect) — a match-less rule counts as the `*`
|
|
64810
|
+
* pattern — and is dropped only when no entry is left.
|
|
64811
|
+
*
|
|
64812
|
+
* Everything else is kept verbatim: rules Kiro saved ("Always allow/deny"),
|
|
64813
|
+
* hand-written rules, rules with `exclude`, rules for capabilities rulesync
|
|
64814
|
+
* does not map, malformed entries. An existing rule with the same pattern but a
|
|
64815
|
+
* different effect is kept too, with a warning: Kiro resolves deny > ask >
|
|
64816
|
+
* allow, so the stricter rule still wins and a user's `deny` is never loosened.
|
|
64817
|
+
* Rerunning generate is therefore idempotent.
|
|
64818
|
+
*/
|
|
64819
|
+
function mergeKiroCapabilityRules({ existingRules, generatedRules, logger }) {
|
|
64820
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
64821
|
+
for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$6]) {
|
|
64822
|
+
const key = rulePatternKey(rule.capability, pattern);
|
|
64823
|
+
const effects = emitted.get(key) ?? /* @__PURE__ */ new Set();
|
|
64824
|
+
effects.add(rule.effect);
|
|
64825
|
+
emitted.set(key, effects);
|
|
64826
|
+
}
|
|
64827
|
+
const kept = [];
|
|
64828
|
+
let unrelatedManagedEntries = 0;
|
|
64829
|
+
for (const rule of existingRules) {
|
|
64830
|
+
const result = mergeExistingRule({
|
|
64831
|
+
rule,
|
|
64832
|
+
emitted,
|
|
64833
|
+
logger
|
|
64834
|
+
});
|
|
64835
|
+
if (result.rule !== void 0) kept.push(result.rule);
|
|
64836
|
+
unrelatedManagedEntries += result.unrelatedEntries;
|
|
64837
|
+
}
|
|
64838
|
+
if (unrelatedManagedEntries > 0) logger?.warn(`Kiro permissions.yaml keeps ${unrelatedManagedEntries} existing shell/fs_read/fs_write/web_fetch/web_search rule ${unrelatedManagedEntries === 1 ? "entry" : "entries"} that .rulesync/permissions.json does not generate. Rulesync never removes rules from this file and cannot tell which of these it wrote earlier; delete any stale ones by hand.`);
|
|
64839
|
+
return [...kept, ...generatedRules];
|
|
64840
|
+
}
|
|
64841
|
+
const LOCATION_INDEPENDENT_FS_PATTERN = /^(?:\/|~|\*\*|[A-Za-z]:[\\/])/;
|
|
64842
|
+
/**
|
|
64843
|
+
* Permissions adapter for Kiro's capability-based `permissions.yaml` (Kiro IDE
|
|
64844
|
+
* 1.0, and the same engine in Kiro CLI V3). Used for the `kiro-cli` and
|
|
64845
|
+
* `kiro-ide` targets in **global** scope, where it writes the user-level
|
|
64846
|
+
* `~/.kiro/settings/permissions.yaml`. Project scope keeps writing the agent
|
|
64847
|
+
* config `.kiro/agents/default.json` through {@link KiroPermissions}: the
|
|
64848
|
+
* workspace-level `permissions.yaml` lives under a hash-derived directory in the
|
|
64849
|
+
* home directory, not in the repository, so it cannot be generated.
|
|
64850
|
+
*
|
|
64851
|
+
* Mapping (rulesync canonical -> Kiro rule):
|
|
64852
|
+
* - Category -> `capability`: `bash` -> `shell`, `read` -> `fs_read`,
|
|
64853
|
+
* `edit`/`write` -> `fs_write`, `webfetch` -> `web_fetch`, `websearch` ->
|
|
64854
|
+
* `web_search`. Other categories are skipped with a warning.
|
|
64855
|
+
* - Action -> `effect` 1:1, including `ask`.
|
|
64856
|
+
* - Pattern -> `match` entry. The catch-all `*` becomes a rule with no `match`
|
|
64857
|
+
* (the whole capability), because a filesystem `*` only matches one path
|
|
64858
|
+
* component in Kiro.
|
|
64859
|
+
*
|
|
64860
|
+
* Kiro also writes into this file (consent decisions persisted globally), so it
|
|
64861
|
+
* is merged, never replaced or deleted: every top-level key other than `rules`
|
|
64862
|
+
* is preserved, and existing rules are kept except for entries that exactly
|
|
64863
|
+
* repeat a generated rule (see {@link mergeKiroCapabilityRules}). Rulesync
|
|
64864
|
+
* never retracts a rule an earlier run wrote; it warns instead. A run that
|
|
64865
|
+
* changes nothing returns the existing content verbatim; otherwise the file is
|
|
64866
|
+
* re-serialized and its YAML comments are lost.
|
|
64867
|
+
*
|
|
64868
|
+
* @see https://kiro.dev/docs/permissions/
|
|
64869
|
+
*/
|
|
64870
|
+
var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPermissions {
|
|
64871
|
+
constructor(params) {
|
|
64872
|
+
super({
|
|
64873
|
+
...params,
|
|
64874
|
+
fileContent: params.fileContent ?? ""
|
|
64875
|
+
});
|
|
64876
|
+
}
|
|
64877
|
+
isDeletable() {
|
|
64878
|
+
return false;
|
|
64879
|
+
}
|
|
64880
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
64881
|
+
return true;
|
|
64882
|
+
}
|
|
64883
|
+
static getSettablePaths(_options) {
|
|
64884
|
+
return {
|
|
64885
|
+
relativeDirPath: KIRO_SETTINGS_DIR_PATH,
|
|
64886
|
+
relativeFilePath: KIRO_PERMISSIONS_FILE_NAME
|
|
64887
|
+
};
|
|
64888
|
+
}
|
|
64889
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
64890
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64891
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
64892
|
+
return new KiroCapabilityPermissions({
|
|
64893
|
+
outputRoot,
|
|
64894
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64895
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64896
|
+
fileContent,
|
|
64897
|
+
validate,
|
|
64898
|
+
global: true
|
|
64899
|
+
});
|
|
64900
|
+
}
|
|
64901
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
64902
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64903
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
64904
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
64905
|
+
let parsed;
|
|
64906
|
+
try {
|
|
64907
|
+
parsed = loadYaml(existingContent) ?? {};
|
|
64908
|
+
} catch (error) {
|
|
64909
|
+
throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
64910
|
+
}
|
|
64911
|
+
if (!isRecord$1(parsed)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: not a map`);
|
|
64912
|
+
if (parsed.rules !== void 0 && !Array.isArray(parsed.rules)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: 'rules' is not a list`);
|
|
64913
|
+
const config = rulesyncPermissions.getJson();
|
|
64914
|
+
if (config.kiro?.toolsSettings !== void 0) logger?.warn("Kiro permissions: 'kiro.toolsSettings' applies only to the project-scope agent config (.kiro/agents/default.json); ignoring it for ~/.kiro/settings/permissions.yaml.");
|
|
64915
|
+
const generatedRules = buildKiroCapabilityRules({
|
|
64916
|
+
config,
|
|
64917
|
+
logger
|
|
64918
|
+
});
|
|
64919
|
+
const rules = mergeKiroCapabilityRules({
|
|
64920
|
+
existingRules: parsed.rules ?? [],
|
|
64921
|
+
generatedRules,
|
|
64922
|
+
logger
|
|
64923
|
+
});
|
|
64924
|
+
const next = { ...parsed };
|
|
64925
|
+
if (rules.length > 0 || parsed.rules !== void 0) next.rules = rules;
|
|
64926
|
+
return new KiroCapabilityPermissions({
|
|
64927
|
+
outputRoot,
|
|
64928
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64929
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64930
|
+
fileContent: serializeKiroPermissions({
|
|
64931
|
+
next,
|
|
64932
|
+
parsed,
|
|
64933
|
+
existingContent
|
|
64934
|
+
}),
|
|
64935
|
+
validate: true,
|
|
64936
|
+
global: true
|
|
64937
|
+
});
|
|
64938
|
+
}
|
|
64939
|
+
toRulesyncPermissions() {
|
|
64940
|
+
let parsed;
|
|
64941
|
+
try {
|
|
64942
|
+
parsed = loadYaml(this.getFileContent()) ?? {};
|
|
64943
|
+
} catch (error) {
|
|
64944
|
+
throw new Error(`Failed to parse Kiro permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
64945
|
+
}
|
|
64946
|
+
const rules = isRecord$1(parsed) && Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
64947
|
+
const permission = {};
|
|
64948
|
+
for (const rule of rules) {
|
|
64949
|
+
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
|
|
64950
|
+
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
64951
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$2(rule.match) ? rule.match : [];
|
|
64952
|
+
const categoryRules = permission[category] ??= {};
|
|
64953
|
+
for (const pattern of patterns) {
|
|
64954
|
+
const previous = categoryRules[pattern];
|
|
64955
|
+
if (previous === void 0 || ACTION_RANK$2[rule.effect] > ACTION_RANK$2[previous]) categoryRules[pattern] = rule.effect;
|
|
64956
|
+
}
|
|
64957
|
+
}
|
|
64958
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
64959
|
+
}
|
|
64960
|
+
validate() {
|
|
64961
|
+
return {
|
|
64962
|
+
success: true,
|
|
64963
|
+
error: null
|
|
64964
|
+
};
|
|
64965
|
+
}
|
|
64966
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
64967
|
+
return new KiroCapabilityPermissions({
|
|
64968
|
+
outputRoot,
|
|
64969
|
+
relativeDirPath,
|
|
64970
|
+
relativeFilePath,
|
|
64971
|
+
fileContent: "",
|
|
64972
|
+
validate: false,
|
|
64973
|
+
global: true
|
|
64974
|
+
});
|
|
64975
|
+
}
|
|
64976
|
+
};
|
|
64977
|
+
/**
|
|
64978
|
+
* Convert the canonical `permission` block into Kiro capability rules: one rule
|
|
64979
|
+
* per (capability, effect) carrying every non-catch-all pattern as `match`, plus
|
|
64980
|
+
* a match-less rule when the catch-all `*` is set.
|
|
64981
|
+
*/
|
|
64982
|
+
function buildKiroCapabilityRules({ config, logger }) {
|
|
64983
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
64984
|
+
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
64985
|
+
const capability = CANONICAL_TO_KIRO_CAPABILITY[category];
|
|
64986
|
+
if (capability === void 0) {
|
|
64987
|
+
logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
|
|
64988
|
+
continue;
|
|
64989
|
+
}
|
|
64990
|
+
for (const [pattern, effect] of Object.entries(rules)) {
|
|
64991
|
+
warnOnWorkspaceRelativeFsPattern({
|
|
64992
|
+
capability,
|
|
64993
|
+
category,
|
|
64994
|
+
pattern,
|
|
64995
|
+
logger
|
|
64996
|
+
});
|
|
64997
|
+
const key = `${capability}\u0000${effect}`;
|
|
64998
|
+
const bucket = buckets.get(key) ?? {
|
|
64999
|
+
capability,
|
|
65000
|
+
effect,
|
|
65001
|
+
patterns: /* @__PURE__ */ new Set()
|
|
65002
|
+
};
|
|
65003
|
+
bucket.patterns.add(pattern);
|
|
65004
|
+
buckets.set(key, bucket);
|
|
65005
|
+
}
|
|
65006
|
+
}
|
|
65007
|
+
const result = [];
|
|
65008
|
+
for (const { capability, effect, patterns } of buckets.values()) {
|
|
65009
|
+
if (patterns.has(CATCH_ALL_PATTERN$6)) result.push({
|
|
65010
|
+
capability,
|
|
65011
|
+
effect
|
|
65012
|
+
});
|
|
65013
|
+
const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$6);
|
|
65014
|
+
if (match.length > 0) result.push({
|
|
65015
|
+
capability,
|
|
65016
|
+
match,
|
|
65017
|
+
effect
|
|
65018
|
+
});
|
|
65019
|
+
}
|
|
65020
|
+
return result;
|
|
65021
|
+
}
|
|
65022
|
+
/**
|
|
65023
|
+
* Serialize the merged config. When the merge changes nothing, the existing
|
|
65024
|
+
* content is returned verbatim so a run with nothing to change never rewrites
|
|
65025
|
+
* the file (and never drops its YAML comments).
|
|
65026
|
+
*/
|
|
65027
|
+
function serializeKiroPermissions({ next, parsed, existingContent }) {
|
|
65028
|
+
if ((0, node_util.isDeepStrictEqual)(next, parsed)) return existingContent;
|
|
65029
|
+
return Object.keys(next).length === 0 ? "" : (0, js_yaml.dump)(next);
|
|
65030
|
+
}
|
|
65031
|
+
/**
|
|
65032
|
+
* `~/.kiro/settings/permissions.yaml` applies to every workspace, so a relative
|
|
65033
|
+
* filesystem pattern such as `src/**` is not tied to the project it was written
|
|
65034
|
+
* for. Warn so the author can make it absolute, `~`-relative or `**`-prefixed.
|
|
65035
|
+
*/
|
|
65036
|
+
function warnOnWorkspaceRelativeFsPattern({ capability, category, pattern, logger }) {
|
|
65037
|
+
if (capability !== "fs_read" && capability !== "fs_write") return;
|
|
65038
|
+
if (pattern === CATCH_ALL_PATTERN$6 || LOCATION_INDEPENDENT_FS_PATTERN.test(pattern)) return;
|
|
65039
|
+
logger?.warn(`Kiro permissions.yaml is user-level and applies to every workspace, but the ${category} pattern "${pattern}" is relative. Use an absolute, ~-relative or **-prefixed pattern to target a specific location.`);
|
|
65040
|
+
}
|
|
65041
|
+
/**
|
|
65042
|
+
* Permissions factory for the `kiro-cli` / `kiro-ide` targets: project scope
|
|
65043
|
+
* writes the agent config `.kiro/agents/default.json` ({@link KiroPermissions}),
|
|
65044
|
+
* global scope writes `~/.kiro/settings/permissions.yaml`
|
|
65045
|
+
* ({@link KiroCapabilityPermissions}). The deprecated `kiro` alias stays on
|
|
65046
|
+
* {@link KiroPermissions} only.
|
|
65047
|
+
*/
|
|
65048
|
+
const KiroScopedPermissions = {
|
|
65049
|
+
getSettablePaths(options = {}) {
|
|
65050
|
+
return options.global ? KiroCapabilityPermissions.getSettablePaths(options) : KiroPermissions.getSettablePaths(options);
|
|
65051
|
+
},
|
|
65052
|
+
fromFile(params) {
|
|
65053
|
+
return params.global ? KiroCapabilityPermissions.fromFile(params) : KiroPermissions.fromFile(params);
|
|
65054
|
+
},
|
|
65055
|
+
fromRulesyncPermissions(params) {
|
|
65056
|
+
return params.global ? KiroCapabilityPermissions.fromRulesyncPermissions(params) : KiroPermissions.fromRulesyncPermissions(params);
|
|
65057
|
+
},
|
|
65058
|
+
forDeletion(params) {
|
|
65059
|
+
return params.global ? KiroCapabilityPermissions.forDeletion(params) : KiroPermissions.forDeletion(params);
|
|
65060
|
+
}
|
|
65061
|
+
};
|
|
65062
|
+
//#endregion
|
|
63983
65063
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
63984
65064
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
63985
65065
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
@@ -66007,6 +67087,8 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
66007
67087
|
"eager",
|
|
66008
67088
|
"listDirectory",
|
|
66009
67089
|
"todoWrite",
|
|
67090
|
+
"codeModeOnly",
|
|
67091
|
+
"freeform",
|
|
66010
67092
|
"workflowsEnabled",
|
|
66011
67093
|
"workflowNameOnly",
|
|
66012
67094
|
"executionSandbox"
|
|
@@ -66133,6 +67215,16 @@ const QWEN_SCOPED_TOOLS_KEYS = {
|
|
|
66133
67215
|
rule: "global-machine-wide",
|
|
66134
67216
|
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`todo_write\` tool is registered in this repository.`,
|
|
66135
67217
|
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `todo_write` tool is registered for every project on this machine."
|
|
67218
|
+
},
|
|
67219
|
+
codeModeOnly: {
|
|
67220
|
+
rule: "global-machine-wide",
|
|
67221
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether ordinary tools are reached only through Code Mode's \`exec\` tool in this repository.`,
|
|
67222
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether ordinary tools are reached only through Code Mode's `exec` tool for every project on this machine."
|
|
67223
|
+
},
|
|
67224
|
+
freeform: {
|
|
67225
|
+
rule: "global-machine-wide",
|
|
67226
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether Code Mode's \`exec\` tool takes raw text input on OpenAI Responses models in this repository.`,
|
|
67227
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether Code Mode's `exec` tool takes raw text input on OpenAI Responses models for every project on this machine."
|
|
66136
67228
|
}
|
|
66137
67229
|
};
|
|
66138
67230
|
/**
|
|
@@ -66596,22 +67688,42 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
66596
67688
|
//#endregion
|
|
66597
67689
|
//#region src/features/permissions/reasonix-permissions.ts
|
|
66598
67690
|
/**
|
|
66599
|
-
* Mapping from rulesync canonical tool category names (lowercase) to
|
|
66600
|
-
*
|
|
66601
|
-
*
|
|
66602
|
-
* Reasonix's
|
|
66603
|
-
*
|
|
66604
|
-
*
|
|
66605
|
-
*
|
|
66606
|
-
*
|
|
66607
|
-
*
|
|
66608
|
-
*
|
|
66609
|
-
*
|
|
67691
|
+
* Mapping from rulesync canonical tool category names (lowercase) to the tool
|
|
67692
|
+
* names a Reasonix `[permissions]` rule must use.
|
|
67693
|
+
*
|
|
67694
|
+
* Reasonix matches a rule's tool exactly against its registered tools, which
|
|
67695
|
+
* are snake_case (`read_file`, `write_file`, `grep`, `glob`, `web_fetch`,
|
|
67696
|
+
* `web_search`, `notebook_edit`, `task`). Only `Bash`, `Edit` (every file
|
|
67697
|
+
* writer: `write_file`, `edit_file`, `multi_edit`, `notebook_edit`, ...),
|
|
67698
|
+
* `Browser` and `Computer` are Claude-Code-style family aliases
|
|
67699
|
+
* (`canonicalRuleTool` / `groupOf`), so a `Read(...)` or `Write(...)` rule
|
|
67700
|
+
* matches nothing and Reasonix v2.34.0+ reports it as dormant. Checked on both
|
|
67701
|
+
* release lines: v1.39.8 (`internal/permission/permission.go`) and v2.41.0
|
|
67702
|
+
* (`internal/safety/permission/permission.go`, `rule_group.go`).
|
|
67703
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
67704
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/rule_group.go
|
|
66610
67705
|
*/
|
|
66611
67706
|
const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
66612
67707
|
bash: "Bash",
|
|
66613
|
-
read:
|
|
67708
|
+
read: REASONIX_READ_TOOL_NAME,
|
|
66614
67709
|
edit: "Edit",
|
|
67710
|
+
write: "write_file",
|
|
67711
|
+
webfetch: "web_fetch",
|
|
67712
|
+
websearch: "web_search",
|
|
67713
|
+
grep: "grep",
|
|
67714
|
+
glob: "glob",
|
|
67715
|
+
notebookedit: "notebook_edit",
|
|
67716
|
+
agent: "task"
|
|
67717
|
+
};
|
|
67718
|
+
/**
|
|
67719
|
+
* The Claude-Code-style spellings rulesync emitted before it followed the
|
|
67720
|
+
* Reasonix tool vocabulary. None of them reaches a Reasonix tool, so they are
|
|
67721
|
+
* read back on import (a file generated by an older rulesync still imports)
|
|
67722
|
+
* and counted as managed on generate (the dormant copy is replaced rather than
|
|
67723
|
+
* kept beside the working rule).
|
|
67724
|
+
*/
|
|
67725
|
+
const CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES = {
|
|
67726
|
+
read: REASONIX_LEGACY_READ_TOOL_NAME,
|
|
66615
67727
|
write: "Write",
|
|
66616
67728
|
webfetch: "WebFetch",
|
|
66617
67729
|
websearch: "WebSearch",
|
|
@@ -66621,9 +67733,16 @@ const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
|
66621
67733
|
agent: "Agent"
|
|
66622
67734
|
};
|
|
66623
67735
|
/**
|
|
66624
|
-
* Reverse mapping from Reasonix tool names
|
|
67736
|
+
* Reverse mapping from Reasonix tool names (current and legacy spellings) to
|
|
67737
|
+
* rulesync canonical names.
|
|
66625
67738
|
*/
|
|
66626
|
-
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
67739
|
+
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries([...Object.entries(CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]), ...Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k])]);
|
|
67740
|
+
/** Every Reasonix tool name a canonical category owns on generate. */
|
|
67741
|
+
function reasonixToolNamesOf(canonical) {
|
|
67742
|
+
const legacy = CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES[canonical];
|
|
67743
|
+
const current = toReasonixToolName(canonical);
|
|
67744
|
+
return legacy === void 0 ? [current] : [current, legacy];
|
|
67745
|
+
}
|
|
66627
67746
|
function toReasonixToolName(canonical) {
|
|
66628
67747
|
return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
|
|
66629
67748
|
}
|
|
@@ -66896,7 +68015,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
66896
68015
|
const parsed = parseReasonixConfig(existingContent);
|
|
66897
68016
|
const config = rulesyncPermissions.getJson();
|
|
66898
68017
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
66899
|
-
const managedToolNames = new Set(Object.keys(config.permission).
|
|
68018
|
+
const managedToolNames = new Set(Object.keys(config.permission).flatMap((category) => reasonixToolNamesOf(category)));
|
|
66900
68019
|
const override = config.reasonix;
|
|
66901
68020
|
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
66902
68021
|
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
@@ -66913,12 +68032,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
66913
68032
|
rawOwnedEntries,
|
|
66914
68033
|
logger
|
|
66915
68034
|
});
|
|
66916
|
-
if (logger && managedToolNames.has("
|
|
68035
|
+
if (logger && managedToolNames.has("read_file")) {
|
|
68036
|
+
const readToolNames = new Set(reasonixToolNamesOf("read"));
|
|
66917
68037
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
66918
68038
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
66919
|
-
return toolName
|
|
68039
|
+
return readToolNames.has(toolName);
|
|
66920
68040
|
});
|
|
66921
|
-
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '
|
|
68041
|
+
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '${REASONIX_READ_TOOL_NAME}' tool and will overwrite ${droppedReadDenyEntries.length} existing read deny entries (possibly from ignore feature). Permissions take precedence.`);
|
|
66922
68042
|
}
|
|
66923
68043
|
const mergedPermissions = { ...existingPermissions };
|
|
66924
68044
|
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
@@ -72264,18 +73384,18 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
72264
73384
|
}
|
|
72265
73385
|
}],
|
|
72266
73386
|
["kiro-cli", {
|
|
72267
|
-
class:
|
|
73387
|
+
class: KiroScopedPermissions,
|
|
72268
73388
|
meta: {
|
|
72269
73389
|
supportsProject: true,
|
|
72270
|
-
supportsGlobal:
|
|
73390
|
+
supportsGlobal: true,
|
|
72271
73391
|
supportsImport: true
|
|
72272
73392
|
}
|
|
72273
73393
|
}],
|
|
72274
73394
|
["kiro-ide", {
|
|
72275
|
-
class:
|
|
73395
|
+
class: KiroScopedPermissions,
|
|
72276
73396
|
meta: {
|
|
72277
73397
|
supportsProject: true,
|
|
72278
|
-
supportsGlobal:
|
|
73398
|
+
supportsGlobal: true,
|
|
72279
73399
|
supportsImport: true
|
|
72280
73400
|
}
|
|
72281
73401
|
}],
|
|
@@ -72526,6 +73646,335 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
72526
73646
|
}
|
|
72527
73647
|
};
|
|
72528
73648
|
//#endregion
|
|
73649
|
+
//#region src/types/tool-display.ts
|
|
73650
|
+
const TOOL_DISPLAY = [
|
|
73651
|
+
{
|
|
73652
|
+
key: "agentsmd",
|
|
73653
|
+
label: "AGENTS.md",
|
|
73654
|
+
group: "standard"
|
|
73655
|
+
},
|
|
73656
|
+
{
|
|
73657
|
+
key: "agentsskills",
|
|
73658
|
+
label: "AgentsSkills",
|
|
73659
|
+
group: "standard"
|
|
73660
|
+
},
|
|
73661
|
+
{
|
|
73662
|
+
key: "amp",
|
|
73663
|
+
label: "Amp",
|
|
73664
|
+
group: "ai"
|
|
73665
|
+
},
|
|
73666
|
+
{
|
|
73667
|
+
key: "bob",
|
|
73668
|
+
label: "IBM Bob",
|
|
73669
|
+
group: "ai"
|
|
73670
|
+
},
|
|
73671
|
+
{
|
|
73672
|
+
key: "claudecode",
|
|
73673
|
+
label: "Claude Code",
|
|
73674
|
+
group: "ai"
|
|
73675
|
+
},
|
|
73676
|
+
{
|
|
73677
|
+
key: "claudecode-plugin",
|
|
73678
|
+
label: "Claude Code plugin",
|
|
73679
|
+
group: "ai"
|
|
73680
|
+
},
|
|
73681
|
+
{
|
|
73682
|
+
key: "codebuddy",
|
|
73683
|
+
label: "CodeBuddy Code",
|
|
73684
|
+
group: "ai"
|
|
73685
|
+
},
|
|
73686
|
+
{
|
|
73687
|
+
key: "codewhale",
|
|
73688
|
+
label: "Codewhale",
|
|
73689
|
+
group: "ai"
|
|
73690
|
+
},
|
|
73691
|
+
{
|
|
73692
|
+
key: "codexcli",
|
|
73693
|
+
label: "Codex CLI",
|
|
73694
|
+
group: "ai"
|
|
73695
|
+
},
|
|
73696
|
+
{
|
|
73697
|
+
key: "commandcode",
|
|
73698
|
+
label: "Command Code",
|
|
73699
|
+
group: "ai"
|
|
73700
|
+
},
|
|
73701
|
+
{
|
|
73702
|
+
key: "copilot",
|
|
73703
|
+
label: "GitHub Copilot",
|
|
73704
|
+
group: "ai"
|
|
73705
|
+
},
|
|
73706
|
+
{
|
|
73707
|
+
key: "copilotcli",
|
|
73708
|
+
label: "GitHub Copilot CLI",
|
|
73709
|
+
group: "ai"
|
|
73710
|
+
},
|
|
73711
|
+
{
|
|
73712
|
+
key: "crush",
|
|
73713
|
+
label: "Crush",
|
|
73714
|
+
group: "ai"
|
|
73715
|
+
},
|
|
73716
|
+
{
|
|
73717
|
+
key: "goose",
|
|
73718
|
+
label: "Goose",
|
|
73719
|
+
group: "ai"
|
|
73720
|
+
},
|
|
73721
|
+
{
|
|
73722
|
+
key: "hermesagent",
|
|
73723
|
+
label: "Hermes Agent",
|
|
73724
|
+
group: "ai"
|
|
73725
|
+
},
|
|
73726
|
+
{
|
|
73727
|
+
key: "grokcli",
|
|
73728
|
+
label: "Grok CLI",
|
|
73729
|
+
group: "ai"
|
|
73730
|
+
},
|
|
73731
|
+
{
|
|
73732
|
+
key: "cursor",
|
|
73733
|
+
label: "Cursor",
|
|
73734
|
+
group: "ai"
|
|
73735
|
+
},
|
|
73736
|
+
{
|
|
73737
|
+
key: "deepagents",
|
|
73738
|
+
label: "deepagents-cli",
|
|
73739
|
+
group: "ai"
|
|
73740
|
+
},
|
|
73741
|
+
{
|
|
73742
|
+
key: "factorydroid",
|
|
73743
|
+
label: "Factory Droid",
|
|
73744
|
+
group: "ai"
|
|
73745
|
+
},
|
|
73746
|
+
{
|
|
73747
|
+
key: "opencode",
|
|
73748
|
+
label: "OpenCode",
|
|
73749
|
+
group: "ai"
|
|
73750
|
+
},
|
|
73751
|
+
{
|
|
73752
|
+
key: "openclaw",
|
|
73753
|
+
label: "OpenClaw",
|
|
73754
|
+
group: "ai"
|
|
73755
|
+
},
|
|
73756
|
+
{
|
|
73757
|
+
key: "mimocode",
|
|
73758
|
+
label: "MiMo Code",
|
|
73759
|
+
group: "ai"
|
|
73760
|
+
},
|
|
73761
|
+
{
|
|
73762
|
+
key: "gitlabduo",
|
|
73763
|
+
label: "GitLab Duo CLI",
|
|
73764
|
+
group: "ai"
|
|
73765
|
+
},
|
|
73766
|
+
{
|
|
73767
|
+
key: "cline",
|
|
73768
|
+
label: "Cline",
|
|
73769
|
+
group: "ai"
|
|
73770
|
+
},
|
|
73771
|
+
{
|
|
73772
|
+
key: "kilo",
|
|
73773
|
+
label: "Kilo Code",
|
|
73774
|
+
group: "ai"
|
|
73775
|
+
},
|
|
73776
|
+
{
|
|
73777
|
+
key: "kimi-code",
|
|
73778
|
+
label: "Kimi Code",
|
|
73779
|
+
group: "ai"
|
|
73780
|
+
},
|
|
73781
|
+
{
|
|
73782
|
+
key: "kimi-code-plugin",
|
|
73783
|
+
label: "Kimi Code plugin",
|
|
73784
|
+
group: "ai"
|
|
73785
|
+
},
|
|
73786
|
+
{
|
|
73787
|
+
key: "roo",
|
|
73788
|
+
label: "Roo Code ⚠️",
|
|
73789
|
+
group: "ai"
|
|
73790
|
+
},
|
|
73791
|
+
{
|
|
73792
|
+
key: "zoocode",
|
|
73793
|
+
label: "Zoo Code",
|
|
73794
|
+
group: "ai"
|
|
73795
|
+
},
|
|
73796
|
+
{
|
|
73797
|
+
key: "rovodev",
|
|
73798
|
+
label: "Rovodev (Atlassian) ⚠️",
|
|
73799
|
+
group: "ai"
|
|
73800
|
+
},
|
|
73801
|
+
{
|
|
73802
|
+
key: "takt",
|
|
73803
|
+
label: "Takt",
|
|
73804
|
+
group: "ai"
|
|
73805
|
+
},
|
|
73806
|
+
{
|
|
73807
|
+
key: "trae",
|
|
73808
|
+
label: "Trae",
|
|
73809
|
+
group: "ai"
|
|
73810
|
+
},
|
|
73811
|
+
{
|
|
73812
|
+
key: "tabnine",
|
|
73813
|
+
label: "Tabnine CLI ⚠️",
|
|
73814
|
+
group: "ai"
|
|
73815
|
+
},
|
|
73816
|
+
{
|
|
73817
|
+
key: "continue",
|
|
73818
|
+
label: "Continue ⚠️",
|
|
73819
|
+
group: "ai"
|
|
73820
|
+
},
|
|
73821
|
+
{
|
|
73822
|
+
key: "cortexcode",
|
|
73823
|
+
label: "Snowflake Cortex Code",
|
|
73824
|
+
group: "ai"
|
|
73825
|
+
},
|
|
73826
|
+
{
|
|
73827
|
+
key: "vibe",
|
|
73828
|
+
label: "Vibe Code",
|
|
73829
|
+
group: "ai"
|
|
73830
|
+
},
|
|
73831
|
+
{
|
|
73832
|
+
key: "vibe-plugin",
|
|
73833
|
+
label: "Vibe Code plugin",
|
|
73834
|
+
group: "ai"
|
|
73835
|
+
},
|
|
73836
|
+
{
|
|
73837
|
+
key: "qoder",
|
|
73838
|
+
label: "Qoder",
|
|
73839
|
+
group: "ai"
|
|
73840
|
+
},
|
|
73841
|
+
{
|
|
73842
|
+
key: "qwencode",
|
|
73843
|
+
label: "Qwen Code",
|
|
73844
|
+
group: "ai"
|
|
73845
|
+
},
|
|
73846
|
+
{
|
|
73847
|
+
key: "musecode",
|
|
73848
|
+
label: "Meta Muse Code",
|
|
73849
|
+
group: "ai"
|
|
73850
|
+
},
|
|
73851
|
+
{
|
|
73852
|
+
key: "reasonix",
|
|
73853
|
+
label: "Reasonix",
|
|
73854
|
+
group: "ai"
|
|
73855
|
+
},
|
|
73856
|
+
{
|
|
73857
|
+
key: "codebuff",
|
|
73858
|
+
label: "Codebuff (Freebuff)",
|
|
73859
|
+
group: "ai"
|
|
73860
|
+
},
|
|
73861
|
+
{
|
|
73862
|
+
key: "kiro",
|
|
73863
|
+
label: "Kiro ⚠️",
|
|
73864
|
+
group: "ai"
|
|
73865
|
+
},
|
|
73866
|
+
{
|
|
73867
|
+
key: "kiro-cli",
|
|
73868
|
+
label: "Kiro CLI",
|
|
73869
|
+
group: "ai"
|
|
73870
|
+
},
|
|
73871
|
+
{
|
|
73872
|
+
key: "kiro-ide",
|
|
73873
|
+
label: "Kiro IDE",
|
|
73874
|
+
group: "ai"
|
|
73875
|
+
},
|
|
73876
|
+
{
|
|
73877
|
+
key: "lettacode",
|
|
73878
|
+
label: "Letta Code",
|
|
73879
|
+
group: "ai"
|
|
73880
|
+
},
|
|
73881
|
+
{
|
|
73882
|
+
key: "antigravity-ide",
|
|
73883
|
+
label: "Google Antigravity IDE",
|
|
73884
|
+
group: "ai"
|
|
73885
|
+
},
|
|
73886
|
+
{
|
|
73887
|
+
key: "antigravity-cli",
|
|
73888
|
+
label: "Google Antigravity CLI",
|
|
73889
|
+
group: "ai"
|
|
73890
|
+
},
|
|
73891
|
+
{
|
|
73892
|
+
key: "antigravity-plugin",
|
|
73893
|
+
label: "Google Antigravity plugin",
|
|
73894
|
+
group: "ai"
|
|
73895
|
+
},
|
|
73896
|
+
{
|
|
73897
|
+
key: "aiassistant",
|
|
73898
|
+
label: "JetBrains AI Assistant",
|
|
73899
|
+
group: "ai"
|
|
73900
|
+
},
|
|
73901
|
+
{
|
|
73902
|
+
key: "junie",
|
|
73903
|
+
label: "JetBrains Junie",
|
|
73904
|
+
group: "ai"
|
|
73905
|
+
},
|
|
73906
|
+
{
|
|
73907
|
+
key: "augmentcode",
|
|
73908
|
+
label: "AugmentCode",
|
|
73909
|
+
group: "ai"
|
|
73910
|
+
},
|
|
73911
|
+
{
|
|
73912
|
+
key: "augmentcode-plugin",
|
|
73913
|
+
label: "AugmentCode plugin",
|
|
73914
|
+
group: "ai"
|
|
73915
|
+
},
|
|
73916
|
+
{
|
|
73917
|
+
key: "devin",
|
|
73918
|
+
label: "Devin Desktop",
|
|
73919
|
+
group: "ai"
|
|
73920
|
+
},
|
|
73921
|
+
{
|
|
73922
|
+
key: "devin-plugin",
|
|
73923
|
+
label: "Devin plugin",
|
|
73924
|
+
group: "ai"
|
|
73925
|
+
},
|
|
73926
|
+
{
|
|
73927
|
+
key: "warp",
|
|
73928
|
+
label: "Warp",
|
|
73929
|
+
group: "ai"
|
|
73930
|
+
},
|
|
73931
|
+
{
|
|
73932
|
+
key: "warpcli",
|
|
73933
|
+
label: "Warp Agent CLI",
|
|
73934
|
+
group: "ai"
|
|
73935
|
+
},
|
|
73936
|
+
{
|
|
73937
|
+
key: "replit",
|
|
73938
|
+
label: "Replit",
|
|
73939
|
+
group: "ai"
|
|
73940
|
+
},
|
|
73941
|
+
{
|
|
73942
|
+
key: "pi",
|
|
73943
|
+
label: "Pi Coding Agent",
|
|
73944
|
+
group: "ai"
|
|
73945
|
+
},
|
|
73946
|
+
{
|
|
73947
|
+
key: "omp",
|
|
73948
|
+
label: "oh-my-pi",
|
|
73949
|
+
group: "ai"
|
|
73950
|
+
},
|
|
73951
|
+
{
|
|
73952
|
+
key: "zed",
|
|
73953
|
+
label: "Zed",
|
|
73954
|
+
group: "ai"
|
|
73955
|
+
},
|
|
73956
|
+
{
|
|
73957
|
+
key: "zcode",
|
|
73958
|
+
label: "ZCode (Z.ai)",
|
|
73959
|
+
group: "ai"
|
|
73960
|
+
},
|
|
73961
|
+
{
|
|
73962
|
+
key: "zcode-plugin",
|
|
73963
|
+
label: "ZCode plugin",
|
|
73964
|
+
group: "ai"
|
|
73965
|
+
},
|
|
73966
|
+
{
|
|
73967
|
+
key: "pool",
|
|
73968
|
+
label: "Pool (Poolside)",
|
|
73969
|
+
group: "ai"
|
|
73970
|
+
},
|
|
73971
|
+
{
|
|
73972
|
+
key: "dsh",
|
|
73973
|
+
label: "DeepSeek Harness",
|
|
73974
|
+
group: "ai"
|
|
73975
|
+
}
|
|
73976
|
+
];
|
|
73977
|
+
//#endregion
|
|
72529
73978
|
//#region src/features/skills/simulated-skill.ts
|
|
72530
73979
|
const SimulatedSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
72531
73980
|
name: zod_mini.z.string(),
|
|
@@ -77933,6 +79382,24 @@ var KimiCodeSkill = class extends ToolSkill {
|
|
|
77933
79382
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
77934
79383
|
return targets.includes("*") || targets.includes("kimi-code");
|
|
77935
79384
|
}
|
|
79385
|
+
/**
|
|
79386
|
+
* Commands are emitted into the Kimi-specific skills tree as
|
|
79387
|
+
* `<slug>/SKILL.md` (see `KimiCodeCommand`), so a directory there matching a
|
|
79388
|
+
* current rulesync command slug (of a command that targets `kimi-code`) is
|
|
79389
|
+
* owned by the commands feature: it must not be imported as a skill nor
|
|
79390
|
+
* deleted as an orphan skill. A command aimed only at other tools never
|
|
79391
|
+
* reaches this tree, so it leaves a same-named skill alone. The import-only
|
|
79392
|
+
* `.agents/skills/` root never receives Kimi Code commands, so a same-named
|
|
79393
|
+
* skill in it stays a skill.
|
|
79394
|
+
*/
|
|
79395
|
+
static async isDirOwned({ relativeDirPath, dirName, inputRoots, global }) {
|
|
79396
|
+
if (relativeDirPath !== this.getSettablePaths({ global }).relativeDirPath) return true;
|
|
79397
|
+
return !await rulesyncCommandSlugExists({
|
|
79398
|
+
inputRoots,
|
|
79399
|
+
dirName,
|
|
79400
|
+
toolTarget: "kimi-code"
|
|
79401
|
+
});
|
|
79402
|
+
}
|
|
77936
79403
|
static async fromDir(params) {
|
|
77937
79404
|
const loaded = await this.loadSkillDirContent({
|
|
77938
79405
|
...params,
|
|
@@ -78006,6 +79473,14 @@ var KimiCodePluginSkill = class extends KimiCodeSkill {
|
|
|
78006
79473
|
return { relativeDirPath: KIMI_CODE_PLUGIN_SKILLS_DIR };
|
|
78007
79474
|
}
|
|
78008
79475
|
/**
|
|
79476
|
+
* Unlike `.kimi-code/skills/`, the plugin `skills/` tree is not shared with
|
|
79477
|
+
* the commands feature (`kimi-code-plugin` writes commands to `commands/`),
|
|
79478
|
+
* so every directory in it is a skill.
|
|
79479
|
+
*/
|
|
79480
|
+
static async isDirOwned() {
|
|
79481
|
+
return true;
|
|
79482
|
+
}
|
|
79483
|
+
/**
|
|
78009
79484
|
* The bundle is read from `--output-root <plugin>`, but the imported rulesync
|
|
78010
79485
|
* file belongs to the project's `.rulesync/`, not to the plugin root.
|
|
78011
79486
|
*/
|
|
@@ -78357,7 +79832,8 @@ const OpenCodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
78357
79832
|
license: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78358
79833
|
compatibility: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78359
79834
|
metadata: zod_mini.z.optional(zod_mini.z.unknown()),
|
|
78360
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
79835
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
79836
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
78361
79837
|
});
|
|
78362
79838
|
var OpenCodeSkill = class extends ToolSkill {
|
|
78363
79839
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -78474,7 +79950,8 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78474
79950
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
78475
79951
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
78476
79952
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
78477
|
-
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
79953
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
|
|
79954
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
78478
79955
|
};
|
|
78479
79956
|
const { toolTarget } = this.constructor.layout;
|
|
78480
79957
|
const rulesyncFrontmatter = {
|
|
@@ -78509,13 +79986,18 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78509
79986
|
rootFrontmatter: rulesyncFrontmatter,
|
|
78510
79987
|
section: opencodeSection
|
|
78511
79988
|
});
|
|
79989
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
79990
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
79991
|
+
section: opencodeSection
|
|
79992
|
+
});
|
|
78512
79993
|
const opencodeFrontmatter = {
|
|
78513
79994
|
name: rulesyncFrontmatter.name,
|
|
78514
79995
|
description: rulesyncFrontmatter.description,
|
|
78515
79996
|
...license !== void 0 && { license },
|
|
78516
79997
|
...compatibility !== void 0 && { compatibility },
|
|
78517
79998
|
...metadata !== void 0 && { metadata },
|
|
78518
|
-
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] }
|
|
79999
|
+
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] },
|
|
80000
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
|
|
78519
80001
|
};
|
|
78520
80002
|
const settablePaths = this.getSettablePaths({ global });
|
|
78521
80003
|
return new this({
|
|
@@ -78538,11 +80020,13 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78538
80020
|
...params,
|
|
78539
80021
|
getSettablePaths: (options) => this.getSettablePaths(options)
|
|
78540
80022
|
});
|
|
78541
|
-
const
|
|
78542
|
-
|
|
78543
|
-
|
|
78544
|
-
|
|
78545
|
-
}
|
|
80023
|
+
const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
80024
|
+
const frontmatter = normalizeDisableModelInvocation({
|
|
80025
|
+
frontmatter: loaded.frontmatter,
|
|
80026
|
+
skillFilePath: (0, node_path.join)(skillDirPath, SKILL_FILE_NAME)
|
|
80027
|
+
});
|
|
80028
|
+
const result = OpenCodeSkillFrontmatterSchema.safeParse(frontmatter);
|
|
80029
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
78546
80030
|
return new this({
|
|
78547
80031
|
outputRoot: loaded.outputRoot,
|
|
78548
80032
|
relativeDirPath: loaded.relativeDirPath,
|
|
@@ -78570,6 +80054,52 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78570
80054
|
});
|
|
78571
80055
|
}
|
|
78572
80056
|
};
|
|
80057
|
+
const DISABLE_MODEL_INVOCATION_KEY = "disable-model-invocation";
|
|
80058
|
+
/**
|
|
80059
|
+
* Reads a `disable-model-invocation` value the way OpenCode V2 does
|
|
80060
|
+
* (`booleanValue` in `packages/core/src/config/plugin/skill-file.ts`): a
|
|
80061
|
+
* boolean, the numbers `1` / `0`, or a `true`/`yes`/`on`/`1` /
|
|
80062
|
+
* `false`/`no`/`off`/`0` string compared after trimming and case-folding.
|
|
80063
|
+
* Anything else is `undefined`, which OpenCode ignores.
|
|
80064
|
+
*/
|
|
80065
|
+
function parseOpencodeBoolean(value) {
|
|
80066
|
+
if (typeof value === "boolean") return value;
|
|
80067
|
+
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : void 0;
|
|
80068
|
+
if (typeof value !== "string") return;
|
|
80069
|
+
const normalized = value.trim().toLowerCase();
|
|
80070
|
+
if ([
|
|
80071
|
+
"true",
|
|
80072
|
+
"yes",
|
|
80073
|
+
"on",
|
|
80074
|
+
"1"
|
|
80075
|
+
].includes(normalized)) return true;
|
|
80076
|
+
if ([
|
|
80077
|
+
"false",
|
|
80078
|
+
"no",
|
|
80079
|
+
"off",
|
|
80080
|
+
"0"
|
|
80081
|
+
].includes(normalized)) return false;
|
|
80082
|
+
}
|
|
80083
|
+
/**
|
|
80084
|
+
* Coerces an imported `disable-model-invocation` onto the boolean the schema
|
|
80085
|
+
* types. A spelling OpenCode cannot read is dropped with a warning instead of
|
|
80086
|
+
* failing the import, since OpenCode itself ignores it.
|
|
80087
|
+
*/
|
|
80088
|
+
function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
|
|
80089
|
+
if (!Object.hasOwn(frontmatter, DISABLE_MODEL_INVOCATION_KEY)) return frontmatter;
|
|
80090
|
+
const raw = frontmatter[DISABLE_MODEL_INVOCATION_KEY];
|
|
80091
|
+
const parsed = parseOpencodeBoolean(raw);
|
|
80092
|
+
if (parsed === raw) return frontmatter;
|
|
80093
|
+
const { [DISABLE_MODEL_INVOCATION_KEY]: _dropped, ...rest } = frontmatter;
|
|
80094
|
+
if (parsed === void 0) {
|
|
80095
|
+
warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ignoring unreadable "${DISABLE_MODEL_INVOCATION_KEY}" value ${stripControlCharacters(JSON.stringify(raw) ?? String(raw))}; OpenCode ignores it too.`);
|
|
80096
|
+
return rest;
|
|
80097
|
+
}
|
|
80098
|
+
return {
|
|
80099
|
+
...rest,
|
|
80100
|
+
[DISABLE_MODEL_INVOCATION_KEY]: parsed
|
|
80101
|
+
};
|
|
80102
|
+
}
|
|
78573
80103
|
//#endregion
|
|
78574
80104
|
//#region src/features/skills/mimocode-skill.ts
|
|
78575
80105
|
/**
|
|
@@ -84871,7 +86401,7 @@ var DeepagentsSubagent = class DeepagentsSubagent extends ToolSubagent {
|
|
|
84871
86401
|
//#endregion
|
|
84872
86402
|
//#region src/features/subagents/devin-subagent.ts
|
|
84873
86403
|
const DevinSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
84874
|
-
name: zod_mini.z.string(),
|
|
86404
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
84875
86405
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
84876
86406
|
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
84877
86407
|
"allowed-tools": zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
@@ -84924,7 +86454,7 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84924
86454
|
outputRoot: ".",
|
|
84925
86455
|
frontmatter: {
|
|
84926
86456
|
targets: ["*"],
|
|
84927
|
-
name,
|
|
86457
|
+
name: name ?? this.getSubagentName(),
|
|
84928
86458
|
description,
|
|
84929
86459
|
...Object.keys(devinSection).length > 0 && { devin: devinSection }
|
|
84930
86460
|
},
|
|
@@ -84998,7 +86528,8 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84998
86528
|
const paths = this.getSettablePaths({ global });
|
|
84999
86529
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
85000
86530
|
const fileContent = await readFileContent(filePath);
|
|
85001
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
86531
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
86532
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
85002
86533
|
const result = DevinSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
85003
86534
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
85004
86535
|
return new this({
|
|
@@ -98646,6 +100177,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98646
100177
|
warnForOutputStyleRuleOptions({ rules, factory }) {
|
|
98647
100178
|
const toolTarget = this.toolTarget;
|
|
98648
100179
|
if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
|
|
100180
|
+
const toolLabel = TOOL_DISPLAY.find((entry) => entry.key === toolTarget)?.label ?? toolTarget;
|
|
98649
100181
|
for (const rule of rules) {
|
|
98650
100182
|
const frontmatter = rule.getFrontmatter();
|
|
98651
100183
|
const section = frontmatter[toolTarget];
|
|
@@ -98653,7 +100185,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98653
100185
|
const { channel, name } = section;
|
|
98654
100186
|
const fileName = stripControlCharacters(rule.getRelativeFilePath());
|
|
98655
100187
|
const targets = frontmatter.targets ?? ["*"];
|
|
98656
|
-
if (channel === "output-style" && targets.some((target) => target !== toolTarget)) this.logger.warn(`${fileName} sets ${toolTarget}.channel: output-style but also targets other tools, which receive it as an ordinary rule. Set \`targets: ["${toolTarget}"]\` to keep the output style to ${
|
|
100188
|
+
if (channel === "output-style" && targets.some((target) => target !== toolTarget)) this.logger.warn(`${fileName} sets ${toolTarget}.channel: output-style but also targets other tools, which receive it as an ordinary rule. Set \`targets: ["${toolTarget}"]\` to keep the output style to ${toolLabel}.`);
|
|
98657
100189
|
if (channel !== "output-style" && name !== void 0) this.logger.warn(`${fileName} sets ${toolTarget}.name, which is read only with ${toolTarget}.channel: output-style; ignoring it.`);
|
|
98658
100190
|
}
|
|
98659
100191
|
}
|
|
@@ -100073,13 +101605,15 @@ const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
|
100073
101605
|
* The manifest declares only `name` and `description`, and rejects anything
|
|
100074
101606
|
* else, so the one required field is derived from the plugin directory's name.
|
|
100075
101607
|
* An existing manifest is never read or rewritten: it may carry a
|
|
100076
|
-
* hand-authored `name` or `description` that rulesync has no source for.
|
|
101608
|
+
* hand-authored `name` or `description` that rulesync has no source for. A
|
|
101609
|
+
* manifest kept in one of the manifest folders counts as present too, so no
|
|
101610
|
+
* root stub is written next to it.
|
|
100077
101611
|
*/
|
|
100078
101612
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
100079
101613
|
const paths = [];
|
|
100080
101614
|
for (const outputRoot of outputRoots) {
|
|
100081
101615
|
const filePath = (0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
100082
|
-
if (await
|
|
101616
|
+
if (await hasAntigravityPluginManifest(outputRoot)) continue;
|
|
100083
101617
|
if (await refusesWriteOutsideRoot({
|
|
100084
101618
|
logger,
|
|
100085
101619
|
rootPath: outputRoot,
|
|
@@ -100102,6 +101636,11 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
100102
101636
|
sourceLoadFailed: false
|
|
100103
101637
|
};
|
|
100104
101638
|
}
|
|
101639
|
+
async function hasAntigravityPluginManifest(outputRoot) {
|
|
101640
|
+
const candidates = [(0, node_path.join)(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => (0, node_path.join)(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
101641
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return true;
|
|
101642
|
+
return false;
|
|
101643
|
+
}
|
|
100105
101644
|
//#endregion
|
|
100106
101645
|
//#region src/features/shared/hermes-project-plugin-activation.ts
|
|
100107
101646
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|
|
@@ -103053,10 +104592,10 @@ Object.defineProperty(exports, "generate", {
|
|
|
103053
104592
|
return generate;
|
|
103054
104593
|
}
|
|
103055
104594
|
});
|
|
103056
|
-
Object.defineProperty(exports, "
|
|
104595
|
+
Object.defineProperty(exports, "getFileSizeAndExecutableBit", {
|
|
103057
104596
|
enumerable: true,
|
|
103058
104597
|
get: function() {
|
|
103059
|
-
return
|
|
104598
|
+
return getFileSizeAndExecutableBit;
|
|
103060
104599
|
}
|
|
103061
104600
|
});
|
|
103062
104601
|
Object.defineProperty(exports, "getHomeDirectory", {
|
|
@@ -103101,12 +104640,6 @@ Object.defineProperty(exports, "hasEnclosingMarkOutsideKeycap", {
|
|
|
103101
104640
|
return hasEnclosingMarkOutsideKeycap;
|
|
103102
104641
|
}
|
|
103103
104642
|
});
|
|
103104
|
-
Object.defineProperty(exports, "hasExecutableBit", {
|
|
103105
|
-
enumerable: true,
|
|
103106
|
-
get: function() {
|
|
103107
|
-
return hasExecutableBit;
|
|
103108
|
-
}
|
|
103109
|
-
});
|
|
103110
104643
|
Object.defineProperty(exports, "importFromTool", {
|
|
103111
104644
|
enumerable: true,
|
|
103112
104645
|
get: function() {
|
|
@@ -103167,6 +104700,12 @@ Object.defineProperty(exports, "loadYaml", {
|
|
|
103167
104700
|
return loadYaml;
|
|
103168
104701
|
}
|
|
103169
104702
|
});
|
|
104703
|
+
Object.defineProperty(exports, "mapWithConcurrency", {
|
|
104704
|
+
enumerable: true,
|
|
104705
|
+
get: function() {
|
|
104706
|
+
return mapWithConcurrency;
|
|
104707
|
+
}
|
|
104708
|
+
});
|
|
103170
104709
|
Object.defineProperty(exports, "mergeInputRootConfigs", {
|
|
103171
104710
|
enumerable: true,
|
|
103172
104711
|
get: function() {
|
|
@@ -103191,6 +104730,12 @@ Object.defineProperty(exports, "pathEscapesRoot", {
|
|
|
103191
104730
|
return pathEscapesRoot;
|
|
103192
104731
|
}
|
|
103193
104732
|
});
|
|
104733
|
+
Object.defineProperty(exports, "posixRelativePathEscapesRoot", {
|
|
104734
|
+
enumerable: true,
|
|
104735
|
+
get: function() {
|
|
104736
|
+
return posixRelativePathEscapesRoot;
|
|
104737
|
+
}
|
|
104738
|
+
});
|
|
103194
104739
|
Object.defineProperty(exports, "quoteForLog", {
|
|
103195
104740
|
enumerable: true,
|
|
103196
104741
|
get: function() {
|
|
@@ -103263,10 +104808,10 @@ Object.defineProperty(exports, "resolveRulesyncSourceWritePath", {
|
|
|
103263
104808
|
return resolveRulesyncSourceWritePath;
|
|
103264
104809
|
}
|
|
103265
104810
|
});
|
|
103266
|
-
Object.defineProperty(exports, "
|
|
104811
|
+
Object.defineProperty(exports, "resolvedRelativePath", {
|
|
103267
104812
|
enumerable: true,
|
|
103268
104813
|
get: function() {
|
|
103269
|
-
return
|
|
104814
|
+
return resolvedRelativePath;
|
|
103270
104815
|
}
|
|
103271
104816
|
});
|
|
103272
104817
|
Object.defineProperty(exports, "runWithDirectoryRollback", {
|