rulesync 35.0.0 → 36.0.1
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 +2 -2
- package/dist/cli/index.cjs +447 -15
- package/dist/cli/index.js +447 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D11JFP84.cjs → import-DVcemXlR.cjs} +1167 -213
- package/dist/{import-BbF_R5Qx.js → import-Tya9qZRK.js} +1146 -198
- package/dist/{import-BbF_R5Qx.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
|
@@ -677,6 +677,7 @@ const commandsProcessorToolTargetTuple = [
|
|
|
677
677
|
"hermesagent",
|
|
678
678
|
"junie",
|
|
679
679
|
"kilo",
|
|
680
|
+
"kimi-code",
|
|
680
681
|
"kimi-code-plugin",
|
|
681
682
|
"kiro",
|
|
682
683
|
"kiro-cli",
|
|
@@ -8151,7 +8152,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8151
8152
|
* autonomy/sandbox controls with no canonical permission category — under
|
|
8152
8153
|
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
8153
8154
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8154
|
-
* `todoWrite`, `
|
|
8155
|
+
* `todoWrite`, `codeModeOnly`, `freeform`, `workflowsEnabled`, `workflowNameOnly`,
|
|
8156
|
+
* `executionSandbox`) and
|
|
8155
8157
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8156
8158
|
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8157
8159
|
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
@@ -8170,7 +8172,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8170
8172
|
* changes what the file said is reported in either scope, naming what that key
|
|
8171
8173
|
* decides there — the autonomy and containment
|
|
8172
8174
|
* controls (`approvalMode`, `autoAccept`, `sandbox`, `sandboxImage`), the
|
|
8173
|
-
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8175
|
+
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`,
|
|
8176
|
+
* `codeModeOnly`, `freeform`, and `eager`, which
|
|
8174
8177
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8175
8178
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8176
8179
|
* classifier config, the skill kill-switches, and, because these groups are
|
|
@@ -10863,13 +10866,15 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
10863
10866
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
10864
10867
|
license: z.optional(z.unknown()),
|
|
10865
10868
|
compatibility: z.optional(z.unknown()),
|
|
10866
|
-
metadata: z.optional(z.unknown())
|
|
10869
|
+
metadata: z.optional(z.unknown()),
|
|
10870
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
10867
10871
|
})),
|
|
10868
10872
|
mimocode: z.optional(z.looseObject({
|
|
10869
10873
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
10870
10874
|
license: z.optional(z.unknown()),
|
|
10871
10875
|
compatibility: z.optional(z.unknown()),
|
|
10872
|
-
metadata: z.optional(z.unknown())
|
|
10876
|
+
metadata: z.optional(z.unknown()),
|
|
10877
|
+
"disable-model-invocation": z.optional(z.boolean())
|
|
10873
10878
|
})),
|
|
10874
10879
|
kilo: z.optional(z.looseObject({
|
|
10875
10880
|
"allowed-tools": z.optional(z.array(z.string())),
|
|
@@ -11322,7 +11327,7 @@ async function getLocalSkillDirNames(sourceTree) {
|
|
|
11322
11327
|
* The rulesync skill frontmatter exposes a root-level `disable-model-invocation`
|
|
11323
11328
|
* default that applies to every tool supporting the flag (claudecode, codebuddy, copilot,
|
|
11324
11329
|
* copilotcli, crush, cursor, zed, pi, omp, qwencode, vibe, grokcli, factorydroid, dsh, reasonix,
|
|
11325
|
-
* commandcode, lettacode). Each tool's own section may override that
|
|
11330
|
+
* commandcode, lettacode, opencode, mimocode). Each tool's own section may override that
|
|
11326
11331
|
* default with a per-target value. A defined section value (including `false`)
|
|
11327
11332
|
* always wins over the root default.
|
|
11328
11333
|
*
|
|
@@ -13521,8 +13526,8 @@ function diffSharedConfigOwnedKeys({ fileKey, existingContent, newContent }) {
|
|
|
13521
13526
|
return operations;
|
|
13522
13527
|
}
|
|
13523
13528
|
const READ_TOOL_NAME = "Read";
|
|
13524
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${
|
|
13525
|
-
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})`;
|
|
13526
13531
|
const parsePermissionsBlock = (settings) => {
|
|
13527
13532
|
const permissions = settings.permissions ?? {};
|
|
13528
13533
|
return {
|
|
@@ -13546,9 +13551,9 @@ const withPermissions = (settings, next) => {
|
|
|
13546
13551
|
};
|
|
13547
13552
|
};
|
|
13548
13553
|
const applyIgnoreReadDenies = (params) => {
|
|
13549
|
-
const { settings, readDenies } = params;
|
|
13554
|
+
const { settings, readDenies, readToolName = READ_TOOL_NAME } = params;
|
|
13550
13555
|
const current = parsePermissionsBlock(settings);
|
|
13551
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
13556
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry, readToolName) || readDenies.includes(entry));
|
|
13552
13557
|
return withPermissions(settings, {
|
|
13553
13558
|
allow: current.allow,
|
|
13554
13559
|
ask: current.ask,
|
|
@@ -17875,6 +17880,7 @@ const ANTIGRAVITY_PLUGIN_AGENTS_DIR = "agents";
|
|
|
17875
17880
|
const ANTIGRAVITY_PLUGIN_MCP_FILE_NAME = "mcp_config.json";
|
|
17876
17881
|
const ANTIGRAVITY_PLUGIN_HOOKS_FILE_NAME = "hooks.json";
|
|
17877
17882
|
const ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME = "plugin.json";
|
|
17883
|
+
const ANTIGRAVITY_PLUGIN_MANIFEST_FOLDERS = [".antigravity-plugin", ".gemini-plugin"];
|
|
17878
17884
|
const AUGMENTCODE_PLUGIN_RULES_DIR = "rules";
|
|
17879
17885
|
const AUGMENTCODE_PLUGIN_COMMANDS_DIR = "commands";
|
|
17880
17886
|
const AUGMENTCODE_PLUGIN_AGENTS_DIR = "agents";
|
|
@@ -19521,9 +19527,36 @@ function commandSlug(relativeFilePath) {
|
|
|
19521
19527
|
* skill. Once the command is removed from every source tree's `commands/`
|
|
19522
19528
|
* directory, the directory stops matching and the skills feature cleans
|
|
19523
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.
|
|
19524
19535
|
*/
|
|
19525
|
-
async function rulesyncCommandSlugExists({ inputRoots, dirName }) {
|
|
19526
|
-
|
|
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;
|
|
19527
19560
|
}
|
|
19528
19561
|
//#endregion
|
|
19529
19562
|
//#region src/features/commands/devin-command.ts
|
|
@@ -19541,7 +19574,7 @@ const DEVIN_COMMAND_SECTION_KEYS = [
|
|
|
19541
19574
|
"permissions",
|
|
19542
19575
|
"triggers"
|
|
19543
19576
|
];
|
|
19544
|
-
function commandSkillContent$
|
|
19577
|
+
function commandSkillContent$2(rulesyncCommand) {
|
|
19545
19578
|
const slug = commandSlug(rulesyncCommand.getRelativeFilePath());
|
|
19546
19579
|
const frontmatter = rulesyncCommand.getFrontmatter();
|
|
19547
19580
|
const description = frontmatter.description ?? `${slug} command`;
|
|
@@ -19615,7 +19648,7 @@ var DevinCommand = class DevinCommand extends ToolCommand {
|
|
|
19615
19648
|
relativeDirPath: paths.relativeDirPath,
|
|
19616
19649
|
relativeFilePath: SKILL_FILE_NAME,
|
|
19617
19650
|
slug: commandSlug(rulesyncCommand.getRelativeFilePath()),
|
|
19618
|
-
fileContent: commandSkillContent$
|
|
19651
|
+
fileContent: commandSkillContent$2(rulesyncCommand)
|
|
19619
19652
|
});
|
|
19620
19653
|
}
|
|
19621
19654
|
getFileContent() {
|
|
@@ -21435,6 +21468,180 @@ var KiloCommand = class KiloCommand extends ToolCommand {
|
|
|
21435
21468
|
}
|
|
21436
21469
|
};
|
|
21437
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
|
|
21438
21645
|
//#region src/features/commands/kimi-code-plugin-command.ts
|
|
21439
21646
|
/**
|
|
21440
21647
|
* Kimi Code reads only `name` and `description` from a plugin command. The
|
|
@@ -21589,6 +21796,14 @@ const KIRO_IDE_HOOKS_FILE_NAME = "rulesync.json";
|
|
|
21589
21796
|
const KIRO_MCP_FILE_NAME = "mcp.json";
|
|
21590
21797
|
const KIRO_IGNORE_FILE_NAME = ".kiroignore";
|
|
21591
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";
|
|
21592
21807
|
//#endregion
|
|
21593
21808
|
//#region src/features/commands/kiro-command.ts
|
|
21594
21809
|
var KiroCommand = class KiroCommand extends ToolCommand {
|
|
@@ -23631,6 +23846,11 @@ const TraeCommandFrontmatterSchema = z.looseObject({
|
|
|
23631
23846
|
name: z.optional(z.string())
|
|
23632
23847
|
});
|
|
23633
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
|
+
/**
|
|
23634
23854
|
* Custom command for Trae.
|
|
23635
23855
|
*
|
|
23636
23856
|
* Trae reads Markdown command files under `.trae/commands/` (project, up to
|
|
@@ -23690,10 +23910,11 @@ var TraeCommand = class TraeCommand extends ToolCommand {
|
|
|
23690
23910
|
}
|
|
23691
23911
|
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
23692
23912
|
const rulesyncFrontmatter = rulesyncCommand.getFrontmatter();
|
|
23913
|
+
const { name: _name, ...traeFields } = rulesyncFrontmatter.trae ?? {};
|
|
23693
23914
|
const traeFrontmatter = {
|
|
23694
|
-
name:
|
|
23915
|
+
name: this.deriveName(rulesyncCommand),
|
|
23695
23916
|
description: rulesyncFrontmatter.description,
|
|
23696
|
-
...
|
|
23917
|
+
...traeFields
|
|
23697
23918
|
};
|
|
23698
23919
|
return new TraeCommand({
|
|
23699
23920
|
outputRoot,
|
|
@@ -23719,6 +23940,36 @@ var TraeCommand = class TraeCommand extends ToolCommand {
|
|
|
23719
23940
|
error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${join(this.relativeDirPath, this.relativeFilePath)}: ${formatError(result.error)}`)
|
|
23720
23941
|
};
|
|
23721
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
|
+
}
|
|
23722
23973
|
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
23723
23974
|
return this.isTargetedByRulesyncCommandDefault({
|
|
23724
23975
|
rulesyncCommand,
|
|
@@ -24302,6 +24553,17 @@ const toolCommandFactories = /* @__PURE__ */ new Map([
|
|
|
24302
24553
|
supportsSubdirectory: false
|
|
24303
24554
|
}
|
|
24304
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
|
+
}],
|
|
24305
24567
|
["kimi-code-plugin", {
|
|
24306
24568
|
class: KimiCodePluginCommand,
|
|
24307
24569
|
meta: {
|
|
@@ -24555,7 +24817,8 @@ var CommandsProcessor = class extends FeatureProcessor {
|
|
|
24555
24817
|
await factory.class.validateRulesyncCommands?.({
|
|
24556
24818
|
inputRoots: this.inputRoots,
|
|
24557
24819
|
rulesyncCommands,
|
|
24558
|
-
logger: this.logger
|
|
24820
|
+
logger: this.logger,
|
|
24821
|
+
global: this.global
|
|
24559
24822
|
});
|
|
24560
24823
|
const flattenedPathOrigins = /* @__PURE__ */ new Map();
|
|
24561
24824
|
const toolCommands = [];
|
|
@@ -26235,8 +26498,8 @@ function isSupportedHookType({ type, converterConfig }) {
|
|
|
26235
26498
|
* side either way, so the value is dropped rather than carried into a config
|
|
26236
26499
|
* the tool would ignore or refuse.
|
|
26237
26500
|
*/
|
|
26238
|
-
function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }) {
|
|
26239
|
-
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;
|
|
26240
26503
|
const field = direction === "generated" ? canonical : tool;
|
|
26241
26504
|
if (!isFieldApplicable({
|
|
26242
26505
|
commandOnly,
|
|
@@ -26245,10 +26508,23 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, direction, warn }
|
|
|
26245
26508
|
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not ${direction}.`);
|
|
26246
26509
|
return false;
|
|
26247
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
|
+
}
|
|
26248
26515
|
if (events !== void 0 && !events.has(eventName)) {
|
|
26249
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}.`);
|
|
26250
26517
|
return false;
|
|
26251
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
|
+
}
|
|
26252
26528
|
return true;
|
|
26253
26529
|
}
|
|
26254
26530
|
/**
|
|
@@ -26265,6 +26541,7 @@ function emitKeywordFields({ def, hookType, eventName, fields, warn }) {
|
|
|
26265
26541
|
spec,
|
|
26266
26542
|
hookType,
|
|
26267
26543
|
eventName,
|
|
26544
|
+
flags: def,
|
|
26268
26545
|
direction: "generated",
|
|
26269
26546
|
warn
|
|
26270
26547
|
})) emitted[spec.tool] = spec.trueValue;
|
|
@@ -26286,6 +26563,7 @@ function importKeywordFields({ h, hookType, eventName, fields, warn }) {
|
|
|
26286
26563
|
spec,
|
|
26287
26564
|
hookType,
|
|
26288
26565
|
eventName,
|
|
26566
|
+
flags: h,
|
|
26289
26567
|
direction: "imported",
|
|
26290
26568
|
warn
|
|
26291
26569
|
})) continue;
|
|
@@ -28178,6 +28456,20 @@ const CLAUDE_SPEC = {
|
|
|
28178
28456
|
tool: "continueOnBlock"
|
|
28179
28457
|
}
|
|
28180
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
|
+
}],
|
|
28181
28473
|
arrayPassthroughFields: [{
|
|
28182
28474
|
canonical: "args",
|
|
28183
28475
|
tool: "args",
|
|
@@ -28900,18 +29192,20 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28900
29192
|
canonicalToToolEventNames: CANONICAL_TO_CODEXCLI_EVENT_NAMES,
|
|
28901
29193
|
toolToCanonicalEventNames: CODEXCLI_TO_CANONICAL_EVENT_NAMES,
|
|
28902
29194
|
projectDirVar: "",
|
|
28903
|
-
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
29195
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command", "mcp_tool"]),
|
|
28904
29196
|
passthroughFields: ["name", "description"],
|
|
28905
29197
|
stringPassthroughFields: [{
|
|
28906
29198
|
canonical: "commandWindows",
|
|
28907
|
-
tool: "commandWindows"
|
|
29199
|
+
tool: "commandWindows",
|
|
29200
|
+
commandOnly: true
|
|
28908
29201
|
}, {
|
|
28909
29202
|
canonical: "statusMessage",
|
|
28910
29203
|
tool: "statusMessage"
|
|
28911
29204
|
}],
|
|
28912
29205
|
numberPassthroughFields: [{
|
|
28913
29206
|
canonical: "additionalContextLimit",
|
|
28914
|
-
tool: "additionalContextLimit"
|
|
29207
|
+
tool: "additionalContextLimit",
|
|
29208
|
+
commandOnly: true
|
|
28915
29209
|
}],
|
|
28916
29210
|
booleanPassthroughFields: [{
|
|
28917
29211
|
canonical: "async",
|
|
@@ -28920,6 +29214,74 @@ const CODEXCLI_CONVERTER_CONFIG = {
|
|
|
28920
29214
|
}]
|
|
28921
29215
|
};
|
|
28922
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
|
+
/**
|
|
28923
29285
|
* Build the content for `.codex/config.toml`, cleaning up the deprecated `codex_hooks` key.
|
|
28924
29286
|
* Reads the existing file (if any), parses TOML, and returns the content without writing to
|
|
28925
29287
|
* disk. The caller is responsible for writing via the normal write phase.
|
|
@@ -29012,10 +29374,13 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
29012
29374
|
const config = rulesyncHooks.getJson();
|
|
29013
29375
|
const merged = mergeGeneratedHookLists({
|
|
29014
29376
|
existingContent,
|
|
29015
|
-
generatedHooks:
|
|
29016
|
-
|
|
29017
|
-
|
|
29018
|
-
|
|
29377
|
+
generatedHooks: filterCodexHandlers({
|
|
29378
|
+
hooks: canonicalToToolHooks({
|
|
29379
|
+
config,
|
|
29380
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
29381
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
29382
|
+
logger
|
|
29383
|
+
}),
|
|
29019
29384
|
logger
|
|
29020
29385
|
}),
|
|
29021
29386
|
shape: "matcher-groups",
|
|
@@ -32194,60 +32559,6 @@ var KiloHooks = class KiloHooks extends ToolHooks {
|
|
|
32194
32559
|
}
|
|
32195
32560
|
};
|
|
32196
32561
|
//#endregion
|
|
32197
|
-
//#region src/constants/kimi-code-paths.ts
|
|
32198
|
-
const KIMI_CODE_DIR = ".kimi-code";
|
|
32199
|
-
const KIMI_CODE_RULE_FILE_NAME = "AGENTS.md";
|
|
32200
|
-
const KIMI_CODE_MCP_FILE_NAME = "mcp.json";
|
|
32201
|
-
const KIMI_CODE_CONFIG_FILE_NAME = "config.toml";
|
|
32202
|
-
const KIMI_CODE_SKILLS_DIR_NAME = "skills";
|
|
32203
|
-
const KIMI_CODE_AGENTS_DIR_NAME = "agents";
|
|
32204
|
-
const KIMI_CODE_SKILLS_DIR_PATH = join(KIMI_CODE_DIR, KIMI_CODE_SKILLS_DIR_NAME);
|
|
32205
|
-
join(KIMI_CODE_DIR, KIMI_CODE_AGENTS_DIR_NAME);
|
|
32206
|
-
const KIMI_CODE_SHARED_SKILLS_DIR_PATH = join(".agents", "skills");
|
|
32207
|
-
const KIMI_CODE_SHARED_AGENTS_DIR_PATH = join(".agents", "agents");
|
|
32208
|
-
//#endregion
|
|
32209
|
-
//#region src/utils/kimi-code.ts
|
|
32210
|
-
function getKimiCodeHome() {
|
|
32211
|
-
const configuredHome = process.env.KIMI_CODE_HOME?.trim();
|
|
32212
|
-
return configuredHome ? resolve(configuredHome) : void 0;
|
|
32213
|
-
}
|
|
32214
|
-
function getKimiCodeRelativeDirPath({ global, relativeDirPath = "." }) {
|
|
32215
|
-
return global && getKimiCodeHome() ? relativeDirPath : join(KIMI_CODE_DIR, relativeDirPath);
|
|
32216
|
-
}
|
|
32217
|
-
/**
|
|
32218
|
-
* Both spellings the shared user `config.toml` can take: under `.kimi-code/`,
|
|
32219
|
-
* or at the root of `KIMI_CODE_HOME` when that override names the profile dir.
|
|
32220
|
-
* Declared unconditionally so the derived shared-file keys — and the drift
|
|
32221
|
-
* guards checked against them — do not depend on the ambient environment.
|
|
32222
|
-
*/
|
|
32223
|
-
function getKimiCodeSharedConfigWritePaths() {
|
|
32224
|
-
return [{
|
|
32225
|
-
relativeDirPath: KIMI_CODE_DIR,
|
|
32226
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32227
|
-
}, {
|
|
32228
|
-
relativeDirPath: ".",
|
|
32229
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32230
|
-
}];
|
|
32231
|
-
}
|
|
32232
|
-
/**
|
|
32233
|
-
* The `SHARED_CONFIG_OWNERSHIP` key of the `config.toml` actually being written.
|
|
32234
|
-
* Both spellings carry the same declaration, but passing the key of the file
|
|
32235
|
-
* being written keeps the write path and the drift guards on the same entry.
|
|
32236
|
-
*/
|
|
32237
|
-
function getKimiCodeConfigSharedFileKey({ global }) {
|
|
32238
|
-
return sharedConfigFileKey({
|
|
32239
|
-
relativeDirPath: getKimiCodeRelativeDirPath({ global }),
|
|
32240
|
-
relativeFilePath: KIMI_CODE_CONFIG_FILE_NAME
|
|
32241
|
-
});
|
|
32242
|
-
}
|
|
32243
|
-
function getKimiCodeRulesyncOutputRoot({ nativeOutputRoot, global }) {
|
|
32244
|
-
return getToolRulesyncOutputRoot({
|
|
32245
|
-
nativeOutputRoot,
|
|
32246
|
-
global,
|
|
32247
|
-
toolHome: getKimiCodeHome
|
|
32248
|
-
});
|
|
32249
|
-
}
|
|
32250
|
-
//#endregion
|
|
32251
32562
|
//#region src/features/hooks/kimi-code-hooks.ts
|
|
32252
32563
|
function runFromTrustedDirectory({ command, trustedDirectory }) {
|
|
32253
32564
|
if (process.platform === "win32") return `set "RULESYNC_KIMI_HOOK_CWD=1" && cd /d "${trustedDirectory.replaceAll("%", "%%").replaceAll("\"", "\"\"")}" && ${command}`;
|
|
@@ -35704,7 +36015,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
35704
36015
|
supportsImport: true
|
|
35705
36016
|
},
|
|
35706
36017
|
supportedEvents: CODEXCLI_HOOK_EVENTS,
|
|
35707
|
-
supportedHookTypes: ["command"],
|
|
36018
|
+
supportedHookTypes: ["command", "mcp_tool"],
|
|
35708
36019
|
supportsMatcher: true
|
|
35709
36020
|
}],
|
|
35710
36021
|
["commandcode", {
|
|
@@ -37966,6 +38277,23 @@ function toReasonixTable(value) {
|
|
|
37966
38277
|
if (!isPlainObject$1(value)) return {};
|
|
37967
38278
|
return { ...value };
|
|
37968
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";
|
|
37969
38297
|
//#endregion
|
|
37970
38298
|
//#region src/features/ignore/reasonix-ignore.ts
|
|
37971
38299
|
const permissionsTableOf = (document) => toReasonixTable(document.permissions);
|
|
@@ -37984,10 +38312,15 @@ const asClaudeStyleSettings = (document) => {
|
|
|
37984
38312
|
...table,
|
|
37985
38313
|
allow: toReasonixStringArray(table.allow),
|
|
37986
38314
|
ask: toReasonixStringArray(table.ask),
|
|
37987
|
-
deny: toReasonixStringArray(table.deny)
|
|
38315
|
+
deny: toReasonixStringArray(table.deny).filter((entry) => !isReadDenyEntry(entry, REASONIX_LEGACY_READ_TOOL_NAME))
|
|
37988
38316
|
}
|
|
37989
38317
|
};
|
|
37990
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
|
+
};
|
|
37991
38324
|
/**
|
|
37992
38325
|
* Drop a `[permissions]` table that ended up with nothing in it, so an empty
|
|
37993
38326
|
* `.rulesyncignore` does not add a bare table header to a file that never had
|
|
@@ -38000,13 +38333,16 @@ const withoutEmptyPermissions = (settings) => {
|
|
|
38000
38333
|
return document;
|
|
38001
38334
|
};
|
|
38002
38335
|
/**
|
|
38003
|
-
* Writes `.rulesyncignore` patterns as `
|
|
38336
|
+
* Writes `.rulesyncignore` patterns as `read_file(<pattern>)` entries in the
|
|
38004
38337
|
* `[permissions] deny` table of `reasonix.toml` (project) /
|
|
38005
38338
|
* `~/.reasonix/config.toml` (global).
|
|
38006
38339
|
*
|
|
38007
38340
|
* `deny` is the right target rather than `[sandbox] forbid_read`: deny rules
|
|
38008
38341
|
* take glob specifiers (`Edit(docs/**)`) and are "a hard block in every mode",
|
|
38009
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.
|
|
38010
38346
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
38011
38347
|
*/
|
|
38012
38348
|
var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
@@ -38033,7 +38369,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38033
38369
|
return false;
|
|
38034
38370
|
}
|
|
38035
38371
|
toRulesyncIgnore() {
|
|
38036
|
-
const rulesyncPatterns = this.patterns.
|
|
38372
|
+
const rulesyncPatterns = uniq(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
|
|
38037
38373
|
return new RulesyncIgnore({
|
|
38038
38374
|
outputRoot: this.outputRoot,
|
|
38039
38375
|
relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -38042,7 +38378,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38042
38378
|
});
|
|
38043
38379
|
}
|
|
38044
38380
|
static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false }) {
|
|
38045
|
-
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));
|
|
38046
38382
|
const paths = this.getSettablePaths({ global });
|
|
38047
38383
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
38048
38384
|
const existingDocument = parseSharedConfig({
|
|
@@ -38052,7 +38388,8 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
|
|
|
38052
38388
|
});
|
|
38053
38389
|
const document = withoutEmptyPermissions(applyIgnoreReadDenies({
|
|
38054
38390
|
settings: asClaudeStyleSettings(existingDocument),
|
|
38055
|
-
readDenies
|
|
38391
|
+
readDenies,
|
|
38392
|
+
readToolName: REASONIX_READ_TOOL_NAME
|
|
38056
38393
|
}));
|
|
38057
38394
|
return new ReasonixIgnore({
|
|
38058
38395
|
outputRoot,
|
|
@@ -45555,7 +45892,7 @@ var KiloMcp = class KiloMcp extends ToolMcp {
|
|
|
45555
45892
|
feature: "mcp",
|
|
45556
45893
|
existingContent: fileContent ?? "",
|
|
45557
45894
|
patch: {
|
|
45558
|
-
mcp: convertedMcp,
|
|
45895
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
45559
45896
|
...buildKiloMcpPermissionPatch({
|
|
45560
45897
|
existing: existingDocument,
|
|
45561
45898
|
managedServerNames: uniq([...Object.keys(mcpServers), ...Object.keys(isRecord$1(existingDocument.mcp) ? existingDocument.mcp : {})]),
|
|
@@ -46415,7 +46752,7 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
46415
46752
|
feature: "mcp",
|
|
46416
46753
|
existingContent: fileContent ?? "",
|
|
46417
46754
|
patch: {
|
|
46418
|
-
mcp: convertedMcp,
|
|
46755
|
+
mcp: undefinedWhenEmpty(convertedMcp),
|
|
46419
46756
|
tools: Object.keys(mcpTools).length > 0 ? mcpTools : void 0
|
|
46420
46757
|
},
|
|
46421
46758
|
filePath: join(jsonDir, relativeFilePath),
|
|
@@ -46763,9 +47100,12 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46763
47100
|
/**
|
|
46764
47101
|
* Meta Muse Code MCP servers.
|
|
46765
47102
|
*
|
|
46766
|
-
* Muse Code
|
|
46767
|
-
* block of the GLOBAL user settings file
|
|
46768
|
-
*
|
|
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
|
|
46769
47109
|
* `"schema_version": 1` — a file that omits that key fails every command at
|
|
46770
47110
|
* startup with `malformed settings file` — so the key is bootstrapped when the
|
|
46771
47111
|
* file is created and preserved when it already exists. Other settings keys are
|
|
@@ -46773,6 +47113,7 @@ function convertFromMusecodeFormat(musecodeMcp) {
|
|
|
46773
47113
|
*
|
|
46774
47114
|
* @see https://dev.meta.ai/docs/muse-code/configuration.md
|
|
46775
47115
|
* @see https://dev.meta.ai/docs/muse-code/extending.md
|
|
47116
|
+
* @see https://dev.meta.ai/docs/muse-code/changelog
|
|
46776
47117
|
*/
|
|
46777
47118
|
var MusecodeMcp = class MusecodeMcp extends ToolMcp {
|
|
46778
47119
|
json;
|
|
@@ -48005,7 +48346,7 @@ var QwencodeMcp = class QwencodeMcp extends ToolMcp {
|
|
|
48005
48346
|
feature: "mcp",
|
|
48006
48347
|
existingContent,
|
|
48007
48348
|
patch: {
|
|
48008
|
-
mcpServers: convertToQwencodeFormat(rulesyncMcp.getMcpServers()),
|
|
48349
|
+
mcpServers: undefinedWhenEmpty(convertToQwencodeFormat(rulesyncMcp.getMcpServers())),
|
|
48009
48350
|
...mcpPatch !== void 0 && { mcp: mcpPatch }
|
|
48010
48351
|
},
|
|
48011
48352
|
filePath
|
|
@@ -55417,6 +55758,7 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55417
55758
|
"dialogExpiry",
|
|
55418
55759
|
"feedbackDrafts",
|
|
55419
55760
|
"footerLinksRegexes",
|
|
55761
|
+
"forceLoginGatewayUrl",
|
|
55420
55762
|
"modelPicker",
|
|
55421
55763
|
"pluginConfigs",
|
|
55422
55764
|
"prependPlugins",
|
|
@@ -55430,6 +55772,39 @@ const CLAUDECODE_USER_SCOPE_ONLY_KEYS = /* @__PURE__ */ new Set([
|
|
|
55430
55772
|
"vimInsertModeRemaps"
|
|
55431
55773
|
]);
|
|
55432
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
|
+
/**
|
|
55433
55808
|
* Top-level settings keys neither file rulesync writes can honor, with the file
|
|
55434
55809
|
* that does. `Managed` keys are read only from the settings file an organization
|
|
55435
55810
|
* deploys, and `Global config` keys only from `~/.claude.json` — rulesync writes
|
|
@@ -55467,7 +55842,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
55467
55842
|
disableMobileSimulatorTools: "managed settings",
|
|
55468
55843
|
disableSideloadFlags: "managed settings",
|
|
55469
55844
|
externalEditorContext: "~/.claude.json",
|
|
55470
|
-
forceLoginGatewayUrl: "managed settings",
|
|
55471
55845
|
forceRemoteSettingsRefresh: "managed settings",
|
|
55472
55846
|
gatewayInternalNetworks: "managed settings",
|
|
55473
55847
|
leftArrowOpensAgents: "~/.claude.json",
|
|
@@ -55626,8 +56000,9 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55626
56000
|
/**
|
|
55627
56001
|
* `env` variables a project or local settings file cannot set, because a
|
|
55628
56002
|
* checked-out repository should not control them: where Claude Code stores its
|
|
55629
|
-
* own files, what session content it exports, where telemetry goes,
|
|
55630
|
-
* 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`.
|
|
55631
56006
|
*
|
|
55632
56007
|
* Only the names the settings reference lists are covered; its directory and
|
|
55633
56008
|
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
@@ -55638,6 +56013,10 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
55638
56013
|
*/
|
|
55639
56014
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
55640
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",
|
|
55641
56020
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
55642
56021
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
55643
56022
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
@@ -55646,6 +56025,7 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
55646
56025
|
"CLAUDE_CODE_SYNC_PLUGINS",
|
|
55647
56026
|
"CLAUDE_CODE_SYNC_SKILLS",
|
|
55648
56027
|
"CLAUDE_CODE_TMPDIR",
|
|
56028
|
+
"CLAUDE_CODE_USER_DIALOG_TIMEOUT_MS",
|
|
55649
56029
|
"CLAUDE_CONFIG_DIR",
|
|
55650
56030
|
"ENABLE_BETA_TRACING_DETAILED",
|
|
55651
56031
|
"ENABLE_ENHANCED_TELEMETRY_BETA",
|
|
@@ -55691,6 +56071,13 @@ const CLAUDECODE_ENV_CONTENT_OFF_VALUES = /* @__PURE__ */ new Set([
|
|
|
55691
56071
|
* writing a value Claude Code ignores is the old behavior, while dropping an
|
|
55692
56072
|
* opt-out it honors would remove a privacy setting. `OTEL_LOG_ASSISTANT_RESPONSES`
|
|
55693
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
|
|
55694
56081
|
*/
|
|
55695
56082
|
const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
55696
56083
|
OTEL_LOGS_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
@@ -55698,7 +56085,8 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
55698
56085
|
OTEL_TRACES_EXPORTER: CLAUDECODE_ENV_EXPORTER_OFF_VALUES,
|
|
55699
56086
|
OTEL_LOG_USER_PROMPTS: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55700
56087
|
OTEL_LOG_TOOL_CONTENT: CLAUDECODE_ENV_CONTENT_OFF_VALUES,
|
|
55701
|
-
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
|
|
55702
56090
|
};
|
|
55703
56091
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
55704
56092
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_")) return true;
|
|
@@ -55735,7 +56123,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
55735
56123
|
kept[name] = value;
|
|
55736
56124
|
}
|
|
55737
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.`);
|
|
55738
|
-
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.`);
|
|
55739
56127
|
return kept;
|
|
55740
56128
|
}
|
|
55741
56129
|
/**
|
|
@@ -55966,6 +56354,12 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
55966
56354
|
relativeFilePath: paths.relativeFilePath,
|
|
55967
56355
|
logger
|
|
55968
56356
|
});
|
|
56357
|
+
stripUserScopeOnlyNestedPaths({
|
|
56358
|
+
overrides: overrideTopLevel,
|
|
56359
|
+
global,
|
|
56360
|
+
relativeFilePath: paths.relativeFilePath,
|
|
56361
|
+
logger
|
|
56362
|
+
});
|
|
55969
56363
|
const { filtered: scopedTopLevel, trustAffecting: trustAffectingTopLevel } = stripUnhonoredTopLevelKeys({
|
|
55970
56364
|
overrides: overrideTopLevel,
|
|
55971
56365
|
global,
|
|
@@ -56383,7 +56777,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
56383
56777
|
"ask",
|
|
56384
56778
|
"deny"
|
|
56385
56779
|
];
|
|
56386
|
-
const CATCH_ALL_PATTERN$
|
|
56780
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
56387
56781
|
const MCP_CATEGORY = "mcp";
|
|
56388
56782
|
const MCP_RULE_PREFIX = "mcp__";
|
|
56389
56783
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -56456,7 +56850,7 @@ function categoryOfRule$2(rule) {
|
|
|
56456
56850
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
56457
56851
|
*/
|
|
56458
56852
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
56459
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
56853
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$12;
|
|
56460
56854
|
if (category === MCP_CATEGORY) {
|
|
56461
56855
|
if (catchAll && action === "allow") {
|
|
56462
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').`);
|
|
@@ -56465,7 +56859,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
56465
56859
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
56466
56860
|
}
|
|
56467
56861
|
if (category === "*") {
|
|
56468
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
56862
|
+
if (catchAll) return CATCH_ALL_PATTERN$12;
|
|
56469
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').`);
|
|
56470
56864
|
return null;
|
|
56471
56865
|
}
|
|
@@ -56483,7 +56877,7 @@ function parseCodebuddyRule(rule) {
|
|
|
56483
56877
|
const { specifier } = splitCodebuddyRule(rule);
|
|
56484
56878
|
if (specifier === void 0) return {
|
|
56485
56879
|
category,
|
|
56486
|
-
pattern: CATCH_ALL_PATTERN$
|
|
56880
|
+
pattern: CATCH_ALL_PATTERN$12
|
|
56487
56881
|
};
|
|
56488
56882
|
const trimmed = specifier.trim();
|
|
56489
56883
|
return trimmed === "" ? null : {
|
|
@@ -58734,7 +59128,7 @@ function mapBashActionToDecision(action) {
|
|
|
58734
59128
|
//#region src/features/permissions/commandcode-permissions.ts
|
|
58735
59129
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
58736
59130
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
58737
|
-
const CATCH_ALL_PATTERN$
|
|
59131
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
58738
59132
|
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
58739
59133
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
58740
59134
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
@@ -58759,14 +59153,14 @@ const COMMANDCODE_TOOL_TO_CATEGORY = {
|
|
|
58759
59153
|
web_fetch: "webfetch",
|
|
58760
59154
|
web_search: "websearch"
|
|
58761
59155
|
};
|
|
58762
|
-
const ACTION_RANK$
|
|
59156
|
+
const ACTION_RANK$5 = {
|
|
58763
59157
|
allow: 0,
|
|
58764
59158
|
ask: 1,
|
|
58765
59159
|
deny: 2
|
|
58766
59160
|
};
|
|
58767
59161
|
/** Whether `action` outranks `existing` (deny > ask > allow). */
|
|
58768
59162
|
function isStricterAction({ action, existing }) {
|
|
58769
|
-
return existing === void 0 || ACTION_RANK$
|
|
59163
|
+
return existing === void 0 || ACTION_RANK$5[action] > ACTION_RANK$5[existing];
|
|
58770
59164
|
}
|
|
58771
59165
|
/**
|
|
58772
59166
|
* Why a padded `Shell( * )` cannot stand in for the bare `Shell` in `allow`:
|
|
@@ -58789,10 +59183,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
58789
59183
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
58790
59184
|
*/
|
|
58791
59185
|
function buildCommandcodeRule(category, pattern) {
|
|
58792
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
59186
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$11 || pattern === "";
|
|
58793
59187
|
if (category.startsWith(MCP_CANONICAL_PREFIX$6)) return catchAll ? category : `${category}(${pattern})`;
|
|
58794
59188
|
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$6}${pattern}`;
|
|
58795
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
59189
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$11 : null;
|
|
58796
59190
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
58797
59191
|
if (tool === void 0) return null;
|
|
58798
59192
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -58828,7 +59222,7 @@ function splitCommandcodeRule(rule) {
|
|
|
58828
59222
|
}
|
|
58829
59223
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
58830
59224
|
function isCatchAllSpecifier(inner) {
|
|
58831
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
59225
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$11;
|
|
58832
59226
|
}
|
|
58833
59227
|
/**
|
|
58834
59228
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -58862,34 +59256,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
58862
59256
|
if (server.length === 0) return null;
|
|
58863
59257
|
const everyServer = {
|
|
58864
59258
|
category: "mcp",
|
|
58865
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59259
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58866
59260
|
notInAllow: true
|
|
58867
59261
|
};
|
|
58868
59262
|
if (tool.startsWith(MCP_CANONICAL_PREFIX$6)) {
|
|
58869
|
-
if (server === CATCH_ALL_PATTERN$
|
|
58870
|
-
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;
|
|
58871
59265
|
return {
|
|
58872
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
58873
|
-
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,
|
|
58874
59268
|
notInAllow: false
|
|
58875
59269
|
};
|
|
58876
59270
|
}
|
|
58877
59271
|
const category = `${MCP_CANONICAL_PREFIX$6}${remainder}`;
|
|
58878
|
-
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);
|
|
58879
59273
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
58880
59274
|
category,
|
|
58881
59275
|
pattern: inner,
|
|
58882
59276
|
notInAllow: true
|
|
58883
59277
|
} : null;
|
|
58884
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
58885
|
-
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 {
|
|
58886
59280
|
category: `${MCP_CANONICAL_PREFIX$6}${server}`,
|
|
58887
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59281
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58888
59282
|
notInAllow: true
|
|
58889
59283
|
};
|
|
58890
59284
|
return namesTool ? {
|
|
58891
59285
|
category,
|
|
58892
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59286
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58893
59287
|
notInAllow: false
|
|
58894
59288
|
} : null;
|
|
58895
59289
|
}
|
|
@@ -58913,14 +59307,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58913
59307
|
if (category === "bash") {
|
|
58914
59308
|
if (isCatchAllSpecifier(inner)) return {
|
|
58915
59309
|
category,
|
|
58916
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59310
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58917
59311
|
notInAllow: false
|
|
58918
59312
|
};
|
|
58919
59313
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
58920
59314
|
if (normalized === "" || normalized === ":*") return null;
|
|
58921
59315
|
return isCatchAllSpecifier(normalized) ? {
|
|
58922
59316
|
category,
|
|
58923
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59317
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58924
59318
|
notInAllow: true
|
|
58925
59319
|
} : {
|
|
58926
59320
|
category,
|
|
@@ -58930,7 +59324,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58930
59324
|
}
|
|
58931
59325
|
if (isCatchAllSpecifier(inner)) return {
|
|
58932
59326
|
category,
|
|
58933
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59327
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58934
59328
|
notInAllow: false
|
|
58935
59329
|
};
|
|
58936
59330
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -58949,9 +59343,9 @@ function friendlyToolRule({ category, inner }) {
|
|
|
58949
59343
|
*/
|
|
58950
59344
|
function parseCommandcodeRule(rule) {
|
|
58951
59345
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
58952
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
59346
|
+
if (tool === CATCH_ALL_PATTERN$11) return isCatchAllSpecifier(inner) ? {
|
|
58953
59347
|
category: "*",
|
|
58954
|
-
pattern: CATCH_ALL_PATTERN$
|
|
59348
|
+
pattern: CATCH_ALL_PATTERN$11,
|
|
58955
59349
|
notInAllow: true
|
|
58956
59350
|
} : null;
|
|
58957
59351
|
const lowered = tool.toLowerCase();
|
|
@@ -59021,7 +59415,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
59021
59415
|
}).filter((rule) => {
|
|
59022
59416
|
const parsed = parseCommandcodeRule(rule);
|
|
59023
59417
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
59024
|
-
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})`);
|
|
59025
59419
|
if (key !== "allow" && isStricterAction({
|
|
59026
59420
|
action: key,
|
|
59027
59421
|
existing: replacement
|
|
@@ -59078,7 +59472,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
59078
59472
|
const ranked = /* @__PURE__ */ new Map();
|
|
59079
59473
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
59080
59474
|
for (const [category, rules] of Object.entries(permission)) {
|
|
59081
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
59475
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$11);
|
|
59082
59476
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
59083
59477
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
59084
59478
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -59349,7 +59743,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
59349
59743
|
//#endregion
|
|
59350
59744
|
//#region src/features/permissions/continue-permissions.ts
|
|
59351
59745
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
59352
|
-
const CATCH_ALL_PATTERN$
|
|
59746
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
59353
59747
|
/**
|
|
59354
59748
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
59355
59749
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -59357,7 +59751,7 @@ const CATCH_ALL_PATTERN$9 = "*";
|
|
|
59357
59751
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
59358
59752
|
*/
|
|
59359
59753
|
function normalizeCatchAllPattern$1(pattern) {
|
|
59360
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
59754
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$10 : pattern;
|
|
59361
59755
|
}
|
|
59362
59756
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
59363
59757
|
bash: "Bash",
|
|
@@ -59394,7 +59788,7 @@ function toCanonicalToolName$6(continueName) {
|
|
|
59394
59788
|
* `Tool(pattern)`.
|
|
59395
59789
|
*/
|
|
59396
59790
|
function buildContinuePermissionEntry(toolName, pattern) {
|
|
59397
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
59791
|
+
return pattern === CATCH_ALL_PATTERN$10 ? toolName : `${toolName}(${pattern})`;
|
|
59398
59792
|
}
|
|
59399
59793
|
/**
|
|
59400
59794
|
* Split a permissions.yaml entry into its tool name and argument pattern, or
|
|
@@ -59408,7 +59802,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
59408
59802
|
if (toolName === "") return;
|
|
59409
59803
|
return {
|
|
59410
59804
|
toolName,
|
|
59411
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
59805
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$10 : pattern
|
|
59412
59806
|
};
|
|
59413
59807
|
}
|
|
59414
59808
|
/**
|
|
@@ -59582,7 +59976,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
59582
59976
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59583
59977
|
continue;
|
|
59584
59978
|
}
|
|
59585
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
59979
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$10) {
|
|
59586
59980
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
59587
59981
|
continue;
|
|
59588
59982
|
}
|
|
@@ -59950,14 +60344,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
59950
60344
|
};
|
|
59951
60345
|
//#endregion
|
|
59952
60346
|
//#region src/features/permissions/crush-permissions.ts
|
|
59953
|
-
const CATCH_ALL_PATTERN$
|
|
60347
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
59954
60348
|
/**
|
|
59955
60349
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
59956
60350
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
59957
60351
|
* still be written.
|
|
59958
60352
|
*/
|
|
59959
60353
|
function normalizeCatchAllPattern(pattern) {
|
|
59960
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
60354
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$9 : pattern;
|
|
59961
60355
|
}
|
|
59962
60356
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
59963
60357
|
bash: "bash",
|
|
@@ -60156,7 +60550,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
60156
60550
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
60157
60551
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
60158
60552
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
60159
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
60553
|
+
if (pattern === CATCH_ALL_PATTERN$9) {
|
|
60160
60554
|
catchAllAction = action;
|
|
60161
60555
|
continue;
|
|
60162
60556
|
}
|
|
@@ -60229,11 +60623,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
60229
60623
|
};
|
|
60230
60624
|
for (const entry of disabled) {
|
|
60231
60625
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
60232
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60626
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] = "deny";
|
|
60233
60627
|
}
|
|
60234
60628
|
for (const entry of allowed) {
|
|
60235
60629
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
60236
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
60630
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$9] ??= "allow";
|
|
60237
60631
|
}
|
|
60238
60632
|
return { permission };
|
|
60239
60633
|
}
|
|
@@ -62098,7 +62492,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
62098
62492
|
//#region src/features/permissions/goose-permissions.ts
|
|
62099
62493
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
62100
62494
|
const GOOSE_USER_KEY = "user";
|
|
62101
|
-
const CATCH_ALL_PATTERN$
|
|
62495
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
62102
62496
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
62103
62497
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
62104
62498
|
shell: ["bash"],
|
|
@@ -62110,7 +62504,7 @@ const ACTION_TO_GOOSE_LIST = {
|
|
|
62110
62504
|
ask: "ask_before",
|
|
62111
62505
|
deny: "never_allow"
|
|
62112
62506
|
};
|
|
62113
|
-
const ACTION_RANK$
|
|
62507
|
+
const ACTION_RANK$4 = {
|
|
62114
62508
|
allow: 0,
|
|
62115
62509
|
ask: 1,
|
|
62116
62510
|
deny: 2
|
|
@@ -62262,14 +62656,14 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
62262
62656
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
62263
62657
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
62264
62658
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
62265
|
-
if (pattern !== CATCH_ALL_PATTERN$
|
|
62659
|
+
if (pattern !== CATCH_ALL_PATTERN$8) {
|
|
62266
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.`);
|
|
62267
62661
|
continue;
|
|
62268
62662
|
}
|
|
62269
62663
|
const previous = assigned.get(toolName);
|
|
62270
62664
|
if (previous === action) continue;
|
|
62271
62665
|
if (previous !== void 0) {
|
|
62272
|
-
const stricter = ACTION_RANK$
|
|
62666
|
+
const stricter = ACTION_RANK$4[action] > ACTION_RANK$4[previous] ? action : previous;
|
|
62273
62667
|
logger?.warn(`Goose maps "${category}" onto the "${toolName}" tool, which already has a conflicting permission ("${previous}"). Keeping the stricter "${stricter}" (deny > ask > allow).`);
|
|
62274
62668
|
if (stricter === previous) continue;
|
|
62275
62669
|
const previousList = lists[ACTION_TO_GOOSE_LIST[previous]];
|
|
@@ -62293,7 +62687,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
62293
62687
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
62294
62688
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
62295
62689
|
permission[category] ??= {};
|
|
62296
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
62690
|
+
permission[category][CATCH_ALL_PATTERN$8] = action;
|
|
62297
62691
|
}
|
|
62298
62692
|
}
|
|
62299
62693
|
return { permission };
|
|
@@ -62304,7 +62698,7 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
62304
62698
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
62305
62699
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
62306
62700
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
62307
|
-
const CATCH_ALL_PATTERN$
|
|
62701
|
+
const CATCH_ALL_PATTERN$7 = "*";
|
|
62308
62702
|
const MCP_CANONICAL_PREFIX$3 = "mcp__";
|
|
62309
62703
|
const CATEGORY_TO_GROK_TOOL = {
|
|
62310
62704
|
bash: "Bash",
|
|
@@ -62329,6 +62723,8 @@ const GROK_TOOL_TO_CATEGORY = {
|
|
|
62329
62723
|
...Object.fromEntries(["SendSubagentMessage", "SendAgentMessage"].map((tool) => [tool, "agent"]))
|
|
62330
62724
|
};
|
|
62331
62725
|
const GROK_MCP_TOOL = "MCPTool";
|
|
62726
|
+
const GROK_ALL_TOOLS_ENTRY = "*";
|
|
62727
|
+
const GROK_VERBOSE_ANY_TOOL = "any";
|
|
62332
62728
|
const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
62333
62729
|
...Object.fromEntries(Object.entries(GROK_TOOL_TO_CATEGORY).map(([tool, category]) => [tool.toLowerCase(), category])),
|
|
62334
62730
|
agent_message: "agent"
|
|
@@ -62343,22 +62739,26 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
62343
62739
|
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
62344
62740
|
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
62345
62741
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
62346
|
-
* 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`.
|
|
62347
62745
|
*/
|
|
62348
62746
|
function buildGrokEntry(category, pattern) {
|
|
62747
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$7 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
62349
62748
|
if (category.startsWith(MCP_CANONICAL_PREFIX$3)) {
|
|
62350
62749
|
const remainder = category.slice(5);
|
|
62351
62750
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
62352
62751
|
}
|
|
62353
|
-
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})`;
|
|
62354
62753
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
62355
62754
|
if (tool === void 0) return null;
|
|
62356
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
62755
|
+
return pattern === CATCH_ALL_PATTERN$7 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
62357
62756
|
}
|
|
62358
62757
|
/**
|
|
62359
62758
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
62360
|
-
* pattern. Entries without parentheses are treated as catch-all (`*`)
|
|
62361
|
-
*
|
|
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.
|
|
62362
62762
|
*/
|
|
62363
62763
|
function parseGrokEntry(entry) {
|
|
62364
62764
|
const trimmed = entry.trim();
|
|
@@ -62372,19 +62772,23 @@ function parseGrokEntry(entry) {
|
|
|
62372
62772
|
tool = trimmed.slice(0, parenIndex);
|
|
62373
62773
|
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
62374
62774
|
}
|
|
62775
|
+
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
62776
|
+
category: "*",
|
|
62777
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62778
|
+
};
|
|
62375
62779
|
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$3)) return parseClaudeStyleMcpEntry(tool);
|
|
62376
62780
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
62377
62781
|
category: `${MCP_CANONICAL_PREFIX$3}${inner}`,
|
|
62378
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62782
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62379
62783
|
} : {
|
|
62380
62784
|
category: "mcp",
|
|
62381
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62785
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62382
62786
|
};
|
|
62383
62787
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
62384
62788
|
if (category === void 0) return null;
|
|
62385
62789
|
return {
|
|
62386
62790
|
category,
|
|
62387
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
62791
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$7
|
|
62388
62792
|
};
|
|
62389
62793
|
}
|
|
62390
62794
|
/**
|
|
@@ -62401,14 +62805,14 @@ function parseGrokEntry(entry) {
|
|
|
62401
62805
|
function parseClaudeStyleMcpEntry(entry) {
|
|
62402
62806
|
const rest = entry.slice(5);
|
|
62403
62807
|
if (rest.length === 0) return null;
|
|
62404
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
62808
|
+
if (rest === CATCH_ALL_PATTERN$7) return {
|
|
62405
62809
|
category: "mcp",
|
|
62406
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62810
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62407
62811
|
};
|
|
62408
62812
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
62409
62813
|
return {
|
|
62410
62814
|
category: `${MCP_CANONICAL_PREFIX$3}${address}`,
|
|
62411
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62815
|
+
pattern: CATCH_ALL_PATTERN$7
|
|
62412
62816
|
};
|
|
62413
62817
|
}
|
|
62414
62818
|
/**
|
|
@@ -62429,15 +62833,20 @@ function parseGrokRule(rule) {
|
|
|
62429
62833
|
if (typeof tool !== "string") return null;
|
|
62430
62834
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
62431
62835
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
62432
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
62836
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$7;
|
|
62433
62837
|
const lowerTool = tool.trim().toLowerCase();
|
|
62434
|
-
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 ? {
|
|
62435
62844
|
category: "mcp",
|
|
62436
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62845
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62437
62846
|
action
|
|
62438
62847
|
} : {
|
|
62439
62848
|
category: `${MCP_CANONICAL_PREFIX$3}${resolvedPattern}`,
|
|
62440
|
-
pattern: CATCH_ALL_PATTERN$
|
|
62849
|
+
pattern: CATCH_ALL_PATTERN$7,
|
|
62441
62850
|
action
|
|
62442
62851
|
};
|
|
62443
62852
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -62467,7 +62876,10 @@ function parseGrokRule(rule) {
|
|
|
62467
62876
|
* subagent rather than its launch, and whose pattern is a subagent id
|
|
62468
62877
|
* rather than a subagent type); `mcp__*` maps to `MCPTool(...)` (a scoped
|
|
62469
62878
|
* MCP category folds its address into the parentheses, so a non-`*`
|
|
62470
|
-
* 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
|
|
62471
62883
|
* skipped (with a warning when it carries a `deny` rule, to surface the
|
|
62472
62884
|
* gap). When two canonical rules collapse onto the same Grok
|
|
62473
62885
|
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
@@ -62478,7 +62890,9 @@ function parseGrokRule(rule) {
|
|
|
62478
62890
|
* verbose `[[permission.rules]]` tables
|
|
62479
62891
|
* (`{ action = "allow", tool = "bash", pattern = "git *" }`), whose `tool`
|
|
62480
62892
|
* field is matched case-insensitively against the same tool table, plus
|
|
62481
|
-
* 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
|
|
62482
62896
|
* from the two forms are merged with the same `deny > ask > allow`
|
|
62483
62897
|
* precedence. When neither form carries a rule (older configs), the coarse
|
|
62484
62898
|
* `[ui] permission_mode` is used as a fallback. Generate always emits the
|
|
@@ -62503,7 +62917,7 @@ function parseGrokRule(rule) {
|
|
|
62503
62917
|
* tools it models and (in global scope) the `[ui] permission_mode` value, while
|
|
62504
62918
|
* every other key
|
|
62505
62919
|
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
62506
|
-
* 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.
|
|
62507
62921
|
* The file is never deleted.
|
|
62508
62922
|
*/
|
|
62509
62923
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -62585,7 +62999,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62585
62999
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
62586
63000
|
}
|
|
62587
63001
|
const fineGrained = parseGrokPermissionArrays(isRecord$1(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
62588
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
63002
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$7]: legacyModeAction(parsed) } } };
|
|
62589
63003
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
62590
63004
|
}
|
|
62591
63005
|
validate() {
|
|
@@ -62605,14 +63019,14 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
62605
63019
|
});
|
|
62606
63020
|
}
|
|
62607
63021
|
};
|
|
62608
|
-
const ACTION_RANK$
|
|
63022
|
+
const ACTION_RANK$3 = {
|
|
62609
63023
|
allow: 0,
|
|
62610
63024
|
ask: 1,
|
|
62611
63025
|
deny: 2
|
|
62612
63026
|
};
|
|
62613
63027
|
/**
|
|
62614
63028
|
* Collect user-authored entries from an existing `[permission]` array whose
|
|
62615
|
-
* 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
|
|
62616
63030
|
* preserved verbatim so replacing the arrays does not silently drop them
|
|
62617
63031
|
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
62618
63032
|
*/
|
|
@@ -62631,13 +63045,26 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62631
63045
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) for (const [pattern, action] of Object.entries(rules)) {
|
|
62632
63046
|
const entry = buildGrokEntry(category, pattern);
|
|
62633
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
|
+
}
|
|
62634
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.`);
|
|
62635
63053
|
continue;
|
|
62636
63054
|
}
|
|
62637
63055
|
const existing = ranked.get(entry);
|
|
62638
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).`);
|
|
62639
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63057
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) ranked.set(entry, action);
|
|
62640
63058
|
}
|
|
63059
|
+
warnRemovedAllToolsRestrictions({
|
|
63060
|
+
existingPermission,
|
|
63061
|
+
ranked,
|
|
63062
|
+
logger
|
|
63063
|
+
});
|
|
63064
|
+
warnEntriesShadowedByAllTools({
|
|
63065
|
+
ranked,
|
|
63066
|
+
logger
|
|
63067
|
+
});
|
|
62641
63068
|
const allow = unmanagedEntries(existingPermission, "allow");
|
|
62642
63069
|
const deny = unmanagedEntries(existingPermission, "deny");
|
|
62643
63070
|
const ask = unmanagedEntries(existingPermission, "ask");
|
|
@@ -62651,6 +63078,38 @@ function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
|
62651
63078
|
};
|
|
62652
63079
|
}
|
|
62653
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
|
+
/**
|
|
62654
63113
|
* Parse Grok's `[permission]` section back into a canonical permission map,
|
|
62655
63114
|
* reading both supported forms: the compact `allow`/`deny`/`ask` arrays of
|
|
62656
63115
|
* Claude-style entries and the verbose `[[permission.rules]]` tables
|
|
@@ -62669,7 +63128,7 @@ function parseGrokPermissionArrays(permission) {
|
|
|
62669
63128
|
const record = ({ category, pattern, action }) => {
|
|
62670
63129
|
const bucket = result[category] ??= {};
|
|
62671
63130
|
const existing = bucket[pattern];
|
|
62672
|
-
if (existing === void 0 || ACTION_RANK$
|
|
63131
|
+
if (existing === void 0 || ACTION_RANK$3[action] > ACTION_RANK$3[existing]) bucket[pattern] = action;
|
|
62673
63132
|
};
|
|
62674
63133
|
const applyEntries = (entries, action) => {
|
|
62675
63134
|
for (const entry of entries ?? []) {
|
|
@@ -63073,7 +63532,7 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
63073
63532
|
mcpTools: "mcp",
|
|
63074
63533
|
readOutsideProject: "read"
|
|
63075
63534
|
};
|
|
63076
|
-
function isPermissionAction$
|
|
63535
|
+
function isPermissionAction$2(value) {
|
|
63077
63536
|
return PermissionActionSchema.safeParse(value).success;
|
|
63078
63537
|
}
|
|
63079
63538
|
/**
|
|
@@ -63259,7 +63718,7 @@ function convertJunieToRulesyncPermissions({ allowlist }) {
|
|
|
63259
63718
|
for (const rule of Array.isArray(ruleSet.rules) ? ruleSet.rules : []) {
|
|
63260
63719
|
if (!rule || typeof rule !== "object") continue;
|
|
63261
63720
|
const pattern = typeof rule.pattern === "string" ? rule.pattern : typeof rule.prefix === "string" ? rule.prefix : void 0;
|
|
63262
|
-
if (pattern === void 0 || !isPermissionAction$
|
|
63721
|
+
if (pattern === void 0 || !isPermissionAction$2(rule.action)) continue;
|
|
63263
63722
|
(permission[category] ??= {})[pattern] = rule.action;
|
|
63264
63723
|
}
|
|
63265
63724
|
}
|
|
@@ -64218,6 +64677,367 @@ function rulesFromArrays(settings, allowKey, denyKey) {
|
|
|
64218
64677
|
return rules;
|
|
64219
64678
|
}
|
|
64220
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
|
|
64221
65041
|
//#region src/features/permissions/lettacode-permissions.ts
|
|
64222
65042
|
/** Top-level key of `.letta/settings.json` that rulesync owns. */
|
|
64223
65043
|
const LETTACODE_PERMISSIONS_KEY = "permissions";
|
|
@@ -66245,6 +67065,8 @@ const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
|
66245
67065
|
"eager",
|
|
66246
67066
|
"listDirectory",
|
|
66247
67067
|
"todoWrite",
|
|
67068
|
+
"codeModeOnly",
|
|
67069
|
+
"freeform",
|
|
66248
67070
|
"workflowsEnabled",
|
|
66249
67071
|
"workflowNameOnly",
|
|
66250
67072
|
"executionSandbox"
|
|
@@ -66371,6 +67193,16 @@ const QWEN_SCOPED_TOOLS_KEYS = {
|
|
|
66371
67193
|
rule: "global-machine-wide",
|
|
66372
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.`,
|
|
66373
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."
|
|
66374
67206
|
}
|
|
66375
67207
|
};
|
|
66376
67208
|
/**
|
|
@@ -66834,22 +67666,42 @@ function convertQwenToRulesyncPermissions(params) {
|
|
|
66834
67666
|
//#endregion
|
|
66835
67667
|
//#region src/features/permissions/reasonix-permissions.ts
|
|
66836
67668
|
/**
|
|
66837
|
-
* Mapping from rulesync canonical tool category names (lowercase) to
|
|
66838
|
-
*
|
|
66839
|
-
*
|
|
66840
|
-
* Reasonix's
|
|
66841
|
-
*
|
|
66842
|
-
*
|
|
66843
|
-
*
|
|
66844
|
-
*
|
|
66845
|
-
*
|
|
66846
|
-
*
|
|
66847
|
-
*
|
|
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
|
|
66848
67683
|
*/
|
|
66849
67684
|
const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
66850
67685
|
bash: "Bash",
|
|
66851
|
-
read:
|
|
67686
|
+
read: REASONIX_READ_TOOL_NAME,
|
|
66852
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,
|
|
66853
67705
|
write: "Write",
|
|
66854
67706
|
webfetch: "WebFetch",
|
|
66855
67707
|
websearch: "WebSearch",
|
|
@@ -66859,9 +67711,16 @@ const CANONICAL_TO_REASONIX_TOOL_NAMES = {
|
|
|
66859
67711
|
agent: "Agent"
|
|
66860
67712
|
};
|
|
66861
67713
|
/**
|
|
66862
|
-
* Reverse mapping from Reasonix tool names
|
|
67714
|
+
* Reverse mapping from Reasonix tool names (current and legacy spellings) to
|
|
67715
|
+
* rulesync canonical names.
|
|
66863
67716
|
*/
|
|
66864
|
-
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
|
+
}
|
|
66865
67724
|
function toReasonixToolName(canonical) {
|
|
66866
67725
|
return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
|
|
66867
67726
|
}
|
|
@@ -67134,7 +67993,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
67134
67993
|
const parsed = parseReasonixConfig(existingContent);
|
|
67135
67994
|
const config = rulesyncPermissions.getJson();
|
|
67136
67995
|
const { allow, ask, deny } = convertRulesyncToReasonixPermissions(config);
|
|
67137
|
-
const managedToolNames = new Set(Object.keys(config.permission).
|
|
67996
|
+
const managedToolNames = new Set(Object.keys(config.permission).flatMap((category) => reasonixToolNamesOf(category)));
|
|
67138
67997
|
const override = config.reasonix;
|
|
67139
67998
|
const rawAllow = toReasonixStringArray(override?.rawAllow);
|
|
67140
67999
|
const rawAsk = toReasonixStringArray(override?.rawAsk);
|
|
@@ -67151,12 +68010,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
67151
68010
|
rawOwnedEntries,
|
|
67152
68011
|
logger
|
|
67153
68012
|
});
|
|
67154
|
-
if (logger && managedToolNames.has("
|
|
68013
|
+
if (logger && managedToolNames.has("read_file")) {
|
|
68014
|
+
const readToolNames = new Set(reasonixToolNamesOf("read"));
|
|
67155
68015
|
const droppedReadDenyEntries = toReasonixStringArray(existingPermissions.deny).filter((entry) => {
|
|
67156
68016
|
const { toolName } = parseReasonixPermissionEntry(entry);
|
|
67157
|
-
return toolName
|
|
68017
|
+
return readToolNames.has(toolName);
|
|
67158
68018
|
});
|
|
67159
|
-
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.`);
|
|
67160
68020
|
}
|
|
67161
68021
|
const mergedPermissions = { ...existingPermissions };
|
|
67162
68022
|
setOrDeleteEntries(mergedPermissions, "allow", [
|
|
@@ -72502,18 +73362,18 @@ const toolPermissionsFactories = /* @__PURE__ */ new Map([
|
|
|
72502
73362
|
}
|
|
72503
73363
|
}],
|
|
72504
73364
|
["kiro-cli", {
|
|
72505
|
-
class:
|
|
73365
|
+
class: KiroScopedPermissions,
|
|
72506
73366
|
meta: {
|
|
72507
73367
|
supportsProject: true,
|
|
72508
|
-
supportsGlobal:
|
|
73368
|
+
supportsGlobal: true,
|
|
72509
73369
|
supportsImport: true
|
|
72510
73370
|
}
|
|
72511
73371
|
}],
|
|
72512
73372
|
["kiro-ide", {
|
|
72513
|
-
class:
|
|
73373
|
+
class: KiroScopedPermissions,
|
|
72514
73374
|
meta: {
|
|
72515
73375
|
supportsProject: true,
|
|
72516
|
-
supportsGlobal:
|
|
73376
|
+
supportsGlobal: true,
|
|
72517
73377
|
supportsImport: true
|
|
72518
73378
|
}
|
|
72519
73379
|
}],
|
|
@@ -78500,6 +79360,24 @@ var KimiCodeSkill = class extends ToolSkill {
|
|
|
78500
79360
|
const targets = rulesyncSkill.getFrontmatter().targets;
|
|
78501
79361
|
return targets.includes("*") || targets.includes("kimi-code");
|
|
78502
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
|
+
}
|
|
78503
79381
|
static async fromDir(params) {
|
|
78504
79382
|
const loaded = await this.loadSkillDirContent({
|
|
78505
79383
|
...params,
|
|
@@ -78573,6 +79451,14 @@ var KimiCodePluginSkill = class extends KimiCodeSkill {
|
|
|
78573
79451
|
return { relativeDirPath: KIMI_CODE_PLUGIN_SKILLS_DIR };
|
|
78574
79452
|
}
|
|
78575
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
|
+
/**
|
|
78576
79462
|
* The bundle is read from `--output-root <plugin>`, but the imported rulesync
|
|
78577
79463
|
* file belongs to the project's `.rulesync/`, not to the plugin root.
|
|
78578
79464
|
*/
|
|
@@ -78924,7 +79810,8 @@ const OpenCodeSkillFrontmatterSchema = z.looseObject({
|
|
|
78924
79810
|
license: z.optional(z.unknown()),
|
|
78925
79811
|
compatibility: z.optional(z.unknown()),
|
|
78926
79812
|
metadata: z.optional(z.unknown()),
|
|
78927
|
-
"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())
|
|
78928
79815
|
});
|
|
78929
79816
|
var OpenCodeSkill = class extends ToolSkill {
|
|
78930
79817
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -79041,7 +79928,8 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79041
79928
|
...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] },
|
|
79042
79929
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
79043
79930
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
79044
|
-
...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"] }
|
|
79045
79933
|
};
|
|
79046
79934
|
const { toolTarget } = this.constructor.layout;
|
|
79047
79935
|
const rulesyncFrontmatter = {
|
|
@@ -79076,13 +79964,18 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79076
79964
|
rootFrontmatter: rulesyncFrontmatter,
|
|
79077
79965
|
section: opencodeSection
|
|
79078
79966
|
});
|
|
79967
|
+
const disableModelInvocation = resolveDisableModelInvocation({
|
|
79968
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
79969
|
+
section: opencodeSection
|
|
79970
|
+
});
|
|
79079
79971
|
const opencodeFrontmatter = {
|
|
79080
79972
|
name: rulesyncFrontmatter.name,
|
|
79081
79973
|
description: rulesyncFrontmatter.description,
|
|
79082
79974
|
...license !== void 0 && { license },
|
|
79083
79975
|
...compatibility !== void 0 && { compatibility },
|
|
79084
79976
|
...metadata !== void 0 && { metadata },
|
|
79085
|
-
...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 }
|
|
79086
79979
|
};
|
|
79087
79980
|
const settablePaths = this.getSettablePaths({ global });
|
|
79088
79981
|
return new this({
|
|
@@ -79105,11 +79998,13 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79105
79998
|
...params,
|
|
79106
79999
|
getSettablePaths: (options) => this.getSettablePaths(options)
|
|
79107
80000
|
});
|
|
79108
|
-
const
|
|
79109
|
-
|
|
79110
|
-
|
|
79111
|
-
|
|
79112
|
-
}
|
|
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)}`);
|
|
79113
80008
|
return new this({
|
|
79114
80009
|
outputRoot: loaded.outputRoot,
|
|
79115
80010
|
relativeDirPath: loaded.relativeDirPath,
|
|
@@ -79137,6 +80032,52 @@ var OpenCodeSkill = class extends ToolSkill {
|
|
|
79137
80032
|
});
|
|
79138
80033
|
}
|
|
79139
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
|
+
}
|
|
79140
80081
|
//#endregion
|
|
79141
80082
|
//#region src/features/skills/mimocode-skill.ts
|
|
79142
80083
|
/**
|
|
@@ -100642,13 +101583,15 @@ const ANTIGRAVITY_PLUGIN_NAME_PATTERN = /^[a-zA-Z0-9-_]+$/;
|
|
|
100642
101583
|
* The manifest declares only `name` and `description`, and rejects anything
|
|
100643
101584
|
* else, so the one required field is derived from the plugin directory's name.
|
|
100644
101585
|
* An existing manifest is never read or rewritten: it may carry a
|
|
100645
|
-
* 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.
|
|
100646
101589
|
*/
|
|
100647
101590
|
async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger }) {
|
|
100648
101591
|
const paths = [];
|
|
100649
101592
|
for (const outputRoot of outputRoots) {
|
|
100650
101593
|
const filePath = join(outputRoot, ANTIGRAVITY_PLUGIN_MANIFEST_FILE_NAME);
|
|
100651
|
-
if (await
|
|
101594
|
+
if (await hasAntigravityPluginManifest(outputRoot)) continue;
|
|
100652
101595
|
if (await refusesWriteOutsideRoot({
|
|
100653
101596
|
logger,
|
|
100654
101597
|
rootPath: outputRoot,
|
|
@@ -100671,6 +101614,11 @@ async function ensureAntigravityPluginManifests({ outputRoots, dryRun, logger })
|
|
|
100671
101614
|
sourceLoadFailed: false
|
|
100672
101615
|
};
|
|
100673
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
|
+
}
|
|
100674
101622
|
//#endregion
|
|
100675
101623
|
//#region src/features/shared/hermes-project-plugin-activation.ts
|
|
100676
101624
|
function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
|
|
@@ -102968,6 +103916,6 @@ async function importChecksCore(params) {
|
|
|
102968
103916
|
return writtenCount;
|
|
102969
103917
|
}
|
|
102970
103918
|
//#endregion
|
|
102971
|
-
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 };
|
|
102972
103920
|
|
|
102973
|
-
//# sourceMappingURL=import-
|
|
103921
|
+
//# sourceMappingURL=import-Tya9qZRK.js.map
|