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
|
@@ -583,6 +583,7 @@ const ignoreProcessorToolTargetTuple = [
|
|
|
583
583
|
"reasonix",
|
|
584
584
|
"roo",
|
|
585
585
|
"tabnine",
|
|
586
|
+
"trae",
|
|
586
587
|
"devin",
|
|
587
588
|
"vibe",
|
|
588
589
|
"warp",
|
|
@@ -676,6 +677,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
676
677
|
"hermesagent",
|
|
677
678
|
"junie",
|
|
678
679
|
"kilo",
|
|
680
|
+
"kimi-code",
|
|
679
681
|
"kimi-code-plugin",
|
|
680
682
|
"kiro",
|
|
681
683
|
"kiro-cli",
|
|
@@ -692,6 +694,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
692
694
|
"rovodev",
|
|
693
695
|
"tabnine",
|
|
694
696
|
"takt",
|
|
697
|
+
"trae",
|
|
695
698
|
"devin",
|
|
696
699
|
"warp",
|
|
697
700
|
"zcode",
|
|
@@ -1383,20 +1386,22 @@ async function fileExists(filepath) {
|
|
|
1383
1386
|
return false;
|
|
1384
1387
|
}
|
|
1385
1388
|
}
|
|
1386
|
-
|
|
1389
|
+
/**
|
|
1390
|
+
* The size of a file and whether it carries any execute bit, read with a single
|
|
1391
|
+
* `stat`. Windows reports no execute bits, so a file there is never seen as
|
|
1392
|
+
* executable.
|
|
1393
|
+
*/
|
|
1394
|
+
async function getFileSizeAndExecutableBit(filepath) {
|
|
1387
1395
|
try {
|
|
1388
|
-
|
|
1396
|
+
const stats = await stat(filepath);
|
|
1397
|
+
return {
|
|
1398
|
+
size: stats.size,
|
|
1399
|
+
executable: (stats.mode & 73) !== 0
|
|
1400
|
+
};
|
|
1389
1401
|
} catch (error) {
|
|
1390
|
-
throw new Error(`Failed to
|
|
1402
|
+
throw new Error(`Failed to stat "${filepath}": ${formatError(error)}`, { cause: error });
|
|
1391
1403
|
}
|
|
1392
1404
|
}
|
|
1393
|
-
/**
|
|
1394
|
-
* Whether a file carries any execute bit. Windows reports none, so a file
|
|
1395
|
-
* there is never seen as executable.
|
|
1396
|
-
*/
|
|
1397
|
-
async function hasExecutableBit(filepath) {
|
|
1398
|
-
return ((await stat(filepath)).mode & 73) !== 0;
|
|
1399
|
-
}
|
|
1400
1405
|
async function isSymlink(filepath) {
|
|
1401
1406
|
try {
|
|
1402
1407
|
return (await lstat(filepath)).isSymbolicLink();
|
|
@@ -5267,7 +5272,7 @@ const JUNIE_HOOK_EVENTS = [
|
|
|
5267
5272
|
* `PreToolUse`, and on the end reason for `SessionEnd`. Only `type: "command"`
|
|
5268
5273
|
* hooks are supported.
|
|
5269
5274
|
* @see https://docs.gitlab.com/user/gitlab_duo_cli/customize/
|
|
5270
|
-
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/
|
|
5275
|
+
* @see https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/v9.28.0/packages/core/hooks/src/schema.ts
|
|
5271
5276
|
*/
|
|
5272
5277
|
const GITLABDUO_HOOK_EVENTS = [
|
|
5273
5278
|
"sessionStart",
|
|
@@ -5621,6 +5626,7 @@ const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CA
|
|
|
5621
5626
|
*
|
|
5622
5627
|
* @see https://www.codebuddy.ai/docs/cli/hooks
|
|
5623
5628
|
* @see https://www.codebuddy.ai/docs/cli/plugins-reference
|
|
5629
|
+
* @see https://www.npmjs.com/package/@tencent-ai/codebuddy-code/v/2.167.0
|
|
5624
5630
|
*/
|
|
5625
5631
|
const CODEBUDDY_HOOK_EVENTS = [
|
|
5626
5632
|
"sessionStart",
|
|
@@ -8146,7 +8152,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8146
8152
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
8147
8153
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
8148
8154
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8149
|
-
* `todoWrite`, `
|
|
8155
|
+
* `todoWrite`, `codeModeOnly`, `freeform`, `workflowsEnabled`, `workflowNameOnly`,
|
|
8156
|
+
* `executionSandbox`) and
|
|
8150
8157
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8151
8158
|
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8152
8159
|
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
@@ -8165,7 +8172,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8165
8172
|
* changes what the file said is reported in either scope, naming what that key
|
|
8166
8173
|
* decides there — the autonomy and containment
|
|
8167
8174
|
* controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
|
|
8168
|
-
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8175
|
+
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8176
|
+
* `codeModeOnly`, `freeform`, and `eager`, which
|
|
8169
8177
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8170
8178
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8171
8179
|
* classifier config, the skill kill-switches, and, because these groups are
|
|
@@ -10858,13 +10866,15 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
10858
10866
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
10859
10867
|
license: z.optional(z.unknown()),
|
|
10860
10868
|
compatibility: z.optional(z.unknown()),
|
|
10861
|
-
metadata: z.optional(z.unknown())
|
|
10869
|
+
metadata: z.optional(z.unknown()),
|
|
10870
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
10862
10871
|
})),
|
|
10863
10872
|
mimocode: z.optional(z.looseObject({
|
|
10864
10873
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
10865
10874
|
license: z.optional(z.unknown()),
|
|
10866
10875
|
compatibility: z.optional(z.unknown()),
|
|
10867
|
-
metadata: z.optional(z.unknown())
|
|
10876
|
+
metadata: z.optional(z.unknown()),
|
|
10877
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
10868
10878
|
})),
|
|
10869
10879
|
kilo: z.optional(z.looseObject({
|
|
10870
10880
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
@@ -11317,7 +11327,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11317
11327
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11318
11328
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11319
11329
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11320
|
-
* commandcode, lettacode). Each tool's own section may override that
|
|
11330
|
+
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11321
11331
|
* default with a per-target value. A defined section value (including `false`)
|
|
11322
11332
|
* always wins over the root default.
|
|
11323
11333
|
*
|
|
@@ -13516,8 +13526,8 @@ function diffSharedConfigOwnedKeys({ fileKey, existingContent, newContent }) {
|
|
|
13516
13526
|
return operations;
|
|
13517
13527
|
}
|
|
13518
13528
|
const READ_TOOL_NAME = "Read";
|
|
13519
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${
|
|
13520
|
-
const buildReadDenyEntry = (pattern) => `${
|
|
13529
|
+
const isReadDenyEntry = (entry, readToolName = READ_TOOL_NAME) => entry.startsWith(`${readToolName}(`) && entry.endsWith(")");
|
|
13530
|
+
const buildReadDenyEntry = (pattern, readToolName = READ_TOOL_NAME) => `${readToolName}(${pattern})`;
|
|
13521
13531
|
const parsePermissionsBlock = (settings) => {
|
|
13522
13532
|
const permissions = settings.permissions ?? {};
|
|
13523
13533
|
return {
|
|
@@ -13541,9 +13551,9 @@ const withPermissions = (settings, next) => {
|
|
|
13541
13551
|
};
|
|
13542
13552
|
};
|
|
13543
13553
|
const applyIgnoreReadDenies = (params) => {
|
|
13544
|
-
const { settings, readDenies } = params;
|
|
13554
|
+
const { settings, readDenies, readToolName = READ_TOOL_NAME } = params;
|
|
13545
13555
|
const current = parsePermissionsBlock(settings);
|
|
13546
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
13556
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry, readToolName) || readDenies.includes(entry));
|
|
13547
13557
|
return withPermissions(settings, {
|
|
13548
13558
|
allow: current.allow,
|
|
13549
13559
|
ask: current.ask,
|
|
@@ -17870,6 +17880,7 @@ const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
|
17870
17880
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
17871
17881
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
17872
17882
|
const ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME = "plugin.json";
|
|
17883
|
+
const ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS = [".antigravity-plugin", ".gemini-plugin"];
|
|
17873
17884
|
const AUGMENTCODE_PLUGIN_RULES_DIR = "rules";
|
|
17874
17885
|
const AUGMENTCODE_PLUGIN_COMMANDS_DIR = "commands";
|
|
17875
17886
|
const AUGMENTCODE_PLUGIN_AGENTS_DIR = "agents";
|
|
@@ -19516,9 +19527,36 @@ function commandSlug(relativeFilePath) {
|
|
|
19516
19527
|
* skill. Once the command is removed from every source tree's `commands/`
|
|
19517
19528
|
* directory, the directory stops matching and the skills feature cleans
|
|
19518
19529
|
* it up as a regular orphan.
|
|
19530
|
+
*
|
|
19531
|
+
* When `toolTarget` is given, only commands whose `targets` include it (or
|
|
19532
|
+
* `*`) count: a command aimed at other tools never reaches this tool's skills
|
|
19533
|
+
* tree, so it must not shadow a same-named skill there. Commands that cannot
|
|
19534
|
+
* be parsed are skipped, as the commands feature reports them itself.
|
|
19519
19535
|
*/
|
|
19520
|
-
async function rulesyncCommandSlugExists({ inputRoots, dirName }) {
|
|
19521
|
-
|
|
19536
|
+
async function rulesyncCommandSlugExists({ inputRoots, dirName, toolTarget }) {
|
|
19537
|
+
const matching = (await Promise.all(inputRoots.map(async (root) => {
|
|
19538
|
+
const commandsDirPath = join(root, COMMANDS_FEATURE_SUBDIR);
|
|
19539
|
+
return (await findFilesByGlobs("**/*.md", { cwd: commandsDirPath })).map((filePath) => ({
|
|
19540
|
+
root,
|
|
19541
|
+
commandsDirPath,
|
|
19542
|
+
filePath
|
|
19543
|
+
}));
|
|
19544
|
+
}))).flat().filter(({ filePath }) => commandSlug(basename(filePath)) === dirName);
|
|
19545
|
+
if (toolTarget === void 0) return matching.length > 0;
|
|
19546
|
+
for (const { root, commandsDirPath, filePath } of matching) {
|
|
19547
|
+
let targets;
|
|
19548
|
+
try {
|
|
19549
|
+
targets = (await RulesyncCommand.fromFile({
|
|
19550
|
+
outputRoot: root,
|
|
19551
|
+
relativeDirPath: COMMANDS_FEATURE_SUBDIR,
|
|
19552
|
+
relativeFilePath: relative(commandsDirPath, filePath)
|
|
19553
|
+
})).getFrontmatter().targets;
|
|
19554
|
+
} catch {
|
|
19555
|
+
continue;
|
|
19556
|
+
}
|
|
19557
|
+
if (targets.includes("*") || targets.includes(toolTarget)) return true;
|
|
19558
|
+
}
|
|
19559
|
+
return false;
|
|
19522
19560
|
}
|
|
19523
19561
|
//#endregion
|
|
19524
19562
|
//#region src/features/commands/devin-command.ts
|
|
@@ -19536,7 +19574,7 @@ const DEVIN_COMMAND_SECTION_KEYS = [
|
|
|
19536
19574
|
"permissions",
|
|
19537
19575
|
"triggers"
|
|
19538
19576
|
];
|
|
19539
|
-
function commandSkillContent$
|
|
19577
|
+
function commandSkillContent$2(rulesyncCommand) {
|
|
19540
19578
|
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
19541
19579
|
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
19542
19580
|
const description = frontmatter.description ?? `${slug} command`;
|
|
@@ -19610,7 +19648,7 @@ var DevinCommand = class DevinCommand extends ToolCommand {
|
|
|
19610
19648
|
relativeDirPath: paths.relativeDirPath,
|
|
19611
19649
|
relativeFilePath: SKILL_FILE_NAME,
|
|
19612
19650
|
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
19613
|
-
fileContent: commandSkillContent$
|
|
19651
|
+
fileContent: commandSkillContent$2(rulesyncCommand)
|
|
19614
19652
|
});
|
|
19615
19653
|
}
|
|
19616
19654
|
getFileContent() {
|
|
@@ -21430,6 +21468,180 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
21430
21468
|
}
|
|
21431
21469
|
};
|
|
21432
21470
|
//#endregion
|
|
21471
|
+
//#region src/constants/kimi-code-paths.ts
|
|
21472
|
+
const KIMI_CODE_DIR = ".kimi-code";
|
|
21473
|
+
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
21474
|
+
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
21475
|
+
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
21476
|
+
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
21477
|
+
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
21478
|
+
const KIMI_CODE_SKILLS_DIR_PATH = join(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
21479
|
+
join(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
21480
|
+
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
21481
|
+
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = join(".agents", "agents");
|
|
21482
|
+
//#endregion
|
|
21483
|
+
//#region src/utils/kimi-code.ts
|
|
21484
|
+
function getKimiCodeHome() {
|
|
21485
|
+
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
21486
|
+
return configuredHome ? resolve(configuredHome) : void 0;
|
|
21487
|
+
}
|
|
21488
|
+
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
21489
|
+
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
21490
|
+
}
|
|
21491
|
+
/**
|
|
21492
|
+
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
21493
|
+
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
21494
|
+
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
21495
|
+
* guards checked against them — do not depend on the ambient environment.
|
|
21496
|
+
*/
|
|
21497
|
+
function getKimiCodeSharedConfigWritePaths() {
|
|
21498
|
+
return [{
|
|
21499
|
+
relativeDirPath: KIMI_CODE_DIR,
|
|
21500
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21501
|
+
}, {
|
|
21502
|
+
relativeDirPath: ".",
|
|
21503
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21504
|
+
}];
|
|
21505
|
+
}
|
|
21506
|
+
/**
|
|
21507
|
+
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
21508
|
+
* Both spellings carry the same declaration, but passing the key of the file
|
|
21509
|
+
* being written keeps the write path and the drift guards on the same entry.
|
|
21510
|
+
*/
|
|
21511
|
+
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
21512
|
+
return sharedConfigFileKey({
|
|
21513
|
+
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
21514
|
+
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
21515
|
+
});
|
|
21516
|
+
}
|
|
21517
|
+
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
21518
|
+
return getToolRulesyncOutputRoot({
|
|
21519
|
+
nativeOutputRoot,
|
|
21520
|
+
global,
|
|
21521
|
+
toolHome: getKimiCodeHome
|
|
21522
|
+
});
|
|
21523
|
+
}
|
|
21524
|
+
//#endregion
|
|
21525
|
+
//#region src/features/commands/kimi-code-command.ts
|
|
21526
|
+
/**
|
|
21527
|
+
* Kimi Code SKILL.md frontmatter keys a command may author through its
|
|
21528
|
+
* `kimi-code:` section, each with the alias spellings Kimi also accepts. An
|
|
21529
|
+
* alias is normalized to the canonical key; the canonical spelling wins when
|
|
21530
|
+
* both are present.
|
|
21531
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21532
|
+
*/
|
|
21533
|
+
const KIMI_CODE_COMMAND_SECTION_KEYS = [
|
|
21534
|
+
{
|
|
21535
|
+
key: "type",
|
|
21536
|
+
aliases: []
|
|
21537
|
+
},
|
|
21538
|
+
{
|
|
21539
|
+
key: "whenToUse",
|
|
21540
|
+
aliases: ["when-to-use", "when_to_use"]
|
|
21541
|
+
},
|
|
21542
|
+
{
|
|
21543
|
+
key: "disableModelInvocation",
|
|
21544
|
+
aliases: ["disable-model-invocation", "disable_model_invocation"]
|
|
21545
|
+
},
|
|
21546
|
+
{
|
|
21547
|
+
key: "arguments",
|
|
21548
|
+
aliases: []
|
|
21549
|
+
}
|
|
21550
|
+
];
|
|
21551
|
+
function pickKimiCodeSectionKeys(section) {
|
|
21552
|
+
const picked = {};
|
|
21553
|
+
for (const { key, aliases } of KIMI_CODE_COMMAND_SECTION_KEYS) {
|
|
21554
|
+
const spelling = [key, ...aliases].find((candidate) => section[candidate] !== void 0);
|
|
21555
|
+
if (spelling !== void 0) picked[key] = section[spelling];
|
|
21556
|
+
}
|
|
21557
|
+
return picked;
|
|
21558
|
+
}
|
|
21559
|
+
function commandSkillContent$1(rulesyncCommand) {
|
|
21560
|
+
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
21561
|
+
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
21562
|
+
const description = frontmatter.description?.trim() || `${slug} command`;
|
|
21563
|
+
return stringifyFrontmatter(rulesyncCommand.getBody().trim(), {
|
|
21564
|
+
name: slug,
|
|
21565
|
+
description,
|
|
21566
|
+
disableModelInvocation: true,
|
|
21567
|
+
...pickKimiCodeSectionKeys(frontmatter["kimi-code"] ?? {})
|
|
21568
|
+
});
|
|
21569
|
+
}
|
|
21570
|
+
/**
|
|
21571
|
+
* Represents a Kimi Code slash command, emitted as a Kimi Code Skill.
|
|
21572
|
+
*
|
|
21573
|
+
* Kimi Code has no standalone custom-command directory outside plugins: every
|
|
21574
|
+
* external Skill is registered as a slash command (`/skill:<name>`, with a
|
|
21575
|
+
* `/<name>` shorthand when the bare name is free), and the Skill body supports
|
|
21576
|
+
* `$ARGUMENTS`, positional `$0` / `$1`, and named `$<name>` placeholders.
|
|
21577
|
+
* Commands are therefore emitted onto the native skills surface, one
|
|
21578
|
+
* `SKILL.md` per command: `.kimi-code/skills/<slug>/SKILL.md` (project) and
|
|
21579
|
+
* `~/.kimi-code/skills/<slug>/SKILL.md` (global; `$KIMI_CODE_HOME/skills/`
|
|
21580
|
+
* when `KIMI_CODE_HOME` is set). Directory-form `SKILL.md` requires both
|
|
21581
|
+
* `name` and `description`, so both are always written.
|
|
21582
|
+
*
|
|
21583
|
+
* Import and deletion are intentionally no-ops for this target: the skills
|
|
21584
|
+
* feature owns the `.kimi-code/skills/` tree, so importing it as commands
|
|
21585
|
+
* would double-import every skill (mirrors the Devin, Warp and Hermes Agent
|
|
21586
|
+
* commands targets).
|
|
21587
|
+
*
|
|
21588
|
+
* @see https://moonshotai.github.io/kimi-code/en/customization/skills.html
|
|
21589
|
+
* @see https://moonshotai.github.io/kimi-code/en/reference/slash-commands.html
|
|
21590
|
+
*/
|
|
21591
|
+
var KimiCodeCommand = class KimiCodeCommand extends ToolCommand {
|
|
21592
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
21593
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
21594
|
+
rulesyncCommand,
|
|
21595
|
+
toolTarget: "kimi-code"
|
|
21596
|
+
});
|
|
21597
|
+
}
|
|
21598
|
+
static getSettablePaths({ global = false } = {}) {
|
|
21599
|
+
return { relativeDirPath: getKimiCodeRelativeDirPath({
|
|
21600
|
+
global,
|
|
21601
|
+
relativeDirPath: KIMI_CODE_SKILLS_DIR_NAME
|
|
21602
|
+
}) };
|
|
21603
|
+
}
|
|
21604
|
+
constructor({ slug, ...params }) {
|
|
21605
|
+
super({
|
|
21606
|
+
...params,
|
|
21607
|
+
...slug !== void 0 && {
|
|
21608
|
+
relativeDirPath: join(params.relativeDirPath, slug),
|
|
21609
|
+
relativeFilePath: "SKILL.md"
|
|
21610
|
+
}
|
|
21611
|
+
});
|
|
21612
|
+
}
|
|
21613
|
+
validate() {
|
|
21614
|
+
return {
|
|
21615
|
+
success: true,
|
|
21616
|
+
error: null
|
|
21617
|
+
};
|
|
21618
|
+
}
|
|
21619
|
+
toRulesyncCommand() {
|
|
21620
|
+
const slug = basename(dirname(this.getRelativePathFromCwd()));
|
|
21621
|
+
const { frontmatter, body } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
21622
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
21623
|
+
return new RulesyncCommand({
|
|
21624
|
+
relativeDirPath: RULESYNC_COMMANDS_RELATIVE_DIR_PATH,
|
|
21625
|
+
relativeFilePath: `${slug}.md`,
|
|
21626
|
+
frontmatter: { description },
|
|
21627
|
+
body: body.trimStart()
|
|
21628
|
+
});
|
|
21629
|
+
}
|
|
21630
|
+
static fromRulesyncCommand({ outputRoot, rulesyncCommand, global = false }) {
|
|
21631
|
+
const paths = KimiCodeCommand.getSettablePaths({ global });
|
|
21632
|
+
return new KimiCodeCommand({
|
|
21633
|
+
outputRoot,
|
|
21634
|
+
relativeDirPath: paths.relativeDirPath,
|
|
21635
|
+
relativeFilePath: SKILL_FILE_NAME,
|
|
21636
|
+
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
21637
|
+
fileContent: commandSkillContent$1(rulesyncCommand)
|
|
21638
|
+
});
|
|
21639
|
+
}
|
|
21640
|
+
getFileContent() {
|
|
21641
|
+
return this.fileContent;
|
|
21642
|
+
}
|
|
21643
|
+
};
|
|
21644
|
+
//#endregion
|
|
21433
21645
|
//#region src/features/commands/kimi-code-plugin-command.ts
|
|
21434
21646
|
/**
|
|
21435
21647
|
* Kimi Code reads only `name` and `description` from a plugin command. The
|
|
@@ -21584,6 +21796,14 @@ const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
|
|
|
21584
21796
|
const KIRO_MCP_FILE_NAME = "mcp.json";
|
|
21585
21797
|
const KIRO_IGNORE_FILE_NAME = ".kiroignore";
|
|
21586
21798
|
const KIRO_GLOBAL_IGNORE_FILE_NAME = "kiroignore";
|
|
21799
|
+
/**
|
|
21800
|
+
* Kiro's capability-based permission rules (IDE 1.0, CLI V3). The user-level
|
|
21801
|
+
* file lives at `~/.kiro/settings/permissions.yaml`; the workspace-level file
|
|
21802
|
+
* sits under a hash-derived `~/.kiro/workspace-roots/<hash>/` directory outside
|
|
21803
|
+
* the repository, so only the user-level file is generated.
|
|
21804
|
+
* @see https://kiro.dev/docs/permissions/
|
|
21805
|
+
*/
|
|
21806
|
+
const KIRO_PERMISSIONS_FILE_NAME = "permissions.yaml";
|
|
21587
21807
|
//#endregion
|
|
21588
21808
|
//#region src/features/commands/kiro-command.ts
|
|
21589
21809
|
var KiroCommand = class KiroCommand extends ToolCommand {
|
|
@@ -23596,6 +23816,193 @@ var TaktCommand = class TaktCommand extends ToolCommand {
|
|
|
23596
23816
|
}
|
|
23597
23817
|
};
|
|
23598
23818
|
//#endregion
|
|
23819
|
+
//#region src/constants/trae-paths.ts
|
|
23820
|
+
/**
|
|
23821
|
+
* Trae (ByteDance's AI IDE, "TraeCode" in its current docs) keeps project
|
|
23822
|
+
* configuration under `.trae/` and user configuration under `~/.trae/`.
|
|
23823
|
+
*
|
|
23824
|
+
* @see https://docs.trae.ai/ide/rules?_lang=en
|
|
23825
|
+
* @see https://docs.trae.ai/ide/skills?_lang=en
|
|
23826
|
+
* @see https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
23827
|
+
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23828
|
+
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
23829
|
+
*/
|
|
23830
|
+
const TRAE_DIR = ".trae";
|
|
23831
|
+
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
23832
|
+
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
23833
|
+
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
23834
|
+
const TRAE_SKILLS_DIR_PATH = join(TRAE_DIR, "skills");
|
|
23835
|
+
/**
|
|
23836
|
+
* Custom commands: Markdown files under `.trae/commands/` (project, up to three nested
|
|
23837
|
+
* directory levels) and `~/.trae/commands/` (global).
|
|
23838
|
+
*/
|
|
23839
|
+
const TRAE_COMMANDS_DIR_PATH = join(TRAE_DIR, "commands");
|
|
23840
|
+
/** Indexing ignore list (gitignore syntax), `.trae/.ignore`, project scope only. */
|
|
23841
|
+
const TRAE_IGNORE_FILE_NAME = ".ignore";
|
|
23842
|
+
//#endregion
|
|
23843
|
+
//#region src/features/commands/trae-command.ts
|
|
23844
|
+
const TraeCommandFrontmatterSchema = z.looseObject({
|
|
23845
|
+
description: z.optional(z.string()),
|
|
23846
|
+
name: z.optional(z.string())
|
|
23847
|
+
});
|
|
23848
|
+
/**
|
|
23849
|
+
* Trae reads project commands only up to three directory levels below
|
|
23850
|
+
* `.trae/commands/`; a command file nested deeper is never picked up.
|
|
23851
|
+
*/
|
|
23852
|
+
const TRAE_MAX_PROJECT_COMMAND_DIR_DEPTH = 3;
|
|
23853
|
+
/**
|
|
23854
|
+
* Custom command for Trae.
|
|
23855
|
+
*
|
|
23856
|
+
* Trae reads Markdown command files under `.trae/commands/` (project, up to
|
|
23857
|
+
* three levels of nested directories) and `~/.trae/commands/` (global). The
|
|
23858
|
+
* YAML frontmatter carries `name` and `description`, and the body below the
|
|
23859
|
+
* closing `---` holds the instructions. rulesync writes the rulesync
|
|
23860
|
+
* `description`, a `name` equal to the file name stem (as Trae's editor does),
|
|
23861
|
+
* plus any fields of the `trae` section (which win over the derived ones), and
|
|
23862
|
+
* keeps nested command paths.
|
|
23863
|
+
*
|
|
23864
|
+
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23865
|
+
*/
|
|
23866
|
+
var TraeCommand = class TraeCommand extends ToolCommand {
|
|
23867
|
+
frontmatter;
|
|
23868
|
+
body;
|
|
23869
|
+
constructor({ frontmatter, body, ...rest }) {
|
|
23870
|
+
if (rest.validate) {
|
|
23871
|
+
const result = TraeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
23872
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
23873
|
+
}
|
|
23874
|
+
super({
|
|
23875
|
+
...rest,
|
|
23876
|
+
fileContent: stringifyFrontmatter(body, frontmatter)
|
|
23877
|
+
});
|
|
23878
|
+
this.frontmatter = frontmatter;
|
|
23879
|
+
this.body = body;
|
|
23880
|
+
}
|
|
23881
|
+
static getSettablePaths(_options = {}) {
|
|
23882
|
+
return { relativeDirPath: TRAE_COMMANDS_DIR_PATH };
|
|
23883
|
+
}
|
|
23884
|
+
getBody() {
|
|
23885
|
+
return this.body;
|
|
23886
|
+
}
|
|
23887
|
+
getFrontmatter() {
|
|
23888
|
+
return this.frontmatter;
|
|
23889
|
+
}
|
|
23890
|
+
toRulesyncCommand() {
|
|
23891
|
+
const { description, name, ...otherFields } = this.frontmatter;
|
|
23892
|
+
const restFields = name === void 0 || name === basename(this.relativeFilePath, ".md") ? otherFields : {
|
|
23893
|
+
name,
|
|
23894
|
+
...otherFields
|
|
23895
|
+
};
|
|
23896
|
+
const rulesyncFrontmatter = {
|
|
23897
|
+
targets: ["*"],
|
|
23898
|
+
description,
|
|
23899
|
+
...Object.keys(restFields).length > 0 && { trae: restFields }
|
|
23900
|
+
};
|
|
23901
|
+
return new RulesyncCommand({
|
|
23902
|
+
outputRoot: ".",
|
|
23903
|
+
frontmatter: rulesyncFrontmatter,
|
|
23904
|
+
body: this.body,
|
|
23905
|
+
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
23906
|
+
relativeFilePath: this.relativeFilePath,
|
|
23907
|
+
fileContent: stringifyFrontmatter(this.body, rulesyncFrontmatter),
|
|
23908
|
+
validate: true
|
|
23909
|
+
});
|
|
23910
|
+
}
|
|
23911
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
23912
|
+
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
23913
|
+
const { name: _name, ...traeFields } = rulesyncFrontmatter.trae ?? {};
|
|
23914
|
+
const traeFrontmatter = {
|
|
23915
|
+
name: this.deriveName(rulesyncCommand),
|
|
23916
|
+
description: rulesyncFrontmatter.description,
|
|
23917
|
+
...traeFields
|
|
23918
|
+
};
|
|
23919
|
+
return new TraeCommand({
|
|
23920
|
+
outputRoot,
|
|
23921
|
+
frontmatter: traeFrontmatter,
|
|
23922
|
+
body: rulesyncCommand.getBody(),
|
|
23923
|
+
relativeDirPath: this.getSettablePaths({ global }).relativeDirPath,
|
|
23924
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
23925
|
+
validate
|
|
23926
|
+
});
|
|
23927
|
+
}
|
|
23928
|
+
validate() {
|
|
23929
|
+
if (!this.frontmatter) return {
|
|
23930
|
+
success: true,
|
|
23931
|
+
error: null
|
|
23932
|
+
};
|
|
23933
|
+
const result = TraeCommandFrontmatterSchema.safeParse(this.frontmatter);
|
|
23934
|
+
if (result.success) return {
|
|
23935
|
+
success: true,
|
|
23936
|
+
error: null
|
|
23937
|
+
};
|
|
23938
|
+
return {
|
|
23939
|
+
success: false,
|
|
23940
|
+
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
23941
|
+
};
|
|
23942
|
+
}
|
|
23943
|
+
/**
|
|
23944
|
+
* Warn about commands Trae will not load as written: Trae documents `name`
|
|
23945
|
+
* as the command's unique identifier, so two commands deriving the same
|
|
23946
|
+
* `name` (e.g. `a/review.md` and `b/review.md`) collide, and it reads
|
|
23947
|
+
* project commands only up to three nested directory levels. Both files are
|
|
23948
|
+
* still written, so this warns rather than failing the run.
|
|
23949
|
+
*/
|
|
23950
|
+
static validateRulesyncCommands({ rulesyncCommands, logger, global = false }) {
|
|
23951
|
+
const pathsByName = /* @__PURE__ */ new Map();
|
|
23952
|
+
for (const rulesyncCommand of rulesyncCommands) {
|
|
23953
|
+
if (!this.isTargetedByRulesyncCommand(rulesyncCommand)) continue;
|
|
23954
|
+
const relativeFilePath = toPosixPath(rulesyncCommand.getRelativeFilePath());
|
|
23955
|
+
const name = this.deriveName(rulesyncCommand);
|
|
23956
|
+
if (typeof name === "string") pathsByName.set(name, [...pathsByName.get(name) ?? [], relativeFilePath]);
|
|
23957
|
+
const dirDepth = relativeFilePath.split("/").length - 1;
|
|
23958
|
+
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.`);
|
|
23959
|
+
}
|
|
23960
|
+
for (const [name, paths] of pathsByName) {
|
|
23961
|
+
if (paths.length < 2) continue;
|
|
23962
|
+
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.`);
|
|
23963
|
+
}
|
|
23964
|
+
}
|
|
23965
|
+
/**
|
|
23966
|
+
* The `name` Trae sees for a command: the `trae` section's `name` when set,
|
|
23967
|
+
* otherwise the file name stem. A non-string override is returned as is so
|
|
23968
|
+
* frontmatter validation still rejects it.
|
|
23969
|
+
*/
|
|
23970
|
+
static deriveName(rulesyncCommand) {
|
|
23971
|
+
return rulesyncCommand.getFrontmatter().trae?.name ?? basename(rulesyncCommand.getRelativeFilePath(), ".md");
|
|
23972
|
+
}
|
|
23973
|
+
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
23974
|
+
return this.isTargetedByRulesyncCommandDefault({
|
|
23975
|
+
rulesyncCommand,
|
|
23976
|
+
toolTarget: "trae"
|
|
23977
|
+
});
|
|
23978
|
+
}
|
|
23979
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
23980
|
+
const paths = this.getSettablePaths({ global });
|
|
23981
|
+
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
23982
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
23983
|
+
const result = TraeCommandFrontmatterSchema.safeParse(frontmatter);
|
|
23984
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
23985
|
+
return new TraeCommand({
|
|
23986
|
+
outputRoot,
|
|
23987
|
+
relativeDirPath: paths.relativeDirPath,
|
|
23988
|
+
relativeFilePath,
|
|
23989
|
+
frontmatter: result.data,
|
|
23990
|
+
body: body.trim(),
|
|
23991
|
+
validate
|
|
23992
|
+
});
|
|
23993
|
+
}
|
|
23994
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
23995
|
+
return new TraeCommand({
|
|
23996
|
+
outputRoot,
|
|
23997
|
+
relativeDirPath,
|
|
23998
|
+
relativeFilePath,
|
|
23999
|
+
frontmatter: { description: "" },
|
|
24000
|
+
body: "",
|
|
24001
|
+
validate: false
|
|
24002
|
+
});
|
|
24003
|
+
}
|
|
24004
|
+
};
|
|
24005
|
+
//#endregion
|
|
23599
24006
|
//#region src/constants/warp-paths.ts
|
|
23600
24007
|
const WARP_DIR = ".warp";
|
|
23601
24008
|
const WARP_SKILLS_DIR_PATH = join(WARP_DIR, "skills");
|
|
@@ -24146,6 +24553,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24146
24553
|
supportsSubdirectory: false
|
|
24147
24554
|
}
|
|
24148
24555
|
}],
|
|
24556
|
+
["kimi-code", {
|
|
24557
|
+
class: KimiCodeCommand,
|
|
24558
|
+
meta: {
|
|
24559
|
+
extension: "md",
|
|
24560
|
+
supportsProject: true,
|
|
24561
|
+
supportsGlobal: true,
|
|
24562
|
+
isSimulated: false,
|
|
24563
|
+
supportsSubdirectory: false,
|
|
24564
|
+
skipToolFileScan: true
|
|
24565
|
+
}
|
|
24566
|
+
}],
|
|
24149
24567
|
["kimi-code-plugin", {
|
|
24150
24568
|
class: KimiCodePluginCommand,
|
|
24151
24569
|
meta: {
|
|
@@ -24306,6 +24724,16 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24306
24724
|
supportsSubdirectory: false
|
|
24307
24725
|
}
|
|
24308
24726
|
}],
|
|
24727
|
+
["trae", {
|
|
24728
|
+
class: TraeCommand,
|
|
24729
|
+
meta: {
|
|
24730
|
+
extension: "md",
|
|
24731
|
+
supportsProject: true,
|
|
24732
|
+
supportsGlobal: true,
|
|
24733
|
+
isSimulated: false,
|
|
24734
|
+
supportsSubdirectory: true
|
|
24735
|
+
}
|
|
24736
|
+
}],
|
|
24309
24737
|
["devin", {
|
|
24310
24738
|
class: DevinCommand,
|
|
24311
24739
|
meta: {
|
|
@@ -24389,7 +24817,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
24389
24817
|
await factory.class.validateRulesyncCommands?.({
|
|
24390
24818
|
inputRoots: this.inputRoots,
|
|
24391
24819
|
rulesyncCommands,
|
|
24392
|
-
logger: this.logger
|
|
24820
|
+
logger: this.logger,
|
|
24821
|
+
global: this.global
|
|
24393
24822
|
});
|
|
24394
24823
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
24395
24824
|
const toolCommands = [];
|
|
@@ -26069,8 +26498,8 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
26069
26498
|
* side either way, so the value is dropped rather than carried into a config
|
|
26070
26499
|
* the tool would ignore or refuse.
|
|
26071
26500
|
*/
|
|
26072
|
-
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
26073
|
-
const { canonical, tool, commandOnly, events } = spec;
|
|
26501
|
+
function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction, warn }) {
|
|
26502
|
+
const { canonical, tool, commandOnly, hookTypes, events, ignoredEvents, ignoredWhenFlags } = spec;
|
|
26074
26503
|
const field = direction === "generated" ? canonical : tool;
|
|
26075
26504
|
if (!isFieldApplicable({
|
|
26076
26505
|
commandOnly,
|
|
@@ -26079,10 +26508,23 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }
|
|
|
26079
26508
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
26080
26509
|
return false;
|
|
26081
26510
|
}
|
|
26511
|
+
if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
|
|
26512
|
+
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}.`);
|
|
26513
|
+
return false;
|
|
26514
|
+
}
|
|
26082
26515
|
if (events !== void 0 && !events.has(eventName)) {
|
|
26083
26516
|
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}.`);
|
|
26084
26517
|
return false;
|
|
26085
26518
|
}
|
|
26519
|
+
if (ignoredEvents?.has(eventName)) {
|
|
26520
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on "${eventName}", so it is not ${direction}.`);
|
|
26521
|
+
return false;
|
|
26522
|
+
}
|
|
26523
|
+
const setFlag = hookType === "command" ? ignoredWhenFlags?.find((flag) => flags[flag] === true) : void 0;
|
|
26524
|
+
if (setFlag !== void 0) {
|
|
26525
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool ignores "${tool}" on a hook that sets "${setFlag}", so it is not ${direction}.`);
|
|
26526
|
+
return false;
|
|
26527
|
+
}
|
|
26086
26528
|
return true;
|
|
26087
26529
|
}
|
|
26088
26530
|
/**
|
|
@@ -26099,6 +26541,7 @@ function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
|
26099
26541
|
spec,
|
|
26100
26542
|
hookType,
|
|
26101
26543
|
eventName,
|
|
26544
|
+
flags: def,
|
|
26102
26545
|
direction: "generated",
|
|
26103
26546
|
warn
|
|
26104
26547
|
})) emitted[spec.tool] = spec.trueValue;
|
|
@@ -26120,6 +26563,7 @@ function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
|
26120
26563
|
spec,
|
|
26121
26564
|
hookType,
|
|
26122
26565
|
eventName,
|
|
26566
|
+
flags: h,
|
|
26123
26567
|
direction: "imported",
|
|
26124
26568
|
warn
|
|
26125
26569
|
})) continue;
|
|
@@ -28012,6 +28456,20 @@ const CLAUDE_SPEC = {
|
|
|
28012
28456
|
tool: "continueOnBlock"
|
|
28013
28457
|
}
|
|
28014
28458
|
],
|
|
28459
|
+
keywordPassthroughFields: [{
|
|
28460
|
+
canonical: "failClosed",
|
|
28461
|
+
tool: "onFailure",
|
|
28462
|
+
trueValue: "block",
|
|
28463
|
+
falseValue: "continue",
|
|
28464
|
+
hookTypes: /* @__PURE__ */ new Set(["command", "http"]),
|
|
28465
|
+
ignoredEvents: /* @__PURE__ */ new Set([
|
|
28466
|
+
"stop",
|
|
28467
|
+
"subagentStop",
|
|
28468
|
+
"taskCompleted",
|
|
28469
|
+
"teammateIdle"
|
|
28470
|
+
]),
|
|
28471
|
+
ignoredWhenFlags: ["async", "asyncRewake"]
|
|
28472
|
+
}],
|
|
28015
28473
|
arrayPassthroughFields: [{
|
|
28016
28474
|
canonical: "args",
|
|
28017
28475
|
tool: "args",
|
|
@@ -28734,18 +29192,20 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28734
29192
|
canonicalToToolEventNames: CANONICAL_TO_CODEXCLI_EVENT_NAMES,
|
|
28735
29193
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
28736
29194
|
projectDirVar: "",
|
|
28737
|
-
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
29195
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command", "mcp_tool"]),
|
|
28738
29196
|
passthroughFields: ["name", "description"],
|
|
28739
29197
|
stringPassthroughFields: [{
|
|
28740
29198
|
canonical: "commandWindows",
|
|
28741
|
-
tool: "commandWindows"
|
|
29199
|
+
tool: "commandWindows",
|
|
29200
|
+
commandOnly: true
|
|
28742
29201
|
}, {
|
|
28743
29202
|
canonical: "statusMessage",
|
|
28744
29203
|
tool: "statusMessage"
|
|
28745
29204
|
}],
|
|
28746
29205
|
numberPassthroughFields: [{
|
|
28747
29206
|
canonical: "additionalContextLimit",
|
|
28748
|
-
tool: "additionalContextLimit"
|
|
29207
|
+
tool: "additionalContextLimit",
|
|
29208
|
+
commandOnly: true
|
|
28749
29209
|
}],
|
|
28750
29210
|
booleanPassthroughFields: [{
|
|
28751
29211
|
canonical: "async",
|
|
@@ -28754,6 +29214,74 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28754
29214
|
}]
|
|
28755
29215
|
};
|
|
28756
29216
|
/**
|
|
29217
|
+
* Whether a JSON value holds a `null` anywhere. Codex rejects an `mcp_tool`
|
|
29218
|
+
* hook `input` that cannot be represented as TOML (it hashes the handler as
|
|
29219
|
+
* TOML for trust review), and `null` is the one JSON value TOML lacks.
|
|
29220
|
+
*/
|
|
29221
|
+
function containsNull(value) {
|
|
29222
|
+
if (value === null) return true;
|
|
29223
|
+
if (Array.isArray(value)) return value.some(containsNull);
|
|
29224
|
+
if (typeof value === "object") return Object.values(value).some(containsNull);
|
|
29225
|
+
return false;
|
|
29226
|
+
}
|
|
29227
|
+
const WHOLE_FILE_FAILURE = "Codex fails to load the whole hooks file on such a handler";
|
|
29228
|
+
/**
|
|
29229
|
+
* Why this generated handler must not be written, or `undefined` when Codex
|
|
29230
|
+
* can load and run it. Codex deserializes `timeout` as `Option<u64>` on every
|
|
29231
|
+
* handler type, and `server` / `tool` as required strings plus `input` as a
|
|
29232
|
+
* TOML-representable map on `mcp_tool`; a handler that fails that stops the
|
|
29233
|
+
* whole hooks file from loading rather than just that one hook. An empty
|
|
29234
|
+
* `server` / `tool` string still loads but can never resolve to a tool.
|
|
29235
|
+
* https://github.com/openai/codex/blob/main/codex-rs/config/src/hook_config.rs
|
|
29236
|
+
*/
|
|
29237
|
+
function describeRejectedHandler(handler) {
|
|
29238
|
+
const { timeout } = handler;
|
|
29239
|
+
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}`;
|
|
29240
|
+
if (handler.type !== "mcp_tool") return;
|
|
29241
|
+
for (const field of ["server", "tool"]) {
|
|
29242
|
+
const value = handler[field];
|
|
29243
|
+
if (typeof value !== "string") return `it has no "${field}", and ${WHOLE_FILE_FAILURE}`;
|
|
29244
|
+
if (value === "") return `its "${field}" is empty, so it can never resolve to an MCP tool`;
|
|
29245
|
+
}
|
|
29246
|
+
if (containsNull(handler.input)) return `its "input" holds a null, which Codex cannot represent as TOML, and ${WHOLE_FILE_FAILURE}`;
|
|
29247
|
+
}
|
|
29248
|
+
/**
|
|
29249
|
+
* Drop the generated handlers Codex would refuse to load (or could never
|
|
29250
|
+
* run), and warn about the `mcp_tool` ones Codex loads but skips (`SessionEnd`
|
|
29251
|
+
* MCP hooks are skipped with a startup warning). Matcher groups left without
|
|
29252
|
+
* handlers are removed. https://learn.chatgpt.com/docs/hooks
|
|
29253
|
+
*/
|
|
29254
|
+
function filterCodexHandlers({ hooks, logger }) {
|
|
29255
|
+
const result = {};
|
|
29256
|
+
const skippedSessionEndTools = [];
|
|
29257
|
+
for (const [eventName, groups] of Object.entries(hooks)) {
|
|
29258
|
+
const kept = [];
|
|
29259
|
+
for (const group of groups) {
|
|
29260
|
+
const entry = group;
|
|
29261
|
+
if (!Array.isArray(entry.hooks)) {
|
|
29262
|
+
kept.push(group);
|
|
29263
|
+
continue;
|
|
29264
|
+
}
|
|
29265
|
+
const handlers = entry.hooks.filter((handler) => {
|
|
29266
|
+
const reason = describeRejectedHandler(handler);
|
|
29267
|
+
if (reason !== void 0) {
|
|
29268
|
+
logger?.warn(`Skipping a "${String(handler.type)}" hook on Codex CLI "${eventName}": ${reason}.`);
|
|
29269
|
+
return false;
|
|
29270
|
+
}
|
|
29271
|
+
if (eventName === "SessionEnd" && handler.type === "mcp_tool") skippedSessionEndTools.push(`${String(handler.server)}/${String(handler.tool)}`);
|
|
29272
|
+
return true;
|
|
29273
|
+
});
|
|
29274
|
+
if (handlers.length > 0) kept.push({
|
|
29275
|
+
...entry,
|
|
29276
|
+
hooks: handlers
|
|
29277
|
+
});
|
|
29278
|
+
}
|
|
29279
|
+
if (kept.length > 0) result[eventName] = kept;
|
|
29280
|
+
}
|
|
29281
|
+
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(", ")}.`);
|
|
29282
|
+
return result;
|
|
29283
|
+
}
|
|
29284
|
+
/**
|
|
28757
29285
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
28758
29286
|
* Reads the existing file (if any), parses TOML, and returns the content without writing to
|
|
28759
29287
|
* disk. The caller is responsible for writing via the normal write phase.
|
|
@@ -28846,10 +29374,13 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
28846
29374
|
const config = rulesyncHooks.getJson();
|
|
28847
29375
|
const merged = mergeGeneratedHookLists({
|
|
28848
29376
|
existingContent,
|
|
28849
|
-
generatedHooks:
|
|
28850
|
-
|
|
28851
|
-
|
|
28852
|
-
|
|
29377
|
+
generatedHooks: filterCodexHandlers({
|
|
29378
|
+
hooks: canonicalToToolHooks({
|
|
29379
|
+
config,
|
|
29380
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
29381
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
29382
|
+
logger
|
|
29383
|
+
}),
|
|
28853
29384
|
logger
|
|
28854
29385
|
}),
|
|
28855
29386
|
shape: "matcher-groups",
|
|
@@ -32028,60 +32559,6 @@ var KiloHooks = class KiloHooks extends ToolHooks {
|
|
|
32028
32559
|
}
|
|
32029
32560
|
};
|
|
32030
32561
|
//#endregion
|
|
32031
|
-
//#region src/constants/kimi-code-paths.ts
|
|
32032
|
-
const KIMI_CODE_DIR = ".kimi-code";
|
|
32033
|
-
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
32034
|
-
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
32035
|
-
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
32036
|
-
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
32037
|
-
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
32038
|
-
const KIMI_CODE_SKILLS_DIR_PATH = join(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
32039
|
-
join(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
32040
|
-
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
32041
|
-
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = join(".agents", "agents");
|
|
32042
|
-
//#endregion
|
|
32043
|
-
//#region src/utils/kimi-code.ts
|
|
32044
|
-
function getKimiCodeHome() {
|
|
32045
|
-
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
32046
|
-
return configuredHome ? resolve(configuredHome) : void 0;
|
|
32047
|
-
}
|
|
32048
|
-
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
32049
|
-
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
32050
|
-
}
|
|
32051
|
-
/**
|
|
32052
|
-
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
32053
|
-
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
32054
|
-
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
32055
|
-
* guards checked against them — do not depend on the ambient environment.
|
|
32056
|
-
*/
|
|
32057
|
-
function getKimiCodeSharedConfigWritePaths() {
|
|
32058
|
-
return [{
|
|
32059
|
-
relativeDirPath: KIMI_CODE_DIR,
|
|
32060
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32061
|
-
}, {
|
|
32062
|
-
relativeDirPath: ".",
|
|
32063
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32064
|
-
}];
|
|
32065
|
-
}
|
|
32066
|
-
/**
|
|
32067
|
-
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
32068
|
-
* Both spellings carry the same declaration, but passing the key of the file
|
|
32069
|
-
* being written keeps the write path and the drift guards on the same entry.
|
|
32070
|
-
*/
|
|
32071
|
-
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
32072
|
-
return sharedConfigFileKey({
|
|
32073
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
32074
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32075
|
-
});
|
|
32076
|
-
}
|
|
32077
|
-
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
32078
|
-
return getToolRulesyncOutputRoot({
|
|
32079
|
-
nativeOutputRoot,
|
|
32080
|
-
global,
|
|
32081
|
-
toolHome: getKimiCodeHome
|
|
32082
|
-
});
|
|
32083
|
-
}
|
|
32084
|
-
//#endregion
|
|
32085
32562
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
32086
32563
|
function runFromTrustedDirectory({ command, trustedDirectory }) {
|
|
32087
32564
|
if (process.platform === "win32") return `set "RULESYNC_KIMI_HOOK_CWD=1" && cd /d "${trustedDirectory.replaceAll("%", "%%").replaceAll("\"", "\"\"")}" && ${command}`;
|
|
@@ -35538,7 +36015,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
35538
36015
|
supportsImport: true
|
|
35539
36016
|
},
|
|
35540
36017
|
supportedEvents: CODEXCLI_HOOK_EVENTS,
|
|
35541
|
-
supportedHookTypes: ["command"],
|
|
36018
|
+
supportedHookTypes: ["command", "mcp_tool"],
|
|
35542
36019
|
supportsMatcher: true
|
|
35543
36020
|
}],
|
|
35544
36021
|
["commandcode", {
|
|
@@ -37800,6 +38277,23 @@ function toReasonixTable(value) {
|
|
|
37800
38277
|
if (!isPlainObject$1(value)) return {};
|
|
37801
38278
|
return { ...value };
|
|
37802
38279
|
}
|
|
38280
|
+
/**
|
|
38281
|
+
* The tool name a Reasonix `[permissions]` rule must use to reach file reads.
|
|
38282
|
+
* Reasonix matches a rule's tool exactly against its registered tools
|
|
38283
|
+
* (`read_file`, `write_file`, `grep`, ...); only `Bash`, `Edit`, `Browser` and
|
|
38284
|
+
* `Computer` are Claude-Code-style aliases (`canonicalRuleTool` in
|
|
38285
|
+
* `internal/safety/permission/permission.go`), so a `Read(...)` rule matches
|
|
38286
|
+
* nothing. Shared by the `ignore` and `permissions` adapters, which both write
|
|
38287
|
+
* read denies into the same table.
|
|
38288
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
38289
|
+
*/
|
|
38290
|
+
const REASONIX_READ_TOOL_NAME = "read_file";
|
|
38291
|
+
/**
|
|
38292
|
+
* The spelling rulesync wrote for read rules before it followed the Reasonix
|
|
38293
|
+
* tool vocabulary. Still accepted on import, and an entry spelled this way is
|
|
38294
|
+
* treated as rulesync-owned on generate so the dormant copy is replaced.
|
|
38295
|
+
*/
|
|
38296
|
+
const REASONIX_LEGACY_READ_TOOL_NAME = "Read";
|
|
37803
38297
|
//#endregion
|
|
37804
38298
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
37805
38299
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -37818,10 +38312,15 @@ const asClaudeStyleSettings = (document) => {
|
|
|
37818
38312
|
...table,
|
|
37819
38313
|
allow: toReasonixStringArray(table.allow),
|
|
37820
38314
|
ask: toReasonixStringArray(table.ask),
|
|
37821
|
-
deny: toReasonixStringArray(table.deny)
|
|
38315
|
+
deny: toReasonixStringArray(table.deny).filter((entry) => !isReadDenyEntry(entry, REASONIX_LEGACY_READ_TOOL_NAME))
|
|
37822
38316
|
}
|
|
37823
38317
|
};
|
|
37824
38318
|
};
|
|
38319
|
+
/** The pattern of a `read_file(...)` (or legacy `Read(...)`) deny, else null. */
|
|
38320
|
+
const readDenyPatternOf = (entry) => {
|
|
38321
|
+
for (const toolName of [REASONIX_READ_TOOL_NAME, REASONIX_LEGACY_READ_TOOL_NAME]) if (isReadDenyEntry(entry, toolName)) return entry.slice(toolName.length + 1, -1);
|
|
38322
|
+
return null;
|
|
38323
|
+
};
|
|
37825
38324
|
/**
|
|
37826
38325
|
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
37827
38326
|
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
@@ -37834,13 +38333,16 @@ const withoutEmptyPermissions = (settings) => {
|
|
|
37834
38333
|
return document;
|
|
37835
38334
|
};
|
|
37836
38335
|
/**
|
|
37837
|
-
* Writes `.rulesyncignore` patterns as `
|
|
38336
|
+
* Writes `.rulesyncignore` patterns as `read_file(<pattern>)` entries in the
|
|
37838
38337
|
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
37839
38338
|
* `~/.reasonix/config.toml` (global).
|
|
37840
38339
|
*
|
|
37841
38340
|
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
37842
38341
|
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
37843
38342
|
* while `forbid_read` is documented as absolute paths with no glob support.
|
|
38343
|
+
* The tool is spelled `read_file` because Reasonix matches rule tools against
|
|
38344
|
+
* its registered tool names (see {@link REASONIX_READ_TOOL_NAME}); a legacy
|
|
38345
|
+
* `Read(...)` deny is still read back on import.
|
|
37844
38346
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
37845
38347
|
*/
|
|
37846
38348
|
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
@@ -37867,7 +38369,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37867
38369
|
return false;
|
|
37868
38370
|
}
|
|
37869
38371
|
toRulesyncIgnore() {
|
|
37870
|
-
const rulesyncPatterns = this.patterns.
|
|
38372
|
+
const rulesyncPatterns = uniq(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
|
|
37871
38373
|
return new RulesyncIgnore({
|
|
37872
38374
|
outputRoot: this.outputRoot,
|
|
37873
38375
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -37876,7 +38378,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37876
38378
|
});
|
|
37877
38379
|
}
|
|
37878
38380
|
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
37879
|
-
const readDenies = rulesyncIgnore.getFileContent().split(/\r?\n|\r/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((pattern) => buildReadDenyEntry(pattern));
|
|
38381
|
+
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));
|
|
37880
38382
|
const paths = this.getSettablePaths({ global });
|
|
37881
38383
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
37882
38384
|
const existingDocument = parseSharedConfig({
|
|
@@ -37886,7 +38388,8 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
37886
38388
|
});
|
|
37887
38389
|
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
37888
38390
|
settings: asClaudeStyleSettings(existingDocument),
|
|
37889
|
-
readDenies
|
|
38391
|
+
readDenies,
|
|
38392
|
+
readToolName: REASONIX_READ_TOOL_NAME
|
|
37890
38393
|
}));
|
|
37891
38394
|
return new ReasonixIgnore({
|
|
37892
38395
|
outputRoot,
|
|
@@ -38031,6 +38534,61 @@ var TabnineIgnore = class TabnineIgnore extends ToolIgnore {
|
|
|
38031
38534
|
}
|
|
38032
38535
|
};
|
|
38033
38536
|
//#endregion
|
|
38537
|
+
//#region src/features/ignore/trae-ignore.ts
|
|
38538
|
+
/**
|
|
38539
|
+
* Trae ignore file implementation.
|
|
38540
|
+
*
|
|
38541
|
+
* Trae reads `.trae/.ignore` (gitignore syntax) as a supplement to
|
|
38542
|
+
* `.gitignore` when it indexes the codebase; ignored files are also left out
|
|
38543
|
+
* of `#Workspace` / `#Folder` context. There is no user-level ignore file, so
|
|
38544
|
+
* only project scope is supported.
|
|
38545
|
+
*
|
|
38546
|
+
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
38547
|
+
*/
|
|
38548
|
+
var TraeIgnore = class TraeIgnore extends ToolIgnore {
|
|
38549
|
+
static getSettablePaths(_params = {}) {
|
|
38550
|
+
return {
|
|
38551
|
+
relativeDirPath: TRAE_DIR,
|
|
38552
|
+
relativeFilePath: TRAE_IGNORE_FILE_NAME
|
|
38553
|
+
};
|
|
38554
|
+
}
|
|
38555
|
+
toRulesyncIgnore() {
|
|
38556
|
+
return this.toRulesyncIgnoreDefault();
|
|
38557
|
+
}
|
|
38558
|
+
static fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
38559
|
+
const paths = this.getSettablePaths({ global });
|
|
38560
|
+
return new TraeIgnore({
|
|
38561
|
+
outputRoot,
|
|
38562
|
+
relativeDirPath: paths.relativeDirPath,
|
|
38563
|
+
relativeFilePath: paths.relativeFilePath,
|
|
38564
|
+
fileContent: rulesyncIgnore.getFileContent(),
|
|
38565
|
+
global
|
|
38566
|
+
});
|
|
38567
|
+
}
|
|
38568
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
38569
|
+
const { relativeDirPath, relativeFilePath } = this.getSettablePaths({ global });
|
|
38570
|
+
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
|
|
38571
|
+
return new TraeIgnore({
|
|
38572
|
+
outputRoot,
|
|
38573
|
+
relativeDirPath,
|
|
38574
|
+
relativeFilePath,
|
|
38575
|
+
fileContent,
|
|
38576
|
+
validate,
|
|
38577
|
+
global
|
|
38578
|
+
});
|
|
38579
|
+
}
|
|
38580
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
38581
|
+
return new TraeIgnore({
|
|
38582
|
+
outputRoot,
|
|
38583
|
+
relativeDirPath,
|
|
38584
|
+
relativeFilePath,
|
|
38585
|
+
fileContent: "",
|
|
38586
|
+
validate: false,
|
|
38587
|
+
global
|
|
38588
|
+
});
|
|
38589
|
+
}
|
|
38590
|
+
};
|
|
38591
|
+
//#endregion
|
|
38034
38592
|
//#region src/features/ignore/vibe-ignore.ts
|
|
38035
38593
|
var VibeIgnore = class VibeIgnore extends ToolIgnore {
|
|
38036
38594
|
static getSettablePaths() {
|
|
@@ -38259,6 +38817,7 @@ const toolIgnoreFactories = /* @__PURE__ */ new Map([
|
|
|
38259
38817
|
["zoocode", { class: RooIgnore }],
|
|
38260
38818
|
["devin", { class: DevinIgnore }],
|
|
38261
38819
|
["tabnine", { class: TabnineIgnore }],
|
|
38820
|
+
["trae", { class: TraeIgnore }],
|
|
38262
38821
|
["vibe", { class: VibeIgnore }],
|
|
38263
38822
|
["warp", { class: WarpIgnore }],
|
|
38264
38823
|
["zed", { class: ZedIgnore }]
|
|
@@ -43611,12 +44170,14 @@ function toGitlabduoTimeout({ serverName, server, logger }) {
|
|
|
43611
44170
|
/**
|
|
43612
44171
|
* Read GitLab Duo's `timeoutMs` back into the canonical `timeout`
|
|
43613
44172
|
* (milliseconds). A server that already carries a `timeout` is left as is, so
|
|
43614
|
-
* both keys round-trip and `timeoutMs` keeps winning on the next generate.
|
|
44173
|
+
* both keys round-trip and `timeoutMs` keeps winning on the next generate. A
|
|
44174
|
+
* non-number `timeoutMs` is not copied, since the canonical `timeout` must be a
|
|
44175
|
+
* number; it is kept verbatim as a passthrough key instead.
|
|
43615
44176
|
*/
|
|
43616
44177
|
function fromGitlabduoTimeout(server) {
|
|
43617
44178
|
if (typeof server !== "object" || server === null || Array.isArray(server)) return server;
|
|
43618
44179
|
const { timeoutMs, ...rest } = server;
|
|
43619
|
-
if (timeoutMs
|
|
44180
|
+
if (typeof timeoutMs !== "number" || rest.timeout !== void 0) return server;
|
|
43620
44181
|
return {
|
|
43621
44182
|
...rest,
|
|
43622
44183
|
timeout: timeoutMs
|
|
@@ -45331,7 +45892,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
45331
45892
|
feature: "mcp",
|
|
45332
45893
|
existingContent: fileContent ?? "",
|
|
45333
45894
|
patch: {
|
|
45334
|
-
mcp: convertedMcp,
|
|
45895
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
45335
45896
|
...buildKiloMcpPermissionPatch({
|
|
45336
45897
|
existing: existingDocument,
|
|
45337
45898
|
managedServerNames: uniq([...Object.keys(mcpServers), ...Object.keys(isRecord$1(existingDocument.mcp) ? existingDocument.mcp : {})]),
|
|
@@ -46191,7 +46752,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
46191
46752
|
feature: "mcp",
|
|
46192
46753
|
existingContent: fileContent ?? "",
|
|
46193
46754
|
patch: {
|
|
46194
|
-
mcp: convertedMcp,
|
|
46755
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
46195
46756
|
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
46196
46757
|
},
|
|
46197
46758
|
filePath: join(jsonDir, relativeFilePath),
|
|
@@ -46539,9 +47100,12 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46539
47100
|
/**
|
|
46540
47101
|
* Meta Muse Code MCP servers.
|
|
46541
47102
|
*
|
|
46542
|
-
* Muse Code
|
|
46543
|
-
* block of the GLOBAL user settings file
|
|
46544
|
-
*
|
|
47103
|
+
* rulesync writes Muse Code MCP servers only to the `mcp_servers` (or
|
|
47104
|
+
* `mcpServers`) block of the GLOBAL user settings file
|
|
47105
|
+
* `~/.config/muse/settings.json`, the location extending.md documents. Muse
|
|
47106
|
+
* Code 1.4.0+ also loads a trusted project `.mcp.json` (changelog only), which
|
|
47107
|
+
* is not emitted here because the `claudecode` target already writes that
|
|
47108
|
+
* file. The settings file must carry
|
|
46545
47109
|
* `"schema_version": 1` — a file that omits that key fails every command at
|
|
46546
47110
|
* startup with `malformed settings file` — so the key is bootstrapped when the
|
|
46547
47111
|
* file is created and preserved when it already exists. Other settings keys are
|
|
@@ -46549,6 +47113,7 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46549
47113
|
*
|
|
46550
47114
|
* @see https://dev.meta.ai/docs/muse-code/configuration.md
|
|
46551
47115
|
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
47116
|
+
* @see https://dev.meta.ai/docs/muse-code/changelog
|
|
46552
47117
|
*/
|
|
46553
47118
|
var MusecodeMcp = class MusecodeMcp extends ToolMcp {
|
|
46554
47119
|
json;
|
|
@@ -47089,8 +47654,8 @@ function convertFromPiFormat(mcpServers) {
|
|
|
47089
47654
|
* existing file (`autoEnableCodemode`, ...) are preserved. Pi expands `${VAR}`
|
|
47090
47655
|
* in `env` and `headers` values itself, so those are written unchanged.
|
|
47091
47656
|
*
|
|
47092
|
-
* @see https://github.com/earendil-works/pi/blob/
|
|
47093
|
-
* @see https://github.com/earendil-works/pi/blob/
|
|
47657
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/mcp.md
|
|
47658
|
+
* @see https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/src/core/mcp-servers.ts
|
|
47094
47659
|
*/
|
|
47095
47660
|
var PiMcp = class PiMcp extends ToolMcp {
|
|
47096
47661
|
json;
|
|
@@ -47781,7 +48346,7 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
47781
48346
|
feature: "mcp",
|
|
47782
48347
|
existingContent,
|
|
47783
48348
|
patch: {
|
|
47784
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()),
|
|
48349
|
+
mcpServers: undefinedWhenEmpty(convertToQwencodeFormat(rulesyncMcp.getMcpServers())),
|
|
47785
48350
|
...mcpPatch !== void 0 && { mcp: mcpPatch }
|
|
47786
48351
|
},
|
|
47787
48352
|
filePath
|
|
@@ -49204,21 +49769,6 @@ function deriveTransportAllowlist(servers) {
|
|
|
49204
49769
|
return allowlist;
|
|
49205
49770
|
}
|
|
49206
49771
|
//#endregion
|
|
49207
|
-
//#region src/constants/trae-paths.ts
|
|
49208
|
-
/**
|
|
49209
|
-
* Trae (ByteDance's AI IDE, "TraeCode" in its current docs) keeps project
|
|
49210
|
-
* configuration under `.trae/` and user configuration under `~/.trae/`.
|
|
49211
|
-
*
|
|
49212
|
-
* @see https://docs.trae.ai/ide/rules?_lang=en
|
|
49213
|
-
* @see https://docs.trae.ai/ide/skills?_lang=en
|
|
49214
|
-
* @see https://docs.trae.ai/ide/add-mcp-servers?_lang=en
|
|
49215
|
-
*/
|
|
49216
|
-
const TRAE_DIR = ".trae";
|
|
49217
|
-
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
49218
|
-
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
49219
|
-
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
49220
|
-
const TRAE_SKILLS_DIR_PATH = join(TRAE_DIR, "skills");
|
|
49221
|
-
//#endregion
|
|
49222
49772
|
//#region src/features/mcp/trae-mcp.ts
|
|
49223
49773
|
/** A `${...}` reference Trae does not expand: anything but `${workspaceFolder}`. */
|
|
49224
49774
|
const UNEXPANDED_VARIABLE_REGEX = /\$\{(?!workspaceFolder\})[^}]+\}/;
|
|
@@ -51828,6 +52378,11 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
51828
52378
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
51829
52379
|
*/
|
|
51830
52380
|
const REGEX_PREFIX = "regex:";
|
|
52381
|
+
/**
|
|
52382
|
+
* The Antigravity actions whose target is a command: `command`, and
|
|
52383
|
+
* `unsandboxed`, which takes the same word-by-word prefix or `regex:` target.
|
|
52384
|
+
*/
|
|
52385
|
+
const ANTIGRAVITY_COMMAND_ACTIONS = /* @__PURE__ */ new Set(["command", "unsandboxed"]);
|
|
51831
52386
|
const TRAILING_ANCHOR = /(?<!\\)\$$/;
|
|
51832
52387
|
const PLAIN_TARGET_GLOB_CHARACTERS = /[*?[{\\]/;
|
|
51833
52388
|
const REGEX_METACHARACTERS$1 = /[\\^$.|?*+()[\]{}]/;
|
|
@@ -52205,8 +52760,8 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
52205
52760
|
}
|
|
52206
52761
|
/**
|
|
52207
52762
|
* Read one regex word back as a glob, or `undefined` when it uses anything a
|
|
52208
|
-
* glob cannot say. Antigravity anchors each word anyway, so
|
|
52209
|
-
* optional. Only escaped characters and `.*` translate; anything else keeps
|
|
52763
|
+
* glob cannot say. Antigravity anchors each word anyway (`^(?:pattern)$`), so
|
|
52764
|
+
* `^` and `$` are optional. Only escaped characters and `.*` translate; anything else keeps
|
|
52210
52765
|
* the rule a regex.
|
|
52211
52766
|
*/
|
|
52212
52767
|
function regexWordToGlob(word) {
|
|
@@ -52232,8 +52787,10 @@ function regexWordToGlob(word) {
|
|
|
52232
52787
|
}
|
|
52233
52788
|
/** Read a per-word regex back as a glob, or `undefined` when it has no glob spelling. */
|
|
52234
52789
|
function regexToGlob(source) {
|
|
52790
|
+
const words = splitWords(source);
|
|
52791
|
+
if (words.length === 0) return;
|
|
52235
52792
|
const globs = [];
|
|
52236
|
-
for (const word of
|
|
52793
|
+
for (const word of words) {
|
|
52237
52794
|
const glob = regexWordToGlob(word);
|
|
52238
52795
|
if (glob === void 0) return;
|
|
52239
52796
|
globs.push(glob);
|
|
@@ -52258,36 +52815,23 @@ function fromAntigravityCommandTarget(target) {
|
|
|
52258
52815
|
return `${target} *`;
|
|
52259
52816
|
}
|
|
52260
52817
|
/**
|
|
52261
|
-
* Whether a regex word means the same anchored or not: each end is either
|
|
52262
|
-
* anchored (`^`, an unescaped `$`) or open-ended (`.*`).
|
|
52263
|
-
*/
|
|
52264
|
-
function isAnchoredWord(word) {
|
|
52265
|
-
const startAnchored = word.startsWith("^") || word.startsWith(".*");
|
|
52266
|
-
const endAnchored = TRAILING_ANCHOR.test(word) || word.endsWith(".*");
|
|
52267
|
-
return startAnchored && endAnchored;
|
|
52268
|
-
}
|
|
52269
|
-
/**
|
|
52270
52818
|
* Explain what importing an Antigravity `command` target may lose, or return
|
|
52271
52819
|
* `undefined` when the import is faithful.
|
|
52272
52820
|
*
|
|
52273
|
-
*
|
|
52274
|
-
*
|
|
52275
|
-
*
|
|
52276
|
-
*
|
|
52277
|
-
*
|
|
52278
|
-
*
|
|
52279
|
-
*
|
|
52821
|
+
* A plain target holding characters that are special in a glob is most
|
|
52822
|
+
* likely the verbatim output of an older rulesync (`command(rm -rf *)`).
|
|
52823
|
+
* Antigravity matches it literally, so it imports as a regex that matches a
|
|
52824
|
+
* literal `*` word and is inert in the other tools it is exported to.
|
|
52825
|
+
*
|
|
52826
|
+
* A `regex:` word without `^...$` (`regex:rm`) is read as anchored, which is
|
|
52827
|
+
* faithful: Antigravity documents that each token is evaluated as the
|
|
52828
|
+
* anchored regex `^(?:pattern)$`.
|
|
52829
|
+
*
|
|
52830
|
+
* @see https://antigravity.google/docs/permissions
|
|
52280
52831
|
*/
|
|
52281
52832
|
function describeAntigravityCommandImportLoss(target) {
|
|
52282
|
-
if (target.startsWith(REGEX_PREFIX))
|
|
52283
|
-
|
|
52284
|
-
if (splitWords(source).some((word) => !isAnchoredWord(word)) && regexToGlob(source) !== void 0) return {
|
|
52285
|
-
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",
|
|
52286
|
-
denyConsequence: "The imported deny may block less than it does in Antigravity."
|
|
52287
|
-
};
|
|
52288
|
-
return;
|
|
52289
|
-
}
|
|
52290
|
-
if (target !== "*" && PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
52833
|
+
if (target.startsWith(REGEX_PREFIX) || target === "*") return;
|
|
52834
|
+
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
52291
52835
|
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",
|
|
52292
52836
|
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
52293
52837
|
};
|
|
@@ -52319,17 +52863,20 @@ function importAntigravityCommandTarget({ target, entry, decision, logger, toolL
|
|
|
52319
52863
|
* every file.
|
|
52320
52864
|
* - `read_url` takes a domain and covers its subdomains. The URL path is
|
|
52321
52865
|
* ignored, and `*` covers every domain.
|
|
52322
|
-
* - `
|
|
52323
|
-
*
|
|
52866
|
+
* - `execute_url` takes a domain, like `read_url`.
|
|
52867
|
+
* - `command` and `unsandboxed` take literal words or a per-word `regex:`,
|
|
52868
|
+
* matched word by word (see `antigravity-command-patterns.ts`).
|
|
52324
52869
|
*
|
|
52325
52870
|
* A glob copied verbatim (`read_file(**\/*.env)`) is a literal path that
|
|
52326
52871
|
* matches nothing, so a deny written that way blocks nothing. A pattern with
|
|
52327
|
-
* no Antigravity spelling is skipped with a warning instead, except a
|
|
52328
|
-
*
|
|
52872
|
+
* no Antigravity spelling is skipped with a warning instead, except a deny
|
|
52873
|
+
* with a literal lead (the first words of a command, the leading directories
|
|
52874
|
+
* of a path), which is widened to it.
|
|
52329
52875
|
*
|
|
52330
52876
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
52331
52877
|
*/
|
|
52332
52878
|
const PATH_ACTIONS = /* @__PURE__ */ new Set(["read_file", "write_file"]);
|
|
52879
|
+
const URL_ACTIONS = /* @__PURE__ */ new Set(["read_url", "execute_url"]);
|
|
52333
52880
|
const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
|
|
52334
52881
|
"*",
|
|
52335
52882
|
"**",
|
|
@@ -52341,7 +52888,8 @@ const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
|
|
|
52341
52888
|
const DENY_HINTS = {
|
|
52342
52889
|
read_file: "deny a directory or file path instead",
|
|
52343
52890
|
write_file: "deny a directory or file path instead",
|
|
52344
|
-
command: "deny the first words of the command instead"
|
|
52891
|
+
command: "deny the first words of the command instead",
|
|
52892
|
+
unsandboxed: "deny the first words of the command instead"
|
|
52345
52893
|
};
|
|
52346
52894
|
function hasGlob(pattern) {
|
|
52347
52895
|
return BRACE_ALTERNATIVES.test(pattern) || parseGlobPattern(pattern).steps.some((step) => step.kind !== "literal");
|
|
@@ -52392,37 +52940,88 @@ function toUrlTarget(pattern) {
|
|
|
52392
52940
|
};
|
|
52393
52941
|
}
|
|
52394
52942
|
function toTarget(action, pattern) {
|
|
52395
|
-
if (action
|
|
52943
|
+
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandTarget(pattern);
|
|
52396
52944
|
if (PATH_ACTIONS.has(action)) return toPathTarget(pattern);
|
|
52397
|
-
if (action
|
|
52945
|
+
if (URL_ACTIONS.has(action)) return toUrlTarget(pattern);
|
|
52398
52946
|
return { target: pattern };
|
|
52399
52947
|
}
|
|
52400
52948
|
/**
|
|
52949
|
+
* The leading directories of a path glob (`src` for `src/*.ts`), or
|
|
52950
|
+
* `undefined` when it has none. A directory target covers everything inside
|
|
52951
|
+
* it, so it matches at least as much as the glob. The filesystem root alone
|
|
52952
|
+
* (`/*`) is not a lead: a deny of it would block every file. Neither is a lead
|
|
52953
|
+
* with `..` in it.
|
|
52954
|
+
*/
|
|
52955
|
+
function toPathLiteralPrefix(pattern) {
|
|
52956
|
+
const path = pattern.startsWith("./") ? pattern.slice(2) : pattern;
|
|
52957
|
+
const prefix = [];
|
|
52958
|
+
for (const segment of path.split("/").slice(0, -1)) {
|
|
52959
|
+
if (hasGlob(segment) || segment.includes("{") || segment.includes("}")) break;
|
|
52960
|
+
prefix.push(segment);
|
|
52961
|
+
}
|
|
52962
|
+
if (prefix.includes("..") || prefix.every((segment) => segment === "" || segment === ".")) return;
|
|
52963
|
+
return prefix.join("/");
|
|
52964
|
+
}
|
|
52965
|
+
/** The widened target of a deny that has no exact Antigravity spelling. */
|
|
52966
|
+
function toWidenedDenyTarget(action, pattern) {
|
|
52967
|
+
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandLiteralPrefix(pattern);
|
|
52968
|
+
if (PATH_ACTIONS.has(action)) return toPathLiteralPrefix(pattern);
|
|
52969
|
+
}
|
|
52970
|
+
/**
|
|
52971
|
+
* Warn about a path target that is a home or variable reference. Antigravity
|
|
52972
|
+
* documents absolute and workspace-relative paths only, so such a reference
|
|
52973
|
+
* may be matched literally and never apply. The entry is still written, since
|
|
52974
|
+
* dropping a deny would lose it where Antigravity does expand the reference.
|
|
52975
|
+
*/
|
|
52976
|
+
function warnHomeOrVariablePath({ action, target, rule, decision, logger, toolLabel }) {
|
|
52977
|
+
if (!PATH_ACTIONS.has(action) || !HOME_OR_VARIABLE_PATH.test(target)) return;
|
|
52978
|
+
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`;
|
|
52979
|
+
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.`);
|
|
52980
|
+
}
|
|
52981
|
+
/**
|
|
52401
52982
|
* Build the entry for one canonical rule, or return `undefined` when
|
|
52402
|
-
* Antigravity cannot express it. A
|
|
52403
|
-
*
|
|
52404
|
-
*
|
|
52405
|
-
*
|
|
52406
|
-
* deny is warned about loudly, because
|
|
52983
|
+
* Antigravity cannot express it. A deny is widened to its literal lead rather
|
|
52984
|
+
* than skipped (`git push * --force` → `command(git push)`, `src/*.ts` →
|
|
52985
|
+
* `read_file(src)`): Antigravity applies deny over ask and allow, so an
|
|
52986
|
+
* overlapping allow that is translated (`git push *`) cannot let through what
|
|
52987
|
+
* the deny was meant to block. A skipped deny is warned about loudly, because
|
|
52988
|
+
* nothing enforces it, and a widened one is warned about too, because it
|
|
52989
|
+
* blocks more than it says.
|
|
52407
52990
|
*/
|
|
52408
52991
|
function buildAntigravityPermissionEntry({ action, category, pattern, decision, logger, toolLabel }) {
|
|
52409
52992
|
const result = pattern === "*" ? { target: "*" } : toTarget(action, pattern);
|
|
52410
52993
|
const rule = `${category}: { "${pattern}": "${decision}" }`;
|
|
52411
52994
|
if ("skipReason" in result) {
|
|
52412
|
-
const prefix =
|
|
52995
|
+
const prefix = decision === "deny" ? toWidenedDenyTarget(action, pattern) : void 0;
|
|
52413
52996
|
if (prefix !== void 0) {
|
|
52414
|
-
|
|
52997
|
+
const scope = PATH_ACTIONS.has(action) ? `every path under \`${prefix}\`` : `every command that starts with \`${prefix}\``;
|
|
52998
|
+
logger.warn(`${toolLabel} permissions: widening ${rule} to ${action}(${prefix}). ${result.skipReason}. The deny now blocks ${scope}, overriding any allow or ask for them.`);
|
|
52999
|
+
warnHomeOrVariablePath({
|
|
53000
|
+
action,
|
|
53001
|
+
target: prefix,
|
|
53002
|
+
rule,
|
|
53003
|
+
decision,
|
|
53004
|
+
logger,
|
|
53005
|
+
toolLabel
|
|
53006
|
+
});
|
|
52415
53007
|
return `${action}(${prefix})`;
|
|
52416
53008
|
}
|
|
52417
53009
|
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`;
|
|
52418
53010
|
logger.warn(`${toolLabel} permissions: skipping ${rule}. ${result.skipReason}. ${consequence}.`);
|
|
52419
53011
|
return;
|
|
52420
53012
|
}
|
|
52421
|
-
if (result.note !== void 0 && decision !== "deny"
|
|
52422
|
-
|
|
52423
|
-
|
|
52424
|
-
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.`);
|
|
53013
|
+
if (result.note !== void 0 && (decision !== "deny" || URL_ACTIONS.has(action))) {
|
|
53014
|
+
const consequence = decision === "deny" ? ", so the deny also overrides any allow or ask there" : "";
|
|
53015
|
+
logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}${consequence}.`);
|
|
52425
53016
|
}
|
|
53017
|
+
warnHomeOrVariablePath({
|
|
53018
|
+
action,
|
|
53019
|
+
target: result.target,
|
|
53020
|
+
rule,
|
|
53021
|
+
decision,
|
|
53022
|
+
logger,
|
|
53023
|
+
toolLabel
|
|
53024
|
+
});
|
|
52426
53025
|
return result.target === "*" ? action : `${action}(${result.target})`;
|
|
52427
53026
|
}
|
|
52428
53027
|
//#endregion
|
|
@@ -52756,7 +53355,8 @@ function pickDocumentedValue({ key, value, documented }) {
|
|
|
52756
53355
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
52757
53356
|
* Antigravity CLI Fine-Grained Permissions Engine action vocabulary
|
|
52758
53357
|
* (`read_file`, `write_file`, `read_url`, `command`, `mcp`, plus `execute_url` /
|
|
52759
|
-
* `unsandboxed` which have no canonical equivalent and
|
|
53358
|
+
* `unsandboxed` which have no canonical equivalent and keep their names; their
|
|
53359
|
+
* targets are translated like `read_url` / `command` ones). `edit` and
|
|
52760
53360
|
* `write` both map to `write_file`, and `webfetch` / `websearch` both map to
|
|
52761
53361
|
* `read_url`; on import those collapse to the canonical `write` / `webfetch`
|
|
52762
53362
|
* form (a documented, lossy normalization). This shares the action vocabulary of
|
|
@@ -53003,7 +53603,7 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
53003
53603
|
for (const entry of entries) {
|
|
53004
53604
|
const { toolName, pattern: target } = parsePermissionEntry$1(entry);
|
|
53005
53605
|
const canonical = toCanonicalToolName$9(toolName);
|
|
53006
|
-
const pattern = toolName
|
|
53606
|
+
const pattern = ANTIGRAVITY_COMMAND_ACTIONS.has(toolName) ? importAntigravityCommandTarget({
|
|
53007
53607
|
target,
|
|
53008
53608
|
entry,
|
|
53009
53609
|
decision: action,
|
|
@@ -53025,7 +53625,8 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
53025
53625
|
* Deliberate mapping from rulesync canonical permission categories to the
|
|
53026
53626
|
* Antigravity IDE action vocabulary (`read_file`, `write_file`, `read_url`,
|
|
53027
53627
|
* `command`, `mcp`, plus `execute_url` / `unsandboxed` which have no canonical
|
|
53028
|
-
* equivalent and
|
|
53628
|
+
* equivalent and keep their names; their targets are translated like
|
|
53629
|
+
* `read_url` / `command` ones). `edit` and `write` both map to `write_file`, and
|
|
53029
53630
|
* `webfetch` / `websearch` both map to `read_url`; on import those collapse to
|
|
53030
53631
|
* the canonical `write` / `webfetch` form (a documented, lossy normalization).
|
|
53031
53632
|
*/
|
|
@@ -53227,7 +53828,7 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
53227
53828
|
for (const entry of entries) {
|
|
53228
53829
|
const { action: ideAction, pattern: target } = parsePermissionEntry(entry);
|
|
53229
53830
|
const canonical = toCanonicalCategory$2(ideAction);
|
|
53230
|
-
const pattern = ideAction
|
|
53831
|
+
const pattern = ANTIGRAVITY_COMMAND_ACTIONS.has(ideAction) ? importAntigravityCommandTarget({
|
|
53231
53832
|
target,
|
|
53232
53833
|
entry,
|
|
53233
53834
|
decision: action,
|
|
@@ -55157,6 +55758,7 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55157
55758
|
"dialogExpiry",
|
|
55158
55759
|
"feedbackDrafts",
|
|
55159
55760
|
"footerLinksRegexes",
|
|
55761
|
+
"forceLoginGatewayUrl",
|
|
55160
55762
|
"modelPicker",
|
|
55161
55763
|
"pluginConfigs",
|
|
55162
55764
|
"prependPlugins",
|
|
@@ -55170,6 +55772,39 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55170
55772
|
"vimInsertModeRemaps"
|
|
55171
55773
|
]);
|
|
55172
55774
|
/**
|
|
55775
|
+
* Nested settings paths under an otherwise project-honored top-level key that
|
|
55776
|
+
* Claude Code reads only from user settings — the nested counterpart of
|
|
55777
|
+
* {@link CLAUDECODE_USER_SCOPE_ONLY_KEYS}. `worktree` is `Any file`, but
|
|
55778
|
+
* `worktree.location` (where Desktop SSH sessions create worktrees on the
|
|
55779
|
+
* remote machine) is documented with a `User` scope, so it is dropped from a
|
|
55780
|
+
* project `.claude/settings.json` and written only under `--global`, while its
|
|
55781
|
+
* siblings are kept.
|
|
55782
|
+
*
|
|
55783
|
+
* @see https://code.claude.com/docs/en/settings-reference#worktree-location
|
|
55784
|
+
*/
|
|
55785
|
+
const CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS = [["worktree", "location"]];
|
|
55786
|
+
/**
|
|
55787
|
+
* Drops {@link CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS} from the top-level
|
|
55788
|
+
* passthrough at project scope, in place, warning once per dropped path. The
|
|
55789
|
+
* authored subtree is copied before the deletion so the caller's config is not
|
|
55790
|
+
* mutated, and a parent the removal emptied is dropped with it.
|
|
55791
|
+
*/
|
|
55792
|
+
function stripUserScopeOnlyNestedPaths({ overrides, global, relativeFilePath, logger }) {
|
|
55793
|
+
if (global) return;
|
|
55794
|
+
for (const path of CLAUDECODE_USER_SCOPE_ONLY_NESTED_PATHS) {
|
|
55795
|
+
const [topLevelKey] = path;
|
|
55796
|
+
if (topLevelKey === void 0 || !isRecord$1(overrides[topLevelKey])) continue;
|
|
55797
|
+
const copy = { [topLevelKey]: structuredClone(overrides[topLevelKey]) };
|
|
55798
|
+
if (!deleteSandboxPath({
|
|
55799
|
+
target: copy,
|
|
55800
|
+
path
|
|
55801
|
+
})) continue;
|
|
55802
|
+
if (copy[topLevelKey] === void 0) delete overrides[topLevelKey];
|
|
55803
|
+
else overrides[topLevelKey] = copy[topLevelKey];
|
|
55804
|
+
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.`);
|
|
55805
|
+
}
|
|
55806
|
+
}
|
|
55807
|
+
/**
|
|
55173
55808
|
* Top-level settings keys neither file rulesync writes can honor, with the file
|
|
55174
55809
|
* that does. `Managed` keys are read only from the settings file an organization
|
|
55175
55810
|
* deploys, and `Global config` keys only from `~/.claude.json` — rulesync writes
|
|
@@ -55207,7 +55842,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
55207
55842
|
disableMobileSimulatorTools: "managed settings",
|
|
55208
55843
|
disableSideloadFlags: "managed settings",
|
|
55209
55844
|
externalEditorContext: "~/.claude.json",
|
|
55210
|
-
forceLoginGatewayUrl: "managed settings",
|
|
55211
55845
|
forceRemoteSettingsRefresh: "managed settings",
|
|
55212
55846
|
gatewayInternalNetworks: "managed settings",
|
|
55213
55847
|
leftArrowOpensAgents: "~/.claude.json",
|
|
@@ -55366,8 +56000,9 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55366
56000
|
/**
|
|
55367
56001
|
* `env` variables a project or local settings file cannot set, because a
|
|
55368
56002
|
* checked-out repository should not control them: where Claude Code stores its
|
|
55369
|
-
* own files, what session content it exports, where telemetry goes,
|
|
55370
|
-
* starts or syncs
|
|
56003
|
+
* own files, what session content it exports, where telemetry goes, how it
|
|
56004
|
+
* starts or syncs, the timers on an unanswered dialog, and whether it processes
|
|
56005
|
+
* attachments or expands `@` file mentions. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
55371
56006
|
*
|
|
55372
56007
|
* Only the names the settings reference lists are covered; its directory and
|
|
55373
56008
|
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
@@ -55378,6 +56013,10 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55378
56013
|
*/
|
|
55379
56014
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
55380
56015
|
"BETA_TRACING_ENDPOINT",
|
|
56016
|
+
"CLAUDE_AFK_COUNTDOWN_MS",
|
|
56017
|
+
"CLAUDE_AFK_TIMEOUT_MS",
|
|
56018
|
+
"CLAUDE_CODE_DISABLE_ATTACHMENTS",
|
|
56019
|
+
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
55381
56020
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
55382
56021
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
55383
56022
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
@@ -55386,6 +56025,7 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
55386
56025
|
"CLAUDE_CODE_SYNC_PLUGINS",
|
|
55387
56026
|
"CLAUDE_CODE_SYNC_SKILLS",
|
|
55388
56027
|
"CLAUDE_CODE_TMPDIR",
|
|
56028
|
+
"CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS",
|
|
55389
56029
|
"CLAUDE_CONFIG_DIR",
|
|
55390
56030
|
"ENABLE_BETA_TRACING_DETAILED",
|
|
55391
56031
|
"ENABLE_ENHANCED_TELEMETRY_BETA",
|
|
@@ -55431,6 +56071,13 @@ const CLAUDECODE_ENV_CONTENT_OFF_VALUES = /* @__PURE__ */ new Set([
|
|
|
55431
56071
|
* writing a value Claude Code ignores is the old behavior, while dropping an
|
|
55432
56072
|
* opt-out it honors would remove a privacy setting. `OTEL_LOG_ASSISTANT_RESPONSES`
|
|
55433
56073
|
* has no such exception and sits in the plain name table above.
|
|
56074
|
+
*
|
|
56075
|
+
* `CLAUDE_CODE_ENABLE_CFC` follows the same shape for a different reason: a
|
|
56076
|
+
* project cannot turn Claude in Chrome on, since a checked-out repository must
|
|
56077
|
+
* not be able to connect Claude to the user's browser, so only an off value is
|
|
56078
|
+
* kept at project scope.
|
|
56079
|
+
*
|
|
56080
|
+
* @see https://code.claude.com/docs/en/chrome#project-settings-cant-turn-on-chrome
|
|
55434
56081
|
*/
|
|
55435
56082
|
const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
55436
56083
|
OTEL_LOGS_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
@@ -55438,7 +56085,8 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
55438
56085
|
OTEL_TRACES_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
55439
56086
|
OTEL_LOG_USER_PROMPTS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55440
56087
|
OTEL_LOG_TOOL_CONTENT: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55441
|
-
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
56088
|
+
OTEL_LOG_TOOL_DETAILS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
56089
|
+
CLAUDE_CODE_ENABLE_CFC: CLAUDECODE_ENV_CONTENT_OFF_VALUES
|
|
55442
56090
|
};
|
|
55443
56091
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
55444
56092
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_")) return true;
|
|
@@ -55475,7 +56123,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
55475
56123
|
kept[name] = value;
|
|
55476
56124
|
}
|
|
55477
56125
|
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.`);
|
|
55478
|
-
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,
|
|
56126
|
+
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.`);
|
|
55479
56127
|
return kept;
|
|
55480
56128
|
}
|
|
55481
56129
|
/**
|
|
@@ -55706,6 +56354,12 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
55706
56354
|
relativeFilePath: paths.relativeFilePath,
|
|
55707
56355
|
logger
|
|
55708
56356
|
});
|
|
56357
|
+
stripUserScopeOnlyNestedPaths({
|
|
56358
|
+
overrides: overrideTopLevel,
|
|
56359
|
+
global,
|
|
56360
|
+
relativeFilePath: paths.relativeFilePath,
|
|
56361
|
+
logger
|
|
56362
|
+
});
|
|
55709
56363
|
const { filtered: scopedTopLevel, trustAffecting: trustAffectingTopLevel } = stripUnhonoredTopLevelKeys({
|
|
55710
56364
|
overrides: overrideTopLevel,
|
|
55711
56365
|
global,
|
|
@@ -56123,7 +56777,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
56123
56777
|
"ask",
|
|
56124
56778
|
"deny"
|
|
56125
56779
|
];
|
|
56126
|
-
const CATCH_ALL_PATTERN$
|
|
56780
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
56127
56781
|
const MCP_CATEGORY = "mcp";
|
|
56128
56782
|
const MCP_RULE_PREFIX = "mcp__";
|
|
56129
56783
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -56196,7 +56850,7 @@ function categoryOfRule$2(rule) {
|
|
|
56196
56850
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
56197
56851
|
*/
|
|
56198
56852
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
56199
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
56853
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$12;
|
|
56200
56854
|
if (category === MCP_CATEGORY) {
|
|
56201
56855
|
if (catchAll && action === "allow") {
|
|
56202
56856
|
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').`);
|
|
@@ -56205,7 +56859,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
56205
56859
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
56206
56860
|
}
|
|
56207
56861
|
if (category === "*") {
|
|
56208
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
56862
|
+
if (catchAll) return CATCH_ALL_PATTERN$12;
|
|
56209
56863
|
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').`);
|
|
56210
56864
|
return null;
|
|
56211
56865
|
}
|
|
@@ -56223,7 +56877,7 @@ function parseCodebuddyRule(rule) {
|
|
|
56223
56877
|
const { specifier } = splitCodebuddyRule(rule);
|
|
56224
56878
|
if (specifier === void 0) return {
|
|
56225
56879
|
category,
|
|
56226
|
-
pattern: CATCH_ALL_PATTERN$
|
|
56880
|
+
pattern: CATCH_ALL_PATTERN$12
|
|
56227
56881
|
};
|
|
56228
56882
|
const trimmed = specifier.trim();
|
|
56229
56883
|
return trimmed === "" ? null : {
|
|
@@ -58474,7 +59128,7 @@ function mapBashActionToDecision(action) {
|
|
|
58474
59128
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
58475
59129
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
58476
59130
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
58477
|
-
const CATCH_ALL_PATTERN$
|
|
59131
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
58478
59132
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
58479
59133
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
58480
59134
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -58499,14 +59153,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
|
58499
59153
|
web_fetch: "webfetch",
|
|
58500
59154
|
web_search: "websearch"
|
|
58501
59155
|
};
|
|
58502
|
-
const ACTION_RANK$
|
|
59156
|
+
const ACTION_RANK$5 = {
|
|
58503
59157
|
allow: 0,
|
|
58504
59158
|
ask: 1,
|
|
58505
59159
|
deny: 2
|
|
58506
59160
|
};
|
|
58507
59161
|
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
58508
59162
|
function isStricterAction({ action, existing }) {
|
|
58509
|
-
return existing === void 0 || ACTION_RANK$
|
|
59163
|
+
return existing === void 0 || ACTION_RANK$5[action] > ACTION_RANK$5[existing];
|
|
58510
59164
|
}
|
|
58511
59165
|
/**
|
|
58512
59166
|
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
@@ -58529,10 +59183,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
58529
59183
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
58530
59184
|
*/
|
|
58531
59185
|
function buildCommandcodeRule(category, pattern) {
|
|
58532
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
59186
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$11 || pattern === "";
|
|
58533
59187
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
58534
59188
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
58535
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
59189
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$11 : null;
|
|
58536
59190
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
58537
59191
|
if (tool === void 0) return null;
|
|
58538
59192
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -58568,7 +59222,7 @@ function splitCommandcodeRule(rule) {
|
|
|
58568
59222
|
}
|
|
58569
59223
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
58570
59224
|
function isCatchAllSpecifier(inner) {
|
|
58571
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
59225
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$11;
|
|
58572
59226
|
}
|
|
58573
59227
|
/**
|
|
58574
59228
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -58602,34 +59256,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
58602
59256
|
if (server.length === 0) return null;
|
|
58603
59257
|
const everyServer = {
|
|
58604
59258
|
category: "mcp",
|
|
58605
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59259
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58606
59260
|
notInAllow: true
|
|
58607
59261
|
};
|
|
58608
59262
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
58609
|
-
if (server === CATCH_ALL_PATTERN$
|
|
58610
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
59263
|
+
if (server === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59264
|
+
if (server.includes(CATCH_ALL_PATTERN$11)) return null;
|
|
58611
59265
|
return {
|
|
58612
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
58613
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59266
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$11 ? `${MCP_CANONICAL_PREFIX$6}${server}` : `${MCP_CANONICAL_PREFIX$6}${remainder}`,
|
|
59267
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58614
59268
|
notInAllow: false
|
|
58615
59269
|
};
|
|
58616
59270
|
}
|
|
58617
59271
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
58618
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
59272
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$11);
|
|
58619
59273
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
58620
59274
|
category,
|
|
58621
59275
|
pattern: inner,
|
|
58622
59276
|
notInAllow: true
|
|
58623
59277
|
} : null;
|
|
58624
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
58625
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
59278
|
+
if (remainder === CATCH_ALL_PATTERN$11) return everyServer;
|
|
59279
|
+
if (rest === CATCH_ALL_PATTERN$11 && !server.includes(CATCH_ALL_PATTERN$11)) return {
|
|
58626
59280
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
58627
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59281
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58628
59282
|
notInAllow: true
|
|
58629
59283
|
};
|
|
58630
59284
|
return namesTool ? {
|
|
58631
59285
|
category,
|
|
58632
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59286
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58633
59287
|
notInAllow: false
|
|
58634
59288
|
} : null;
|
|
58635
59289
|
}
|
|
@@ -58653,14 +59307,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58653
59307
|
if (category === "bash") {
|
|
58654
59308
|
if (isCatchAllSpecifier(inner)) return {
|
|
58655
59309
|
category,
|
|
58656
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59310
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58657
59311
|
notInAllow: false
|
|
58658
59312
|
};
|
|
58659
59313
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
58660
59314
|
if (normalized === "" || normalized === ":*") return null;
|
|
58661
59315
|
return isCatchAllSpecifier(normalized) ? {
|
|
58662
59316
|
category,
|
|
58663
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59317
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58664
59318
|
notInAllow: true
|
|
58665
59319
|
} : {
|
|
58666
59320
|
category,
|
|
@@ -58670,7 +59324,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58670
59324
|
}
|
|
58671
59325
|
if (isCatchAllSpecifier(inner)) return {
|
|
58672
59326
|
category,
|
|
58673
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59327
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58674
59328
|
notInAllow: false
|
|
58675
59329
|
};
|
|
58676
59330
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -58689,9 +59343,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58689
59343
|
*/
|
|
58690
59344
|
function parseCommandcodeRule(rule) {
|
|
58691
59345
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
58692
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
59346
|
+
if (tool === CATCH_ALL_PATTERN$11) return isCatchAllSpecifier(inner) ? {
|
|
58693
59347
|
category: "*",
|
|
58694
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59348
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58695
59349
|
notInAllow: true
|
|
58696
59350
|
} : null;
|
|
58697
59351
|
const lowered = tool.toLowerCase();
|
|
@@ -58761,7 +59415,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
58761
59415
|
}).filter((rule) => {
|
|
58762
59416
|
const parsed = parseCommandcodeRule(rule);
|
|
58763
59417
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
58764
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
59418
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$11})`);
|
|
58765
59419
|
if (key !== "allow" && isStricterAction({
|
|
58766
59420
|
action: key,
|
|
58767
59421
|
existing: replacement
|
|
@@ -58818,7 +59472,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
58818
59472
|
const ranked = /* @__PURE__ */ new Map();
|
|
58819
59473
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
58820
59474
|
for (const [category, rules] of Object.entries(permission)) {
|
|
58821
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
59475
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$11);
|
|
58822
59476
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
58823
59477
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
58824
59478
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -59089,7 +59743,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
59089
59743
|
//#endregion
|
|
59090
59744
|
//#region src/features/permissions/continue-permissions.ts
|
|
59091
59745
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
59092
|
-
const CATCH_ALL_PATTERN$
|
|
59746
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
59093
59747
|
/**
|
|
59094
59748
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
59095
59749
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -59097,7 +59751,7 @@ const CATCH_ALL_PATTERN$9 = "*";
|
|
|
59097
59751
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
59098
59752
|
*/
|
|
59099
59753
|
function normalizeCatchAllPattern$1(pattern) {
|
|
59100
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
59754
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
|
|
59101
59755
|
}
|
|
59102
59756
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
59103
59757
|
bash: "Bash",
|
|
@@ -59134,7 +59788,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
59134
59788
|
* `Tool(pattern)`.
|
|
59135
59789
|
*/
|
|
59136
59790
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
59137
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
59791
|
+
return pattern === CATCH_ALL_PATTERN$10 ? toolName : `${toolName}(${pattern})`;
|
|
59138
59792
|
}
|
|
59139
59793
|
/**
|
|
59140
59794
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -59148,7 +59802,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
59148
59802
|
if (toolName === "") return;
|
|
59149
59803
|
return {
|
|
59150
59804
|
toolName,
|
|
59151
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
59805
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$10 : pattern
|
|
59152
59806
|
};
|
|
59153
59807
|
}
|
|
59154
59808
|
/**
|
|
@@ -59322,7 +59976,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
59322
59976
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59323
59977
|
continue;
|
|
59324
59978
|
}
|
|
59325
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
59979
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$10) {
|
|
59326
59980
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59327
59981
|
continue;
|
|
59328
59982
|
}
|
|
@@ -59690,14 +60344,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
59690
60344
|
};
|
|
59691
60345
|
//#endregion
|
|
59692
60346
|
//#region src/features/permissions/crush-permissions.ts
|
|
59693
|
-
const CATCH_ALL_PATTERN$
|
|
60347
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
59694
60348
|
/**
|
|
59695
60349
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
59696
60350
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
59697
60351
|
* still be written.
|
|
59698
60352
|
*/
|
|
59699
60353
|
function normalizeCatchAllPattern(pattern) {
|
|
59700
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
60354
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
|
|
59701
60355
|
}
|
|
59702
60356
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
59703
60357
|
bash: "bash",
|
|
@@ -59896,7 +60550,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
59896
60550
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
59897
60551
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
59898
60552
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
59899
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
60553
|
+
if (pattern === CATCH_ALL_PATTERN$9) {
|
|
59900
60554
|
catchAllAction = action;
|
|
59901
60555
|
continue;
|
|
59902
60556
|
}
|
|
@@ -59969,11 +60623,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
59969
60623
|
};
|
|
59970
60624
|
for (const entry of disabled) {
|
|
59971
60625
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
59972
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60626
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] = "deny";
|
|
59973
60627
|
}
|
|
59974
60628
|
for (const entry of allowed) {
|
|
59975
60629
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
59976
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60630
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] ??= "allow";
|
|
59977
60631
|
}
|
|
59978
60632
|
return { permission };
|
|
59979
60633
|
}
|
|
@@ -61838,7 +62492,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
61838
62492
|
//#region src/features/permissions/goose-permissions.ts
|
|
61839
62493
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
61840
62494
|
const GOOSE_USER_KEY = "user";
|
|
61841
|
-
const CATCH_ALL_PATTERN$
|
|
62495
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
61842
62496
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
61843
62497
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
61844
62498
|
shell: ["bash"],
|
|
@@ -61850,7 +62504,7 @@ const ACTION_TO_GOOSE_LIST = {
|
|
|
61850
62504
|
ask: "ask_before",
|
|
61851
62505
|
deny: "never_allow"
|
|
61852
62506
|
};
|
|
61853
|
-
const ACTION_RANK$
|
|
62507
|
+
const ACTION_RANK$4 = {
|
|
61854
62508
|
allow: 0,
|
|
61855
62509
|
ask: 1,
|
|
61856
62510
|
deny: 2
|
|
@@ -62002,14 +62656,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62002
62656
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
62003
62657
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
62004
62658
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
62005
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
62659
|
+
if (pattern !== CATCH_ALL_PATTERN$8) {
|
|
62006
62660
|
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.`);
|
|
62007
62661
|
continue;
|
|
62008
62662
|
}
|
|
62009
62663
|
const previous = assigned.get(toolName);
|
|
62010
62664
|
if (previous === action) continue;
|
|
62011
62665
|
if (previous !== void 0) {
|
|
62012
|
-
const stricter = ACTION_RANK$
|
|
62666
|
+
const stricter = ACTION_RANK$4[action] > ACTION_RANK$4[previous] ? action : previous;
|
|
62013
62667
|
logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
|
|
62014
62668
|
if (stricter === previous) continue;
|
|
62015
62669
|
const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
|
|
@@ -62033,7 +62687,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
62033
62687
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62034
62688
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62035
62689
|
permission[category] ??= {};
|
|
62036
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
62690
|
+
permission[category][CATCH_ALL_PATTERN$8] = action;
|
|
62037
62691
|
}
|
|
62038
62692
|
}
|
|
62039
62693
|
return { permission };
|
|
@@ -62044,7 +62698,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
62044
62698
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
62045
62699
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
62046
62700
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
62047
|
-
const CATCH_ALL_PATTERN$
|
|
62701
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
62048
62702
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
62049
62703
|
const CATEGORY_TO_GROK_TOOL = {
|
|
62050
62704
|
bash: "Bash",
|
|
@@ -62069,6 +62723,8 @@ const GROK_TOOL_TO_CATEGORY = {
|
|
|
62069
62723
|
...Object.fromEntries(["SendSubagentMessage", "SendAgentMessage"].map((tool) => [tool, "agent"]))
|
|
62070
62724
|
};
|
|
62071
62725
|
const GROK_MCP_TOOL = "MCPTool";
|
|
62726
|
+
const GROK_ALL_TOOLS_ENTRY = "*";
|
|
62727
|
+
const GROK_VERBOSE_ANY_TOOL = "any";
|
|
62072
62728
|
const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
62073
62729
|
...Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category])),
|
|
62074
62730
|
agent_message: "agent"
|
|
@@ -62083,22 +62739,26 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
62083
62739
|
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
62084
62740
|
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
62085
62741
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
62086
|
-
* concrete pattern emits `Tool(pattern)`.
|
|
62742
|
+
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
62743
|
+
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
62744
|
+
* under it has no Grok spelling and returns `null`.
|
|
62087
62745
|
*/
|
|
62088
62746
|
function buildGrokEntry(category, pattern) {
|
|
62747
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$7 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
62089
62748
|
if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
|
|
62090
62749
|
const remainder = category.slice(5);
|
|
62091
62750
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
62092
62751
|
}
|
|
62093
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
62752
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
62094
62753
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
62095
62754
|
if (tool === void 0) return null;
|
|
62096
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
62755
|
+
return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
62097
62756
|
}
|
|
62098
62757
|
/**
|
|
62099
62758
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
62100
|
-
* pattern. Entries without parentheses are treated as catch-all (`*`)
|
|
62101
|
-
*
|
|
62759
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`), and the
|
|
62760
|
+
* bare `*` rule becomes the all-tools `*` category. Returns `null` for tool
|
|
62761
|
+
* prefixes with no canonical equivalent.
|
|
62102
62762
|
*/
|
|
62103
62763
|
function parseGrokEntry(entry) {
|
|
62104
62764
|
const trimmed = entry.trim();
|
|
@@ -62112,19 +62772,23 @@ function parseGrokEntry(entry) {
|
|
|
62112
62772
|
tool = trimmed.slice(0, parenIndex);
|
|
62113
62773
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
62114
62774
|
}
|
|
62775
|
+
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
62776
|
+
category: "*",
|
|
62777
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62778
|
+
};
|
|
62115
62779
|
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
|
|
62116
62780
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
62117
62781
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
62118
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62782
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62119
62783
|
} : {
|
|
62120
62784
|
category: "mcp",
|
|
62121
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62785
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62122
62786
|
};
|
|
62123
62787
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
62124
62788
|
if (category === void 0) return null;
|
|
62125
62789
|
return {
|
|
62126
62790
|
category,
|
|
62127
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
62791
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$7
|
|
62128
62792
|
};
|
|
62129
62793
|
}
|
|
62130
62794
|
/**
|
|
@@ -62141,14 +62805,14 @@ function parseGrokEntry(entry) {
|
|
|
62141
62805
|
function parseClaudeStyleMcpEntry(entry) {
|
|
62142
62806
|
const rest = entry.slice(5);
|
|
62143
62807
|
if (rest.length === 0) return null;
|
|
62144
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
62808
|
+
if (rest === CATCH_ALL_PATTERN$7) return {
|
|
62145
62809
|
category: "mcp",
|
|
62146
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62810
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62147
62811
|
};
|
|
62148
62812
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
62149
62813
|
return {
|
|
62150
62814
|
category: `${MCP_CANONICAL_PREFIX$3}${address}`,
|
|
62151
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62815
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62152
62816
|
};
|
|
62153
62817
|
}
|
|
62154
62818
|
/**
|
|
@@ -62169,15 +62833,20 @@ function parseGrokRule(rule) {
|
|
|
62169
62833
|
if (typeof tool !== "string") return null;
|
|
62170
62834
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
62171
62835
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
62172
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
62836
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$7;
|
|
62173
62837
|
const lowerTool = tool.trim().toLowerCase();
|
|
62174
|
-
if (
|
|
62838
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62839
|
+
category: "*",
|
|
62840
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62841
|
+
action
|
|
62842
|
+
} : null;
|
|
62843
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$7 ? {
|
|
62175
62844
|
category: "mcp",
|
|
62176
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62845
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62177
62846
|
action
|
|
62178
62847
|
} : {
|
|
62179
62848
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
62180
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62849
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62181
62850
|
action
|
|
62182
62851
|
};
|
|
62183
62852
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -62207,7 +62876,10 @@ function parseGrokRule(rule) {
|
|
|
62207
62876
|
* subagent rather than its launch, and whose pattern is a subagent id
|
|
62208
62877
|
* rather than a subagent type); `mcp__*` maps to `MCPTool(...)` (a scoped
|
|
62209
62878
|
* MCP category folds its address into the parentheses, so a non-`*`
|
|
62210
|
-
* argument pattern on it is not represented)
|
|
62879
|
+
* argument pattern on it is not represented); the all-tools `*` category
|
|
62880
|
+
* maps to Grok's bare `*` rule (which matches every tool) for its
|
|
62881
|
+
* catch-all pattern, while a narrower `*` pattern is skipped with a
|
|
62882
|
+
* warning. The one category with no Grok tool (`notebookedit`) is
|
|
62211
62883
|
* skipped (with a warning when it carries a `deny` rule, to surface the
|
|
62212
62884
|
* gap). When two canonical rules collapse onto the same Grok
|
|
62213
62885
|
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
@@ -62218,7 +62890,9 @@ function parseGrokRule(rule) {
|
|
|
62218
62890
|
* verbose `[[permission.rules]]` tables
|
|
62219
62891
|
* (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
|
|
62220
62892
|
* field is matched case-insensitively against the same tool table, plus
|
|
62221
|
-
* the documented `mcp` alias for the compact form's `MCPTool`.
|
|
62893
|
+
* the documented `mcp` alias for the compact form's `MCPTool`. A bare `*`
|
|
62894
|
+
* entry and a tool-wide verbose `any` rule both import as the all-tools
|
|
62895
|
+
* `*` category. Rules
|
|
62222
62896
|
* from the two forms are merged with the same `deny > ask > allow`
|
|
62223
62897
|
* precedence. When neither form carries a rule (older configs), the coarse
|
|
62224
62898
|
* `[ui] permission_mode` is used as a fallback. Generate always emits the
|
|
@@ -62243,7 +62917,7 @@ function parseGrokRule(rule) {
|
|
|
62243
62917
|
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
62244
62918
|
* every other key
|
|
62245
62919
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
62246
|
-
* entries for tool prefixes rulesync cannot model (e.g. `
|
|
62920
|
+
* entries for tool prefixes rulesync cannot model (e.g. a pattern-only entry such as `src/**`) are preserved.
|
|
62247
62921
|
* The file is never deleted.
|
|
62248
62922
|
*/
|
|
62249
62923
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -62325,7 +62999,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62325
62999
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
62326
63000
|
}
|
|
62327
63001
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
62328
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
63002
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$7]: legacyModeAction(parsed) } } };
|
|
62329
63003
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
62330
63004
|
}
|
|
62331
63005
|
validate() {
|
|
@@ -62345,14 +63019,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62345
63019
|
});
|
|
62346
63020
|
}
|
|
62347
63021
|
};
|
|
62348
|
-
const ACTION_RANK$
|
|
63022
|
+
const ACTION_RANK$3 = {
|
|
62349
63023
|
allow: 0,
|
|
62350
63024
|
ask: 1,
|
|
62351
63025
|
deny: 2
|
|
62352
63026
|
};
|
|
62353
63027
|
/**
|
|
62354
63028
|
* Collect user-authored entries from an existing `[permission]` array whose
|
|
62355
|
-
* tool prefix rulesync cannot model (e.g. `
|
|
63029
|
+
* tool prefix rulesync cannot model (e.g. a pattern-only entry such as `src/**`). Such entries are
|
|
62356
63030
|
* preserved verbatim so replacing the arrays does not silently drop them
|
|
62357
63031
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
62358
63032
|
*/
|
|
@@ -62371,13 +63045,26 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62371
63045
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) for (const [pattern, action] of Object.entries(rules)) {
|
|
62372
63046
|
const entry = buildGrokEntry(category, pattern);
|
|
62373
63047
|
if (entry === null) {
|
|
63048
|
+
if (category === "*") {
|
|
63049
|
+
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').`);
|
|
63050
|
+
continue;
|
|
63051
|
+
}
|
|
62374
63052
|
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.`);
|
|
62375
63053
|
continue;
|
|
62376
63054
|
}
|
|
62377
63055
|
const existing = ranked.get(entry);
|
|
62378
63056
|
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).`);
|
|
62379
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63057
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) ranked.set(entry, action);
|
|
62380
63058
|
}
|
|
63059
|
+
warnRemovedAllToolsRestrictions({
|
|
63060
|
+
existingPermission,
|
|
63061
|
+
ranked,
|
|
63062
|
+
logger
|
|
63063
|
+
});
|
|
63064
|
+
warnEntriesShadowedByAllTools({
|
|
63065
|
+
ranked,
|
|
63066
|
+
logger
|
|
63067
|
+
});
|
|
62381
63068
|
const allow = unmanagedEntries(existingPermission, "allow");
|
|
62382
63069
|
const deny = unmanagedEntries(existingPermission, "deny");
|
|
62383
63070
|
const ask = unmanagedEntries(existingPermission, "ask");
|
|
@@ -62391,6 +63078,38 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62391
63078
|
};
|
|
62392
63079
|
}
|
|
62393
63080
|
/**
|
|
63081
|
+
* Warn when generate drops an existing bare `*` `deny`/`ask` entry without
|
|
63082
|
+
* writing an equally strict one back. Such an entry is Rulesync-owned (it maps
|
|
63083
|
+
* to the all-tools `*` category), so a config without a matching canonical
|
|
63084
|
+
* rule removes it — which silently lifts a restriction on every tool.
|
|
63085
|
+
*/
|
|
63086
|
+
function warnRemovedAllToolsRestrictions({ existingPermission, ranked, logger }) {
|
|
63087
|
+
if (!logger) return;
|
|
63088
|
+
const generated = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63089
|
+
for (const action of ["deny", "ask"]) {
|
|
63090
|
+
const entries = existingPermission[action];
|
|
63091
|
+
if (!isStringArray$2(entries)) continue;
|
|
63092
|
+
if (!entries.some((entry) => parseGrokEntry(entry)?.category === "*")) continue;
|
|
63093
|
+
if (generated !== void 0 && ACTION_RANK$3[generated] >= ACTION_RANK$3[action]) continue;
|
|
63094
|
+
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.`);
|
|
63095
|
+
}
|
|
63096
|
+
}
|
|
63097
|
+
/**
|
|
63098
|
+
* Warn about entries that a bare `*` `deny`/`ask` makes ineffective: Grok
|
|
63099
|
+
* evaluates by severity (`deny > ask > allow`), so a specific `allow` (and,
|
|
63100
|
+
* under a `*` deny, a specific `ask`) never takes effect next to it. Shell
|
|
63101
|
+
* rules are already folded by `honorAllToolsOnBash`, so this covers the other
|
|
63102
|
+
* tools.
|
|
63103
|
+
*/
|
|
63104
|
+
function warnEntriesShadowedByAllTools({ ranked, logger }) {
|
|
63105
|
+
if (!logger) return;
|
|
63106
|
+
const allTools = ranked.get(GROK_ALL_TOOLS_ENTRY);
|
|
63107
|
+
if (allTools === void 0 || allTools === "allow") return;
|
|
63108
|
+
const shadowed = [...ranked].filter(([entry, action]) => entry !== GROK_ALL_TOOLS_ENTRY && ACTION_RANK$3[action] < ACTION_RANK$3[allTools]).map(([entry]) => entry).toSorted();
|
|
63109
|
+
if (shadowed.length === 0) return;
|
|
63110
|
+
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(", ")}.`);
|
|
63111
|
+
}
|
|
63112
|
+
/**
|
|
62394
63113
|
* Parse Grok's `[permission]` section back into a canonical permission map,
|
|
62395
63114
|
* reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
|
|
62396
63115
|
* Claude-style entries and the verbose `[[permission.rules]]` tables
|
|
@@ -62409,7 +63128,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
62409
63128
|
const record = ({ category, pattern, action }) => {
|
|
62410
63129
|
const bucket = result[category] ??= {};
|
|
62411
63130
|
const existing = bucket[pattern];
|
|
62412
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63131
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) bucket[pattern] = action;
|
|
62413
63132
|
};
|
|
62414
63133
|
const applyEntries = (entries, action) => {
|
|
62415
63134
|
for (const entry of entries ?? []) {
|
|
@@ -62813,7 +63532,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
62813
63532
|
mcpTools: "mcp",
|
|
62814
63533
|
readOutsideProject: "read"
|
|
62815
63534
|
};
|
|
62816
|
-
function isPermissionAction$
|
|
63535
|
+
function isPermissionAction$2(value) {
|
|
62817
63536
|
return PermissionActionSchema.safeParse(value).success;
|
|
62818
63537
|
}
|
|
62819
63538
|
/**
|
|
@@ -62999,7 +63718,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
|
62999
63718
|
for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
|
|
63000
63719
|
if (!rule || typeof rule !== "object") continue;
|
|
63001
63720
|
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
63002
|
-
if (pattern === void 0 || !isPermissionAction$
|
|
63721
|
+
if (pattern === void 0 || !isPermissionAction$2(rule.action)) continue;
|
|
63003
63722
|
(permission[category] ??= {})[pattern] = rule.action;
|
|
63004
63723
|
}
|
|
63005
63724
|
}
|
|
@@ -63958,6 +64677,367 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
63958
64677
|
return rules;
|
|
63959
64678
|
}
|
|
63960
64679
|
//#endregion
|
|
64680
|
+
//#region src/features/permissions/kiro-capability-permissions.ts
|
|
64681
|
+
const CATCH_ALL_PATTERN$6 = "*";
|
|
64682
|
+
const CANONICAL_TO_KIRO_CAPABILITY = {
|
|
64683
|
+
bash: "shell",
|
|
64684
|
+
read: "fs_read",
|
|
64685
|
+
edit: "fs_write",
|
|
64686
|
+
write: "fs_write",
|
|
64687
|
+
webfetch: "web_fetch",
|
|
64688
|
+
websearch: "web_search"
|
|
64689
|
+
};
|
|
64690
|
+
const KIRO_CAPABILITY_TO_CANONICAL = {
|
|
64691
|
+
shell: "bash",
|
|
64692
|
+
fs_read: "read",
|
|
64693
|
+
fs_write: "write",
|
|
64694
|
+
web_fetch: "webfetch",
|
|
64695
|
+
web_search: "websearch"
|
|
64696
|
+
};
|
|
64697
|
+
const MANAGED_CAPABILITIES = new Set(Object.keys(KIRO_CAPABILITY_TO_CANONICAL));
|
|
64698
|
+
const ACTION_RANK$2 = {
|
|
64699
|
+
allow: 0,
|
|
64700
|
+
ask: 1,
|
|
64701
|
+
deny: 2
|
|
64702
|
+
};
|
|
64703
|
+
function isPermissionAction$1(value) {
|
|
64704
|
+
return value === "allow" || value === "ask" || value === "deny";
|
|
64705
|
+
}
|
|
64706
|
+
/**
|
|
64707
|
+
* Whether an existing `permissions.yaml` rule can be read back into the
|
|
64708
|
+
* canonical model: a rule for a capability rulesync maps, with no `exclude`
|
|
64709
|
+
* list (the canonical model has no exclusion). Used on import only.
|
|
64710
|
+
*/
|
|
64711
|
+
function isImportableRule(rule) {
|
|
64712
|
+
return isRecord$1(rule) && typeof rule.capability === "string" && MANAGED_CAPABILITIES.has(rule.capability) && rule.exclude === void 0;
|
|
64713
|
+
}
|
|
64714
|
+
/**
|
|
64715
|
+
* Key identifying one (capability, pattern) pair. The catch-all `*` stands for
|
|
64716
|
+
* a match-less rule.
|
|
64717
|
+
*/
|
|
64718
|
+
function rulePatternKey(capability, pattern) {
|
|
64719
|
+
return `${capability}\u0000${pattern}`;
|
|
64720
|
+
}
|
|
64721
|
+
/**
|
|
64722
|
+
* Classify one existing (capability, pattern, effect) entry against the
|
|
64723
|
+
* generated rules: an exact duplicate (same effect) is replaced by the
|
|
64724
|
+
* generated rule; a different effect is a conflict and is kept; anything
|
|
64725
|
+
* rulesync does not emit is unrelated and kept.
|
|
64726
|
+
*/
|
|
64727
|
+
function classifyExistingEntry({ emitted, capability, pattern, effect }) {
|
|
64728
|
+
const effects = emitted.get(rulePatternKey(capability, pattern));
|
|
64729
|
+
if (effects === void 0) return "unrelated";
|
|
64730
|
+
return isPermissionAction$1(effect) && effects.has(effect) ? "duplicate" : "conflict";
|
|
64731
|
+
}
|
|
64732
|
+
/**
|
|
64733
|
+
* Merge one existing rule against the generated entries (see
|
|
64734
|
+
* {@link mergeKiroCapabilityRules}). Returns the rule to keep (possibly with a
|
|
64735
|
+
* narrowed `match`), or `undefined` when every entry is an exact duplicate, and
|
|
64736
|
+
* how many entries rulesync does not generate.
|
|
64737
|
+
*/
|
|
64738
|
+
function mergeExistingRule({ rule, emitted, logger }) {
|
|
64739
|
+
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 {
|
|
64740
|
+
rule,
|
|
64741
|
+
unrelatedEntries: 0
|
|
64742
|
+
};
|
|
64743
|
+
const capability = rule.capability;
|
|
64744
|
+
const isCatchAll = rule.match === void 0;
|
|
64745
|
+
const patterns = isStringArray$2(rule.match) ? rule.match : [CATCH_ALL_PATTERN$6];
|
|
64746
|
+
if (patterns.length === 0) return {
|
|
64747
|
+
rule,
|
|
64748
|
+
unrelatedEntries: 0
|
|
64749
|
+
};
|
|
64750
|
+
const remaining = [];
|
|
64751
|
+
let unrelatedEntries = 0;
|
|
64752
|
+
for (const pattern of patterns) {
|
|
64753
|
+
const outcome = classifyExistingEntry({
|
|
64754
|
+
emitted,
|
|
64755
|
+
capability,
|
|
64756
|
+
pattern,
|
|
64757
|
+
effect: rule.effect
|
|
64758
|
+
});
|
|
64759
|
+
if (outcome === "duplicate") continue;
|
|
64760
|
+
remaining.push(pattern);
|
|
64761
|
+
if (outcome === "unrelated") {
|
|
64762
|
+
unrelatedEntries++;
|
|
64763
|
+
continue;
|
|
64764
|
+
}
|
|
64765
|
+
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).`);
|
|
64766
|
+
}
|
|
64767
|
+
if (remaining.length === 0) return {
|
|
64768
|
+
rule: void 0,
|
|
64769
|
+
unrelatedEntries
|
|
64770
|
+
};
|
|
64771
|
+
if (remaining.length === patterns.length || isCatchAll) return {
|
|
64772
|
+
rule,
|
|
64773
|
+
unrelatedEntries
|
|
64774
|
+
};
|
|
64775
|
+
return {
|
|
64776
|
+
rule: {
|
|
64777
|
+
...rule,
|
|
64778
|
+
match: remaining
|
|
64779
|
+
},
|
|
64780
|
+
unrelatedEntries
|
|
64781
|
+
};
|
|
64782
|
+
}
|
|
64783
|
+
/**
|
|
64784
|
+
* Merge the existing rules with the generated ones. Rulesync keeps no
|
|
64785
|
+
* ownership record in the file, so it only removes what it is about to write
|
|
64786
|
+
* again: an existing rule loses the `match` entries that exactly duplicate a
|
|
64787
|
+
* generated (capability, pattern, effect) — a match-less rule counts as the `*`
|
|
64788
|
+
* pattern — and is dropped only when no entry is left.
|
|
64789
|
+
*
|
|
64790
|
+
* Everything else is kept verbatim: rules Kiro saved ("Always allow/deny"),
|
|
64791
|
+
* hand-written rules, rules with `exclude`, rules for capabilities rulesync
|
|
64792
|
+
* does not map, malformed entries. An existing rule with the same pattern but a
|
|
64793
|
+
* different effect is kept too, with a warning: Kiro resolves deny > ask >
|
|
64794
|
+
* allow, so the stricter rule still wins and a user's `deny` is never loosened.
|
|
64795
|
+
* Rerunning generate is therefore idempotent.
|
|
64796
|
+
*/
|
|
64797
|
+
function mergeKiroCapabilityRules({ existingRules, generatedRules, logger }) {
|
|
64798
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
64799
|
+
for (const rule of generatedRules) for (const pattern of rule.match ?? [CATCH_ALL_PATTERN$6]) {
|
|
64800
|
+
const key = rulePatternKey(rule.capability, pattern);
|
|
64801
|
+
const effects = emitted.get(key) ?? /* @__PURE__ */ new Set();
|
|
64802
|
+
effects.add(rule.effect);
|
|
64803
|
+
emitted.set(key, effects);
|
|
64804
|
+
}
|
|
64805
|
+
const kept = [];
|
|
64806
|
+
let unrelatedManagedEntries = 0;
|
|
64807
|
+
for (const rule of existingRules) {
|
|
64808
|
+
const result = mergeExistingRule({
|
|
64809
|
+
rule,
|
|
64810
|
+
emitted,
|
|
64811
|
+
logger
|
|
64812
|
+
});
|
|
64813
|
+
if (result.rule !== void 0) kept.push(result.rule);
|
|
64814
|
+
unrelatedManagedEntries += result.unrelatedEntries;
|
|
64815
|
+
}
|
|
64816
|
+
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.`);
|
|
64817
|
+
return [...kept, ...generatedRules];
|
|
64818
|
+
}
|
|
64819
|
+
const LOCATION_INDEPENDENT_FS_PATTERN = /^(?:\/|~|\*\*|[A-Za-z]:[\\/])/;
|
|
64820
|
+
/**
|
|
64821
|
+
* Permissions adapter for Kiro's capability-based `permissions.yaml` (Kiro IDE
|
|
64822
|
+
* 1.0, and the same engine in Kiro CLI V3). Used for the `kiro-cli` and
|
|
64823
|
+
* `kiro-ide` targets in **global** scope, where it writes the user-level
|
|
64824
|
+
* `~/.kiro/settings/permissions.yaml`. Project scope keeps writing the agent
|
|
64825
|
+
* config `.kiro/agents/default.json` through {@link KiroPermissions}: the
|
|
64826
|
+
* workspace-level `permissions.yaml` lives under a hash-derived directory in the
|
|
64827
|
+
* home directory, not in the repository, so it cannot be generated.
|
|
64828
|
+
*
|
|
64829
|
+
* Mapping (rulesync canonical -> Kiro rule):
|
|
64830
|
+
* - Category -> `capability`: `bash` -> `shell`, `read` -> `fs_read`,
|
|
64831
|
+
* `edit`/`write` -> `fs_write`, `webfetch` -> `web_fetch`, `websearch` ->
|
|
64832
|
+
* `web_search`. Other categories are skipped with a warning.
|
|
64833
|
+
* - Action -> `effect` 1:1, including `ask`.
|
|
64834
|
+
* - Pattern -> `match` entry. The catch-all `*` becomes a rule with no `match`
|
|
64835
|
+
* (the whole capability), because a filesystem `*` only matches one path
|
|
64836
|
+
* component in Kiro.
|
|
64837
|
+
*
|
|
64838
|
+
* Kiro also writes into this file (consent decisions persisted globally), so it
|
|
64839
|
+
* is merged, never replaced or deleted: every top-level key other than `rules`
|
|
64840
|
+
* is preserved, and existing rules are kept except for entries that exactly
|
|
64841
|
+
* repeat a generated rule (see {@link mergeKiroCapabilityRules}). Rulesync
|
|
64842
|
+
* never retracts a rule an earlier run wrote; it warns instead. A run that
|
|
64843
|
+
* changes nothing returns the existing content verbatim; otherwise the file is
|
|
64844
|
+
* re-serialized and its YAML comments are lost.
|
|
64845
|
+
*
|
|
64846
|
+
* @see https://kiro.dev/docs/permissions/
|
|
64847
|
+
*/
|
|
64848
|
+
var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPermissions {
|
|
64849
|
+
constructor(params) {
|
|
64850
|
+
super({
|
|
64851
|
+
...params,
|
|
64852
|
+
fileContent: params.fileContent ?? ""
|
|
64853
|
+
});
|
|
64854
|
+
}
|
|
64855
|
+
isDeletable() {
|
|
64856
|
+
return false;
|
|
64857
|
+
}
|
|
64858
|
+
shouldSkipCreationWhenPayloadEmpty() {
|
|
64859
|
+
return true;
|
|
64860
|
+
}
|
|
64861
|
+
static getSettablePaths(_options) {
|
|
64862
|
+
return {
|
|
64863
|
+
relativeDirPath: KIRO_SETTINGS_DIR_PATH,
|
|
64864
|
+
relativeFilePath: KIRO_PERMISSIONS_FILE_NAME
|
|
64865
|
+
};
|
|
64866
|
+
}
|
|
64867
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true }) {
|
|
64868
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64869
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
64870
|
+
return new KiroCapabilityPermissions({
|
|
64871
|
+
outputRoot,
|
|
64872
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64873
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64874
|
+
fileContent,
|
|
64875
|
+
validate,
|
|
64876
|
+
global: true
|
|
64877
|
+
});
|
|
64878
|
+
}
|
|
64879
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger }) {
|
|
64880
|
+
const paths = this.getSettablePaths({ global: true });
|
|
64881
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
64882
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
64883
|
+
let parsed;
|
|
64884
|
+
try {
|
|
64885
|
+
parsed = loadYaml(existingContent) ?? {};
|
|
64886
|
+
} catch (error) {
|
|
64887
|
+
throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
64888
|
+
}
|
|
64889
|
+
if (!isRecord$1(parsed)) throw new Error(`Failed to parse existing Kiro permissions.yaml at ${filePath}: not a map`);
|
|
64890
|
+
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`);
|
|
64891
|
+
const config = rulesyncPermissions.getJson();
|
|
64892
|
+
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.");
|
|
64893
|
+
const generatedRules = buildKiroCapabilityRules({
|
|
64894
|
+
config,
|
|
64895
|
+
logger
|
|
64896
|
+
});
|
|
64897
|
+
const rules = mergeKiroCapabilityRules({
|
|
64898
|
+
existingRules: parsed.rules ?? [],
|
|
64899
|
+
generatedRules,
|
|
64900
|
+
logger
|
|
64901
|
+
});
|
|
64902
|
+
const next = { ...parsed };
|
|
64903
|
+
if (rules.length > 0 || parsed.rules !== void 0) next.rules = rules;
|
|
64904
|
+
return new KiroCapabilityPermissions({
|
|
64905
|
+
outputRoot,
|
|
64906
|
+
relativeDirPath: paths.relativeDirPath,
|
|
64907
|
+
relativeFilePath: paths.relativeFilePath,
|
|
64908
|
+
fileContent: serializeKiroPermissions({
|
|
64909
|
+
next,
|
|
64910
|
+
parsed,
|
|
64911
|
+
existingContent
|
|
64912
|
+
}),
|
|
64913
|
+
validate: true,
|
|
64914
|
+
global: true
|
|
64915
|
+
});
|
|
64916
|
+
}
|
|
64917
|
+
toRulesyncPermissions() {
|
|
64918
|
+
let parsed;
|
|
64919
|
+
try {
|
|
64920
|
+
parsed = loadYaml(this.getFileContent()) ?? {};
|
|
64921
|
+
} catch (error) {
|
|
64922
|
+
throw new Error(`Failed to parse Kiro permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
64923
|
+
}
|
|
64924
|
+
const rules = isRecord$1(parsed) && Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
64925
|
+
const permission = {};
|
|
64926
|
+
for (const rule of rules) {
|
|
64927
|
+
if (!isImportableRule(rule) || !isRecord$1(rule) || !isPermissionAction$1(rule.effect)) continue;
|
|
64928
|
+
const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
|
|
64929
|
+
const patterns = rule.match === void 0 ? [CATCH_ALL_PATTERN$6] : isStringArray$2(rule.match) ? rule.match : [];
|
|
64930
|
+
const categoryRules = permission[category] ??= {};
|
|
64931
|
+
for (const pattern of patterns) {
|
|
64932
|
+
const previous = categoryRules[pattern];
|
|
64933
|
+
if (previous === void 0 || ACTION_RANK$2[rule.effect] > ACTION_RANK$2[previous]) categoryRules[pattern] = rule.effect;
|
|
64934
|
+
}
|
|
64935
|
+
}
|
|
64936
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
64937
|
+
}
|
|
64938
|
+
validate() {
|
|
64939
|
+
return {
|
|
64940
|
+
success: true,
|
|
64941
|
+
error: null
|
|
64942
|
+
};
|
|
64943
|
+
}
|
|
64944
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
64945
|
+
return new KiroCapabilityPermissions({
|
|
64946
|
+
outputRoot,
|
|
64947
|
+
relativeDirPath,
|
|
64948
|
+
relativeFilePath,
|
|
64949
|
+
fileContent: "",
|
|
64950
|
+
validate: false,
|
|
64951
|
+
global: true
|
|
64952
|
+
});
|
|
64953
|
+
}
|
|
64954
|
+
};
|
|
64955
|
+
/**
|
|
64956
|
+
* Convert the canonical `permission` block into Kiro capability rules: one rule
|
|
64957
|
+
* per (capability, effect) carrying every non-catch-all pattern as `match`, plus
|
|
64958
|
+
* a match-less rule when the catch-all `*` is set.
|
|
64959
|
+
*/
|
|
64960
|
+
function buildKiroCapabilityRules({ config, logger }) {
|
|
64961
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
64962
|
+
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
64963
|
+
const capability = CANONICAL_TO_KIRO_CAPABILITY[category];
|
|
64964
|
+
if (capability === void 0) {
|
|
64965
|
+
logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
|
|
64966
|
+
continue;
|
|
64967
|
+
}
|
|
64968
|
+
for (const [pattern, effect] of Object.entries(rules)) {
|
|
64969
|
+
warnOnWorkspaceRelativeFsPattern({
|
|
64970
|
+
capability,
|
|
64971
|
+
category,
|
|
64972
|
+
pattern,
|
|
64973
|
+
logger
|
|
64974
|
+
});
|
|
64975
|
+
const key = `${capability}\u0000${effect}`;
|
|
64976
|
+
const bucket = buckets.get(key) ?? {
|
|
64977
|
+
capability,
|
|
64978
|
+
effect,
|
|
64979
|
+
patterns: /* @__PURE__ */ new Set()
|
|
64980
|
+
};
|
|
64981
|
+
bucket.patterns.add(pattern);
|
|
64982
|
+
buckets.set(key, bucket);
|
|
64983
|
+
}
|
|
64984
|
+
}
|
|
64985
|
+
const result = [];
|
|
64986
|
+
for (const { capability, effect, patterns } of buckets.values()) {
|
|
64987
|
+
if (patterns.has(CATCH_ALL_PATTERN$6)) result.push({
|
|
64988
|
+
capability,
|
|
64989
|
+
effect
|
|
64990
|
+
});
|
|
64991
|
+
const match = [...patterns].filter((pattern) => pattern !== CATCH_ALL_PATTERN$6);
|
|
64992
|
+
if (match.length > 0) result.push({
|
|
64993
|
+
capability,
|
|
64994
|
+
match,
|
|
64995
|
+
effect
|
|
64996
|
+
});
|
|
64997
|
+
}
|
|
64998
|
+
return result;
|
|
64999
|
+
}
|
|
65000
|
+
/**
|
|
65001
|
+
* Serialize the merged config. When the merge changes nothing, the existing
|
|
65002
|
+
* content is returned verbatim so a run with nothing to change never rewrites
|
|
65003
|
+
* the file (and never drops its YAML comments).
|
|
65004
|
+
*/
|
|
65005
|
+
function serializeKiroPermissions({ next, parsed, existingContent }) {
|
|
65006
|
+
if (isDeepStrictEqual(next, parsed)) return existingContent;
|
|
65007
|
+
return Object.keys(next).length === 0 ? "" : dump(next);
|
|
65008
|
+
}
|
|
65009
|
+
/**
|
|
65010
|
+
* `~/.kiro/settings/permissions.yaml` applies to every workspace, so a relative
|
|
65011
|
+
* filesystem pattern such as `src/**` is not tied to the project it was written
|
|
65012
|
+
* for. Warn so the author can make it absolute, `~`-relative or `**`-prefixed.
|
|
65013
|
+
*/
|
|
65014
|
+
function warnOnWorkspaceRelativeFsPattern({ capability, category, pattern, logger }) {
|
|
65015
|
+
if (capability !== "fs_read" && capability !== "fs_write") return;
|
|
65016
|
+
if (pattern === CATCH_ALL_PATTERN$6 || LOCATION_INDEPENDENT_FS_PATTERN.test(pattern)) return;
|
|
65017
|
+
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.`);
|
|
65018
|
+
}
|
|
65019
|
+
/**
|
|
65020
|
+
* Permissions factory for the `kiro-cli` / `kiro-ide` targets: project scope
|
|
65021
|
+
* writes the agent config `.kiro/agents/default.json` ({@link KiroPermissions}),
|
|
65022
|
+
* global scope writes `~/.kiro/settings/permissions.yaml`
|
|
65023
|
+
* ({@link KiroCapabilityPermissions}). The deprecated `kiro` alias stays on
|
|
65024
|
+
* {@link KiroPermissions} only.
|
|
65025
|
+
*/
|
|
65026
|
+
const KiroScopedPermissions = {
|
|
65027
|
+
getSettablePaths(options = {}) {
|
|
65028
|
+
return options.global ? KiroCapabilityPermissions.getSettablePaths(options) : KiroPermissions.getSettablePaths(options);
|
|
65029
|
+
},
|
|
65030
|
+
fromFile(params) {
|
|
65031
|
+
return params.global ? KiroCapabilityPermissions.fromFile(params) : KiroPermissions.fromFile(params);
|
|
65032
|
+
},
|
|
65033
|
+
fromRulesyncPermissions(params) {
|
|
65034
|
+
return params.global ? KiroCapabilityPermissions.fromRulesyncPermissions(params) : KiroPermissions.fromRulesyncPermissions(params);
|
|
65035
|
+
},
|
|
65036
|
+
forDeletion(params) {
|
|
65037
|
+
return params.global ? KiroCapabilityPermissions.forDeletion(params) : KiroPermissions.forDeletion(params);
|
|
65038
|
+
}
|
|
65039
|
+
};
|
|
65040
|
+
//#endregion
|
|
63961
65041
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
63962
65042
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
63963
65043
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
@@ -65985,6 +67065,8 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
65985
67065
|
"eager",
|
|
65986
67066
|
"listDirectory",
|
|
65987
67067
|
"todoWrite",
|
|
67068
|
+
"codeModeOnly",
|
|
67069
|
+
"freeform",
|
|
65988
67070
|
"workflowsEnabled",
|
|
65989
67071
|
"workflowNameOnly",
|
|
65990
67072
|
"executionSandbox"
|
|
@@ -66111,6 +67193,16 @@ const QWEN_SCOPED_TOOLS_KEYS = {
|
|
|
66111
67193
|
rule: "global-machine-wide",
|
|
66112
67194
|
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.`,
|
|
66113
67195
|
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."
|
|
67196
|
+
},
|
|
67197
|
+
codeModeOnly: {
|
|
67198
|
+
rule: "global-machine-wide",
|
|
67199
|
+
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.`,
|
|
67200
|
+
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."
|
|
67201
|
+
},
|
|
67202
|
+
freeform: {
|
|
67203
|
+
rule: "global-machine-wide",
|
|
67204
|
+
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.`,
|
|
67205
|
+
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."
|
|
66114
67206
|
}
|
|
66115
67207
|
};
|
|
66116
67208
|
/**
|
|
@@ -66574,22 +67666,42 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
66574
67666
|
//#endregion
|
|
66575
67667
|
//#region src/features/permissions/reasonix-permissions.ts
|
|
66576
67668
|
/**
|
|
66577
|
-
* Mapping from rulesync canonical tool category names (lowercase) to
|
|
66578
|
-
*
|
|
66579
|
-
*
|
|
66580
|
-
* Reasonix's
|
|
66581
|
-
*
|
|
66582
|
-
*
|
|
66583
|
-
*
|
|
66584
|
-
*
|
|
66585
|
-
*
|
|
66586
|
-
*
|
|
66587
|
-
*
|
|
67669
|
+
* Mapping from rulesync canonical tool category names (lowercase) to the tool
|
|
67670
|
+
* names a Reasonix `[permissions]` rule must use.
|
|
67671
|
+
*
|
|
67672
|
+
* Reasonix matches a rule's tool exactly against its registered tools, which
|
|
67673
|
+
* are snake_case (`read_file`, `write_file`, `grep`, `glob`, `web_fetch`,
|
|
67674
|
+
* `web_search`, `notebook_edit`, `task`). Only `Bash`, `Edit` (every file
|
|
67675
|
+
* writer: `write_file`, `edit_file`, `multi_edit`, `notebook_edit`, ...),
|
|
67676
|
+
* `Browser` and `Computer` are Claude-Code-style family aliases
|
|
67677
|
+
* (`canonicalRuleTool` / `groupOf`), so a `Read(...)` or `Write(...)` rule
|
|
67678
|
+
* matches nothing and Reasonix v2.34.0+ reports it as dormant. Checked on both
|
|
67679
|
+
* release lines: v1.39.8 (`internal/permission/permission.go`) and v2.41.0
|
|
67680
|
+
* (`internal/safety/permission/permission.go`, `rule_group.go`).
|
|
67681
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/permission.go
|
|
67682
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.41.0/internal/safety/permission/rule_group.go
|
|
66588
67683
|
*/
|
|
66589
67684
|
const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
66590
67685
|
bash: "Bash",
|
|
66591
|
-
read:
|
|
67686
|
+
read: REASONIX_READ_TOOL_NAME,
|
|
66592
67687
|
edit: "Edit",
|
|
67688
|
+
write: "write_file",
|
|
67689
|
+
webfetch: "web_fetch",
|
|
67690
|
+
websearch: "web_search",
|
|
67691
|
+
grep: "grep",
|
|
67692
|
+
glob: "glob",
|
|
67693
|
+
notebookedit: "notebook_edit",
|
|
67694
|
+
agent: "task"
|
|
67695
|
+
};
|
|
67696
|
+
/**
|
|
67697
|
+
* The Claude-Code-style spellings rulesync emitted before it followed the
|
|
67698
|
+
* Reasonix tool vocabulary. None of them reaches a Reasonix tool, so they are
|
|
67699
|
+
* read back on import (a file generated by an older rulesync still imports)
|
|
67700
|
+
* and counted as managed on generate (the dormant copy is replaced rather than
|
|
67701
|
+
* kept beside the working rule).
|
|
67702
|
+
*/
|
|
67703
|
+
const CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES = {
|
|
67704
|
+
read: REASONIX_LEGACY_READ_TOOL_NAME,
|
|
66593
67705
|
write: "Write",
|
|
66594
67706
|
webfetch: "WebFetch",
|
|
66595
67707
|
websearch: "WebSearch",
|
|
@@ -66599,9 +67711,16 @@ const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
|
66599
67711
|
agent: "Agent"
|
|
66600
67712
|
};
|
|
66601
67713
|
/**
|
|
66602
|
-
* Reverse mapping from Reasonix tool names
|
|
67714
|
+
* Reverse mapping from Reasonix tool names (current and legacy spellings) to
|
|
67715
|
+
* rulesync canonical names.
|
|
66603
67716
|
*/
|
|
66604
|
-
const REASONIX_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
67717
|
+
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])]);
|
|
67718
|
+
/** Every Reasonix tool name a canonical category owns on generate. */
|
|
67719
|
+
function reasonixToolNamesOf(canonical) {
|
|
67720
|
+
const legacy = CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES[canonical];
|
|
67721
|
+
const current = toReasonixToolName(canonical);
|
|
67722
|
+
return legacy === void 0 ? [current] : [current, legacy];
|
|
67723
|
+
}
|
|
66605
67724
|
function toReasonixToolName(canonical) {
|
|
66606
67725
|
return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
|
|
66607
67726
|
}
|
|
@@ -66874,7 +67993,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
66874
67993
|
const parsed = parseReasonixConfig(existingContent);
|
|
66875
67994
|
const config = rulesyncPermissions.getJson();
|
|
66876
67995
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
66877
|
-
const managedToolNames = new Set(Object.keys(config.permission).
|
|
67996
|
+
const managedToolNames = new Set(Object.keys(config.permission).flatMap((category) => reasonixToolNamesOf(category)));
|
|
66878
67997
|
const override = config.reasonix;
|
|
66879
67998
|
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
66880
67999
|
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
@@ -66891,12 +68010,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
66891
68010
|
rawOwnedEntries,
|
|
66892
68011
|
logger
|
|
66893
68012
|
});
|
|
66894
|
-
if (logger && managedToolNames.has("
|
|
68013
|
+
if (logger && managedToolNames.has("read_file")) {
|
|
68014
|
+
const readToolNames = new Set(reasonixToolNamesOf("read"));
|
|
66895
68015
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
66896
68016
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
66897
|
-
return toolName
|
|
68017
|
+
return readToolNames.has(toolName);
|
|
66898
68018
|
});
|
|
66899
|
-
if (droppedReadDenyEntries.length > 0) logger.warn(`Permissions feature manages '
|
|
68019
|
+
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.`);
|
|
66900
68020
|
}
|
|
66901
68021
|
const mergedPermissions = { ...existingPermissions };
|
|
66902
68022
|
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
@@ -72242,18 +73362,18 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
72242
73362
|
}
|
|
72243
73363
|
}],
|
|
72244
73364
|
["kiro-cli", {
|
|
72245
|
-
class:
|
|
73365
|
+
class: KiroScopedPermissions,
|
|
72246
73366
|
meta: {
|
|
72247
73367
|
supportsProject: true,
|
|
72248
|
-
supportsGlobal:
|
|
73368
|
+
supportsGlobal: true,
|
|
72249
73369
|
supportsImport: true
|
|
72250
73370
|
}
|
|
72251
73371
|
}],
|
|
72252
73372
|
["kiro-ide", {
|
|
72253
|
-
class:
|
|
73373
|
+
class: KiroScopedPermissions,
|
|
72254
73374
|
meta: {
|
|
72255
73375
|
supportsProject: true,
|
|
72256
|
-
supportsGlobal:
|
|
73376
|
+
supportsGlobal: true,
|
|
72257
73377
|
supportsImport: true
|
|
72258
73378
|
}
|
|
72259
73379
|
}],
|
|
@@ -72504,6 +73624,335 @@ var PermissionsProcessor = class extends FeatureProcessor {
|
|
|
72504
73624
|
}
|
|
72505
73625
|
};
|
|
72506
73626
|
//#endregion
|
|
73627
|
+
//#region src/types/tool-display.ts
|
|
73628
|
+
const TOOL_DISPLAY = [
|
|
73629
|
+
{
|
|
73630
|
+
key: "agentsmd",
|
|
73631
|
+
label: "AGENTS.md",
|
|
73632
|
+
group: "standard"
|
|
73633
|
+
},
|
|
73634
|
+
{
|
|
73635
|
+
key: "agentsskills",
|
|
73636
|
+
label: "AgentsSkills",
|
|
73637
|
+
group: "standard"
|
|
73638
|
+
},
|
|
73639
|
+
{
|
|
73640
|
+
key: "amp",
|
|
73641
|
+
label: "Amp",
|
|
73642
|
+
group: "ai"
|
|
73643
|
+
},
|
|
73644
|
+
{
|
|
73645
|
+
key: "bob",
|
|
73646
|
+
label: "IBM Bob",
|
|
73647
|
+
group: "ai"
|
|
73648
|
+
},
|
|
73649
|
+
{
|
|
73650
|
+
key: "claudecode",
|
|
73651
|
+
label: "Claude Code",
|
|
73652
|
+
group: "ai"
|
|
73653
|
+
},
|
|
73654
|
+
{
|
|
73655
|
+
key: "claudecode-plugin",
|
|
73656
|
+
label: "Claude Code plugin",
|
|
73657
|
+
group: "ai"
|
|
73658
|
+
},
|
|
73659
|
+
{
|
|
73660
|
+
key: "codebuddy",
|
|
73661
|
+
label: "CodeBuddy Code",
|
|
73662
|
+
group: "ai"
|
|
73663
|
+
},
|
|
73664
|
+
{
|
|
73665
|
+
key: "codewhale",
|
|
73666
|
+
label: "Codewhale",
|
|
73667
|
+
group: "ai"
|
|
73668
|
+
},
|
|
73669
|
+
{
|
|
73670
|
+
key: "codexcli",
|
|
73671
|
+
label: "Codex CLI",
|
|
73672
|
+
group: "ai"
|
|
73673
|
+
},
|
|
73674
|
+
{
|
|
73675
|
+
key: "commandcode",
|
|
73676
|
+
label: "Command Code",
|
|
73677
|
+
group: "ai"
|
|
73678
|
+
},
|
|
73679
|
+
{
|
|
73680
|
+
key: "copilot",
|
|
73681
|
+
label: "GitHub Copilot",
|
|
73682
|
+
group: "ai"
|
|
73683
|
+
},
|
|
73684
|
+
{
|
|
73685
|
+
key: "copilotcli",
|
|
73686
|
+
label: "GitHub Copilot CLI",
|
|
73687
|
+
group: "ai"
|
|
73688
|
+
},
|
|
73689
|
+
{
|
|
73690
|
+
key: "crush",
|
|
73691
|
+
label: "Crush",
|
|
73692
|
+
group: "ai"
|
|
73693
|
+
},
|
|
73694
|
+
{
|
|
73695
|
+
key: "goose",
|
|
73696
|
+
label: "Goose",
|
|
73697
|
+
group: "ai"
|
|
73698
|
+
},
|
|
73699
|
+
{
|
|
73700
|
+
key: "hermesagent",
|
|
73701
|
+
label: "Hermes Agent",
|
|
73702
|
+
group: "ai"
|
|
73703
|
+
},
|
|
73704
|
+
{
|
|
73705
|
+
key: "grokcli",
|
|
73706
|
+
label: "Grok CLI",
|
|
73707
|
+
group: "ai"
|
|
73708
|
+
},
|
|
73709
|
+
{
|
|
73710
|
+
key: "cursor",
|
|
73711
|
+
label: "Cursor",
|
|
73712
|
+
group: "ai"
|
|
73713
|
+
},
|
|
73714
|
+
{
|
|
73715
|
+
key: "deepagents",
|
|
73716
|
+
label: "deepagents-cli",
|
|
73717
|
+
group: "ai"
|
|
73718
|
+
},
|
|
73719
|
+
{
|
|
73720
|
+
key: "factorydroid",
|
|
73721
|
+
label: "Factory Droid",
|
|
73722
|
+
group: "ai"
|
|
73723
|
+
},
|
|
73724
|
+
{
|
|
73725
|
+
key: "opencode",
|
|
73726
|
+
label: "OpenCode",
|
|
73727
|
+
group: "ai"
|
|
73728
|
+
},
|
|
73729
|
+
{
|
|
73730
|
+
key: "openclaw",
|
|
73731
|
+
label: "OpenClaw",
|
|
73732
|
+
group: "ai"
|
|
73733
|
+
},
|
|
73734
|
+
{
|
|
73735
|
+
key: "mimocode",
|
|
73736
|
+
label: "MiMo Code",
|
|
73737
|
+
group: "ai"
|
|
73738
|
+
},
|
|
73739
|
+
{
|
|
73740
|
+
key: "gitlabduo",
|
|
73741
|
+
label: "GitLab Duo CLI",
|
|
73742
|
+
group: "ai"
|
|
73743
|
+
},
|
|
73744
|
+
{
|
|
73745
|
+
key: "cline",
|
|
73746
|
+
label: "Cline",
|
|
73747
|
+
group: "ai"
|
|
73748
|
+
},
|
|
73749
|
+
{
|
|
73750
|
+
key: "kilo",
|
|
73751
|
+
label: "Kilo Code",
|
|
73752
|
+
group: "ai"
|
|
73753
|
+
},
|
|
73754
|
+
{
|
|
73755
|
+
key: "kimi-code",
|
|
73756
|
+
label: "Kimi Code",
|
|
73757
|
+
group: "ai"
|
|
73758
|
+
},
|
|
73759
|
+
{
|
|
73760
|
+
key: "kimi-code-plugin",
|
|
73761
|
+
label: "Kimi Code plugin",
|
|
73762
|
+
group: "ai"
|
|
73763
|
+
},
|
|
73764
|
+
{
|
|
73765
|
+
key: "roo",
|
|
73766
|
+
label: "Roo Code ⚠️",
|
|
73767
|
+
group: "ai"
|
|
73768
|
+
},
|
|
73769
|
+
{
|
|
73770
|
+
key: "zoocode",
|
|
73771
|
+
label: "Zoo Code",
|
|
73772
|
+
group: "ai"
|
|
73773
|
+
},
|
|
73774
|
+
{
|
|
73775
|
+
key: "rovodev",
|
|
73776
|
+
label: "Rovodev (Atlassian) ⚠️",
|
|
73777
|
+
group: "ai"
|
|
73778
|
+
},
|
|
73779
|
+
{
|
|
73780
|
+
key: "takt",
|
|
73781
|
+
label: "Takt",
|
|
73782
|
+
group: "ai"
|
|
73783
|
+
},
|
|
73784
|
+
{
|
|
73785
|
+
key: "trae",
|
|
73786
|
+
label: "Trae",
|
|
73787
|
+
group: "ai"
|
|
73788
|
+
},
|
|
73789
|
+
{
|
|
73790
|
+
key: "tabnine",
|
|
73791
|
+
label: "Tabnine CLI ⚠️",
|
|
73792
|
+
group: "ai"
|
|
73793
|
+
},
|
|
73794
|
+
{
|
|
73795
|
+
key: "continue",
|
|
73796
|
+
label: "Continue ⚠️",
|
|
73797
|
+
group: "ai"
|
|
73798
|
+
},
|
|
73799
|
+
{
|
|
73800
|
+
key: "cortexcode",
|
|
73801
|
+
label: "Snowflake Cortex Code",
|
|
73802
|
+
group: "ai"
|
|
73803
|
+
},
|
|
73804
|
+
{
|
|
73805
|
+
key: "vibe",
|
|
73806
|
+
label: "Vibe Code",
|
|
73807
|
+
group: "ai"
|
|
73808
|
+
},
|
|
73809
|
+
{
|
|
73810
|
+
key: "vibe-plugin",
|
|
73811
|
+
label: "Vibe Code plugin",
|
|
73812
|
+
group: "ai"
|
|
73813
|
+
},
|
|
73814
|
+
{
|
|
73815
|
+
key: "qoder",
|
|
73816
|
+
label: "Qoder",
|
|
73817
|
+
group: "ai"
|
|
73818
|
+
},
|
|
73819
|
+
{
|
|
73820
|
+
key: "qwencode",
|
|
73821
|
+
label: "Qwen Code",
|
|
73822
|
+
group: "ai"
|
|
73823
|
+
},
|
|
73824
|
+
{
|
|
73825
|
+
key: "musecode",
|
|
73826
|
+
label: "Meta Muse Code",
|
|
73827
|
+
group: "ai"
|
|
73828
|
+
},
|
|
73829
|
+
{
|
|
73830
|
+
key: "reasonix",
|
|
73831
|
+
label: "Reasonix",
|
|
73832
|
+
group: "ai"
|
|
73833
|
+
},
|
|
73834
|
+
{
|
|
73835
|
+
key: "codebuff",
|
|
73836
|
+
label: "Codebuff (Freebuff)",
|
|
73837
|
+
group: "ai"
|
|
73838
|
+
},
|
|
73839
|
+
{
|
|
73840
|
+
key: "kiro",
|
|
73841
|
+
label: "Kiro ⚠️",
|
|
73842
|
+
group: "ai"
|
|
73843
|
+
},
|
|
73844
|
+
{
|
|
73845
|
+
key: "kiro-cli",
|
|
73846
|
+
label: "Kiro CLI",
|
|
73847
|
+
group: "ai"
|
|
73848
|
+
},
|
|
73849
|
+
{
|
|
73850
|
+
key: "kiro-ide",
|
|
73851
|
+
label: "Kiro IDE",
|
|
73852
|
+
group: "ai"
|
|
73853
|
+
},
|
|
73854
|
+
{
|
|
73855
|
+
key: "lettacode",
|
|
73856
|
+
label: "Letta Code",
|
|
73857
|
+
group: "ai"
|
|
73858
|
+
},
|
|
73859
|
+
{
|
|
73860
|
+
key: "antigravity-ide",
|
|
73861
|
+
label: "Google Antigravity IDE",
|
|
73862
|
+
group: "ai"
|
|
73863
|
+
},
|
|
73864
|
+
{
|
|
73865
|
+
key: "antigravity-cli",
|
|
73866
|
+
label: "Google Antigravity CLI",
|
|
73867
|
+
group: "ai"
|
|
73868
|
+
},
|
|
73869
|
+
{
|
|
73870
|
+
key: "antigravity-plugin",
|
|
73871
|
+
label: "Google Antigravity plugin",
|
|
73872
|
+
group: "ai"
|
|
73873
|
+
},
|
|
73874
|
+
{
|
|
73875
|
+
key: "aiassistant",
|
|
73876
|
+
label: "JetBrains AI Assistant",
|
|
73877
|
+
group: "ai"
|
|
73878
|
+
},
|
|
73879
|
+
{
|
|
73880
|
+
key: "junie",
|
|
73881
|
+
label: "JetBrains Junie",
|
|
73882
|
+
group: "ai"
|
|
73883
|
+
},
|
|
73884
|
+
{
|
|
73885
|
+
key: "augmentcode",
|
|
73886
|
+
label: "AugmentCode",
|
|
73887
|
+
group: "ai"
|
|
73888
|
+
},
|
|
73889
|
+
{
|
|
73890
|
+
key: "augmentcode-plugin",
|
|
73891
|
+
label: "AugmentCode plugin",
|
|
73892
|
+
group: "ai"
|
|
73893
|
+
},
|
|
73894
|
+
{
|
|
73895
|
+
key: "devin",
|
|
73896
|
+
label: "Devin Desktop",
|
|
73897
|
+
group: "ai"
|
|
73898
|
+
},
|
|
73899
|
+
{
|
|
73900
|
+
key: "devin-plugin",
|
|
73901
|
+
label: "Devin plugin",
|
|
73902
|
+
group: "ai"
|
|
73903
|
+
},
|
|
73904
|
+
{
|
|
73905
|
+
key: "warp",
|
|
73906
|
+
label: "Warp",
|
|
73907
|
+
group: "ai"
|
|
73908
|
+
},
|
|
73909
|
+
{
|
|
73910
|
+
key: "warpcli",
|
|
73911
|
+
label: "Warp Agent CLI",
|
|
73912
|
+
group: "ai"
|
|
73913
|
+
},
|
|
73914
|
+
{
|
|
73915
|
+
key: "replit",
|
|
73916
|
+
label: "Replit",
|
|
73917
|
+
group: "ai"
|
|
73918
|
+
},
|
|
73919
|
+
{
|
|
73920
|
+
key: "pi",
|
|
73921
|
+
label: "Pi Coding Agent",
|
|
73922
|
+
group: "ai"
|
|
73923
|
+
},
|
|
73924
|
+
{
|
|
73925
|
+
key: "omp",
|
|
73926
|
+
label: "oh-my-pi",
|
|
73927
|
+
group: "ai"
|
|
73928
|
+
},
|
|
73929
|
+
{
|
|
73930
|
+
key: "zed",
|
|
73931
|
+
label: "Zed",
|
|
73932
|
+
group: "ai"
|
|
73933
|
+
},
|
|
73934
|
+
{
|
|
73935
|
+
key: "zcode",
|
|
73936
|
+
label: "ZCode (Z.ai)",
|
|
73937
|
+
group: "ai"
|
|
73938
|
+
},
|
|
73939
|
+
{
|
|
73940
|
+
key: "zcode-plugin",
|
|
73941
|
+
label: "ZCode plugin",
|
|
73942
|
+
group: "ai"
|
|
73943
|
+
},
|
|
73944
|
+
{
|
|
73945
|
+
key: "pool",
|
|
73946
|
+
label: "Pool (Poolside)",
|
|
73947
|
+
group: "ai"
|
|
73948
|
+
},
|
|
73949
|
+
{
|
|
73950
|
+
key: "dsh",
|
|
73951
|
+
label: "DeepSeek Harness",
|
|
73952
|
+
group: "ai"
|
|
73953
|
+
}
|
|
73954
|
+
];
|
|
73955
|
+
//#endregion
|
|
72507
73956
|
//#region src/features/skills/simulated-skill.ts
|
|
72508
73957
|
const SimulatedSkillFrontmatterSchema = z.looseObject({
|
|
72509
73958
|
name: z.string(),
|
|
@@ -77911,6 +79360,24 @@ var KimiCodeSkill = class extends ToolSkill {
|
|
|
77911
79360
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
77912
79361
|
return targets.includes("*") || targets.includes("kimi-code");
|
|
77913
79362
|
}
|
|
79363
|
+
/**
|
|
79364
|
+
* Commands are emitted into the Kimi-specific skills tree as
|
|
79365
|
+
* `<slug>/SKILL.md` (see `KimiCodeCommand`), so a directory there matching a
|
|
79366
|
+
* current rulesync command slug (of a command that targets `kimi-code`) is
|
|
79367
|
+
* owned by the commands feature: it must not be imported as a skill nor
|
|
79368
|
+
* deleted as an orphan skill. A command aimed only at other tools never
|
|
79369
|
+
* reaches this tree, so it leaves a same-named skill alone. The import-only
|
|
79370
|
+
* `.agents/skills/` root never receives Kimi Code commands, so a same-named
|
|
79371
|
+
* skill in it stays a skill.
|
|
79372
|
+
*/
|
|
79373
|
+
static async isDirOwned({ relativeDirPath, dirName, inputRoots, global }) {
|
|
79374
|
+
if (relativeDirPath !== this.getSettablePaths({ global }).relativeDirPath) return true;
|
|
79375
|
+
return !await rulesyncCommandSlugExists({
|
|
79376
|
+
inputRoots,
|
|
79377
|
+
dirName,
|
|
79378
|
+
toolTarget: "kimi-code"
|
|
79379
|
+
});
|
|
79380
|
+
}
|
|
77914
79381
|
static async fromDir(params) {
|
|
77915
79382
|
const loaded = await this.loadSkillDirContent({
|
|
77916
79383
|
...params,
|
|
@@ -77984,6 +79451,14 @@ var KimiCodePluginSkill = class extends KimiCodeSkill {
|
|
|
77984
79451
|
return { relativeDirPath: KIMI_CODE_PLUGIN_SKILLS_DIR };
|
|
77985
79452
|
}
|
|
77986
79453
|
/**
|
|
79454
|
+
* Unlike `.kimi-code/skills/`, the plugin `skills/` tree is not shared with
|
|
79455
|
+
* the commands feature (`kimi-code-plugin` writes commands to `commands/`),
|
|
79456
|
+
* so every directory in it is a skill.
|
|
79457
|
+
*/
|
|
79458
|
+
static async isDirOwned() {
|
|
79459
|
+
return true;
|
|
79460
|
+
}
|
|
79461
|
+
/**
|
|
77987
79462
|
* The bundle is read from `--output-root <plugin>`, but the imported rulesync
|
|
77988
79463
|
* file belongs to the project's `.rulesync/`, not to the plugin root.
|
|
77989
79464
|
*/
|
|
@@ -78335,7 +79810,8 @@ const OpenCodeSkillFrontmatterSchema = z.looseObject({
|
|
|
78335
79810
|
license: z.optional(z.unknown()),
|
|
78336
79811
|
compatibility: z.optional(z.unknown()),
|
|
78337
79812
|
metadata: z.optional(z.unknown()),
|
|
78338
|
-
"allowed-tools": z.optional(z.array(z.string()))
|
|
79813
|
+
"allowed-tools": z.optional(z.array(z.string())),
|
|
79814
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
78339
79815
|
});
|
|
78340
79816
|
var OpenCodeSkill = class extends ToolSkill {
|
|
78341
79817
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -78452,7 +79928,8 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78452
79928
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
78453
79929
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
78454
79930
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
78455
|
-
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
79931
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
|
|
79932
|
+
...frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
78456
79933
|
};
|
|
78457
79934
|
const { toolTarget } = this.constructor.layout;
|
|
78458
79935
|
const rulesyncFrontmatter = {
|
|
@@ -78487,13 +79964,18 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78487
79964
|
rootFrontmatter: rulesyncFrontmatter,
|
|
78488
79965
|
section: opencodeSection
|
|
78489
79966
|
});
|
|
79967
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
79968
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
79969
|
+
section: opencodeSection
|
|
79970
|
+
});
|
|
78490
79971
|
const opencodeFrontmatter = {
|
|
78491
79972
|
name: rulesyncFrontmatter.name,
|
|
78492
79973
|
description: rulesyncFrontmatter.description,
|
|
78493
79974
|
...license !== void 0 && { license },
|
|
78494
79975
|
...compatibility !== void 0 && { compatibility },
|
|
78495
79976
|
...metadata !== void 0 && { metadata },
|
|
78496
|
-
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] }
|
|
79977
|
+
...opencodeSection?.["allowed-tools"] !== void 0 && { "allowed-tools": opencodeSection["allowed-tools"] },
|
|
79978
|
+
...disableModelInvocation !== void 0 && { "disable-model-invocation": disableModelInvocation }
|
|
78497
79979
|
};
|
|
78498
79980
|
const settablePaths = this.getSettablePaths({ global });
|
|
78499
79981
|
return new this({
|
|
@@ -78516,11 +79998,13 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78516
79998
|
...params,
|
|
78517
79999
|
getSettablePaths: (options) => this.getSettablePaths(options)
|
|
78518
80000
|
});
|
|
78519
|
-
const
|
|
78520
|
-
|
|
78521
|
-
|
|
78522
|
-
|
|
78523
|
-
}
|
|
80001
|
+
const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
|
|
80002
|
+
const frontmatter = normalizeDisableModelInvocation({
|
|
80003
|
+
frontmatter: loaded.frontmatter,
|
|
80004
|
+
skillFilePath: join(skillDirPath, SKILL_FILE_NAME)
|
|
80005
|
+
});
|
|
80006
|
+
const result = OpenCodeSkillFrontmatterSchema.safeParse(frontmatter);
|
|
80007
|
+
if (!result.success) throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
|
|
78524
80008
|
return new this({
|
|
78525
80009
|
outputRoot: loaded.outputRoot,
|
|
78526
80010
|
relativeDirPath: loaded.relativeDirPath,
|
|
@@ -78548,6 +80032,52 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
78548
80032
|
});
|
|
78549
80033
|
}
|
|
78550
80034
|
};
|
|
80035
|
+
const DISABLE_MODEL_INVOCATION_KEY = "disable-model-invocation";
|
|
80036
|
+
/**
|
|
80037
|
+
* Reads a `disable-model-invocation` value the way OpenCode V2 does
|
|
80038
|
+
* (`booleanValue` in `packages/core/src/config/plugin/skill-file.ts`): a
|
|
80039
|
+
* boolean, the numbers `1` / `0`, or a `true`/`yes`/`on`/`1` /
|
|
80040
|
+
* `false`/`no`/`off`/`0` string compared after trimming and case-folding.
|
|
80041
|
+
* Anything else is `undefined`, which OpenCode ignores.
|
|
80042
|
+
*/
|
|
80043
|
+
function parseOpencodeBoolean(value) {
|
|
80044
|
+
if (typeof value === "boolean") return value;
|
|
80045
|
+
if (typeof value === "number") return value === 1 ? true : value === 0 ? false : void 0;
|
|
80046
|
+
if (typeof value !== "string") return;
|
|
80047
|
+
const normalized = value.trim().toLowerCase();
|
|
80048
|
+
if ([
|
|
80049
|
+
"true",
|
|
80050
|
+
"yes",
|
|
80051
|
+
"on",
|
|
80052
|
+
"1"
|
|
80053
|
+
].includes(normalized)) return true;
|
|
80054
|
+
if ([
|
|
80055
|
+
"false",
|
|
80056
|
+
"no",
|
|
80057
|
+
"off",
|
|
80058
|
+
"0"
|
|
80059
|
+
].includes(normalized)) return false;
|
|
80060
|
+
}
|
|
80061
|
+
/**
|
|
80062
|
+
* Coerces an imported `disable-model-invocation` onto the boolean the schema
|
|
80063
|
+
* types. A spelling OpenCode cannot read is dropped with a warning instead of
|
|
80064
|
+
* failing the import, since OpenCode itself ignores it.
|
|
80065
|
+
*/
|
|
80066
|
+
function normalizeDisableModelInvocation({ frontmatter, skillFilePath }) {
|
|
80067
|
+
if (!Object.hasOwn(frontmatter, DISABLE_MODEL_INVOCATION_KEY)) return frontmatter;
|
|
80068
|
+
const raw = frontmatter[DISABLE_MODEL_INVOCATION_KEY];
|
|
80069
|
+
const parsed = parseOpencodeBoolean(raw);
|
|
80070
|
+
if (parsed === raw) return frontmatter;
|
|
80071
|
+
const { [DISABLE_MODEL_INVOCATION_KEY]: _dropped, ...rest } = frontmatter;
|
|
80072
|
+
if (parsed === void 0) {
|
|
80073
|
+
warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ignoring unreadable "${DISABLE_MODEL_INVOCATION_KEY}" value ${stripControlCharacters(JSON.stringify(raw) ?? String(raw))}; OpenCode ignores it too.`);
|
|
80074
|
+
return rest;
|
|
80075
|
+
}
|
|
80076
|
+
return {
|
|
80077
|
+
...rest,
|
|
80078
|
+
[DISABLE_MODEL_INVOCATION_KEY]: parsed
|
|
80079
|
+
};
|
|
80080
|
+
}
|
|
78551
80081
|
//#endregion
|
|
78552
80082
|
//#region src/features/skills/mimocode-skill.ts
|
|
78553
80083
|
/**
|
|
@@ -84849,7 +86379,7 @@ var DeepagentsSubagent = class DeepagentsSubagent extends ToolSubagent {
|
|
|
84849
86379
|
//#endregion
|
|
84850
86380
|
//#region src/features/subagents/devin-subagent.ts
|
|
84851
86381
|
const DevinSubagentFrontmatterSchema = z.looseObject({
|
|
84852
|
-
name: z.string(),
|
|
86382
|
+
name: z.optional(z.string()),
|
|
84853
86383
|
description: z.optional(z.string()),
|
|
84854
86384
|
model: z.optional(z.string()),
|
|
84855
86385
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
@@ -84902,7 +86432,7 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84902
86432
|
outputRoot: ".",
|
|
84903
86433
|
frontmatter: {
|
|
84904
86434
|
targets: ["*"],
|
|
84905
|
-
name,
|
|
86435
|
+
name: name ?? this.getSubagentName(),
|
|
84906
86436
|
description,
|
|
84907
86437
|
...Object.keys(devinSection).length > 0 && { devin: devinSection }
|
|
84908
86438
|
},
|
|
@@ -84976,7 +86506,8 @@ var DevinSubagent = class extends ToolSubagent {
|
|
|
84976
86506
|
const paths = this.getSettablePaths({ global });
|
|
84977
86507
|
const filePath = join(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
84978
86508
|
const fileContent = await readFileContent(filePath);
|
|
84979
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
86509
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
86510
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
84980
86511
|
const result = DevinSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
84981
86512
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
84982
86513
|
return new this({
|
|
@@ -98624,6 +100155,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98624
100155
|
warnForOutputStyleRuleOptions({ rules, factory }) {
|
|
98625
100156
|
const toolTarget = this.toolTarget;
|
|
98626
100157
|
if (toolTarget !== "factorydroid" && toolTarget !== "qoder") return;
|
|
100158
|
+
const toolLabel = TOOL_DISPLAY.find((entry) => entry.key === toolTarget)?.label ?? toolTarget;
|
|
98627
100159
|
for (const rule of rules) {
|
|
98628
100160
|
const frontmatter = rule.getFrontmatter();
|
|
98629
100161
|
const section = frontmatter[toolTarget];
|
|
@@ -98631,7 +100163,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
98631
100163
|
const { channel, name } = section;
|
|
98632
100164
|
const fileName = stripControlCharacters(rule.getRelativeFilePath());
|
|
98633
100165
|
const targets = frontmatter.targets ?? ["*"];
|
|
98634
|
-
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 ${
|
|
100166
|
+
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}.`);
|
|
98635
100167
|
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.`);
|
|
98636
100168
|
}
|
|
98637
100169
|
}
|
|
@@ -100051,13 +101583,15 @@ const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
|
100051
101583
|
* The manifest declares only `name` and `description`, and rejects anything
|
|
100052
101584
|
* else, so the one required field is derived from the plugin directory's name.
|
|
100053
101585
|
* An existing manifest is never read or rewritten: it may carry a
|
|
100054
|
-
* hand-authored `name` or `description` that rulesync has no source for.
|
|
101586
|
+
* hand-authored `name` or `description` that rulesync has no source for. A
|
|
101587
|
+
* manifest kept in one of the manifest folders counts as present too, so no
|
|
101588
|
+
* root stub is written next to it.
|
|
100055
101589
|
*/
|
|
100056
101590
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
100057
101591
|
const paths = [];
|
|
100058
101592
|
for (const outputRoot of outputRoots) {
|
|
100059
101593
|
const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
100060
|
-
if (await
|
|
101594
|
+
if (await hasAntigravityPluginManifest(outputRoot)) continue;
|
|
100061
101595
|
if (await refusesWriteOutsideRoot({
|
|
100062
101596
|
logger,
|
|
100063
101597
|
rootPath: outputRoot,
|
|
@@ -100080,6 +101614,11 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
100080
101614
|
sourceLoadFailed: false
|
|
100081
101615
|
};
|
|
100082
101616
|
}
|
|
101617
|
+
async function hasAntigravityPluginManifest(outputRoot) {
|
|
101618
|
+
const candidates = [join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME), ...ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS.map((folder) => join(outputRoot, folder, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME))];
|
|
101619
|
+
for (const candidate of candidates) if (await fileExists(candidate)) return true;
|
|
101620
|
+
return false;
|
|
101621
|
+
}
|
|
100083
101622
|
//#endregion
|
|
100084
101623
|
//#region src/features/shared/hermes-project-plugin-activation.ts
|
|
100085
101624
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|
|
@@ -102377,6 +103916,6 @@ async function importChecksCore(params) {
|
|
|
102377
103916
|
return writtenCount;
|
|
102378
103917
|
}
|
|
102379
103918
|
//#endregion
|
|
102380
|
-
export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A,
|
|
103919
|
+
export { RulesyncHooks as $, removeDirectoryStrict as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, parseCommaSeparatedList as Bn, getFileSizeAndExecutableBit as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_IGNORE_RELATIVE_FILE_PATH as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, truncateText as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, ALL_FEATURES_WITH_WILDCARD as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as In, directoryExists as It, RulesyncRule as J, quoteForLog as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, hasDeceptiveHiddenCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_PERMISSIONS_SCHEMA_URL as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_RELATIVE_DIR_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_MCP_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_RULES_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectory as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, RULESYNC_USER_CONFIG_DIR_NAME as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_CONFIG_SCHEMA_URL as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, DEPRECATED_FEATURE_REPLACEMENTS as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, ALL_FEATURES as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, formatError as Wn, isSymlink as Wt, RulesyncPermissions as X, stripControlCharactersKeepingLineFeeds as Xn, readFileContent as Xt, RulesyncRuleFrontmatterSchema as Y, stripControlCharacters as Yn, posixRelativePathEscapesRoot as Yt, RulesyncMcp as Z, stripHiddenCharacters as Zn, readFileContentOrNull as Zt, LETTACODE_DIR as _, MAX_FILE_SIZE as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, runWithDirectoryRollback as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, writeFileBuffer as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ALL_TOOL_TARGETS as dn, ConfigResolver as dt, removeFile as en, getRulesyncSourceCandidates as et, displayWidthOf as f, ALL_TOOL_TARGETS_WITH_WILDCARD as fn, mergeInputRootConfigs as ft, HooksProcessor as g, FEATURE_SOURCE_TREE_ENTRIES as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, CURATED_RULES_FEATURE_SUBDIR as hn, ConfigFileSchema as ht, inspectInputRoots as i, resolvedRelativePath as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_MCP_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, writeFileContent as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, ToolTargetSchema as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, removeTempDirectory as nn, parseJsonc as nt, convertFromTool as o, splitPathSegments as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, PACKAGING_TOOL_TARGETS as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, hasEnclosingMarkOutsideKeycap as qn, listSubdirectoryNames as qt, generate as r, resolvePath as rn, RulesyncCommand as rt, isPackagingToolTarget as s, toPosixPath as sn, stringifyFrontmatter as st, importFromTool as t, removeFileStrict as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, mapWithConcurrency as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CONFIG_RELATIVE_FILE_PATH as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_CHECKS_RELATIVE_DIR_PATH as yn, findControlCharacter as yt, QWENCODE_DIR as z, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as zn, findFilesByGlobs as zt };
|
|
102381
103920
|
|
|
102382
|
-
//# sourceMappingURL=import-
|
|
103921
|
+
//# sourceMappingURL=import-Tya9qZRK.js.map
|