rulesync 43.4.2 → 43.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.cjs +6 -6
- package/dist/cli/index.js +6 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BWOC-Tig.js → import--Y_A56qx.js} +1179 -264
- package/dist/{import-BWOC-Tig.js.map → import--Y_A56qx.js.map} +1 -1
- package/dist/{import-ClFasGQ6.cjs → import-C-oT_Hdp.cjs} +1195 -280
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -873,6 +873,7 @@ const hooksProcessorToolTargetTuple = [
|
|
|
873
873
|
"qwencode",
|
|
874
874
|
"reasonix",
|
|
875
875
|
"tabnine",
|
|
876
|
+
"trae",
|
|
876
877
|
"grokcli",
|
|
877
878
|
"gitlabduo",
|
|
878
879
|
"lettacode",
|
|
@@ -3923,6 +3924,7 @@ const SHARED_USER_MANAGED_CONFIG_PATHS = [
|
|
|
3923
3924
|
".poolside/settings.yaml",
|
|
3924
3925
|
".config/poolside/settings.yaml",
|
|
3925
3926
|
".tabnine/agent/settings.json",
|
|
3927
|
+
".trae/hooks.json",
|
|
3926
3928
|
".vibe/config.toml",
|
|
3927
3929
|
".zcode/config.json",
|
|
3928
3930
|
".zcode/cli/config.json",
|
|
@@ -6005,6 +6007,34 @@ const CANONICAL_TO_CORTEXCODE_EVENT_NAMES = {
|
|
|
6005
6007
|
};
|
|
6006
6008
|
const CORTEXCODE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_CORTEXCODE_EVENT_NAMES);
|
|
6007
6009
|
/**
|
|
6010
|
+
* Hook events supported by TRAE.
|
|
6011
|
+
*
|
|
6012
|
+
* TRAE reads `.trae/hooks.json` (project) and `~/.trae/hooks.json` (user):
|
|
6013
|
+
* `{ "version"?: 1, "hooks": { "<Event>": [{ "matcher"?, "hooks": [{ "type":
|
|
6014
|
+
* "command", "command", "timeout"? }] }] } }` with Claude-Code-style
|
|
6015
|
+
* PascalCase event names. `$TRAE_PROJECT_DIR` resolves to the workspace root
|
|
6016
|
+
* inside commands.
|
|
6017
|
+
*
|
|
6018
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
6019
|
+
*/
|
|
6020
|
+
const TRAE_HOOK_EVENTS = [
|
|
6021
|
+
"sessionStart",
|
|
6022
|
+
"beforeSubmitPrompt",
|
|
6023
|
+
"preToolUse",
|
|
6024
|
+
"postToolUse",
|
|
6025
|
+
"stop",
|
|
6026
|
+
"notification"
|
|
6027
|
+
];
|
|
6028
|
+
const CANONICAL_TO_TRAE_EVENT_NAMES = {
|
|
6029
|
+
sessionStart: "SessionStart",
|
|
6030
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
6031
|
+
preToolUse: "PreToolUse",
|
|
6032
|
+
postToolUse: "PostToolUse",
|
|
6033
|
+
stop: "Stop",
|
|
6034
|
+
notification: "Notification"
|
|
6035
|
+
};
|
|
6036
|
+
const TRAE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_TRAE_EVENT_NAMES);
|
|
6037
|
+
/**
|
|
6008
6038
|
* Hook events supported by CodeBuddy Code.
|
|
6009
6039
|
*
|
|
6010
6040
|
* CodeBuddy Code reads hooks from the `hooks` key of `.codebuddy/settings.json`
|
|
@@ -6477,6 +6507,7 @@ const HooksConfigSchema = z.looseObject({
|
|
|
6477
6507
|
bob: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6478
6508
|
codebuddy: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6479
6509
|
cortexcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6510
|
+
trae: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6480
6511
|
commandcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6481
6512
|
lettacode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
6482
6513
|
continue: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
@@ -8378,17 +8409,29 @@ const CodewhalePermissionsOverrideSchema = z.looseObject({
|
|
|
8378
8409
|
rules: z.optional(z.array(CodewhalePermissionRuleSchema))
|
|
8379
8410
|
});
|
|
8380
8411
|
/**
|
|
8381
|
-
* The
|
|
8382
|
-
*
|
|
8412
|
+
* The values VS Code's `chat.permissions.default` setting accepts: the starting
|
|
8413
|
+
* permission level of new Local-harness chat sessions — `default` (Default
|
|
8383
8414
|
* Permissions), `autoApprove` (Bypass Approvals) and `autopilot` (Autopilot).
|
|
8384
8415
|
*
|
|
8385
8416
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
8386
8417
|
*/
|
|
8387
|
-
const
|
|
8418
|
+
const COPILOT_LOCAL_PERMISSION_LEVELS = [
|
|
8388
8419
|
"default",
|
|
8389
8420
|
"autoApprove",
|
|
8390
8421
|
"autopilot"
|
|
8391
8422
|
];
|
|
8423
|
+
/**
|
|
8424
|
+
* The starting permission level the `copilot.defaultPermissionMode` override
|
|
8425
|
+
* sets for new chat sessions: the {@link COPILOT_LOCAL_PERMISSION_LEVELS}, plus
|
|
8426
|
+
* `assisted` (Assisted permissions, GA for Copilot-harness sessions since VS
|
|
8427
|
+
* Code 1.144 and unavailable to Local sessions). Each value is written to both
|
|
8428
|
+
* `chat.permissions.default` (Local sessions) and the `approvals` / `mode` of
|
|
8429
|
+
* `chat.defaultConfiguration` (Copilot-harness sessions, VS Code 1.144+).
|
|
8430
|
+
*
|
|
8431
|
+
* @see https://code.visualstudio.com/updates/v1_144
|
|
8432
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
8433
|
+
*/
|
|
8434
|
+
const COPILOT_DEFAULT_PERMISSION_MODES = [...COPILOT_LOCAL_PERMISSION_LEVELS, "assisted"];
|
|
8392
8435
|
/** `chat.agent.sandbox.enabled`: `"off"` or `"on"`. */
|
|
8393
8436
|
const CopilotSandboxEnabledSchema = z.enum(["off", "on"]);
|
|
8394
8437
|
/** `chat.agent.sandbox.fileSystem.userConfiguredPaths`. */
|
|
@@ -8429,7 +8472,7 @@ const CopilotSandboxSchema = z.strictObject({
|
|
|
8429
8472
|
/**
|
|
8430
8473
|
* Tool-scoped override block for GitHub Copilot in VS Code.
|
|
8431
8474
|
* `defaultPermissionMode` writes the workspace `chat.permissions.default`
|
|
8432
|
-
*
|
|
8475
|
+
* and `chat.defaultConfiguration` settings, and `sandbox` writes the `chat.agent.sandbox.*` settings; neither
|
|
8433
8476
|
* has a canonical category.
|
|
8434
8477
|
*/
|
|
8435
8478
|
const CopilotPermissionsOverrideSchema = z.looseObject({
|
|
@@ -9311,6 +9354,7 @@ const TaktPermissionsOverrideSchema = z.looseObject({
|
|
|
9311
9354
|
})),
|
|
9312
9355
|
workflow_runtime_prepare: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9313
9356
|
workflow_command_gates: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9357
|
+
workflow_system_commands: z.optional(z.object({ custom_scripts: z.optional(z.boolean()) })),
|
|
9314
9358
|
sync_conflict_resolver: z.optional(z.object({ auto_approve_tools: z.optional(z.boolean()) })),
|
|
9315
9359
|
allow_git_hooks: z.optional(z.boolean()),
|
|
9316
9360
|
allow_git_filters: z.optional(z.boolean())
|
|
@@ -9578,6 +9622,7 @@ const CodexSandboxModeSchema = z.enum([
|
|
|
9578
9622
|
"workspace-write",
|
|
9579
9623
|
"danger-full-access"
|
|
9580
9624
|
]);
|
|
9625
|
+
const CODEX_U32_MAX = 4294967295;
|
|
9581
9626
|
/**
|
|
9582
9627
|
* Codex CLI's reviewer for approval requests. `guardian_subagent` is a legacy
|
|
9583
9628
|
* value Codex still accepts for backward compatibility, so it is included so
|
|
@@ -9646,6 +9691,9 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
|
|
|
9646
9691
|
* | `guardian_subagent`), or a table for the richer reviewer config.
|
|
9647
9692
|
* Defaults to `auto_review` when neither the override nor the existing
|
|
9648
9693
|
* config sets it.
|
|
9694
|
+
* - `auto_review` — the `[auto_review]` table that configures the reviewer
|
|
9695
|
+
* `approvals_reviewer = "auto_review"` selects (`policy`, `extra_policy`,
|
|
9696
|
+
* `timeout_ms`, …). A loose table, shallow-merged like `apps` / `tui`.
|
|
9649
9697
|
* - `git_write_rules` — whether the managed profile's `:workspace_roots` table
|
|
9650
9698
|
* emits the default `.git` carve-out (`".git/**" = "write"`). Codex's
|
|
9651
9699
|
* `:workspace` baseline makes `.git` read-only, which denies basic git
|
|
@@ -9659,7 +9707,8 @@ const CodexBasePermissionProfileSchema = z.enum(CODEX_BASE_PERMISSION_PROFILES);
|
|
|
9659
9707
|
* The keys written to `config.toml` are an **allowlist**, not verbatim
|
|
9660
9708
|
* passthrough: only `CODEXCLI_OVERRIDE_KEYS`
|
|
9661
9709
|
* (`src/constants/codexcli-paths.ts` — `approval_policy`, `sandbox_mode`,
|
|
9662
|
-
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`
|
|
9710
|
+
* `sandbox_workspace_write`, `apps`, `approvals_reviewer`, `auto_review`,
|
|
9711
|
+
* `tui`) are emitted, and
|
|
9663
9712
|
* `computeCodexcliOverridePatch` skips anything else with a warning.
|
|
9664
9713
|
* `base_permission_profile` and `git_write_rules` are consumed by the profile
|
|
9665
9714
|
* builder rather than written, as described above, and `permission` is the
|
|
@@ -9691,6 +9740,26 @@ const CodexcliPermissionsOverrideSchema = z.looseObject({
|
|
|
9691
9740
|
apps: z.optional(z.looseObject({})),
|
|
9692
9741
|
approvals_reviewer: z.optional(z.union([CodexApprovalsReviewerSchema, z.looseObject({})])),
|
|
9693
9742
|
/**
|
|
9743
|
+
* The `[auto_review]` table of `config.toml`, which configures the reviewer
|
|
9744
|
+
* that `approvals_reviewer = "auto_review"` (Rulesync's default) selects:
|
|
9745
|
+
* `policy` / `extra_policy` (text inserted into the reviewer prompt),
|
|
9746
|
+
* `timeout_ms` (the review budget, Codex 0.165.0+; `0` waits without a
|
|
9747
|
+
* deadline), `circuit_break_action` (`default` | `strict`) and
|
|
9748
|
+
* `conversation_history_max_output_tokens` (a positive token cap). Codex
|
|
9749
|
+
* rejects the whole config file when one of these has the wrong type or is
|
|
9750
|
+
* out of range (`timeout_ms` is a `u32`, the token cap a `NonZeroUsize`), so
|
|
9751
|
+
* they are checked here; every other key passes through verbatim.
|
|
9752
|
+
*
|
|
9753
|
+
* @see https://github.com/openai/codex/blob/rust-v0.166.0/codex-rs/config/src/config_toml.rs (`AutoReviewToml`)
|
|
9754
|
+
*/
|
|
9755
|
+
auto_review: z.optional(z.looseObject({
|
|
9756
|
+
policy: z.optional(z.string()),
|
|
9757
|
+
extra_policy: z.optional(z.string()),
|
|
9758
|
+
timeout_ms: z.optional(z.int().check(z.nonnegative(), z.lte(CODEX_U32_MAX))),
|
|
9759
|
+
circuit_break_action: z.optional(z.enum(["default", "strict"])),
|
|
9760
|
+
conversation_history_max_output_tokens: z.optional(z.int().check(z.positive()))
|
|
9761
|
+
})),
|
|
9762
|
+
/**
|
|
9694
9763
|
* The `[tui]` table of `config.toml` (e.g. `vim_mode_default`, `keymap.*`).
|
|
9695
9764
|
* Not a permission surface, but like `apps` it is a top-level table with no
|
|
9696
9765
|
* canonical category, and Codex adds keys to it faster than an explicit model
|
|
@@ -12297,6 +12366,7 @@ const CODEXCLI_OVERRIDE_KEYS = [
|
|
|
12297
12366
|
"sandbox_workspace_write",
|
|
12298
12367
|
"apps",
|
|
12299
12368
|
"approvals_reviewer",
|
|
12369
|
+
"auto_review",
|
|
12300
12370
|
"tui"
|
|
12301
12371
|
];
|
|
12302
12372
|
// src/constants/takt-paths.ts
|
|
@@ -13719,6 +13789,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13719
13789
|
"workflow_arpeggio",
|
|
13720
13790
|
"workflow_runtime_prepare",
|
|
13721
13791
|
"workflow_command_gates",
|
|
13792
|
+
"workflow_system_commands",
|
|
13722
13793
|
"sync_conflict_resolver",
|
|
13723
13794
|
"allow_git_hooks",
|
|
13724
13795
|
"allow_git_filters"
|
|
@@ -13860,6 +13931,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13860
13931
|
"chat.tools.edits.autoApprove",
|
|
13861
13932
|
"chat.tools.urls.autoApprove",
|
|
13862
13933
|
"chat.permissions.default",
|
|
13934
|
+
"chat.defaultConfiguration",
|
|
13863
13935
|
"chat.agent.sandbox.enabled",
|
|
13864
13936
|
"chat.agent.sandbox.network.allowNetwork",
|
|
13865
13937
|
"chat.agent.sandbox.network.allowLocalNetwork",
|
|
@@ -14118,6 +14190,14 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
14118
14190
|
}
|
|
14119
14191
|
}
|
|
14120
14192
|
},
|
|
14193
|
+
".trae/hooks.json": {
|
|
14194
|
+
format: "json",
|
|
14195
|
+
invalidRootPolicy: "error",
|
|
14196
|
+
features: { hooks: {
|
|
14197
|
+
kind: "replace-owned-keys",
|
|
14198
|
+
ownedKeys: ["hooks"]
|
|
14199
|
+
} }
|
|
14200
|
+
},
|
|
14121
14201
|
".trae/permission/global.json": {
|
|
14122
14202
|
format: "json",
|
|
14123
14203
|
invalidRootPolicy: "error",
|
|
@@ -23243,10 +23323,13 @@ var TaktCommand = class TaktCommand extends ToolCommand {
|
|
|
23243
23323
|
* @see https://docs.trae.ai/ide/slash-commands?_lang=en
|
|
23244
23324
|
* @see https://docs.trae.ai/ide/ignore-files?_lang=en
|
|
23245
23325
|
* @see https://docs.trae.ai/ide/permission-and-approval?_lang=en
|
|
23326
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
23246
23327
|
*/
|
|
23247
23328
|
const TRAE_DIR = ".trae";
|
|
23248
23329
|
/** Project MCP servers (`mcpServers` object), project scope only. */
|
|
23249
23330
|
const TRAE_MCP_FILE_NAME = "mcp.json";
|
|
23331
|
+
/** Hooks: `.trae/hooks.json` (project) and `~/.trae/hooks.json` (global). */
|
|
23332
|
+
const TRAE_HOOKS_FILE_NAME = "hooks.json";
|
|
23250
23333
|
/** Skills: `.trae/skills/<name>/SKILL.md` (project) and `~/.trae/skills/` (global). */
|
|
23251
23334
|
const TRAE_SKILLS_DIR_PATH = join(TRAE_DIR, "skills");
|
|
23252
23335
|
/**
|
|
@@ -25171,12 +25254,80 @@ var AiassistantSkill = class AiassistantSkill extends ToolSkill {
|
|
|
25171
25254
|
});
|
|
25172
25255
|
}
|
|
25173
25256
|
};
|
|
25257
|
+
// src/features/shared/amp-settings.ts
|
|
25258
|
+
/**
|
|
25259
|
+
* Parse an Amp `settings.json` / `settings.jsonc` document.
|
|
25260
|
+
*
|
|
25261
|
+
* Shared by the MCP and permissions adapters because both read the very same
|
|
25262
|
+
* file. Two separately maintained copies drifted once already: the permissions
|
|
25263
|
+
* side was hardened while the MCP side kept calling `jsonc-parser` directly,
|
|
25264
|
+
* which left it importing MCP servers reachable only through an injected
|
|
25265
|
+
* `__proto__`.
|
|
25266
|
+
*
|
|
25267
|
+
* {@link parseJsonc} is the strict, sanitizing parser: it throws on any syntax
|
|
25268
|
+
* error instead of returning `jsonc-parser`'s best-effort value, and it
|
|
25269
|
+
* rebuilds every object from its own enumerable entries, so a root `__proto__`
|
|
25270
|
+
* cannot swap the returned object's prototype and `constructor` / `prototype`
|
|
25271
|
+
* never survive as own keys, at any depth.
|
|
25272
|
+
*
|
|
25273
|
+
* Removal is silent, matching every other tool-side adapter (opencode, kilo,
|
|
25274
|
+
* copilot). Reporting the removed keys through `droppedPollutionKeysError` is
|
|
25275
|
+
* reserved for the three files a user authors under `.rulesync/`, whose whole
|
|
25276
|
+
* purpose is to be turned into tool config — a key that vanishes there needs
|
|
25277
|
+
* explaining, whereas here the surrounding settings are the user's own file
|
|
25278
|
+
* and are left alone.
|
|
25279
|
+
*
|
|
25280
|
+
* Note the consequence for a root `__proto__`: sanitizing runs before the
|
|
25281
|
+
* plain-object check, so such a document parses successfully with the key
|
|
25282
|
+
* stripped rather than failing that check. That is deliberate — dropping the
|
|
25283
|
+
* one poisoned key keeps the user's unrelated settings — and matches the
|
|
25284
|
+
* sanitize-before-the-root-check design the shared config gateway documents.
|
|
25285
|
+
*/
|
|
25286
|
+
function parseAmpSettings({ fileContent }) {
|
|
25287
|
+
let parsed;
|
|
25288
|
+
try {
|
|
25289
|
+
parsed = parseJsonc(fileContent || "{}");
|
|
25290
|
+
} catch (error) {
|
|
25291
|
+
throw new Error(`Failed to parse Amp settings: ${formatError(error)}`, { cause: error });
|
|
25292
|
+
}
|
|
25293
|
+
if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
|
|
25294
|
+
return parsed;
|
|
25295
|
+
}
|
|
25296
|
+
/**
|
|
25297
|
+
* Probe `<jsonDir>/settings.jsonc` first, falling back to `settings.json`, so
|
|
25298
|
+
* an existing user file is read-modified-written in place instead of a fresh
|
|
25299
|
+
* `.json` sibling being created next to a hand-authored `.jsonc`. Defaults to
|
|
25300
|
+
* `settings.json` when neither file exists: Amp's canonically documented
|
|
25301
|
+
* settings file (`.amp/settings.json`), shared by the MCP and permissions
|
|
25302
|
+
* adapters, so generating both on a clean repo writes a single settings file.
|
|
25303
|
+
*/
|
|
25304
|
+
async function resolveAmpSettingsFile(jsonDir) {
|
|
25305
|
+
const jsoncContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_JSONC_FILE_NAME));
|
|
25306
|
+
if (jsoncContent !== null) return {
|
|
25307
|
+
fileContent: jsoncContent,
|
|
25308
|
+
relativeFilePath: AMP_SETTINGS_JSONC_FILE_NAME
|
|
25309
|
+
};
|
|
25310
|
+
const jsonContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_FILE_NAME));
|
|
25311
|
+
if (jsonContent !== null) return {
|
|
25312
|
+
fileContent: jsonContent,
|
|
25313
|
+
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
25314
|
+
};
|
|
25315
|
+
return {
|
|
25316
|
+
fileContent: null,
|
|
25317
|
+
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
25318
|
+
};
|
|
25319
|
+
}
|
|
25174
25320
|
// src/features/skills/amp-skill.ts
|
|
25175
25321
|
const AmpSkillFrontmatterSchema = z.looseObject({
|
|
25176
25322
|
name: z.string(),
|
|
25177
25323
|
description: z.string()
|
|
25178
25324
|
});
|
|
25179
25325
|
/**
|
|
25326
|
+
* Amp setting that makes it skip `~/.config/agents/skills/` and
|
|
25327
|
+
* `~/.agents/skills/`. https://ampcode.com/docs/cli/settings
|
|
25328
|
+
*/
|
|
25329
|
+
const AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY = "amp.skills.disableGlobalAgentsSkills";
|
|
25330
|
+
/**
|
|
25180
25331
|
* Represents an Amp Agent Skill directory.
|
|
25181
25332
|
*
|
|
25182
25333
|
* Amp natively reads Agent Skills following the open standard: a directory with a
|
|
@@ -25211,6 +25362,27 @@ var AmpSkill = class AmpSkill extends ToolSkill {
|
|
|
25211
25362
|
...global && { importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH, AMP_SKILLS_NATIVE_GLOBAL_DIR] }
|
|
25212
25363
|
};
|
|
25213
25364
|
}
|
|
25365
|
+
/**
|
|
25366
|
+
* Warn when the user's Amp settings disable the global agent-agnostic skill
|
|
25367
|
+
* roots, because the global skills rulesync writes to
|
|
25368
|
+
* `~/.config/agents/skills/` would then never load. Only the user settings
|
|
25369
|
+
* file is checked: a workspace `.amp/settings.json(c)` depends on where Amp
|
|
25370
|
+
* is started, which global generation cannot know.
|
|
25371
|
+
*/
|
|
25372
|
+
static async getGenerationWarnings({ outputRoot, global, logger }) {
|
|
25373
|
+
if (!global) return [];
|
|
25374
|
+
const { fileContent, relativeFilePath } = await resolveAmpSettingsFile(join(outputRoot, AMP_GLOBAL_DIR));
|
|
25375
|
+
if (fileContent === null) return [];
|
|
25376
|
+
let settings;
|
|
25377
|
+
try {
|
|
25378
|
+
settings = parseAmpSettings({ fileContent });
|
|
25379
|
+
} catch (error) {
|
|
25380
|
+
logger.debug(`Skipping the Amp skills settings check for ${join(AMP_GLOBAL_DIR, relativeFilePath)}: ${formatError(error)}`);
|
|
25381
|
+
return [];
|
|
25382
|
+
}
|
|
25383
|
+
if (settings[AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY] !== true) return [];
|
|
25384
|
+
return [`Amp will not load the global skills rulesync writes to ~/${AMP_SKILLS_GLOBAL_DIR}/: '${AMP_DISABLE_GLOBAL_AGENTS_SKILLS_KEY}' is true in ~/${join(AMP_GLOBAL_DIR, relativeFilePath)}.`];
|
|
25385
|
+
}
|
|
25214
25386
|
getFrontmatter() {
|
|
25215
25387
|
return AmpSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
25216
25388
|
}
|
|
@@ -33187,7 +33359,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
33187
33359
|
async convertRulesyncDirsToToolDirs(rulesyncDirs) {
|
|
33188
33360
|
const rulesyncSkills = rulesyncDirs.filter((dir) => dir instanceof RulesyncSkill);
|
|
33189
33361
|
const factory = this.getFactory(this.toolTarget);
|
|
33190
|
-
|
|
33362
|
+
const toolSkills = (await Promise.all(rulesyncSkills.map(async (rulesyncSkill) => {
|
|
33191
33363
|
const isClaudecodeScheduledTask = rulesyncSkill.getFrontmatter().claudecode?.["scheduled-task"] === true;
|
|
33192
33364
|
if (isClaudecodeScheduledTask && this.toolTarget !== "claudecode" && this.toolTarget !== "claudecode-legacy") return null;
|
|
33193
33365
|
if (!factory.class.isTargetedByRulesyncSkill(rulesyncSkill)) return null;
|
|
@@ -33214,6 +33386,15 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
33214
33386
|
logger: this.logger
|
|
33215
33387
|
});
|
|
33216
33388
|
}))).filter((skill) => skill !== null);
|
|
33389
|
+
if (toolSkills.length > 0 && factory.class.getGenerationWarnings) {
|
|
33390
|
+
const warnings = await factory.class.getGenerationWarnings({
|
|
33391
|
+
outputRoot: this.outputRoot,
|
|
33392
|
+
global: this.global,
|
|
33393
|
+
logger: this.logger
|
|
33394
|
+
});
|
|
33395
|
+
for (const warning of warnings) this.logger.warn(warning);
|
|
33396
|
+
}
|
|
33397
|
+
return toolSkills;
|
|
33217
33398
|
}
|
|
33218
33399
|
async convertToolDirsToRulesyncDirs(toolDirs) {
|
|
33219
33400
|
const toolSkills = toolDirs.filter((dir) => dir instanceof ToolSkill);
|
|
@@ -34101,7 +34282,7 @@ function normalizeAugmentcodeToolList(value) {
|
|
|
34101
34282
|
return tools.length > 0 ? tools : void 0;
|
|
34102
34283
|
}
|
|
34103
34284
|
const AugmentcodeSubagentFrontmatterSchema = z.looseObject({
|
|
34104
|
-
name: z.string(),
|
|
34285
|
+
name: z.optional(z.string()),
|
|
34105
34286
|
description: z.optional(z.string()),
|
|
34106
34287
|
color: z.optional(z.string()),
|
|
34107
34288
|
model: z.optional(z.string()),
|
|
@@ -34160,7 +34341,7 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34160
34341
|
outputRoot: ".",
|
|
34161
34342
|
frontmatter: {
|
|
34162
34343
|
targets: ["*"],
|
|
34163
|
-
name,
|
|
34344
|
+
name: name ?? basename(this.getRelativeFilePath(), extname(this.getRelativeFilePath())),
|
|
34164
34345
|
description,
|
|
34165
34346
|
...Object.keys(augmentcodeSection).length > 0 && { augmentcode: augmentcodeSection }
|
|
34166
34347
|
},
|
|
@@ -34226,7 +34407,8 @@ var AugmentcodeSubagent = class AugmentcodeSubagent extends ToolSubagent {
|
|
|
34226
34407
|
const dirPath = relativeDirPath ?? this.getSettablePaths({ global }).relativeDirPath;
|
|
34227
34408
|
const filePath = join(outputRoot, dirPath, relativeFilePath);
|
|
34228
34409
|
const fileContent = await readFileContent(filePath);
|
|
34229
|
-
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
34410
|
+
const { frontmatter, body: content, hasFrontmatter } = parseFrontmatter(fileContent, filePath);
|
|
34411
|
+
if (!hasFrontmatter) throw new Error(`Missing frontmatter in ${filePath}`);
|
|
34230
34412
|
const result = AugmentcodeSubagentFrontmatterSchema.safeParse(frontmatter);
|
|
34231
34413
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
34232
34414
|
return new AugmentcodeSubagent({
|
|
@@ -41809,7 +41991,23 @@ function sanitizeOpencodeMcpNamePart(value) {
|
|
|
41809
41991
|
* key that matches nothing.
|
|
41810
41992
|
*/
|
|
41811
41993
|
function opencodeMcpToolPermissionKey(serverName, toolName) {
|
|
41812
|
-
return `${
|
|
41994
|
+
return `${opencodeMcpServerKeyPrefix(serverName)}${toolName.replace(/[^a-zA-Z0-9_\-*?]/g, "_")}`;
|
|
41995
|
+
}
|
|
41996
|
+
/** The sanitized `<server>_` prefix every permission key of the server's tools starts with. */
|
|
41997
|
+
function opencodeMcpServerKeyPrefix(serverName) {
|
|
41998
|
+
return `${sanitizeOpencodeMcpNamePart(serverName)}_`;
|
|
41999
|
+
}
|
|
42000
|
+
/**
|
|
42001
|
+
* Whether `key` is exactly the sanitized `<server>_` prefix of one of
|
|
42002
|
+
* `serverNames` (e.g. `a_b_` for server `a.b` or `a_b`). No tool has an empty
|
|
42003
|
+
* name, so such a key filters nothing of that server, and
|
|
42004
|
+
* {@link findOpencodeMcpToolKeyOwner} gives it no owner. Read as an unlisted
|
|
42005
|
+
* server's filter instead, it would be split at the first `_` into a phantom
|
|
42006
|
+
* server (`a` with tool `b_`), so importers leave it out of the rulesync
|
|
42007
|
+
* config, and the OpenCode family's generate keeps it in the file as it was.
|
|
42008
|
+
*/
|
|
42009
|
+
function isBareOpencodeMcpServerPrefix({ key, serverNames }) {
|
|
42010
|
+
return serverNames.some((serverName) => key === opencodeMcpServerKeyPrefix(serverName));
|
|
41813
42011
|
}
|
|
41814
42012
|
/**
|
|
41815
42013
|
* The server among `serverNames` whose tool a permission key names, with the
|
|
@@ -41823,7 +42021,7 @@ function findOpencodeMcpToolKeyOwner({ key, serverNames, builtinKeys, acceptRawP
|
|
|
41823
42021
|
if (builtinKeys.has(key)) return;
|
|
41824
42022
|
let owner;
|
|
41825
42023
|
for (const serverName of serverNames) {
|
|
41826
|
-
const sanitized =
|
|
42024
|
+
const sanitized = opencodeMcpServerKeyPrefix(serverName);
|
|
41827
42025
|
const prefixes = acceptRawPrefix ? uniq([sanitized, `${serverName}_`]) : [sanitized];
|
|
41828
42026
|
for (const prefix of prefixes) if (key.length > prefix.length && key.startsWith(prefix) && (owner === void 0 || prefix.length > owner.prefix.length)) owner = {
|
|
41829
42027
|
serverName,
|
|
@@ -42284,7 +42482,10 @@ function convertFromKiloFormat(kiloMcp, tools, permission) {
|
|
|
42284
42482
|
const serverNames = Object.keys(kiloMcp);
|
|
42285
42483
|
return {
|
|
42286
42484
|
...Object.fromEntries(Object.entries(kiloMcp).map(([serverName, serverConfig]) => [serverName, kiloServerToRulesync(serverName, serverConfig, splitKiloServerTools(serverName, serverNames, filters))])),
|
|
42287
|
-
...orphanMcpToolFiltersToRulesync(kiloMcp, tools)
|
|
42485
|
+
...orphanMcpToolFiltersToRulesync(kiloMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findKiloMcpToolKeyOwner(key, serverNames) && !isBareOpencodeMcpServerPrefix({
|
|
42486
|
+
key,
|
|
42487
|
+
serverNames
|
|
42488
|
+
}))))
|
|
42288
42489
|
};
|
|
42289
42490
|
}
|
|
42290
42491
|
/**
|
|
@@ -42802,16 +43003,20 @@ function convertFromOpencodeFormat(opencodeMcp, tools, remoteType) {
|
|
|
42802
43003
|
...orphanMcpToolFiltersToRulesync(opencodeMcp, Object.fromEntries(Object.entries(tools ?? {}).filter(([key]) => !findOpencodeToolOwner({
|
|
42803
43004
|
key,
|
|
42804
43005
|
serverNames
|
|
43006
|
+
}) && !isBareOpencodeMcpServerPrefix({
|
|
43007
|
+
key,
|
|
43008
|
+
serverNames
|
|
42805
43009
|
})))),
|
|
42806
43010
|
...convertOpencodeServers(opencodeMcp, tools, remoteType)
|
|
42807
43011
|
};
|
|
42808
43012
|
}
|
|
43013
|
+
const OPENCODE_MCP_BUILTIN_TOOL_KEYS = /* @__PURE__ */ new Set();
|
|
42809
43014
|
/** Recognize both OpenCode's sanitized names and keys older rulesync versions wrote. */
|
|
42810
43015
|
function findOpencodeToolOwner({ key, serverNames }) {
|
|
42811
43016
|
return findOpencodeMcpToolKeyOwner({
|
|
42812
43017
|
key,
|
|
42813
43018
|
serverNames,
|
|
42814
|
-
builtinKeys:
|
|
43019
|
+
builtinKeys: OPENCODE_MCP_BUILTIN_TOOL_KEYS,
|
|
42815
43020
|
acceptRawPrefix: true
|
|
42816
43021
|
});
|
|
42817
43022
|
}
|
|
@@ -42981,6 +43186,40 @@ function convertToOpencodeFormat(mcpServers, existingMcp, configShape, logger) {
|
|
|
42981
43186
|
tools
|
|
42982
43187
|
};
|
|
42983
43188
|
}
|
|
43189
|
+
/**
|
|
43190
|
+
* Copy each key of `existingTools` that is a bare sanitized `<server>_` prefix
|
|
43191
|
+
* of one of `serverNames` into `tools`, unless a generated filter already
|
|
43192
|
+
* writes the same key or another listed server owns it (`a_b_` is tool `b_` of
|
|
43193
|
+
* server `a` when `a` is listed too). An owned key is the rulesync config's to
|
|
43194
|
+
* write, so carrying it over would keep a filter the user removed. Mutates
|
|
43195
|
+
* `tools` in place.
|
|
43196
|
+
*/
|
|
43197
|
+
function preserveBareOpencodeMcpServerPrefixKeys({ tools, existingTools, serverNames }) {
|
|
43198
|
+
for (const [key, enabled] of Object.entries(existingTools ?? {})) if (!Object.hasOwn(tools, key) && isBareOpencodeMcpServerPrefix({
|
|
43199
|
+
key,
|
|
43200
|
+
serverNames
|
|
43201
|
+
}) && !findOpencodeToolOwner({
|
|
43202
|
+
key,
|
|
43203
|
+
serverNames
|
|
43204
|
+
})) tools[key] = enabled;
|
|
43205
|
+
}
|
|
43206
|
+
/**
|
|
43207
|
+
* When the existing file does not pass the schema, its `tools` map cannot be
|
|
43208
|
+
* read, so a bare `<server>_` key in it is not carried over. Say so rather
|
|
43209
|
+
* than lift the deny it may hold without a word.
|
|
43210
|
+
*/
|
|
43211
|
+
function warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({ existingJson, serverNames, logger }) {
|
|
43212
|
+
const rawTools = isRecord(existingJson) ? existingJson.tools : void 0;
|
|
43213
|
+
if (!isRecord(rawTools)) return;
|
|
43214
|
+
const bareKeys = Object.keys(rawTools).filter((key) => isBareOpencodeMcpServerPrefix({
|
|
43215
|
+
key,
|
|
43216
|
+
serverNames
|
|
43217
|
+
}) && !findOpencodeToolOwner({
|
|
43218
|
+
key,
|
|
43219
|
+
serverNames
|
|
43220
|
+
}));
|
|
43221
|
+
if (bareKeys.length > 0) logger?.warn(`OpenCode MCP: the existing config does not match the expected schema, so the bare server-prefix tool ${bareKeys.length === 1 ? "key" : "keys"} ${bareKeys.map(quoteValueForWarning).join(", ")} ${bareKeys.length === 1 ? "is" : "are"} not kept in "tools".`);
|
|
43222
|
+
}
|
|
42984
43223
|
const OpencodeMcpFeatureOptionsSchema = z.looseObject({ configShape: z.optional(z.enum(["v1", "v2"])) });
|
|
42985
43224
|
var OpencodeMcp = class extends ToolMcp {
|
|
42986
43225
|
/** Directory layout; OpenCode forks (MiMo Code) override it. */
|
|
@@ -43113,6 +43352,16 @@ var OpencodeMcp = class extends ToolMcp {
|
|
|
43113
43352
|
convertedMcp,
|
|
43114
43353
|
mcpServers: transformedServers
|
|
43115
43354
|
});
|
|
43355
|
+
if (existingMcp.success) preserveBareOpencodeMcpServerPrefixKeys({
|
|
43356
|
+
tools: mcpTools,
|
|
43357
|
+
existingTools: existingMcp.data.tools,
|
|
43358
|
+
serverNames: Object.keys(transformedServers)
|
|
43359
|
+
});
|
|
43360
|
+
else warnAboutUnreadableBareOpencodeMcpServerPrefixKeys({
|
|
43361
|
+
existingJson: this.lowerConfig(existingJson),
|
|
43362
|
+
serverNames: Object.keys(transformedServers),
|
|
43363
|
+
logger
|
|
43364
|
+
});
|
|
43116
43365
|
if (this.layout.configVersion === "v2") warnAboutOpencodeV2SseServers({
|
|
43117
43366
|
mcpServers: transformedServers,
|
|
43118
43367
|
convertedMcp,
|
|
@@ -47351,6 +47600,45 @@ var DshRule = class DshRule extends NestedAgentsmdRule {
|
|
|
47351
47600
|
};
|
|
47352
47601
|
}
|
|
47353
47602
|
};
|
|
47603
|
+
// src/features/rules/output-style-paths.ts
|
|
47604
|
+
/**
|
|
47605
|
+
* Shared helpers for tool rule adapters that write opted-in rules as custom
|
|
47606
|
+
* output styles (`<tool dir>/output-styles/<name>.md`), e.g. Qoder and
|
|
47607
|
+
* Factory Droid. Both tools read only the direct `.md` children of their
|
|
47608
|
+
* output-styles directory, with optional `name` / `description` frontmatter.
|
|
47609
|
+
*/
|
|
47610
|
+
/**
|
|
47611
|
+
* Whether the given path is a direct `.md` child of `outputStylesDirPath`.
|
|
47612
|
+
*/
|
|
47613
|
+
function isDirectMarkdownChild({ outputStylesDirPath, relativeDirPath, relativeFilePath }) {
|
|
47614
|
+
return relativeDirPath === outputStylesDirPath && extname(relativeFilePath) === ".md" && basename(relativeFilePath) === relativeFilePath;
|
|
47615
|
+
}
|
|
47616
|
+
/**
|
|
47617
|
+
* Whether a rule opts into the `output-style` channel: only a non-root rule
|
|
47618
|
+
* can, since an output style is a file of its own.
|
|
47619
|
+
*/
|
|
47620
|
+
function isOutputStyleChannel({ root, channel }) {
|
|
47621
|
+
return !root && channel === "output-style";
|
|
47622
|
+
}
|
|
47623
|
+
/**
|
|
47624
|
+
* Return the file stem of an output-style source rule, throwing `message`
|
|
47625
|
+
* when the rule is nested in a subdirectory of the rules directory: the
|
|
47626
|
+
* output file is named after the rule, and the tools read only the top level
|
|
47627
|
+
* of their output-styles directory.
|
|
47628
|
+
*/
|
|
47629
|
+
function resolveTopLevelOutputStyleStem({ sourcePath, message }) {
|
|
47630
|
+
if (basename(sourcePath) !== sourcePath) throw new Error(message);
|
|
47631
|
+
return basename(sourcePath, extname(sourcePath));
|
|
47632
|
+
}
|
|
47633
|
+
/**
|
|
47634
|
+
* The output style's frontmatter: only the `name` and `description` that are set.
|
|
47635
|
+
*/
|
|
47636
|
+
function buildOutputStyleFrontmatter({ name, description }) {
|
|
47637
|
+
return {
|
|
47638
|
+
...name !== void 0 && { name },
|
|
47639
|
+
...description !== void 0 && { description }
|
|
47640
|
+
};
|
|
47641
|
+
}
|
|
47354
47642
|
// src/features/rules/factorydroid-rule.ts
|
|
47355
47643
|
/**
|
|
47356
47644
|
* The file-name glob `getExtraFixedFiles` returns for the output-styles
|
|
@@ -47455,7 +47743,11 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47455
47743
|
* child of the output-styles directory, at either scope.
|
|
47456
47744
|
*/
|
|
47457
47745
|
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
47458
|
-
return
|
|
47746
|
+
return isDirectMarkdownChild({
|
|
47747
|
+
outputStylesDirPath: this.getOutputStylesDirPath(),
|
|
47748
|
+
relativeDirPath,
|
|
47749
|
+
relativeFilePath
|
|
47750
|
+
});
|
|
47459
47751
|
}
|
|
47460
47752
|
/**
|
|
47461
47753
|
* Which fixed-file channel, if any, owns the given output path. Matching on
|
|
@@ -47495,7 +47787,10 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47495
47787
|
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
47496
47788
|
static isOutputStyleRule(rulesyncRule) {
|
|
47497
47789
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47498
|
-
return
|
|
47790
|
+
return isOutputStyleChannel({
|
|
47791
|
+
root: frontmatter.root,
|
|
47792
|
+
channel: frontmatter.factorydroid?.channel
|
|
47793
|
+
});
|
|
47499
47794
|
}
|
|
47500
47795
|
/**
|
|
47501
47796
|
* Factory Droid loads the channel files itself, so listing one in the root
|
|
@@ -47627,15 +47922,17 @@ var FactorydroidRule = class FactorydroidRule extends ToolRule {
|
|
|
47627
47922
|
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
47628
47923
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
47629
47924
|
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
47630
|
-
|
|
47631
|
-
|
|
47925
|
+
const stem = resolveTopLevelOutputStyleStem({
|
|
47926
|
+
sourcePath,
|
|
47927
|
+
message: `Rule '${sourcePath}' sets factorydroid.channel: output-style but is nested in a subdirectory: Factory Droid loads only direct .md children of .factory/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`
|
|
47928
|
+
});
|
|
47632
47929
|
const name = frontmatter.factorydroid?.name;
|
|
47633
47930
|
const styleName = name ?? stem;
|
|
47634
47931
|
if (FACTORYDROID_RESERVED_OUTPUT_STYLE_NAMES.includes(styleName.trim().toLowerCase())) throw new Error(`Rule '${sourcePath}' sets factorydroid.channel: output-style with the style name '${styleName}', which is reserved: Factory Droid's built-in Default and Concise styles cannot be replaced. Rename the rule file or set factorydroid.name.`);
|
|
47635
|
-
const styleFrontmatter = {
|
|
47636
|
-
|
|
47637
|
-
|
|
47638
|
-
};
|
|
47932
|
+
const styleFrontmatter = buildOutputStyleFrontmatter({
|
|
47933
|
+
name,
|
|
47934
|
+
description: frontmatter.description
|
|
47935
|
+
});
|
|
47639
47936
|
const body = rulesyncRule.getBody();
|
|
47640
47937
|
return new FactorydroidRule({
|
|
47641
47938
|
outputRoot,
|
|
@@ -49271,12 +49568,19 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
49271
49568
|
* `.qoder/output-styles/`, at either scope.
|
|
49272
49569
|
*/
|
|
49273
49570
|
static isOutputStylePath({ relativeDirPath, relativeFilePath }) {
|
|
49274
|
-
return
|
|
49571
|
+
return isDirectMarkdownChild({
|
|
49572
|
+
outputStylesDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
49573
|
+
relativeDirPath,
|
|
49574
|
+
relativeFilePath
|
|
49575
|
+
});
|
|
49275
49576
|
}
|
|
49276
49577
|
/** Whether a rule opts into the `output-style` channel (non-root only). */
|
|
49277
49578
|
static isOutputStyleRule(rulesyncRule) {
|
|
49278
49579
|
const frontmatter = rulesyncRule.getFrontmatter();
|
|
49279
|
-
return
|
|
49580
|
+
return isOutputStyleChannel({
|
|
49581
|
+
root: frontmatter.root,
|
|
49582
|
+
channel: frontmatter.qoder?.channel
|
|
49583
|
+
});
|
|
49280
49584
|
}
|
|
49281
49585
|
/**
|
|
49282
49586
|
* The output-styles directory, enumerated for import only: it also holds
|
|
@@ -49387,16 +49691,18 @@ var QoderRule = class QoderRule extends ToolRule {
|
|
|
49387
49691
|
static buildOutputStyle({ outputRoot, rulesyncRule, validate }) {
|
|
49388
49692
|
const { description, qoder } = rulesyncRule.getFrontmatter();
|
|
49389
49693
|
const sourcePath = rulesyncRule.getRelativeFilePath();
|
|
49390
|
-
|
|
49391
|
-
|
|
49694
|
+
const stem = resolveTopLevelOutputStyleStem({
|
|
49695
|
+
sourcePath,
|
|
49696
|
+
message: `Rule '${sourcePath}' sets qoder.channel: output-style but is nested in a subdirectory: Qoder reads only the files directly in .qoder/output-styles/, so move it to the top of ${RULESYNC_RULES_RELATIVE_DIR_PATH}.`
|
|
49697
|
+
});
|
|
49392
49698
|
return new QoderRule({
|
|
49393
49699
|
outputRoot,
|
|
49394
49700
|
relativeDirPath: QODER_OUTPUT_STYLES_DIR_PATH,
|
|
49395
|
-
relativeFilePath: `${
|
|
49396
|
-
frontmatter: {
|
|
49397
|
-
|
|
49398
|
-
|
|
49399
|
-
},
|
|
49701
|
+
relativeFilePath: `${stem}.md`,
|
|
49702
|
+
frontmatter: buildOutputStyleFrontmatter({
|
|
49703
|
+
name: qoder?.name,
|
|
49704
|
+
description
|
|
49705
|
+
}),
|
|
49400
49706
|
body: rulesyncRule.getBody(),
|
|
49401
49707
|
validate,
|
|
49402
49708
|
root: false,
|
|
@@ -51667,6 +51973,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
51667
51973
|
factory
|
|
51668
51974
|
});
|
|
51669
51975
|
await this.warnForWarpLegacyRootFile(toolRules);
|
|
51976
|
+
this.warnForAugmentcodePluginRules(toolRules);
|
|
51670
51977
|
await this.warnForInstructionBudget({
|
|
51671
51978
|
toolRules,
|
|
51672
51979
|
meta
|
|
@@ -52058,6 +52365,20 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
52058
52365
|
this.logger.warn(`Writing ${stripControlCharacters(rootFileRelativePath)} for ${this.toolTarget} means ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} will no longer be read. Run \`rulesync import --targets ${this.toolTarget}\` first to carry that content into ${RULESYNC_RULES_RELATIVE_DIR_PATH}, or delete ${listedNames.length === 1 ? "it" : "them"} once you have checked the content is already in the root file.`);
|
|
52059
52366
|
}
|
|
52060
52367
|
/**
|
|
52368
|
+
* Auggie 1.x (still a prerelease on npm at the time of writing; the stable
|
|
52369
|
+
* line is 0.36.0) no longer loads `rules/` from plugins: "Rules shipped in
|
|
52370
|
+
* plugins (`rules/` in a plugin)" is listed among the removed features, while
|
|
52371
|
+
* commands, agents, skills, hooks and MCP servers still load. The rule files
|
|
52372
|
+
* are still written, because the stable 0.x CLI reads them, but under 1.x the
|
|
52373
|
+
* bundle silently loses them, so say so.
|
|
52374
|
+
* @see https://docs.augmentcode.com/cli/whats-new
|
|
52375
|
+
* @see https://docs.augmentcode.com/cli/v0/plugins
|
|
52376
|
+
*/
|
|
52377
|
+
warnForAugmentcodePluginRules(toolRules) {
|
|
52378
|
+
if (this.toolTarget !== "augmentcode-plugin" || toolRules.length === 0) return;
|
|
52379
|
+
this.logger.warn(`augmentcode-plugin: plugin rules (rules/*.md) are loaded by Auggie 0.x only; Auggie 1.x no longer reads rules/ from plugins, so these ${toolRules.length} rule file(s) have no effect there. Ship instructions that must reach Auggie 1.x users as a skill, or have them generate the augmentcode target in their project.`);
|
|
52380
|
+
}
|
|
52381
|
+
/**
|
|
52061
52382
|
* Warp still reads a legacy `WARP.md`, and when both `WARP.md` and
|
|
52062
52383
|
* `AGENTS.md` sit in the same directory, `WARP.md` takes priority — so an
|
|
52063
52384
|
* `AGENTS.md` this run writes next to one would never be read. That holds for
|
|
@@ -56059,9 +56380,9 @@ function groupDefinitionsByMatcher({ definitions, eventName, converterConfig, wa
|
|
|
56059
56380
|
});
|
|
56060
56381
|
translatedMatchers.set(normalizedMatcher, matcher);
|
|
56061
56382
|
}
|
|
56062
|
-
const key = [matcher, ...subdividingFields.map(({ canonical, valueType }) => {
|
|
56383
|
+
const key = [matcher, ...subdividingFields.map(({ canonical, valueType, events }) => {
|
|
56063
56384
|
const value = def[canonical];
|
|
56064
|
-
return isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
56385
|
+
return isGroupFieldOnEvent(events, eventName) && isGroupPassthroughValue(value, valueType) ? stableJson(value) : "";
|
|
56065
56386
|
})].join("\0");
|
|
56066
56387
|
const group = byMatcher.get(key);
|
|
56067
56388
|
if (group) group.defs.push(def);
|
|
@@ -56102,6 +56423,7 @@ function runsInPosixShell({ shell, converterConfig }) {
|
|
|
56102
56423
|
* Apply the optional project directory variable prefix to a command string.
|
|
56103
56424
|
*/
|
|
56104
56425
|
function applyCommandPrefix({ def, converterConfig }) {
|
|
56426
|
+
if (converterConfig.stripsProjectDirVarOnImportOnly) return def.command;
|
|
56105
56427
|
const commandText = def.command;
|
|
56106
56428
|
const trimmedCommand = typeof commandText === "string" ? commandText.trimStart() : void 0;
|
|
56107
56429
|
const unquotedCommand = trimmedCommand?.replace(/^["']/, "");
|
|
@@ -56310,12 +56632,16 @@ const describeInvalidObject = ({ tool, canonical, value }) => isPlainObject(valu
|
|
|
56310
56632
|
value
|
|
56311
56633
|
})} Importing it would fail validation on the next run.` : `Dropping "${tool}" while importing a hook: it must be an object.`;
|
|
56312
56634
|
const describeInvalidRecord = ({ tool }) => `Dropping "${tool}" while importing a hook: it must be a map of strings whose keys are non-empty and free of "=", and whose keys and values carry no newline, carriage return or NUL characters.`;
|
|
56635
|
+
function isGroupFieldOnEvent(events, eventName) {
|
|
56636
|
+
return events === void 0 || events.has(eventName);
|
|
56637
|
+
}
|
|
56313
56638
|
/**
|
|
56314
56639
|
* Check a value against the shape its field documents. A string field also
|
|
56315
56640
|
* rejects control characters, matching the canonical `safeString` so an
|
|
56316
56641
|
* imported value cannot fail validation on the next generate.
|
|
56317
56642
|
*/
|
|
56318
56643
|
function isGroupPassthroughValue(value, valueType = "object") {
|
|
56644
|
+
if (valueType === "number") return typeof value === "number" && Number.isFinite(value);
|
|
56319
56645
|
if (valueType === "string") return typeof value === "string" && !CONTROL_CHARS.some((char) => value.includes(char));
|
|
56320
56646
|
return isPlainObject(value);
|
|
56321
56647
|
}
|
|
@@ -56325,10 +56651,14 @@ function isGroupPassthroughValue(value, valueType = "object") {
|
|
|
56325
56651
|
*/
|
|
56326
56652
|
function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }) {
|
|
56327
56653
|
const emitted = {};
|
|
56328
|
-
for (const { canonical, tool, valueType } of converterConfig.groupPassthroughFields ?? []) {
|
|
56654
|
+
for (const { canonical, tool, valueType, events } of converterConfig.groupPassthroughFields ?? []) {
|
|
56329
56655
|
const carried = defs.map((def) => def[canonical]);
|
|
56330
56656
|
const first = carried.find((value) => isGroupPassthroughValue(value, valueType));
|
|
56331
56657
|
if (first === void 0) continue;
|
|
56658
|
+
if (!isGroupFieldOnEvent(events, eventName)) {
|
|
56659
|
+
logger?.warn(`"${canonical}" on "${eventName}" hooks is ignored — the tool supports "${tool}" only on ${[...events ?? []].map((event) => `"${event}"`).join(", ")} hooks.`);
|
|
56660
|
+
continue;
|
|
56661
|
+
}
|
|
56332
56662
|
const firstStable = stableJson(first);
|
|
56333
56663
|
const agrees = (value) => isGroupPassthroughValue(value, valueType) && stableJson(value) === firstStable;
|
|
56334
56664
|
if (!carried.every(agrees)) logger?.warn(`"${tool}" belongs to the whole matcher group on "${eventName}" hooks, so every hook in this group gets ${quoteValueForWarning(first)} — including any that asked for something else, or for nothing.`);
|
|
@@ -56340,9 +56670,9 @@ function emitGroupPassthroughFields({ defs, eventName, converterConfig, logger }
|
|
|
56340
56670
|
* Import the configured group-level passthrough fields onto a definition,
|
|
56341
56671
|
* reversing {@link emitGroupPassthroughFields}.
|
|
56342
56672
|
*/
|
|
56343
|
-
function importGroupPassthroughFields({ rawEntry, converterConfig }) {
|
|
56673
|
+
function importGroupPassthroughFields({ rawEntry, eventName, converterConfig }) {
|
|
56344
56674
|
const entry = rawEntry;
|
|
56345
|
-
return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool, valueType }) => isGroupPassthroughValue(entry[tool], valueType)).map(({ canonical, tool }) => [canonical, entry[tool]]));
|
|
56675
|
+
return Object.fromEntries((converterConfig.groupPassthroughFields ?? []).filter(({ tool, valueType, events }) => isGroupFieldOnEvent(events, eventName) && isGroupPassthroughValue(entry[tool], valueType)).map(({ canonical, tool }) => [canonical, entry[tool]]));
|
|
56346
56676
|
}
|
|
56347
56677
|
/**
|
|
56348
56678
|
* Emit the payload fields specific to a hook type — `url`/`headers`/
|
|
@@ -56668,19 +56998,23 @@ function stripCommandPrefix({ command, args, shell, converterConfig, warn }) {
|
|
|
56668
56998
|
const cmd = typeof command === "string" ? command : void 0;
|
|
56669
56999
|
const { projectDirVar } = converterConfig;
|
|
56670
57000
|
if (projectDirVar === "" || typeof cmd !== "string") return cmd;
|
|
56671
|
-
|
|
56672
|
-
|
|
56673
|
-
|
|
56674
|
-
|
|
56675
|
-
|
|
56676
|
-
|
|
56677
|
-
|
|
56678
|
-
|
|
56679
|
-
|
|
56680
|
-
|
|
56681
|
-
|
|
56682
|
-
|
|
56683
|
-
|
|
57001
|
+
let stripped = cmd;
|
|
57002
|
+
for (const variable of [projectDirVar, ...converterConfig.importedProjectDirVarAliases ?? []]) {
|
|
57003
|
+
const result = importProjectDirVariable({
|
|
57004
|
+
command: stripped,
|
|
57005
|
+
projectDirVar: variable,
|
|
57006
|
+
firstWordOnly: isExecFormHook({
|
|
57007
|
+
args,
|
|
57008
|
+
converterConfig
|
|
57009
|
+
}) || !runsInPosixShell({
|
|
57010
|
+
shell,
|
|
57011
|
+
converterConfig
|
|
57012
|
+
})
|
|
57013
|
+
});
|
|
57014
|
+
stripped = result.command;
|
|
57015
|
+
if (warn && result.unrestored && !converterConfig.stripsProjectDirVarOnImportOnly) warn(`Hook command ${quoteValueForWarning(cmd)} was imported as ${quoteValueForWarning(stripped)}: a ${variable} path that is not a script the command runs becomes relative to the hook's working directory and is not restored on generate. Put the exact command in a tool-specific hooks override (such as "claudecode.hooks") to keep the variable.`);
|
|
57016
|
+
if (warn && result.retained) warn(`Hook command ${quoteValueForWarning(cmd)} keeps ${variable} in an assignment or option value, or in a later word of a command whose later words are not normalized, so the imported command still refers to it and other tools that do not define it receive it verbatim. Put the exact command in a tool-specific hooks override (such as "claudecode.hooks") if other tools should not see it.`);
|
|
57017
|
+
}
|
|
56684
57018
|
return stripped;
|
|
56685
57019
|
}
|
|
56686
57020
|
/**
|
|
@@ -56721,7 +57055,7 @@ function isStringRecord(value) {
|
|
|
56721
57055
|
}
|
|
56722
57056
|
/** Compare object values without letting key order decide the answer. */
|
|
56723
57057
|
function stableJson(value) {
|
|
56724
|
-
if (typeof value
|
|
57058
|
+
if (typeof value !== "object") return JSON.stringify(value);
|
|
56725
57059
|
return JSON.stringify(Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b))));
|
|
56726
57060
|
}
|
|
56727
57061
|
/**
|
|
@@ -56961,6 +57295,7 @@ function toolHookToCanonical({ h, rawEntry, eventName, converterConfig, warn })
|
|
|
56961
57295
|
}),
|
|
56962
57296
|
...importGroupPassthroughFields({
|
|
56963
57297
|
rawEntry,
|
|
57298
|
+
eventName,
|
|
56964
57299
|
converterConfig
|
|
56965
57300
|
}),
|
|
56966
57301
|
...matcher !== void 0 && matcher !== null && matcher !== "" && { matcher }
|
|
@@ -57013,15 +57348,26 @@ function definingFields({ h, rawEntry, hookType, converterConfig }) {
|
|
|
57013
57348
|
* is the group-level twin of `matcher`: importing the group without it would
|
|
57014
57349
|
* widen every hook in it, so the group is skipped instead.
|
|
57015
57350
|
*/
|
|
57016
|
-
function describeGroupSkipReason({ rawEntry, converterConfig }) {
|
|
57351
|
+
function describeGroupSkipReason({ rawEntry, eventName, converterConfig }) {
|
|
57017
57352
|
const entry = rawEntry;
|
|
57018
|
-
for (const { tool, valueType, subdividesGroup } of converterConfig.groupPassthroughFields ?? []) {
|
|
57353
|
+
for (const { tool, valueType, subdividesGroup, events, onInvalidImport } of converterConfig.groupPassthroughFields ?? []) {
|
|
57019
57354
|
const value = entry[tool];
|
|
57020
|
-
if (subdividesGroup !== true || value === void 0) continue;
|
|
57355
|
+
if (subdividesGroup !== true || onInvalidImport === "drop-field" || value === void 0 || !isGroupFieldOnEvent(events, eventName)) continue;
|
|
57021
57356
|
if (!isGroupPassthroughValue(value, valueType)) return `Skipping the hooks of a matcher group while importing: its "${tool}" (${quoteValueForWarning(value)}) is unusable, and these hooks run only where it matches. Importing them without it would widen when they fire, so they are skipped.`;
|
|
57022
57357
|
}
|
|
57023
57358
|
}
|
|
57024
57359
|
/**
|
|
57360
|
+
* Warn about each `"drop-field"` group value that is present but unusable;
|
|
57361
|
+
* {@link importGroupPassthroughFields} leaves it off the imported hooks.
|
|
57362
|
+
*/
|
|
57363
|
+
function warnDroppedGroupFields({ rawEntry, eventName, converterConfig, warn }) {
|
|
57364
|
+
const entry = rawEntry;
|
|
57365
|
+
for (const { tool, valueType, events, onInvalidImport } of converterConfig.groupPassthroughFields ?? []) {
|
|
57366
|
+
const value = entry[tool];
|
|
57367
|
+
if (onInvalidImport === "drop-field" && value !== void 0 && isGroupFieldOnEvent(events, eventName) && !isGroupPassthroughValue(value, valueType)) warn?.(`Dropping the unusable "${tool}" (${quoteValueForWarning(value)}) of a "${eventName}" matcher group while importing; its hooks are imported without it.`);
|
|
57368
|
+
}
|
|
57369
|
+
}
|
|
57370
|
+
/**
|
|
57025
57371
|
* Why this hook cannot be imported, or `undefined` when it can.
|
|
57026
57372
|
*/
|
|
57027
57373
|
function describeHookSkipReason({ h, rawEntry, hookType, converterConfig }) {
|
|
@@ -57048,12 +57394,19 @@ function toolMatcherEntryToCanonical({ rawEntry, eventName, converterConfig, war
|
|
|
57048
57394
|
const hookDefs = rawEntry.hooks ?? [];
|
|
57049
57395
|
const groupSkipReason = describeGroupSkipReason({
|
|
57050
57396
|
rawEntry,
|
|
57397
|
+
eventName,
|
|
57051
57398
|
converterConfig
|
|
57052
57399
|
});
|
|
57053
57400
|
if (groupSkipReason !== void 0) {
|
|
57054
57401
|
warn?.(groupSkipReason);
|
|
57055
57402
|
return [];
|
|
57056
57403
|
}
|
|
57404
|
+
warnDroppedGroupFields({
|
|
57405
|
+
rawEntry,
|
|
57406
|
+
eventName,
|
|
57407
|
+
converterConfig,
|
|
57408
|
+
warn
|
|
57409
|
+
});
|
|
57057
57410
|
const definitions = [];
|
|
57058
57411
|
for (const h of hookDefs) {
|
|
57059
57412
|
const skipReason = describeHookSkipReason({
|
|
@@ -60392,6 +60745,23 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
|
|
|
60392
60745
|
});
|
|
60393
60746
|
}
|
|
60394
60747
|
};
|
|
60748
|
+
// src/features/hooks/hook-matcher-tool-names.ts
|
|
60749
|
+
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60750
|
+
function isMatcherName(token) {
|
|
60751
|
+
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60752
|
+
}
|
|
60753
|
+
/**
|
|
60754
|
+
* Rewrites the whole tool names inside a hook `matcher` regex through `names`,
|
|
60755
|
+
* leaving escapes, character classes and unknown names as written. Used by
|
|
60756
|
+
* adapters whose runtime tool names differ from the canonical (Claude Code)
|
|
60757
|
+
* spelling.
|
|
60758
|
+
*/
|
|
60759
|
+
function replaceMatcherNames(matcher, names) {
|
|
60760
|
+
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60761
|
+
}
|
|
60762
|
+
function invertToolNames(names) {
|
|
60763
|
+
return Object.fromEntries(Object.entries(names).map(([canonical, tool]) => [tool, canonical]));
|
|
60764
|
+
}
|
|
60395
60765
|
// src/features/hooks/cortexcode-hooks.ts
|
|
60396
60766
|
const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
|
|
60397
60767
|
const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
@@ -60402,14 +60772,7 @@ const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
|
|
|
60402
60772
|
Grep: "grep",
|
|
60403
60773
|
Glob: "glob"
|
|
60404
60774
|
};
|
|
60405
|
-
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES =
|
|
60406
|
-
const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
|
|
60407
|
-
function isMatcherName(token) {
|
|
60408
|
-
return !token.startsWith("\\") && !token.startsWith("[");
|
|
60409
|
-
}
|
|
60410
|
-
function replaceMatcherNames(matcher, names) {
|
|
60411
|
-
return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
|
|
60412
|
-
}
|
|
60775
|
+
const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = invertToolNames(CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60413
60776
|
function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
|
|
60414
60777
|
const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
|
|
60415
60778
|
const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
|
|
@@ -65570,6 +65933,61 @@ var TabnineHooks = class TabnineHooks extends ToolHooks {
|
|
|
65570
65933
|
});
|
|
65571
65934
|
}
|
|
65572
65935
|
};
|
|
65936
|
+
// src/features/hooks/trae-hooks.ts
|
|
65937
|
+
const TRAE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set([
|
|
65938
|
+
"sessionStart",
|
|
65939
|
+
"beforeSubmitPrompt",
|
|
65940
|
+
"stop"
|
|
65941
|
+
]);
|
|
65942
|
+
const CANONICAL_TO_TRAE_TOOL_NAMES = { Bash: "RunCommand" };
|
|
65943
|
+
const TRAE_TO_CANONICAL_TOOL_NAMES = invertToolNames(CANONICAL_TO_TRAE_TOOL_NAMES);
|
|
65944
|
+
const TRAE_SPEC = {
|
|
65945
|
+
displayName: "TRAE",
|
|
65946
|
+
overrideKey: "trae",
|
|
65947
|
+
converterConfig: {
|
|
65948
|
+
supportedEvents: TRAE_HOOK_EVENTS,
|
|
65949
|
+
canonicalToToolEventNames: CANONICAL_TO_TRAE_EVENT_NAMES,
|
|
65950
|
+
toolToCanonicalEventNames: TRAE_TO_CANONICAL_EVENT_NAMES,
|
|
65951
|
+
projectDirVar: "$TRAE_PROJECT_DIR",
|
|
65952
|
+
stripsProjectDirVarOnImportOnly: true,
|
|
65953
|
+
importedProjectDirVarAliases: ["$CLAUDE_PROJECT_DIR"],
|
|
65954
|
+
noMatcherEvents: TRAE_NO_MATCHER_EVENTS,
|
|
65955
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
65956
|
+
groupPassthroughFields: [{
|
|
65957
|
+
canonical: "loop_limit",
|
|
65958
|
+
tool: "loop_limit",
|
|
65959
|
+
valueType: "number",
|
|
65960
|
+
events: /* @__PURE__ */ new Set(["stop"]),
|
|
65961
|
+
subdividesGroup: true,
|
|
65962
|
+
onInvalidImport: "drop-field"
|
|
65963
|
+
}],
|
|
65964
|
+
matcherTranslation: {
|
|
65965
|
+
toTool: ({ matcher }) => replaceMatcherNames(matcher, CANONICAL_TO_TRAE_TOOL_NAMES),
|
|
65966
|
+
toCanonical: (matcher) => replaceMatcherNames(matcher, TRAE_TO_CANONICAL_TOOL_NAMES)
|
|
65967
|
+
}
|
|
65968
|
+
}
|
|
65969
|
+
};
|
|
65970
|
+
/**
|
|
65971
|
+
* TRAE hooks.
|
|
65972
|
+
*
|
|
65973
|
+
* Hooks live under the top-level `hooks` key of the dedicated `.trae/hooks.json`
|
|
65974
|
+
* (project) and `~/.trae/hooks.json` (user scope). The IDE also writes a
|
|
65975
|
+
* `version` key there, so generation merges the `hooks` key into the file (see
|
|
65976
|
+
* `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
|
|
65977
|
+
*
|
|
65978
|
+
* @see https://docs.trae.ai/ide/hook-configuration-reference?_lang=en
|
|
65979
|
+
*/
|
|
65980
|
+
var TraeHooks = class extends SettingsJsonHooks {
|
|
65981
|
+
static getSpec() {
|
|
65982
|
+
return TRAE_SPEC;
|
|
65983
|
+
}
|
|
65984
|
+
static getSettablePaths(_options = {}) {
|
|
65985
|
+
return {
|
|
65986
|
+
relativeDirPath: TRAE_DIR,
|
|
65987
|
+
relativeFilePath: TRAE_HOOKS_FILE_NAME
|
|
65988
|
+
};
|
|
65989
|
+
}
|
|
65990
|
+
};
|
|
65573
65991
|
// src/features/hooks/vibe-hooks.ts
|
|
65574
65992
|
const VIBE_DIR = ".vibe";
|
|
65575
65993
|
const VIBE_HOOKS_FILE_NAME = "hooks.toml";
|
|
@@ -66754,6 +67172,21 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
66754
67172
|
supportedHookTypes: ["command"],
|
|
66755
67173
|
supportsMatcher: true
|
|
66756
67174
|
}],
|
|
67175
|
+
["trae", {
|
|
67176
|
+
class: TraeHooks,
|
|
67177
|
+
meta: {
|
|
67178
|
+
supportsProject: true,
|
|
67179
|
+
supportsGlobal: true,
|
|
67180
|
+
supportsImport: true
|
|
67181
|
+
},
|
|
67182
|
+
...converterCapabilities(TraeHooks.getConverterConfig()),
|
|
67183
|
+
supportsMatcher: true,
|
|
67184
|
+
matcherEvents: [
|
|
67185
|
+
"preToolUse",
|
|
67186
|
+
"postToolUse",
|
|
67187
|
+
"notification"
|
|
67188
|
+
]
|
|
67189
|
+
}],
|
|
66757
67190
|
["vibe", {
|
|
66758
67191
|
class: VibeHooks,
|
|
66759
67192
|
meta: {
|
|
@@ -68862,69 +69295,6 @@ var AiassistantMcp = class extends ToolMcp {
|
|
|
68862
69295
|
});
|
|
68863
69296
|
}
|
|
68864
69297
|
};
|
|
68865
|
-
// src/features/shared/amp-settings.ts
|
|
68866
|
-
/**
|
|
68867
|
-
* Parse an Amp `settings.json` / `settings.jsonc` document.
|
|
68868
|
-
*
|
|
68869
|
-
* Shared by the MCP and permissions adapters because both read the very same
|
|
68870
|
-
* file. Two separately maintained copies drifted once already: the permissions
|
|
68871
|
-
* side was hardened while the MCP side kept calling `jsonc-parser` directly,
|
|
68872
|
-
* which left it importing MCP servers reachable only through an injected
|
|
68873
|
-
* `__proto__`.
|
|
68874
|
-
*
|
|
68875
|
-
* {@link parseJsonc} is the strict, sanitizing parser: it throws on any syntax
|
|
68876
|
-
* error instead of returning `jsonc-parser`'s best-effort value, and it
|
|
68877
|
-
* rebuilds every object from its own enumerable entries, so a root `__proto__`
|
|
68878
|
-
* cannot swap the returned object's prototype and `constructor` / `prototype`
|
|
68879
|
-
* never survive as own keys, at any depth.
|
|
68880
|
-
*
|
|
68881
|
-
* Removal is silent, matching every other tool-side adapter (opencode, kilo,
|
|
68882
|
-
* copilot). Reporting the removed keys through `droppedPollutionKeysError` is
|
|
68883
|
-
* reserved for the three files a user authors under `.rulesync/`, whose whole
|
|
68884
|
-
* purpose is to be turned into tool config — a key that vanishes there needs
|
|
68885
|
-
* explaining, whereas here the surrounding settings are the user's own file
|
|
68886
|
-
* and are left alone.
|
|
68887
|
-
*
|
|
68888
|
-
* Note the consequence for a root `__proto__`: sanitizing runs before the
|
|
68889
|
-
* plain-object check, so such a document parses successfully with the key
|
|
68890
|
-
* stripped rather than failing that check. That is deliberate — dropping the
|
|
68891
|
-
* one poisoned key keeps the user's unrelated settings — and matches the
|
|
68892
|
-
* sanitize-before-the-root-check design the shared config gateway documents.
|
|
68893
|
-
*/
|
|
68894
|
-
function parseAmpSettings({ fileContent }) {
|
|
68895
|
-
let parsed;
|
|
68896
|
-
try {
|
|
68897
|
-
parsed = parseJsonc(fileContent || "{}");
|
|
68898
|
-
} catch (error) {
|
|
68899
|
-
throw new Error(`Failed to parse Amp settings: ${formatError(error)}`, { cause: error });
|
|
68900
|
-
}
|
|
68901
|
-
if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
|
|
68902
|
-
return parsed;
|
|
68903
|
-
}
|
|
68904
|
-
/**
|
|
68905
|
-
* Probe `<jsonDir>/settings.jsonc` first, falling back to `settings.json`, so
|
|
68906
|
-
* an existing user file is read-modified-written in place instead of a fresh
|
|
68907
|
-
* `.json` sibling being created next to a hand-authored `.jsonc`. Defaults to
|
|
68908
|
-
* `settings.json` when neither file exists: Amp's canonically documented
|
|
68909
|
-
* settings file (`.amp/settings.json`), shared by the MCP and permissions
|
|
68910
|
-
* adapters, so generating both on a clean repo writes a single settings file.
|
|
68911
|
-
*/
|
|
68912
|
-
async function resolveAmpSettingsFile(jsonDir) {
|
|
68913
|
-
const jsoncContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_JSONC_FILE_NAME));
|
|
68914
|
-
if (jsoncContent !== null) return {
|
|
68915
|
-
fileContent: jsoncContent,
|
|
68916
|
-
relativeFilePath: AMP_SETTINGS_JSONC_FILE_NAME
|
|
68917
|
-
};
|
|
68918
|
-
const jsonContent = await readFileContentOrNull(join(jsonDir, AMP_SETTINGS_FILE_NAME));
|
|
68919
|
-
if (jsonContent !== null) return {
|
|
68920
|
-
fileContent: jsonContent,
|
|
68921
|
-
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
68922
|
-
};
|
|
68923
|
-
return {
|
|
68924
|
-
fileContent: null,
|
|
68925
|
-
relativeFilePath: AMP_SETTINGS_FILE_NAME
|
|
68926
|
-
};
|
|
68927
|
-
}
|
|
68928
69298
|
// src/features/mcp/amp-mcp.ts
|
|
68929
69299
|
const AMP_MCP_SERVERS_KEY = "amp.mcpServers";
|
|
68930
69300
|
function filterMcpServers(mcpServers) {
|
|
@@ -71910,7 +72280,7 @@ var CopilotMcp = class CopilotMcp extends ToolMcp {
|
|
|
71910
72280
|
fileKey: sharedConfigFileKey(paths),
|
|
71911
72281
|
feature: "mcp",
|
|
71912
72282
|
existingContent,
|
|
71913
|
-
patch: { servers: convertToCopilotFormat(mcpServers) },
|
|
72283
|
+
patch: { servers: undefinedWhenEmpty(convertToCopilotFormat(mcpServers)) },
|
|
71914
72284
|
filePath,
|
|
71915
72285
|
logger
|
|
71916
72286
|
}),
|
|
@@ -74231,14 +74601,20 @@ function resolveGooseUrl(config) {
|
|
|
74231
74601
|
}
|
|
74232
74602
|
/**
|
|
74233
74603
|
* Determines the Goose `type` for a server based on its command/url/transport.
|
|
74604
|
+
*
|
|
74605
|
+
* Goose no longer has an SSE extension type (it skips a `type: sse` entry as
|
|
74606
|
+
* unsupported), so every remote server is written as `streamable_http`.
|
|
74234
74607
|
*/
|
|
74235
74608
|
function resolveGooseType(config, url) {
|
|
74236
74609
|
if (config.command !== void 0) return "stdio";
|
|
74237
|
-
if (url !== void 0) return
|
|
74610
|
+
if (url !== void 0) return "streamable_http";
|
|
74238
74611
|
return canonicalTransport(config) === "builtin" ? "builtin" : "stdio";
|
|
74239
74612
|
}
|
|
74240
74613
|
/**
|
|
74241
|
-
* The Goose extension types that carry an MCP server.
|
|
74614
|
+
* The Goose extension types that carry an MCP server. `sse` is no longer one
|
|
74615
|
+
* Goose reads, but it stays here so a stale `type: sse` entry is still
|
|
74616
|
+
* recognized: imported as canonical `sse` and replaced by the regenerated
|
|
74617
|
+
* same-name `streamable_http` entry. Goose also documents
|
|
74242
74618
|
* `builtin`, `platform`, `frontend` and `inline_python` extensions, which have
|
|
74243
74619
|
* no canonical MCP counterpart: they name capabilities Goose provides itself
|
|
74244
74620
|
* rather than a server rulesync could describe.
|
|
@@ -74298,7 +74674,8 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
74298
74674
|
warnWithFallback(logger, `Goose extension "${name}" has no command to run; skipping it rather than writing a stdio extension Goose cannot start to ~/.config/goose/config.yaml.`);
|
|
74299
74675
|
return;
|
|
74300
74676
|
}
|
|
74301
|
-
} else if (gooseType === "
|
|
74677
|
+
} else if (gooseType === "streamable_http") {
|
|
74678
|
+
if (canonicalTransport(config) === "sse") warnWithFallback(logger, `Goose extension "${name}" uses SSE, which Goose no longer supports; writing it as streamable_http, which only works if the server also speaks Streamable HTTP.`);
|
|
74302
74679
|
if (url !== void 0) ext.uri = url;
|
|
74303
74680
|
if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
|
|
74304
74681
|
}
|
|
@@ -74311,8 +74688,9 @@ function convertServerToGooseExtension(name, config, logger) {
|
|
|
74311
74688
|
* Converts rulesync canonical MCP servers into Goose `extensions:` entries.
|
|
74312
74689
|
*
|
|
74313
74690
|
* Goose uses a non-standard schema: `name`, `type` (`stdio` | `streamable_http`
|
|
74314
|
-
* | `
|
|
74315
|
-
*
|
|
74691
|
+
* | `builtin`), `cmd`/`args`/`envs` for stdio, `uri`/`headers` for remote, plus
|
|
74692
|
+
* `enabled` and `timeout`. A canonical `sse` server is written as
|
|
74693
|
+
* `streamable_http` with a warning, since Goose dropped SSE support.
|
|
74316
74694
|
*
|
|
74317
74695
|
* `extensions:` is co-owned: alongside the MCP servers rulesync manages it also
|
|
74318
74696
|
* holds Goose's own `builtin`/`platform`/`frontend`/`inline_python` extensions
|
|
@@ -74443,7 +74821,8 @@ function convertToGoosePluginMcpServers(mcpServers, logger) {
|
|
|
74443
74821
|
* `~/.config/goose/config.yaml`. That file also holds other Goose settings
|
|
74444
74822
|
* (model, provider, ...), so generation merges the `extensions:` block into the
|
|
74445
74823
|
* existing config instead of overwriting it, and the file is never deleted.
|
|
74446
|
-
* This location supports both stdio and remote (
|
|
74824
|
+
* This location supports both stdio and remote servers (SSE is written as
|
|
74825
|
+
* `streamable_http`, since Goose no longer reads `type: sse`).
|
|
74447
74826
|
* - **Project**: a stdio-only open-plugin manifest at
|
|
74448
74827
|
* `.agents/plugins/rulesync/.mcp.json` (Goose v1.39.0+), reusing the same
|
|
74449
74828
|
* `.agents/plugins/rulesync/` tree as Goose hooks. The manifest uses the
|
|
@@ -79161,6 +79540,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
79161
79540
|
const managedNames = new Set(Object.keys(rulesyncServers));
|
|
79162
79541
|
const mcpServers = Object.entries(rulesyncServers).map(([name, server]) => rulesyncMcpServerToVibe(name, server, existingByName.get(name)));
|
|
79163
79542
|
const unmanagedServers = existingServers.filter((server) => !managedNames.has(server.name));
|
|
79543
|
+
const servers = [...mcpServers, ...unmanagedServers];
|
|
79164
79544
|
return new VibeMcp({
|
|
79165
79545
|
outputRoot,
|
|
79166
79546
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -79169,7 +79549,7 @@ var VibeMcp = class VibeMcp extends ToolMcp {
|
|
|
79169
79549
|
fileKey: sharedConfigFileKey(paths),
|
|
79170
79550
|
feature: "mcp",
|
|
79171
79551
|
existingContent,
|
|
79172
|
-
patch: { mcp_servers:
|
|
79552
|
+
patch: { mcp_servers: servers.length > 0 ? servers : void 0 },
|
|
79173
79553
|
filePath
|
|
79174
79554
|
}),
|
|
79175
79555
|
validate,
|
|
@@ -82300,6 +82680,57 @@ function matchesGlobStep(step, character) {
|
|
|
82300
82680
|
const admitted = step.members.has(character) || step.ranges.some(([low, high]) => code >= low && code <= high);
|
|
82301
82681
|
return step.negated ? !admitted : admitted;
|
|
82302
82682
|
}
|
|
82683
|
+
/**
|
|
82684
|
+
* Whether `value` matches `glob` in full, with `*`, `?` and `[...]` carrying
|
|
82685
|
+
* their usual meaning. The glob comes first, unlike Node's own
|
|
82686
|
+
* `path.matchesGlob(path, pattern)`.
|
|
82687
|
+
*
|
|
82688
|
+
* The glob is walked step by step rather than translated to a regex, because
|
|
82689
|
+
* both sides of a comparison can come from a file a repository carries: `*a*a*a*a*b`
|
|
82690
|
+
* translated to `^.*a.*a.*a.*a.*b$` takes minutes against a forty-character
|
|
82691
|
+
* name, while walking the steps is linear in the name per `*`. Remembering only
|
|
82692
|
+
* the most recent `*` to fall back to is enough — an earlier one can always give
|
|
82693
|
+
* up whatever the later one needed.
|
|
82694
|
+
*/
|
|
82695
|
+
function matchesGlob(glob, value) {
|
|
82696
|
+
return compileGlob(glob)(value);
|
|
82697
|
+
}
|
|
82698
|
+
/**
|
|
82699
|
+
* Parse `glob` once and return a predicate that walks it, for a caller that
|
|
82700
|
+
* tests the same glob against a whole list of names.
|
|
82701
|
+
*/
|
|
82702
|
+
function compileGlob(glob) {
|
|
82703
|
+
const steps = parseGlob(glob);
|
|
82704
|
+
return (value) => matchesParsedGlob(steps, value);
|
|
82705
|
+
}
|
|
82706
|
+
function matchesParsedGlob(steps, value) {
|
|
82707
|
+
const characters = [...value];
|
|
82708
|
+
let stepIndex = 0;
|
|
82709
|
+
let characterIndex = 0;
|
|
82710
|
+
let starStepIndex = -1;
|
|
82711
|
+
let starCharacterIndex = 0;
|
|
82712
|
+
while (characterIndex < characters.length) {
|
|
82713
|
+
const step = steps[stepIndex];
|
|
82714
|
+
if (step?.kind === "star") {
|
|
82715
|
+
starStepIndex = stepIndex;
|
|
82716
|
+
starCharacterIndex = characterIndex;
|
|
82717
|
+
stepIndex += 1;
|
|
82718
|
+
continue;
|
|
82719
|
+
}
|
|
82720
|
+
if (step !== void 0 && matchesGlobStep(step, characters[characterIndex] ?? "")) {
|
|
82721
|
+
stepIndex += 1;
|
|
82722
|
+
characterIndex += 1;
|
|
82723
|
+
continue;
|
|
82724
|
+
}
|
|
82725
|
+
if (starStepIndex < 0) return false;
|
|
82726
|
+
starCharacterIndex += 1;
|
|
82727
|
+
stepIndex = starStepIndex + 1;
|
|
82728
|
+
characterIndex = starCharacterIndex;
|
|
82729
|
+
}
|
|
82730
|
+
let remaining = stepIndex;
|
|
82731
|
+
while (steps[remaining]?.kind === "star") remaining += 1;
|
|
82732
|
+
return remaining === steps.length;
|
|
82733
|
+
}
|
|
82303
82734
|
/** Whether two single-character steps can both match one same character. */
|
|
82304
82735
|
function stepsShareACharacter(left, right) {
|
|
82305
82736
|
if (left.kind === "any" || right.kind === "any") return true;
|
|
@@ -82542,6 +82973,20 @@ function isUnicodeClass(rest) {
|
|
|
82542
82973
|
}));
|
|
82543
82974
|
}
|
|
82544
82975
|
/**
|
|
82976
|
+
* Return a function that, for a `[:` at `index`, gives the position of the
|
|
82977
|
+
* next `:]` (Go reads everything between as the class name), or -1. The
|
|
82978
|
+
* position is cached and `index` only grows, so a long run of `[:` with no
|
|
82979
|
+
* `:]` costs linear time rather than a scan to the end at each one.
|
|
82980
|
+
*/
|
|
82981
|
+
function posixEndFinder(source) {
|
|
82982
|
+
let next = -2;
|
|
82983
|
+
return (index) => {
|
|
82984
|
+
if (!source.startsWith("[:", index)) return -1;
|
|
82985
|
+
if (next !== -1 && next < index + 2) next = source.indexOf(":]", index + 2);
|
|
82986
|
+
return next;
|
|
82987
|
+
};
|
|
82988
|
+
}
|
|
82989
|
+
/**
|
|
82545
82990
|
* Replace each POSIX class (`[:alpha:]`) inside a bracket class with a plain
|
|
82546
82991
|
* character, so JavaScript can compile the rest, or return `undefined` for an
|
|
82547
82992
|
* unknown one. Only inside a bracket is `[:name:]` a POSIX class; a bare
|
|
@@ -82551,6 +82996,7 @@ function replacePosixClasses(source) {
|
|
|
82551
82996
|
let result = "";
|
|
82552
82997
|
let inClass = false;
|
|
82553
82998
|
let index = 0;
|
|
82999
|
+
const findPosixEnd = posixEndFinder(source);
|
|
82554
83000
|
while (index < source.length) {
|
|
82555
83001
|
const character = source[index] ?? "";
|
|
82556
83002
|
if (character === "\\") {
|
|
@@ -82575,11 +83021,11 @@ function replacePosixClasses(source) {
|
|
|
82575
83021
|
}
|
|
82576
83022
|
continue;
|
|
82577
83023
|
}
|
|
82578
|
-
const
|
|
82579
|
-
if (
|
|
82580
|
-
if (!POSIX_CLASSES.has(
|
|
83024
|
+
const posixEnd = findPosixEnd(index);
|
|
83025
|
+
if (posixEnd !== -1) {
|
|
83026
|
+
if (!POSIX_CLASSES.has(source.slice(index + 2, posixEnd).replace(/^\^/, ""))) return;
|
|
82581
83027
|
result += "a";
|
|
82582
|
-
index
|
|
83028
|
+
index = posixEnd + 2;
|
|
82583
83029
|
continue;
|
|
82584
83030
|
}
|
|
82585
83031
|
if (character === "]") inClass = false;
|
|
@@ -82658,6 +83104,7 @@ function hasClassEscapeRangeEnd(source) {
|
|
|
82658
83104
|
let inClass = false;
|
|
82659
83105
|
let afterCharacter = false;
|
|
82660
83106
|
let index = 0;
|
|
83107
|
+
const findPosixEnd = posixEndFinder(source);
|
|
82661
83108
|
while (index < source.length) {
|
|
82662
83109
|
const character = source[index] ?? "";
|
|
82663
83110
|
if (character === "\\") {
|
|
@@ -82673,7 +83120,7 @@ function hasClassEscapeRangeEnd(source) {
|
|
|
82673
83120
|
});
|
|
82674
83121
|
continue;
|
|
82675
83122
|
}
|
|
82676
|
-
const posixEnd =
|
|
83123
|
+
const posixEnd = findPosixEnd(index);
|
|
82677
83124
|
if (character === "]" || posixEnd !== -1) {
|
|
82678
83125
|
inClass = character !== "]";
|
|
82679
83126
|
afterCharacter = false;
|
|
@@ -83235,6 +83682,8 @@ function buildToolPatternEntry({ toolName, pattern }) {
|
|
|
83235
83682
|
* - `execute_url` takes a domain, like `read_url`.
|
|
83236
83683
|
* - `command` and `unsandboxed` take literal words or a per-word `regex:`,
|
|
83237
83684
|
* matched word by word (see `antigravity-command-patterns.ts`).
|
|
83685
|
+
* - `mcp` takes `server/tool` or `server/*`, matched exactly. A canonical
|
|
83686
|
+
* `mcp__<server>__<tool>` category is written as that target.
|
|
83238
83687
|
*
|
|
83239
83688
|
* A glob copied verbatim (`read_file(**\/*.env)`) is a literal path that
|
|
83240
83689
|
* matches nothing, so a deny written that way blocks nothing. A pattern with
|
|
@@ -83258,7 +83707,8 @@ const DENY_HINTS = {
|
|
|
83258
83707
|
read_file: "deny a directory or file path instead",
|
|
83259
83708
|
write_file: "deny a directory or file path instead",
|
|
83260
83709
|
command: "deny the first words of the command instead",
|
|
83261
|
-
unsandboxed: "deny the first words of the command instead"
|
|
83710
|
+
unsandboxed: "deny the first words of the command instead",
|
|
83711
|
+
mcp: "deny the whole MCP server or tool instead"
|
|
83262
83712
|
};
|
|
83263
83713
|
function hasGlob(pattern) {
|
|
83264
83714
|
return BRACE_ALTERNATIVES.test(pattern) || parseGlobPattern(pattern).steps.some((step) => step.kind !== "literal");
|
|
@@ -83314,6 +83764,40 @@ function toTarget(action, pattern) {
|
|
|
83314
83764
|
if (URL_ACTIONS.has(action)) return toUrlTarget(pattern);
|
|
83315
83765
|
return { target: pattern };
|
|
83316
83766
|
}
|
|
83767
|
+
const MCP_CATEGORY_PREFIX = "mcp__";
|
|
83768
|
+
const MCP_TARGET_SEPARATORS = /[/()]/;
|
|
83769
|
+
/**
|
|
83770
|
+
* The `mcp` target of a canonical `mcp__<server>__<tool>` category:
|
|
83771
|
+
* `<server>/<tool>`, `<server>/*` for a whole server (`mcp__<server>`,
|
|
83772
|
+
* `mcp__<server>__*`), or `*` for every MCP tool (`mcp__*`, `mcp__*__*`). The
|
|
83773
|
+
* server ends at the first `__`, as elsewhere in rulesync. Antigravity matches
|
|
83774
|
+
* names exactly, so a glob, an empty name, or a `/` or parenthesis in a name
|
|
83775
|
+
* has no target; when only the tool part is at fault, its server is the `lead`
|
|
83776
|
+
* a deny widens to.
|
|
83777
|
+
*/
|
|
83778
|
+
function toMcpTarget(category) {
|
|
83779
|
+
const remainder = category.slice(5);
|
|
83780
|
+
const separator = remainder.indexOf("__");
|
|
83781
|
+
const server = separator === -1 ? remainder : remainder.slice(0, separator);
|
|
83782
|
+
const tool = separator === -1 ? "*" : remainder.slice(separator + 2);
|
|
83783
|
+
if (server === "*" && tool === "*") return { target: "*" };
|
|
83784
|
+
const skipReason = "Antigravity MCP targets name one server or one tool exactly. Only `mcp__<server>`, `mcp__<server>__*`, `mcp__<server>__<tool>` or `mcp__*` can be expressed";
|
|
83785
|
+
if (server.length === 0 || MCP_TARGET_SEPARATORS.test(server) || hasGlob(server)) return { skipReason };
|
|
83786
|
+
if (tool === "*") return { target: `${server}/*` };
|
|
83787
|
+
return tool.length === 0 || MCP_TARGET_SEPARATORS.test(tool) || hasGlob(tool) ? {
|
|
83788
|
+
skipReason,
|
|
83789
|
+
lead: `${server}/*`
|
|
83790
|
+
} : { target: `${server}/${tool}` };
|
|
83791
|
+
}
|
|
83792
|
+
/** The target of a canonical `mcp__...` rule, with the `lead` a deny widens to. */
|
|
83793
|
+
function toMcpRuleTarget(category, pattern) {
|
|
83794
|
+
const target = toMcpTarget(category);
|
|
83795
|
+
if (pattern === "*" || !("target" in target)) return target;
|
|
83796
|
+
return {
|
|
83797
|
+
skipReason: "Antigravity MCP targets name a server or a tool, not the tool's arguments",
|
|
83798
|
+
lead: target.target
|
|
83799
|
+
};
|
|
83800
|
+
}
|
|
83317
83801
|
/**
|
|
83318
83802
|
* The leading directories of a path glob (`src` for `src/*.ts`), or
|
|
83319
83803
|
* `undefined` when it has none. A directory target covers everything inside
|
|
@@ -83336,6 +83820,20 @@ function toWidenedDenyTarget(action, pattern) {
|
|
|
83336
83820
|
if (ANTIGRAVITY_COMMAND_ACTIONS.has(action)) return toAntigravityCommandLiteralPrefix(pattern);
|
|
83337
83821
|
if (PATH_ACTIONS.has(action)) return toPathLiteralPrefix(pattern);
|
|
83338
83822
|
}
|
|
83823
|
+
/** The entry a deny is widened to, and what it now blocks. */
|
|
83824
|
+
function describeWidenedDeny({ action, prefix, isMcp }) {
|
|
83825
|
+
if (isMcp) return prefix === "*" ? {
|
|
83826
|
+
entry: action,
|
|
83827
|
+
scope: "every MCP tool"
|
|
83828
|
+
} : {
|
|
83829
|
+
entry: `${action}(${prefix})`,
|
|
83830
|
+
scope: `every call of \`${action}(${prefix})\``
|
|
83831
|
+
};
|
|
83832
|
+
return {
|
|
83833
|
+
entry: `${action}(${prefix})`,
|
|
83834
|
+
scope: PATH_ACTIONS.has(action) ? `every path under \`${prefix}\`` : `every command that starts with \`${prefix}\``
|
|
83835
|
+
};
|
|
83836
|
+
}
|
|
83339
83837
|
/**
|
|
83340
83838
|
* Warn about a path target that is a home or variable reference. Antigravity
|
|
83341
83839
|
* documents absolute and workspace-relative paths only, so such a reference
|
|
@@ -83357,14 +83855,20 @@ function warnHomeOrVariablePath({ action, target, rule, decision, logger, toolLa
|
|
|
83357
83855
|
* nothing enforces it, and a widened one is warned about too, because it
|
|
83358
83856
|
* blocks more than it says.
|
|
83359
83857
|
*/
|
|
83360
|
-
function buildAntigravityPermissionEntry({ action, category, pattern, decision, logger, toolLabel }) {
|
|
83361
|
-
const
|
|
83858
|
+
function buildAntigravityPermissionEntry({ action: toolAction, category, pattern, decision, logger, toolLabel }) {
|
|
83859
|
+
const isMcp = category.startsWith(MCP_CATEGORY_PREFIX);
|
|
83860
|
+
const action = isMcp ? "mcp" : toolAction;
|
|
83861
|
+
const result = isMcp ? toMcpRuleTarget(category, pattern) : pattern === "*" ? { target: "*" } : toTarget(action, pattern);
|
|
83362
83862
|
const rule = `${category}: { "${pattern}": "${decision}" }`;
|
|
83363
83863
|
if ("skipReason" in result) {
|
|
83364
|
-
const prefix = decision
|
|
83864
|
+
const prefix = decision !== "deny" ? void 0 : isMcp ? result.lead : toWidenedDenyTarget(action, pattern);
|
|
83365
83865
|
if (prefix !== void 0) {
|
|
83366
|
-
const
|
|
83367
|
-
|
|
83866
|
+
const { entry, scope } = describeWidenedDeny({
|
|
83867
|
+
action,
|
|
83868
|
+
prefix,
|
|
83869
|
+
isMcp
|
|
83870
|
+
});
|
|
83871
|
+
logger.warn(`${toolLabel} permissions: widening ${rule} to ${entry}. ${result.skipReason}. The deny now blocks ${scope}, overriding any allow or ask for them.`);
|
|
83368
83872
|
warnHomeOrVariablePath({
|
|
83369
83873
|
action,
|
|
83370
83874
|
target: prefix,
|
|
@@ -83373,7 +83877,7 @@ function buildAntigravityPermissionEntry({ action, category, pattern, decision,
|
|
|
83373
83877
|
logger,
|
|
83374
83878
|
toolLabel
|
|
83375
83879
|
});
|
|
83376
|
-
return
|
|
83880
|
+
return entry;
|
|
83377
83881
|
}
|
|
83378
83882
|
const consequence = decision === "deny" ? `This deny is NOT enforced in ${toolLabel}; ${DENY_HINTS[action] ?? "write it in a form Antigravity can match"}` : decision === "ask" ? `${toolLabel} falls back to its default for it, so it runs without a prompt when ${toolLabel} is set to always proceed` : `${toolLabel} falls back to its default for it`;
|
|
83379
83883
|
logger.warn(`${toolLabel} permissions: skipping ${rule}. ${result.skipReason}. ${consequence}.`);
|
|
@@ -83517,6 +84021,7 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
|
|
|
83517
84021
|
mcp: "mcp"
|
|
83518
84022
|
};
|
|
83519
84023
|
function toAntigravityCliToolName(canonical) {
|
|
84024
|
+
if (canonical.startsWith("mcp__")) return "mcp";
|
|
83520
84025
|
return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
|
|
83521
84026
|
}
|
|
83522
84027
|
function toCanonicalToolName$9(cliName) {
|
|
@@ -83579,10 +84084,11 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
83579
84084
|
logger
|
|
83580
84085
|
});
|
|
83581
84086
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toAntigravityCliToolName(category)));
|
|
84087
|
+
const isManagedToolName = (toolName) => managedToolNames.has(toolName) || managedToolNames.has("mcp") && toolName.startsWith("mcp__");
|
|
83582
84088
|
const existingPermissions = settings.permissions ?? {};
|
|
83583
|
-
const preservedAllow = (existingPermissions.allow ?? []).filter((entry) => !
|
|
83584
|
-
const preservedAsk = (existingPermissions.ask ?? []).filter((entry) => !
|
|
83585
|
-
const preservedDeny = (existingPermissions.deny ?? []).filter((entry) => !
|
|
84089
|
+
const preservedAllow = (existingPermissions.allow ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
84090
|
+
const preservedAsk = (existingPermissions.ask ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
84091
|
+
const preservedDeny = (existingPermissions.deny ?? []).filter((entry) => !isManagedToolName(parseToolPatternEntry(entry).toolName));
|
|
83586
84092
|
const mergedPermissions = { ...existingPermissions };
|
|
83587
84093
|
const mergedAllow = uniq([...preservedAllow, ...allow].toSorted());
|
|
83588
84094
|
const mergedAsk = uniq([...preservedAsk, ...ask].toSorted());
|
|
@@ -83691,6 +84197,7 @@ const IDE_ACTION_TO_CANONICAL = {
|
|
|
83691
84197
|
mcp: "mcp"
|
|
83692
84198
|
};
|
|
83693
84199
|
function toIdeAction(canonical) {
|
|
84200
|
+
if (canonical.startsWith("mcp__")) return "mcp";
|
|
83694
84201
|
return CANONICAL_TO_IDE_ACTION[canonical] ?? canonical;
|
|
83695
84202
|
}
|
|
83696
84203
|
function toCanonicalCategory$2(ideAction) {
|
|
@@ -83747,8 +84254,9 @@ var AntigravityIdePermissions = class AntigravityIdePermissions extends ToolPerm
|
|
|
83747
84254
|
logger
|
|
83748
84255
|
});
|
|
83749
84256
|
const managedActions = new Set(Object.keys(config.permission).map((category) => toIdeAction(category)));
|
|
84257
|
+
const isManagedAction = (action) => managedActions.has(action) || managedActions.has("mcp") && action.startsWith("mcp__");
|
|
83750
84258
|
const existingPermissions = settings.permissions ?? {};
|
|
83751
|
-
const preserve = (entries) => (entries ?? []).filter((entry) => !
|
|
84259
|
+
const preserve = (entries) => (entries ?? []).filter((entry) => !isManagedAction(parseToolPatternEntry(entry).toolName));
|
|
83752
84260
|
const mergedPermissions = { ...existingPermissions };
|
|
83753
84261
|
const mergedAllow = uniq([...preserve(existingPermissions.allow), ...allow].toSorted());
|
|
83754
84262
|
const mergedAsk = uniq([...preserve(existingPermissions.ask), ...ask].toSorted());
|
|
@@ -83836,9 +84344,13 @@ const CANONICAL_TO_AUGMENT_TOOL_NAMES = {
|
|
|
83836
84344
|
const AUGMENT_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_AUGMENT_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
83837
84345
|
const CURRENT_TO_LEGACY_AUGMENT_TOOL_NAMES = {
|
|
83838
84346
|
terminal: "launch-process",
|
|
84347
|
+
bash: "launch-process",
|
|
84348
|
+
shell: "launch-process",
|
|
83839
84349
|
read: "view",
|
|
83840
84350
|
edit: "str-replace-editor",
|
|
83841
|
-
|
|
84351
|
+
str_replace_editor: "str-replace-editor",
|
|
84352
|
+
write: "save-file",
|
|
84353
|
+
save_file: "save-file"
|
|
83842
84354
|
};
|
|
83843
84355
|
function toAugmentToolName(canonical) {
|
|
83844
84356
|
return lookupOwn({
|
|
@@ -83990,6 +84502,7 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
83990
84502
|
while (i < body.length) {
|
|
83991
84503
|
const ch = body[i];
|
|
83992
84504
|
if (ch === "\\" && i + 1 < body.length) {
|
|
84505
|
+
if (isGlobSpecial(body[i + 1])) return false;
|
|
83993
84506
|
i += 2;
|
|
83994
84507
|
continue;
|
|
83995
84508
|
}
|
|
@@ -84006,6 +84519,37 @@ function isShellRegexRoundtrippable(regex) {
|
|
|
84006
84519
|
}
|
|
84007
84520
|
return true;
|
|
84008
84521
|
}
|
|
84522
|
+
/** Characters a glob reads as syntax (wildcards or the start of a character class). */
|
|
84523
|
+
function isGlobSpecial(ch) {
|
|
84524
|
+
return ch === "*" || ch === "?" || ch === "[";
|
|
84525
|
+
}
|
|
84526
|
+
/**
|
|
84527
|
+
* Whether `shellRegexToGlob` can approximate a `shellInputRegex` without widening it. The lossy
|
|
84528
|
+
* conversion keeps regex quantifiers as characters, and a `*` or `?` left in the glob becomes a
|
|
84529
|
+
* wildcard: `^rm -rf /tmp/a*$` (a run of `a`s) would turn into the glob `rm -rf /tmp/a*`, which
|
|
84530
|
+
* matches `rm -rf /tmp/a /home`. So a regex is unsafe when it has an escaped literal `\*` / `\?` /
|
|
84531
|
+
* `\[` (a `[` would open a character class in targets whose globs support one),
|
|
84532
|
+
* or a bare quantifier `*` / `?` / `+` / `{` that is not part of a `.*` run (which the glob `*`
|
|
84533
|
+
* reproduces faithfully).
|
|
84534
|
+
*/
|
|
84535
|
+
function canApproximateShellRegexWithoutWidening(regex) {
|
|
84536
|
+
let i = 0;
|
|
84537
|
+
while (i < regex.length) {
|
|
84538
|
+
const ch = regex[i];
|
|
84539
|
+
if (ch === "\\" && i + 1 < regex.length) {
|
|
84540
|
+
if (isGlobSpecial(regex[i + 1])) return false;
|
|
84541
|
+
i += 2;
|
|
84542
|
+
continue;
|
|
84543
|
+
}
|
|
84544
|
+
if (ch === "." && regex[i + 1] === "*") {
|
|
84545
|
+
i += 2;
|
|
84546
|
+
continue;
|
|
84547
|
+
}
|
|
84548
|
+
if (ch === "*" || ch === "?" || ch === "+" || ch === "{") return false;
|
|
84549
|
+
i += 1;
|
|
84550
|
+
}
|
|
84551
|
+
return true;
|
|
84552
|
+
}
|
|
84009
84553
|
const MANAGED_AUGMENT_TOOL_NAMES = new Set(Object.values(CANONICAL_TO_AUGMENT_TOOL_NAMES));
|
|
84010
84554
|
var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermissions {
|
|
84011
84555
|
constructor(params) {
|
|
@@ -84442,12 +84986,18 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
|
|
|
84442
84986
|
if (legacyToolName === "launch-process" && entry.shellInputRegex) {
|
|
84443
84987
|
const regex = entry.shellInputRegex;
|
|
84444
84988
|
if (isShellRegexRoundtrippable(regex)) pattern = shellRegexToGlob(regex);
|
|
84445
|
-
else
|
|
84446
|
-
|
|
84447
|
-
|
|
84448
|
-
|
|
84449
|
-
|
|
84450
|
-
|
|
84989
|
+
else {
|
|
84990
|
+
if (action !== "deny" && !canApproximateShellRegexWithoutWidening(regex)) {
|
|
84991
|
+
logger?.warn(`AugmentCode permissions: skipping ${action} entry for tool '${entry.toolName}': shellInputRegex '${regex}' uses a quantifier or a literal '*' / '?' that a glob cannot express, and converting it would widen the rule. The entry is not imported.`);
|
|
84992
|
+
continue;
|
|
84993
|
+
}
|
|
84994
|
+
if (action === "deny") {
|
|
84995
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as the catch-all '*' pattern (fail-closed) so the deny rule cannot be silently narrowed on regenerate.`);
|
|
84996
|
+
pattern = "*";
|
|
84997
|
+
} else {
|
|
84998
|
+
pattern = shellRegexToGlob(regex);
|
|
84999
|
+
logger?.warn(`AugmentCode permissions: shellInputRegex '${regex}' on tool '${entry.toolName}' is not faithfully roundtrippable to a glob. Importing as glob '${pattern}'; the rule may match a different set of inputs after regenerate.`);
|
|
85000
|
+
}
|
|
84451
85001
|
}
|
|
84452
85002
|
} else pattern = "*";
|
|
84453
85003
|
if (isPrototypePollutionKey(pattern)) {
|
|
@@ -85940,15 +86490,19 @@ const CLAUDECODE_ENV_IGNORED_IN_EVERY_FILE = /* @__PURE__ */ new Set([
|
|
|
85940
86490
|
* `env` variables a project or local settings file cannot set, because a
|
|
85941
86491
|
* checked-out repository should not control them: where Claude Code stores its
|
|
85942
86492
|
* own files, what session content it exports, where telemetry goes, how it
|
|
85943
|
-
* starts or syncs, the timers on an unanswered
|
|
85944
|
-
*
|
|
86493
|
+
* starts or syncs, which claude.ai sign-in it uses, the timers on an unanswered
|
|
86494
|
+
* dialog, and whether it processes attachments or expands `@` file mentions,
|
|
86495
|
+
* and the screen reader (`CLAUDE_AX_*`) variables. Claude Code drops them there, so they are skipped at project scope and emitted only under `--global`.
|
|
85945
86496
|
*
|
|
85946
|
-
* Only the names the settings reference lists are covered; its directory
|
|
85947
|
-
* Windows groups are introduced with "such as", so an unlisted member of those
|
|
86497
|
+
* Only the names the settings reference lists are covered; its directory,
|
|
86498
|
+
* Windows and sign-in groups are introduced with "such as", so an unlisted member of those
|
|
85948
86499
|
* families is still written. Windows variable names are case-insensitive, so
|
|
85949
|
-
* that group is matched without regard to case.
|
|
86500
|
+
* that group is matched without regard to case. `ALLUSERSPROFILE`,
|
|
86501
|
+
* `SystemDrive` and the `CommonProgramFiles` family are ignored there since
|
|
86502
|
+
* Claude Code v2.1.285.
|
|
85950
86503
|
*
|
|
85951
86504
|
* @see https://code.claude.com/docs/en/settings-reference#variables-claude-code-ignores-in-env
|
|
86505
|
+
* @see https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md
|
|
85952
86506
|
*/
|
|
85953
86507
|
const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
85954
86508
|
"BETA_TRACING_ENDPOINT",
|
|
@@ -85958,6 +86512,9 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85958
86512
|
"CLAUDE_CODE_DISABLE_FILE_MENTIONS",
|
|
85959
86513
|
"CLAUDE_CODE_ENABLE_TELEMETRY",
|
|
85960
86514
|
"CLAUDE_CODE_ENHANCED_TELEMETRY_BETA",
|
|
86515
|
+
"CLAUDE_CODE_OAUTH_REFRESH_TOKEN",
|
|
86516
|
+
"CLAUDE_CODE_OAUTH_SCOPES",
|
|
86517
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
85961
86518
|
"CLAUDE_CODE_PLUGIN_CACHE_DIR",
|
|
85962
86519
|
"CLAUDE_CODE_PLUGIN_SEED_DIR",
|
|
85963
86520
|
"CLAUDE_CODE_PROCESS_WRAPPER",
|
|
@@ -85978,14 +86535,23 @@ const CLAUDECODE_ENV_PROJECT_IGNORED_NAMES = /* @__PURE__ */ new Set([
|
|
|
85978
86535
|
"TMPDIR"
|
|
85979
86536
|
]);
|
|
85980
86537
|
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES = /* @__PURE__ */ new Set([
|
|
86538
|
+
"ALLUSERSPROFILE",
|
|
86539
|
+
"COMMONPROGRAMW6432",
|
|
85981
86540
|
"COMSPEC",
|
|
85982
86541
|
"LOCALAPPDATA",
|
|
85983
86542
|
"PATHEXT",
|
|
85984
86543
|
"PROGRAMDATA",
|
|
85985
86544
|
"PROGRAMW6432",
|
|
85986
86545
|
"PSMODULEPATH",
|
|
86546
|
+
"SYSTEMDRIVE",
|
|
85987
86547
|
"SYSTEMROOT"
|
|
85988
86548
|
]);
|
|
86549
|
+
/**
|
|
86550
|
+
* Windows name prefixes matched as whole families: `ProgramFiles*` and
|
|
86551
|
+
* `CommonProgramFiles*`. `ProgramW6432` and `CommonProgramW6432` are exact
|
|
86552
|
+
* names in the set above.
|
|
86553
|
+
*/
|
|
86554
|
+
const CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES = ["PROGRAMFILES", "COMMONPROGRAMFILES"];
|
|
85989
86555
|
const CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES = [
|
|
85990
86556
|
"_ENDPOINT",
|
|
85991
86557
|
"_HEADERS",
|
|
@@ -86030,7 +86596,7 @@ const CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES = {
|
|
|
86030
86596
|
function isProjectIgnoredEnvVariable(name, value) {
|
|
86031
86597
|
if (CLAUDECODE_ENV_PROJECT_IGNORED_NAMES.has(name) || name.startsWith("XDG_") || name.startsWith("CLAUDE_AX_")) return true;
|
|
86032
86598
|
const upper = name.toUpperCase();
|
|
86033
|
-
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || upper.startsWith(
|
|
86599
|
+
if (CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_NAMES.has(upper) || CLAUDECODE_ENV_PROJECT_IGNORED_WINDOWS_PREFIXES.some((prefix) => upper.startsWith(prefix))) return true;
|
|
86034
86600
|
if (name.startsWith("OTEL_EXPORTER_OTLP_") && CLAUDECODE_ENV_OTLP_IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix))) return true;
|
|
86035
86601
|
if (Object.hasOwn(CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES, name)) {
|
|
86036
86602
|
const offValues = CLAUDECODE_ENV_PROJECT_HONORED_OFF_VALUES[name];
|
|
@@ -86062,7 +86628,7 @@ function stripIgnoredEnvVariables({ env, global, relativeFilePath, logger }) {
|
|
|
86062
86628
|
kept[name] = value;
|
|
86063
86629
|
}
|
|
86064
86630
|
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.`);
|
|
86065
|
-
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.`);
|
|
86631
|
+
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 the claude.ai sign-in, 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.`);
|
|
86066
86632
|
return kept;
|
|
86067
86633
|
}
|
|
86068
86634
|
/**
|
|
@@ -86712,7 +87278,7 @@ const CODEBUDDY_RULE_LISTS = [
|
|
|
86712
87278
|
"ask",
|
|
86713
87279
|
"deny"
|
|
86714
87280
|
];
|
|
86715
|
-
const CATCH_ALL_PATTERN$
|
|
87281
|
+
const CATCH_ALL_PATTERN$14 = "*";
|
|
86716
87282
|
const MCP_CATEGORY = "mcp";
|
|
86717
87283
|
const MCP_RULE_PREFIX = "mcp__";
|
|
86718
87284
|
const ALL_MCP_RULE = "mcp__*";
|
|
@@ -86782,7 +87348,7 @@ function categoryOfRule$2(rule) {
|
|
|
86782
87348
|
* and reach the `Bash` rules through `honorAllToolsOnBash`.
|
|
86783
87349
|
*/
|
|
86784
87350
|
function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
86785
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
87351
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$14;
|
|
86786
87352
|
if (category === MCP_CATEGORY) {
|
|
86787
87353
|
if (catchAll && action === "allow") {
|
|
86788
87354
|
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').`);
|
|
@@ -86791,7 +87357,7 @@ function buildCodebuddyRule({ category, pattern, action, logger }) {
|
|
|
86791
87357
|
return catchAll ? ALL_MCP_RULE : `${MCP_RULE_PREFIX}${pattern}`;
|
|
86792
87358
|
}
|
|
86793
87359
|
if (category === "*") {
|
|
86794
|
-
if (catchAll) return CATCH_ALL_PATTERN$
|
|
87360
|
+
if (catchAll) return CATCH_ALL_PATTERN$14;
|
|
86795
87361
|
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').`);
|
|
86796
87362
|
return null;
|
|
86797
87363
|
}
|
|
@@ -86809,7 +87375,7 @@ function parseCodebuddyRule(rule) {
|
|
|
86809
87375
|
const { specifier } = splitCodebuddyRule(rule);
|
|
86810
87376
|
if (specifier === void 0) return {
|
|
86811
87377
|
category,
|
|
86812
|
-
pattern: CATCH_ALL_PATTERN$
|
|
87378
|
+
pattern: CATCH_ALL_PATTERN$14
|
|
86813
87379
|
};
|
|
86814
87380
|
const trimmed = specifier.trim();
|
|
86815
87381
|
return trimmed === "" ? null : {
|
|
@@ -89135,8 +89701,8 @@ function mapBashActionToDecision(action) {
|
|
|
89135
89701
|
// src/features/permissions/commandcode-permissions.ts
|
|
89136
89702
|
/** Top-level key of `.commandcode/settings.json` that rulesync owns. */
|
|
89137
89703
|
const COMMANDCODE_PERMISSIONS_KEY = "permissions";
|
|
89138
|
-
const CATCH_ALL_PATTERN$
|
|
89139
|
-
const MCP_CANONICAL_PREFIX$
|
|
89704
|
+
const CATCH_ALL_PATTERN$13 = "*";
|
|
89705
|
+
const MCP_CANONICAL_PREFIX$8 = "mcp__";
|
|
89140
89706
|
const COMMANDCODE_ALL_MCP_RULE = "mcp__*";
|
|
89141
89707
|
const CATEGORY_TO_COMMANDCODE_TOOL = {
|
|
89142
89708
|
bash: "Shell",
|
|
@@ -89181,10 +89747,10 @@ const PADDED_SHELL_WILDCARD_REASON = "a pattern narrower than the bare 'Shell' (
|
|
|
89181
89747
|
* restrictions and reach the `Shell` entries through `honorAllToolsOnBash`.
|
|
89182
89748
|
*/
|
|
89183
89749
|
function buildCommandcodeRule(category, pattern) {
|
|
89184
|
-
const catchAll = pattern === CATCH_ALL_PATTERN$
|
|
89185
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89186
|
-
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$
|
|
89187
|
-
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$
|
|
89750
|
+
const catchAll = pattern === CATCH_ALL_PATTERN$13 || pattern === "";
|
|
89751
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$8)) return catchAll ? category : `${category}(${pattern})`;
|
|
89752
|
+
if (category === "mcp") return catchAll ? COMMANDCODE_ALL_MCP_RULE : `${MCP_CANONICAL_PREFIX$8}${pattern}`;
|
|
89753
|
+
if (category === "*") return catchAll ? CATCH_ALL_PATTERN$13 : null;
|
|
89188
89754
|
const tool = Object.hasOwn(CATEGORY_TO_COMMANDCODE_TOOL, category) ? CATEGORY_TO_COMMANDCODE_TOOL[category] : void 0;
|
|
89189
89755
|
if (tool === void 0) return null;
|
|
89190
89756
|
return catchAll ? tool : `${tool}(${pattern})`;
|
|
@@ -89220,7 +89786,7 @@ function splitCommandcodeRule(rule) {
|
|
|
89220
89786
|
}
|
|
89221
89787
|
/** Whether a specifier means "no specifier" to Command Code: only `` and `*` do. */
|
|
89222
89788
|
function isCatchAllSpecifier(inner) {
|
|
89223
|
-
return inner === "" || inner === CATCH_ALL_PATTERN$
|
|
89789
|
+
return inner === "" || inner === CATCH_ALL_PATTERN$13;
|
|
89224
89790
|
}
|
|
89225
89791
|
/**
|
|
89226
89792
|
* Read an MCP-shaped rule the way Command Code does (`command-code` 1.54.0,
|
|
@@ -89254,34 +89820,34 @@ function parseMcpCommandcodeRule({ tool, inner }) {
|
|
|
89254
89820
|
if (server.length === 0) return null;
|
|
89255
89821
|
const everyServer = {
|
|
89256
89822
|
category: "mcp",
|
|
89257
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89823
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89258
89824
|
notInAllow: true
|
|
89259
89825
|
};
|
|
89260
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89261
|
-
if (server === CATCH_ALL_PATTERN$
|
|
89262
|
-
if (server.includes(CATCH_ALL_PATTERN$
|
|
89826
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8)) {
|
|
89827
|
+
if (server === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89828
|
+
if (server.includes(CATCH_ALL_PATTERN$13)) return null;
|
|
89263
89829
|
return {
|
|
89264
|
-
category: rest === "" || rest === CATCH_ALL_PATTERN$
|
|
89265
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89830
|
+
category: rest === "" || rest === CATCH_ALL_PATTERN$13 ? `${MCP_CANONICAL_PREFIX$8}${server}` : `${MCP_CANONICAL_PREFIX$8}${remainder}`,
|
|
89831
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89266
89832
|
notInAllow: false
|
|
89267
89833
|
};
|
|
89268
89834
|
}
|
|
89269
|
-
const category = `${MCP_CANONICAL_PREFIX$
|
|
89270
|
-
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$
|
|
89835
|
+
const category = `${MCP_CANONICAL_PREFIX$8}${remainder}`;
|
|
89836
|
+
const namesTool = separator !== -1 && rest.length > 0 && !remainder.includes(CATCH_ALL_PATTERN$13);
|
|
89271
89837
|
if (!isCatchAllSpecifier(inner)) return namesTool ? {
|
|
89272
89838
|
category,
|
|
89273
89839
|
pattern: inner,
|
|
89274
89840
|
notInAllow: true
|
|
89275
89841
|
} : null;
|
|
89276
|
-
if (remainder === CATCH_ALL_PATTERN$
|
|
89277
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
89278
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
89279
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89842
|
+
if (remainder === CATCH_ALL_PATTERN$13) return everyServer;
|
|
89843
|
+
if (rest === CATCH_ALL_PATTERN$13 && !server.includes(CATCH_ALL_PATTERN$13)) return {
|
|
89844
|
+
category: `${MCP_CANONICAL_PREFIX$8}${server}`,
|
|
89845
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89280
89846
|
notInAllow: true
|
|
89281
89847
|
};
|
|
89282
89848
|
return namesTool ? {
|
|
89283
89849
|
category,
|
|
89284
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89850
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89285
89851
|
notInAllow: false
|
|
89286
89852
|
} : null;
|
|
89287
89853
|
}
|
|
@@ -89305,14 +89871,14 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89305
89871
|
if (category === "bash") {
|
|
89306
89872
|
if (isCatchAllSpecifier(inner)) return {
|
|
89307
89873
|
category,
|
|
89308
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89874
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89309
89875
|
notInAllow: false
|
|
89310
89876
|
};
|
|
89311
89877
|
const normalized = inner.trim().replace(/\s+/g, " ");
|
|
89312
89878
|
if (normalized === "" || normalized === ":*") return null;
|
|
89313
89879
|
return isCatchAllSpecifier(normalized) ? {
|
|
89314
89880
|
category,
|
|
89315
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89881
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89316
89882
|
notInAllow: true
|
|
89317
89883
|
} : {
|
|
89318
89884
|
category,
|
|
@@ -89322,7 +89888,7 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89322
89888
|
}
|
|
89323
89889
|
if (isCatchAllSpecifier(inner)) return {
|
|
89324
89890
|
category,
|
|
89325
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89891
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89326
89892
|
notInAllow: false
|
|
89327
89893
|
};
|
|
89328
89894
|
return isCatchAllSpecifier(inner.trim()) ? null : {
|
|
@@ -89341,13 +89907,13 @@ function friendlyToolRule({ category, inner }) {
|
|
|
89341
89907
|
*/
|
|
89342
89908
|
function parseCommandcodeRule(rule) {
|
|
89343
89909
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89344
|
-
if (tool === CATCH_ALL_PATTERN$
|
|
89910
|
+
if (tool === CATCH_ALL_PATTERN$13) return isCatchAllSpecifier(inner) ? {
|
|
89345
89911
|
category: "*",
|
|
89346
|
-
pattern: CATCH_ALL_PATTERN$
|
|
89912
|
+
pattern: CATCH_ALL_PATTERN$13,
|
|
89347
89913
|
notInAllow: true
|
|
89348
89914
|
} : null;
|
|
89349
89915
|
const lowered = tool.toLowerCase();
|
|
89350
|
-
if (lowered.startsWith(MCP_CANONICAL_PREFIX$
|
|
89916
|
+
if (lowered.startsWith(MCP_CANONICAL_PREFIX$8)) return parseMcpCommandcodeRule({
|
|
89351
89917
|
tool,
|
|
89352
89918
|
inner
|
|
89353
89919
|
});
|
|
@@ -89365,12 +89931,12 @@ function parseCommandcodeRule(rule) {
|
|
|
89365
89931
|
*/
|
|
89366
89932
|
function isFoldedMcpToolNameRule({ rule, category }) {
|
|
89367
89933
|
const { tool } = splitCommandcodeRule(rule);
|
|
89368
|
-
return category.startsWith(MCP_CANONICAL_PREFIX$
|
|
89934
|
+
return category.startsWith(MCP_CANONICAL_PREFIX$8) && !tool.startsWith(MCP_CANONICAL_PREFIX$8);
|
|
89369
89935
|
}
|
|
89370
89936
|
/** The `Tool` half of `Tool(specifier)`, when the rule is an exact-prefix MCP rule with a specifier. */
|
|
89371
89937
|
function scopedMcpRuleTool(rule) {
|
|
89372
89938
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89373
|
-
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
89939
|
+
return !isCatchAllSpecifier(inner) && tool.startsWith(MCP_CANONICAL_PREFIX$8) ? tool : null;
|
|
89374
89940
|
}
|
|
89375
89941
|
/**
|
|
89376
89942
|
* The string entries of a Command Code permission list. Command Code reads
|
|
@@ -89413,7 +89979,7 @@ function preservedRules({ existingPermissions, key, managedCategories, written,
|
|
|
89413
89979
|
}).filter((rule) => {
|
|
89414
89980
|
const parsed = parseCommandcodeRule(rule);
|
|
89415
89981
|
if (parsed === null || !managedCategories.has(parsed.category)) return true;
|
|
89416
|
-
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$
|
|
89982
|
+
const replacement = written.get(`${parsed.category}(${parsed.pattern})`) ?? written.get(`${parsed.category}(${CATCH_ALL_PATTERN$13})`);
|
|
89417
89983
|
if (key !== "allow" && isStricterAction({
|
|
89418
89984
|
action: key,
|
|
89419
89985
|
existing: replacement
|
|
@@ -89470,7 +90036,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89470
90036
|
const ranked = /* @__PURE__ */ new Map();
|
|
89471
90037
|
const managedCategories = /* @__PURE__ */ new Set();
|
|
89472
90038
|
for (const [category, rules] of Object.entries(permission)) {
|
|
89473
|
-
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$
|
|
90039
|
+
const categoryRule = buildCommandcodeRule(category, CATCH_ALL_PATTERN$13);
|
|
89474
90040
|
const managedCategory = categoryRule === null ? null : parseCommandcodeRule(categoryRule);
|
|
89475
90041
|
if (managedCategory !== null) managedCategories.add(managedCategory.category);
|
|
89476
90042
|
for (const [pattern, action] of Object.entries(rules)) {
|
|
@@ -89539,7 +90105,7 @@ function buildCommandcodeRuleLists({ config, existingPermissions, logger }) {
|
|
|
89539
90105
|
function skippedAllowImportMessage({ rule, category }) {
|
|
89540
90106
|
const { tool, inner } = splitCommandcodeRule(rule);
|
|
89541
90107
|
if (category === "bash") return `Command Code reads '${rule}' in "allow" as ${PADDED_SHELL_WILDCARD_REASON}, which rulesync cannot import without widening the grant to every command, so it was not imported.`;
|
|
89542
|
-
if (tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
90108
|
+
if (tool.startsWith(MCP_CANONICAL_PREFIX$8) || isCatchAllSpecifier(inner)) return `Command Code ignores '${rule}' in "allow" (an allow rule must name what it grants), so it was not imported.`;
|
|
89543
90109
|
return `Command Code globs the specifier of '${rule}' in "allow" against the call's arguments, which rulesync cannot import without widening the grant to the whole tool, so it was not imported.`;
|
|
89544
90110
|
}
|
|
89545
90111
|
/**
|
|
@@ -89734,7 +90300,7 @@ function parseCommandcodeSettings({ fileContent, relativePath }) {
|
|
|
89734
90300
|
}
|
|
89735
90301
|
// src/features/permissions/continue-permissions.ts
|
|
89736
90302
|
const CONTINUE_GLOBAL_ONLY_MESSAGE = "Continue permissions are global-only; use --global to sync ~/.continue/permissions.yaml";
|
|
89737
|
-
const CATCH_ALL_PATTERN$
|
|
90303
|
+
const CATCH_ALL_PATTERN$12 = "*";
|
|
89738
90304
|
/**
|
|
89739
90305
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Continue
|
|
89740
90306
|
* would match `Tool(**)` against every argument just as the bare tool name
|
|
@@ -89742,7 +90308,7 @@ const CATCH_ALL_PATTERN$11 = "*";
|
|
|
89742
90308
|
* the bare entry instead of being skipped as a pattern-specific rule.
|
|
89743
90309
|
*/
|
|
89744
90310
|
function normalizeCatchAllPattern$1(pattern) {
|
|
89745
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
90311
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$12 : pattern;
|
|
89746
90312
|
}
|
|
89747
90313
|
const CANONICAL_TO_CONTINUE_TOOL_NAMES = {
|
|
89748
90314
|
bash: "Bash",
|
|
@@ -89786,7 +90352,7 @@ function parseContinuePermissionEntry(entry) {
|
|
|
89786
90352
|
if (toolName === "") return;
|
|
89787
90353
|
return {
|
|
89788
90354
|
toolName,
|
|
89789
|
-
pattern: pattern === "" ? CATCH_ALL_PATTERN$
|
|
90355
|
+
pattern: pattern === "" ? CATCH_ALL_PATTERN$12 : pattern
|
|
89790
90356
|
};
|
|
89791
90357
|
}
|
|
89792
90358
|
/**
|
|
@@ -89951,7 +90517,7 @@ function convertRulesyncToContinuePermissions({ config, logger }) {
|
|
|
89951
90517
|
logger?.warn(`Continue permissions.yaml cannot hold a parenthesis inside a pattern, so the "${action}" rule for "${category}" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89952
90518
|
continue;
|
|
89953
90519
|
}
|
|
89954
|
-
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$
|
|
90520
|
+
if (toolName === "*" && pattern !== CATCH_ALL_PATTERN$12) {
|
|
89955
90521
|
logger?.warn(`Continue permissions.yaml cannot scope a pattern to every tool, so the "${action}" rule for "*" (pattern ${quoteValueForWarning(pattern)}) was skipped.`);
|
|
89956
90522
|
continue;
|
|
89957
90523
|
}
|
|
@@ -90007,13 +90573,116 @@ const AUTO_APPROVE_KEYS = {
|
|
|
90007
90573
|
webfetch: "chat.tools.urls.autoApprove"
|
|
90008
90574
|
};
|
|
90009
90575
|
/**
|
|
90010
|
-
* The workspace setting holding the starting permission level of new
|
|
90011
|
-
* sessions, authored through the
|
|
90576
|
+
* The workspace setting holding the starting permission level of new Local
|
|
90577
|
+
* (`local` harness) chat sessions, authored through the
|
|
90578
|
+
* `copilot.defaultPermissionMode` override. VS Code 1.144 narrowed it to Local
|
|
90579
|
+
* sessions; the Copilot harness reads {@link DEFAULT_CONFIGURATION_KEY}.
|
|
90012
90580
|
*
|
|
90013
90581
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
90014
90582
|
*/
|
|
90015
90583
|
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
90016
90584
|
/**
|
|
90585
|
+
* The workspace setting holding the starting `mode` (`interactive` / `plan` /
|
|
90586
|
+
* `autopilot`) and `approvals` (`manual` / `assisted` / `allowAll`) of new
|
|
90587
|
+
* agent sessions such as the Copilot harness, the default since VS Code 1.143.
|
|
90588
|
+
* It shipped in stable VS Code 1.144 as an object setting with
|
|
90589
|
+
* `additionalProperties: false`; older releases ignore it.
|
|
90590
|
+
*
|
|
90591
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts
|
|
90592
|
+
* @see https://github.com/microsoft/vscode/blob/1.144.0/src/vs/workbench/contrib/chat/common/constants.ts
|
|
90593
|
+
*/
|
|
90594
|
+
const DEFAULT_CONFIGURATION_KEY = "chat.defaultConfiguration";
|
|
90595
|
+
/**
|
|
90596
|
+
* What each `copilot.defaultPermissionMode` value writes. `local` goes to
|
|
90597
|
+
* `chat.permissions.default`; `approvals` and `autopilot` go to
|
|
90598
|
+
* `chat.defaultConfiguration`. The Local picker has no Assisted level, so
|
|
90599
|
+
* `assisted` starts Local sessions with Default Permissions — the stricter
|
|
90600
|
+
* fallback. Autopilot is a permission level in the Local picker but a `mode`
|
|
90601
|
+
* in `chat.defaultConfiguration`, where it runs with every tool call approved.
|
|
90602
|
+
*/
|
|
90603
|
+
const DEFAULT_PERMISSION_MODE_MAPPINGS = {
|
|
90604
|
+
default: {
|
|
90605
|
+
local: "default",
|
|
90606
|
+
approvals: "manual",
|
|
90607
|
+
autopilot: false
|
|
90608
|
+
},
|
|
90609
|
+
autoApprove: {
|
|
90610
|
+
local: "autoApprove",
|
|
90611
|
+
approvals: "allowAll",
|
|
90612
|
+
autopilot: false
|
|
90613
|
+
},
|
|
90614
|
+
autopilot: {
|
|
90615
|
+
local: "autopilot",
|
|
90616
|
+
approvals: "allowAll",
|
|
90617
|
+
autopilot: true
|
|
90618
|
+
},
|
|
90619
|
+
assisted: {
|
|
90620
|
+
local: "default",
|
|
90621
|
+
approvals: "assisted",
|
|
90622
|
+
autopilot: false
|
|
90623
|
+
}
|
|
90624
|
+
};
|
|
90625
|
+
/**
|
|
90626
|
+
* The `chat.defaultConfiguration` value for `mode`. Rulesync owns only whether
|
|
90627
|
+
* the mode is Autopilot: authoring Autopilot sets it, and any other value
|
|
90628
|
+
* removes any Autopilot mode in the file (whether an earlier run or a person
|
|
90629
|
+
* wrote it, since the two cannot be told apart), while an `interactive` or
|
|
90630
|
+
* `plan` mode is kept.
|
|
90631
|
+
*/
|
|
90632
|
+
function buildDefaultConfigurationValue({ defaultPermissionMode, existingSettings }) {
|
|
90633
|
+
const { approvals, autopilot } = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode];
|
|
90634
|
+
const existing = existingSettings[DEFAULT_CONFIGURATION_KEY];
|
|
90635
|
+
const value = { ...isPlainObject(existing) ? existing : {} };
|
|
90636
|
+
if (autopilot) value.mode = "autopilot";
|
|
90637
|
+
else if (value.mode === "autopilot") delete value.mode;
|
|
90638
|
+
value.approvals = approvals;
|
|
90639
|
+
return value;
|
|
90640
|
+
}
|
|
90641
|
+
/**
|
|
90642
|
+
* The `copilot.defaultPermissionMode` values from least to most permissive,
|
|
90643
|
+
* used to settle a file whose two keys disagree.
|
|
90644
|
+
*/
|
|
90645
|
+
const DEFAULT_PERMISSION_MODE_RANK = {
|
|
90646
|
+
default: 0,
|
|
90647
|
+
assisted: 1,
|
|
90648
|
+
autoApprove: 2,
|
|
90649
|
+
autopilot: 3
|
|
90650
|
+
};
|
|
90651
|
+
/**
|
|
90652
|
+
* The `copilot.defaultPermissionMode` a `chat.defaultConfiguration` value
|
|
90653
|
+
* stands for. Autopilot is recognized only together with `allowAll`, the pair
|
|
90654
|
+
* Rulesync writes for it: an Autopilot mode with stricter approvals is read
|
|
90655
|
+
* from `approvals` alone, so that regenerating never escalates it to Allow
|
|
90656
|
+
* All. An object without `approvals` is read as `default`, since VS Code then
|
|
90657
|
+
* starts sessions with Manual permissions.
|
|
90658
|
+
*/
|
|
90659
|
+
function readDefaultConfigurationMode(configuration) {
|
|
90660
|
+
if (!isPlainObject(configuration)) return;
|
|
90661
|
+
switch (configuration.approvals) {
|
|
90662
|
+
case void 0:
|
|
90663
|
+
case "manual": return "default";
|
|
90664
|
+
case "assisted": return "assisted";
|
|
90665
|
+
case "allowAll": return configuration.mode === "autopilot" ? "autopilot" : "autoApprove";
|
|
90666
|
+
default: return;
|
|
90667
|
+
}
|
|
90668
|
+
}
|
|
90669
|
+
/**
|
|
90670
|
+
* Read `copilot.defaultPermissionMode` back from the settings. When the two
|
|
90671
|
+
* keys agree — `chat.permissions.default` holds the Local level Rulesync
|
|
90672
|
+
* writes for the `chat.defaultConfiguration` value — the latter is returned,
|
|
90673
|
+
* since only it can say `assisted`. When they disagree, the less permissive of
|
|
90674
|
+
* the two is returned, so regenerating never raises either harness's starting
|
|
90675
|
+
* permissions. Either key alone is read as it stands.
|
|
90676
|
+
*/
|
|
90677
|
+
function readDefaultPermissionMode(settings) {
|
|
90678
|
+
const fromConfiguration = readDefaultConfigurationMode(settings[DEFAULT_CONFIGURATION_KEY]);
|
|
90679
|
+
const localValue = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
90680
|
+
const fromLocal = COPILOT_LOCAL_PERMISSION_LEVELS.find((level) => level === localValue);
|
|
90681
|
+
if (fromConfiguration === void 0 || fromLocal === void 0) return fromConfiguration ?? fromLocal;
|
|
90682
|
+
if (DEFAULT_PERMISSION_MODE_MAPPINGS[fromConfiguration].local === fromLocal) return fromConfiguration;
|
|
90683
|
+
return DEFAULT_PERMISSION_MODE_RANK[fromLocal] < DEFAULT_PERMISSION_MODE_RANK[fromConfiguration] ? fromLocal : fromConfiguration;
|
|
90684
|
+
}
|
|
90685
|
+
/**
|
|
90017
90686
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
90018
90687
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
90019
90688
|
*/
|
|
@@ -90200,9 +90869,6 @@ function readSandboxFromSettings(settings) {
|
|
|
90200
90869
|
}
|
|
90201
90870
|
return Object.keys(sandbox).length > 0 ? sandbox : void 0;
|
|
90202
90871
|
}
|
|
90203
|
-
function isCopilotDefaultPermissionMode(value) {
|
|
90204
|
-
return COPILOT_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90205
|
-
}
|
|
90206
90872
|
function asAutoApproveMap(value) {
|
|
90207
90873
|
if (!isPlainObject(value)) return {};
|
|
90208
90874
|
const result = {};
|
|
@@ -90246,9 +90912,11 @@ function buildAutoApproveValue({ category, rules }) {
|
|
|
90246
90912
|
* `false`. A key whose canonical category is absent entirely
|
|
90247
90913
|
* is left untouched, so authoring only `bash` rules never disturbs a
|
|
90248
90914
|
* hand-written edits or urls map.
|
|
90249
|
-
* The `copilot.defaultPermissionMode` override writes
|
|
90250
|
-
* (
|
|
90251
|
-
*
|
|
90915
|
+
* The `copilot.defaultPermissionMode` override writes both
|
|
90916
|
+
* `chat.permissions.default` (Local sessions) and `chat.defaultConfiguration`
|
|
90917
|
+
* (Copilot-harness sessions, VS Code 1.144+; see
|
|
90918
|
+
* {@link DEFAULT_PERMISSION_MODE_MAPPINGS}); without it both keys are left
|
|
90919
|
+
* untouched. The `copilot.sandbox` override writes the Agent Host sandbox
|
|
90252
90920
|
* settings (`chat.agent.sandbox.*`, see {@link SANDBOX_LEAVES}) per key, and
|
|
90253
90921
|
* warns about every value it writes that loosens the sandbox.
|
|
90254
90922
|
* Only project scope is modeled: VS Code's user-scope settings.json lives at a
|
|
@@ -90299,17 +90967,24 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90299
90967
|
rules
|
|
90300
90968
|
});
|
|
90301
90969
|
}
|
|
90970
|
+
const existingSettings = () => parseSharedConfig({
|
|
90971
|
+
format: "jsonc",
|
|
90972
|
+
fileContent: existingContent,
|
|
90973
|
+
filePath
|
|
90974
|
+
});
|
|
90302
90975
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
90303
|
-
if (defaultPermissionMode !== void 0)
|
|
90976
|
+
if (defaultPermissionMode !== void 0) {
|
|
90977
|
+
patch[DEFAULT_PERMISSION_MODE_KEY$1] = DEFAULT_PERMISSION_MODE_MAPPINGS[defaultPermissionMode].local;
|
|
90978
|
+
patch[DEFAULT_CONFIGURATION_KEY] = buildDefaultConfigurationValue({
|
|
90979
|
+
defaultPermissionMode,
|
|
90980
|
+
existingSettings: existingSettings()
|
|
90981
|
+
});
|
|
90982
|
+
}
|
|
90304
90983
|
const sandbox = config.copilot?.sandbox;
|
|
90305
90984
|
if (sandbox !== void 0) {
|
|
90306
90985
|
Object.assign(patch, buildSandboxPatch({
|
|
90307
90986
|
sandbox,
|
|
90308
|
-
existingSettings:
|
|
90309
|
-
format: "jsonc",
|
|
90310
|
-
fileContent: existingContent,
|
|
90311
|
-
filePath
|
|
90312
|
-
})
|
|
90987
|
+
existingSettings: existingSettings()
|
|
90313
90988
|
}));
|
|
90314
90989
|
warnOnTrustAffectingEntries({
|
|
90315
90990
|
toolLabel: COPILOT_TOOL_LABEL,
|
|
@@ -90356,10 +91031,10 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
90356
91031
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
90357
91032
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
90358
91033
|
}
|
|
90359
|
-
const defaultPermissionMode = settings
|
|
91034
|
+
const defaultPermissionMode = readDefaultPermissionMode(settings);
|
|
90360
91035
|
const sandbox = readSandboxFromSettings(settings);
|
|
90361
91036
|
const copilot = {
|
|
90362
|
-
...
|
|
91037
|
+
...defaultPermissionMode !== void 0 && { defaultPermissionMode },
|
|
90363
91038
|
...sandbox !== void 0 && { sandbox }
|
|
90364
91039
|
};
|
|
90365
91040
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
@@ -90557,14 +91232,14 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
90557
91232
|
}
|
|
90558
91233
|
};
|
|
90559
91234
|
// src/features/permissions/crush-permissions.ts
|
|
90560
|
-
const CATCH_ALL_PATTERN$
|
|
91235
|
+
const CATCH_ALL_PATTERN$11 = "*";
|
|
90561
91236
|
/**
|
|
90562
91237
|
* A run of `*` (`**`, `***`) is the catch-all spelled another way: Crush has no
|
|
90563
91238
|
* argument patterns at all, so it is the only pattern a rule can carry and
|
|
90564
91239
|
* still be written.
|
|
90565
91240
|
*/
|
|
90566
91241
|
function normalizeCatchAllPattern(pattern) {
|
|
90567
|
-
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$
|
|
91242
|
+
return /^\*+$/.test(pattern) ? CATCH_ALL_PATTERN$11 : pattern;
|
|
90568
91243
|
}
|
|
90569
91244
|
const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
90570
91245
|
bash: "bash",
|
|
@@ -90576,11 +91251,11 @@ const CANONICAL_TO_CRUSH_TOOL_NAMES = {
|
|
|
90576
91251
|
webfetch: "fetch"
|
|
90577
91252
|
};
|
|
90578
91253
|
const CRUSH_TO_CANONICAL_TOOL_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_CRUSH_TOOL_NAMES).map(([k, v]) => [v, k]));
|
|
90579
|
-
const MCP_CANONICAL_PREFIX$
|
|
91254
|
+
const MCP_CANONICAL_PREFIX$7 = "mcp__";
|
|
90580
91255
|
const MCP_CRUSH_PREFIX = "mcp_";
|
|
90581
91256
|
function toCrushToolName(canonical) {
|
|
90582
91257
|
if (Object.hasOwn(CANONICAL_TO_CRUSH_TOOL_NAMES, canonical)) return CANONICAL_TO_CRUSH_TOOL_NAMES[canonical] ?? canonical;
|
|
90583
|
-
if (canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91258
|
+
if (canonical.startsWith(MCP_CANONICAL_PREFIX$7) && canonical.length > 5) return `${MCP_CRUSH_PREFIX}${canonical.slice(5).replaceAll("__", "_")}`;
|
|
90584
91259
|
return canonical;
|
|
90585
91260
|
}
|
|
90586
91261
|
function toCanonicalToolName$5(crushName) {
|
|
@@ -90754,7 +91429,7 @@ function resolveCategoryAction({ category, toolName, rules, logger }) {
|
|
|
90754
91429
|
for (const [rawPattern, action] of Object.entries(rules)) {
|
|
90755
91430
|
if (isPrototypePollutionKey(rawPattern)) continue;
|
|
90756
91431
|
const pattern = normalizeCatchAllPattern(rawPattern);
|
|
90757
|
-
if (pattern === CATCH_ALL_PATTERN$
|
|
91432
|
+
if (pattern === CATCH_ALL_PATTERN$11) {
|
|
90758
91433
|
catchAllAction = action;
|
|
90759
91434
|
continue;
|
|
90760
91435
|
}
|
|
@@ -90827,11 +91502,11 @@ function convertCrushListsToRulesync({ allowed, disabled }) {
|
|
|
90827
91502
|
};
|
|
90828
91503
|
for (const entry of disabled) {
|
|
90829
91504
|
if (entry === "" || isPrototypePollutionKey(entry)) continue;
|
|
90830
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91505
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] = "deny";
|
|
90831
91506
|
}
|
|
90832
91507
|
for (const entry of allowed) {
|
|
90833
91508
|
if (entry === "" || entry.includes(":") || isPrototypePollutionKey(entry)) continue;
|
|
90834
|
-
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$
|
|
91509
|
+
bucketFor(toCanonicalToolName$5(entry))[CATCH_ALL_PATTERN$11] ??= "allow";
|
|
90835
91510
|
}
|
|
90836
91511
|
return { permission };
|
|
90837
91512
|
}
|
|
@@ -90877,13 +91552,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
90877
91552
|
WebFetch: "webfetch",
|
|
90878
91553
|
Mcp: "mcp"
|
|
90879
91554
|
};
|
|
90880
|
-
const MCP_CANONICAL_PREFIX$
|
|
91555
|
+
const MCP_CANONICAL_PREFIX$6 = "mcp__";
|
|
90881
91556
|
/**
|
|
90882
91557
|
* Returns true if the canonical category is the per-tool MCP form
|
|
90883
91558
|
* `mcp__<server>__<tool>`.
|
|
90884
91559
|
*/
|
|
90885
91560
|
function isMcpScopedCategory(canonical) {
|
|
90886
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91561
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$6) && canonical.length > 5;
|
|
90887
91562
|
}
|
|
90888
91563
|
function toCursorType(canonical) {
|
|
90889
91564
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -90917,7 +91592,7 @@ function toCanonicalCategory$1(cursorType, pattern) {
|
|
|
90917
91592
|
if (match) {
|
|
90918
91593
|
const server = match[1] ?? "*";
|
|
90919
91594
|
const tool = match[2] ?? "*";
|
|
90920
|
-
return `${MCP_CANONICAL_PREFIX$
|
|
91595
|
+
return `${MCP_CANONICAL_PREFIX$6}${server}__${tool}`;
|
|
90921
91596
|
}
|
|
90922
91597
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
90923
91598
|
}
|
|
@@ -91179,7 +91854,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
91179
91854
|
const { toolName: type, pattern } = parseToolPatternEntry(entry);
|
|
91180
91855
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
91181
91856
|
if (!permission[canonical]) permission[canonical] = {};
|
|
91182
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$
|
|
91857
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$6) ? "*" : pattern;
|
|
91183
91858
|
permission[canonical][canonicalPattern] = action;
|
|
91184
91859
|
}
|
|
91185
91860
|
};
|
|
@@ -92678,7 +93353,7 @@ function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
|
92678
93353
|
// src/features/permissions/goose-permissions.ts
|
|
92679
93354
|
const GOOSE_GLOBAL_ONLY_MESSAGE = "Goose permissions are global-only; use --global to sync ~/.config/goose/permission.yaml";
|
|
92680
93355
|
const GOOSE_USER_KEY = "user";
|
|
92681
|
-
const CATCH_ALL_PATTERN$
|
|
93356
|
+
const CATCH_ALL_PATTERN$10 = "*";
|
|
92682
93357
|
const RULESYNC_TO_GOOSE_TOOL_NAME = { bash: "shell" };
|
|
92683
93358
|
const GOOSE_TO_RULESYNC_TOOL_NAMES = {
|
|
92684
93359
|
shell: ["bash"],
|
|
@@ -92827,7 +93502,7 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
|
|
|
92827
93502
|
const assigned = /* @__PURE__ */ new Map();
|
|
92828
93503
|
for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
|
|
92829
93504
|
const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
|
|
92830
|
-
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$
|
|
93505
|
+
for (const [pattern, action] of Object.entries(rules)) if (pattern !== CATCH_ALL_PATTERN$10) 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.`);
|
|
92831
93506
|
const { action, downgraded } = failSafeCatchAllAction({
|
|
92832
93507
|
rules,
|
|
92833
93508
|
allToolsRules: category === "*" ? void 0 : config.permission["*"]
|
|
@@ -92860,7 +93535,7 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
92860
93535
|
const action = GOOSE_LIST_TO_ACTION[key];
|
|
92861
93536
|
for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
|
|
92862
93537
|
permission[category] ??= {};
|
|
92863
|
-
permission[category][CATCH_ALL_PATTERN$
|
|
93538
|
+
permission[category][CATCH_ALL_PATTERN$10] = action;
|
|
92864
93539
|
}
|
|
92865
93540
|
}
|
|
92866
93541
|
return { permission };
|
|
@@ -92870,8 +93545,8 @@ const GROKCLI_UI_KEY = "ui";
|
|
|
92870
93545
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
92871
93546
|
const GROKCLI_PERMISSION_KEY = "permission";
|
|
92872
93547
|
const GROKCLI_AUTO_PERMISSION_MODE = "auto";
|
|
92873
|
-
const CATCH_ALL_PATTERN$
|
|
92874
|
-
const MCP_CANONICAL_PREFIX$
|
|
93548
|
+
const CATCH_ALL_PATTERN$9 = "*";
|
|
93549
|
+
const MCP_CANONICAL_PREFIX$5 = "mcp__";
|
|
92875
93550
|
const CATEGORY_TO_GROK_TOOL = {
|
|
92876
93551
|
bash: "Bash",
|
|
92877
93552
|
read: "Read",
|
|
@@ -92903,6 +93578,25 @@ const GROK_TOOL_TO_CATEGORY_LOWER = {
|
|
|
92903
93578
|
};
|
|
92904
93579
|
const GROK_MCP_TOOL_ALIASES = /* @__PURE__ */ new Set(["mcp", GROK_MCP_TOOL.toLowerCase()]);
|
|
92905
93580
|
const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
93581
|
+
const GROK_PATH_PATTERN_CATEGORIES = /* @__PURE__ */ new Set([
|
|
93582
|
+
"read",
|
|
93583
|
+
"edit",
|
|
93584
|
+
"write",
|
|
93585
|
+
"grep",
|
|
93586
|
+
"glob"
|
|
93587
|
+
]);
|
|
93588
|
+
const CLAUDE_ABSOLUTE_PATH_PREFIX = "//";
|
|
93589
|
+
/**
|
|
93590
|
+
* Rewrite a canonical path pattern into the anchor Grok understands. A
|
|
93591
|
+
* Claude-style `//abs` absolute pattern becomes `/abs`; without this the rule
|
|
93592
|
+
* would match nothing on Grok, so a `deny` or `ask` rule would silently give
|
|
93593
|
+
* no protection. Every other pattern is returned unchanged.
|
|
93594
|
+
*/
|
|
93595
|
+
function toGrokPathPattern(category, pattern) {
|
|
93596
|
+
if (!GROK_PATH_PATTERN_CATEGORIES.has(category)) return pattern;
|
|
93597
|
+
if (!pattern.startsWith(CLAUDE_ABSOLUTE_PATH_PREFIX)) return pattern;
|
|
93598
|
+
return `/${pattern.replace(/^\/+/, "")}`;
|
|
93599
|
+
}
|
|
92906
93600
|
/**
|
|
92907
93601
|
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
92908
93602
|
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
@@ -92913,18 +93607,20 @@ const GROKCLI_PERMISSION_RULES_KEY = "rules";
|
|
|
92913
93607
|
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
92914
93608
|
* concrete pattern emits `Tool(pattern)`. The all-tools `*` category becomes
|
|
92915
93609
|
* Grok's bare `*` rule for its catch-all pattern only; a narrower pattern
|
|
92916
|
-
* under it has no Grok spelling and returns `null`.
|
|
93610
|
+
* under it has no Grok spelling and returns `null`. A Claude-style `//abs`
|
|
93611
|
+
* path pattern on a file tool is rewritten to Grok's `/abs` (see
|
|
93612
|
+
* `toGrokPathPattern`).
|
|
92917
93613
|
*/
|
|
92918
93614
|
function buildGrokEntry(category, pattern) {
|
|
92919
|
-
if (category === "*") return pattern === CATCH_ALL_PATTERN$
|
|
92920
|
-
if (category.startsWith(MCP_CANONICAL_PREFIX$
|
|
93615
|
+
if (category === "*") return pattern === CATCH_ALL_PATTERN$9 ? GROK_ALL_TOOLS_ENTRY : null;
|
|
93616
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX$5)) {
|
|
92921
93617
|
const remainder = category.slice(5);
|
|
92922
93618
|
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
92923
93619
|
}
|
|
92924
|
-
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$
|
|
93620
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
92925
93621
|
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
92926
93622
|
if (tool === void 0) return null;
|
|
92927
|
-
return pattern === CATCH_ALL_PATTERN$
|
|
93623
|
+
return pattern === CATCH_ALL_PATTERN$9 || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
|
|
92928
93624
|
}
|
|
92929
93625
|
/**
|
|
92930
93626
|
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
@@ -92946,21 +93642,21 @@ function parseGrokEntry(entry) {
|
|
|
92946
93642
|
}
|
|
92947
93643
|
if (parenIndex === -1 && tool === GROK_ALL_TOOLS_ENTRY) return {
|
|
92948
93644
|
category: "*",
|
|
92949
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93645
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92950
93646
|
};
|
|
92951
|
-
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$
|
|
93647
|
+
if (parenIndex === -1 && tool.startsWith(MCP_CANONICAL_PREFIX$5)) return parseClaudeStyleMcpEntry(tool);
|
|
92952
93648
|
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
92953
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92954
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93649
|
+
category: `${MCP_CANONICAL_PREFIX$5}${inner}`,
|
|
93650
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92955
93651
|
} : {
|
|
92956
93652
|
category: "mcp",
|
|
92957
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93653
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92958
93654
|
};
|
|
92959
93655
|
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
92960
93656
|
if (category === void 0) return null;
|
|
92961
93657
|
return {
|
|
92962
93658
|
category,
|
|
92963
|
-
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$
|
|
93659
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$9
|
|
92964
93660
|
};
|
|
92965
93661
|
}
|
|
92966
93662
|
/**
|
|
@@ -92977,14 +93673,14 @@ function parseGrokEntry(entry) {
|
|
|
92977
93673
|
function parseClaudeStyleMcpEntry(entry) {
|
|
92978
93674
|
const rest = entry.slice(5);
|
|
92979
93675
|
if (rest.length === 0) return null;
|
|
92980
|
-
if (rest === CATCH_ALL_PATTERN$
|
|
93676
|
+
if (rest === CATCH_ALL_PATTERN$9) return {
|
|
92981
93677
|
category: "mcp",
|
|
92982
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93678
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92983
93679
|
};
|
|
92984
93680
|
const address = rest.includes("__") ? rest : `${rest}__*`;
|
|
92985
93681
|
return {
|
|
92986
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
92987
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93682
|
+
category: `${MCP_CANONICAL_PREFIX$5}${address}`,
|
|
93683
|
+
pattern: CATCH_ALL_PATTERN$9
|
|
92988
93684
|
};
|
|
92989
93685
|
}
|
|
92990
93686
|
/**
|
|
@@ -93005,20 +93701,20 @@ function parseGrokRule(rule) {
|
|
|
93005
93701
|
if (typeof tool !== "string") return null;
|
|
93006
93702
|
if (pattern !== void 0 && typeof pattern !== "string") return null;
|
|
93007
93703
|
const trimmedPattern = pattern?.trim() ?? "";
|
|
93008
|
-
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$
|
|
93704
|
+
const resolvedPattern = trimmedPattern.length > 0 ? trimmedPattern : CATCH_ALL_PATTERN$9;
|
|
93009
93705
|
const lowerTool = tool.trim().toLowerCase();
|
|
93010
|
-
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93706
|
+
if (lowerTool === GROK_VERBOSE_ANY_TOOL) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
93011
93707
|
category: "*",
|
|
93012
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93708
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93013
93709
|
action
|
|
93014
93710
|
} : null;
|
|
93015
|
-
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$
|
|
93711
|
+
if (GROK_MCP_TOOL_ALIASES.has(lowerTool)) return resolvedPattern === CATCH_ALL_PATTERN$9 ? {
|
|
93016
93712
|
category: "mcp",
|
|
93017
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93713
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93018
93714
|
action
|
|
93019
93715
|
} : {
|
|
93020
|
-
category: `${MCP_CANONICAL_PREFIX$
|
|
93021
|
-
pattern: CATCH_ALL_PATTERN$
|
|
93716
|
+
category: `${MCP_CANONICAL_PREFIX$5}${resolvedPattern}`,
|
|
93717
|
+
pattern: CATCH_ALL_PATTERN$9,
|
|
93022
93718
|
action
|
|
93023
93719
|
};
|
|
93024
93720
|
if (!Object.hasOwn(GROK_TOOL_TO_CATEGORY_LOWER, lowerTool)) return null;
|
|
@@ -93168,7 +93864,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
93168
93864
|
throw new Error(`Failed to parse Grok config.toml content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
93169
93865
|
}
|
|
93170
93866
|
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
93171
|
-
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$
|
|
93867
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$9]: legacyModeAction(parsed) } } };
|
|
93172
93868
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
93173
93869
|
}
|
|
93174
93870
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
@@ -93353,6 +94049,195 @@ function ensureCategory(permission, category) {
|
|
|
93353
94049
|
function removeEmptyCategories(permission) {
|
|
93354
94050
|
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
93355
94051
|
}
|
|
94052
|
+
/** The prefix Hermes's MCP trust gate keys its persisted "Always" answers with. */
|
|
94053
|
+
const MCP_ALLOWLIST_PREFIX = "mcp:";
|
|
94054
|
+
const MCP_CANONICAL_PREFIX$4 = "mcp__";
|
|
94055
|
+
const CATCH_ALL_PATTERN$8 = "*";
|
|
94056
|
+
/**
|
|
94057
|
+
* Whether a `command_allowlist` entry is an MCP trust-gate grant rather than a
|
|
94058
|
+
* shell-command pattern. Since Hermes Agent v0.22.6, answering **Always** to
|
|
94059
|
+
* the approval prompt of a `trust: untrusted` MCP server appends
|
|
94060
|
+
* `mcp:<server>/<tool>` to `command_allowlist` (`_trust_approval_key` in
|
|
94061
|
+
* `tools/mcp_tool_handlers.py`), and the gate looks the key up by exact set
|
|
94062
|
+
* membership, so no `mcp:` entry is ever a command.
|
|
94063
|
+
*/
|
|
94064
|
+
function isMcpAllowlistEntry(entry) {
|
|
94065
|
+
return entry.startsWith(MCP_ALLOWLIST_PREFIX);
|
|
94066
|
+
}
|
|
94067
|
+
/**
|
|
94068
|
+
* The canonical category (`mcp__<server>__<tool>`) a Hermes `mcp:<server>/<tool>`
|
|
94069
|
+
* grant stands for, or `null` when it has none: a malformed entry, a server
|
|
94070
|
+
* name containing `__` (the canonical name would read back as another server),
|
|
94071
|
+
* or a `*` in either name (Hermes compares the key literally, while a canonical
|
|
94072
|
+
* `*` is a wildcard).
|
|
94073
|
+
*/
|
|
94074
|
+
function mcpAllowlistEntryToCategory(entry) {
|
|
94075
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
94076
|
+
if (parsed === null) return null;
|
|
94077
|
+
const { server, tool } = parsed;
|
|
94078
|
+
if (server.includes("__") || server.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
94079
|
+
if (tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
94080
|
+
return `${MCP_CANONICAL_PREFIX$4}${server}__${tool}`;
|
|
94081
|
+
}
|
|
94082
|
+
/** The server and tool an `mcp:<server>/<tool>` entry names, or `null` when it is malformed. */
|
|
94083
|
+
function parseMcpAllowlistEntry(entry) {
|
|
94084
|
+
if (!isMcpAllowlistEntry(entry)) return null;
|
|
94085
|
+
const rest = entry.slice(4);
|
|
94086
|
+
const slash = rest.indexOf("/");
|
|
94087
|
+
const server = slash === -1 ? "" : rest.slice(0, slash);
|
|
94088
|
+
const tool = slash === -1 ? "" : rest.slice(slash + 1);
|
|
94089
|
+
return server.length === 0 || tool.length === 0 ? null : {
|
|
94090
|
+
server,
|
|
94091
|
+
tool
|
|
94092
|
+
};
|
|
94093
|
+
}
|
|
94094
|
+
/**
|
|
94095
|
+
* The server and tool a canonical `mcp__<server>__<tool>` category names, or
|
|
94096
|
+
* `null` when it names no single tool (a whole server, a wildcard, or the bare
|
|
94097
|
+
* `mcp` category). Only such a category can become one Hermes grant.
|
|
94098
|
+
*/
|
|
94099
|
+
function parseMcpToolCategory(category) {
|
|
94100
|
+
if (!category.startsWith(MCP_CANONICAL_PREFIX$4)) return null;
|
|
94101
|
+
const remainder = category.slice(5);
|
|
94102
|
+
const separator = remainder.indexOf("__");
|
|
94103
|
+
if (separator <= 0) return null;
|
|
94104
|
+
const server = remainder.slice(0, separator);
|
|
94105
|
+
const tool = remainder.slice(separator + 2);
|
|
94106
|
+
if (tool.length === 0 || server.includes("/") || server.includes(CATCH_ALL_PATTERN$8) || tool.includes(CATCH_ALL_PATTERN$8)) return null;
|
|
94107
|
+
return {
|
|
94108
|
+
server,
|
|
94109
|
+
tool
|
|
94110
|
+
};
|
|
94111
|
+
}
|
|
94112
|
+
function isMcpCategory$1(category) {
|
|
94113
|
+
return category === "mcp" || category.startsWith(MCP_CANONICAL_PREFIX$4);
|
|
94114
|
+
}
|
|
94115
|
+
function isRestricting(action) {
|
|
94116
|
+
return action === "deny" || action === "ask";
|
|
94117
|
+
}
|
|
94118
|
+
/** The first `deny` or `ask` rule of a category, as `<category>: <pattern> (<action>)`. */
|
|
94119
|
+
function firstRestrictingRule(category, rules, covers = () => true) {
|
|
94120
|
+
const found = Object.entries(rules ?? {}).find(([pattern, action]) => isRestricting(action) && covers(pattern));
|
|
94121
|
+
return found ? `${category}: ${found[0]} (${found[1]})` : null;
|
|
94122
|
+
}
|
|
94123
|
+
/**
|
|
94124
|
+
* Whether an MCP name glob (the remainder of an `mcp__...` category, or a
|
|
94125
|
+
* pattern of the bare `mcp` category) covers `server`'s tool `tool`: it names
|
|
94126
|
+
* the tool (`github__delete_repo`, `github__*`, `*__delete_repo`, `*`) or the
|
|
94127
|
+
* whole server (`github`).
|
|
94128
|
+
*/
|
|
94129
|
+
function mcpNameGlobCovers(glob, server, tool) {
|
|
94130
|
+
return glob === `${server}__${tool}` || glob === server || matchesGlob(glob, `${server}__${tool}`) || matchesGlob(glob, server);
|
|
94131
|
+
}
|
|
94132
|
+
/**
|
|
94133
|
+
* The `deny` or `ask` rule that outranks an allow on `server`'s tool `tool`, or
|
|
94134
|
+
* `null` when none does: any one under the all-tools `*` category (a rule there
|
|
94135
|
+
* applies to every tool, so even a narrower pattern restricts some of the
|
|
94136
|
+
* tool's calls), one under the bare `mcp` category whose pattern covers the
|
|
94137
|
+
* tool, or any one in an `mcp__...` category whose name covers the tool — its
|
|
94138
|
+
* own category included, since a deny or ask scoped to some of its calls still
|
|
94139
|
+
* forbids approving all of them. A Hermes grant approves every call of the
|
|
94140
|
+
* tool and Hermes has no ask tier, so such an allow is withheld, the same way
|
|
94141
|
+
* a `bash` allow a stricter rule covers is.
|
|
94142
|
+
*/
|
|
94143
|
+
function findMcpToolRestriction(permission, server, tool) {
|
|
94144
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
94145
|
+
let found = null;
|
|
94146
|
+
if (category === "*") found = firstRestrictingRule(category, rules);
|
|
94147
|
+
else if (category === "mcp") found = firstRestrictingRule(category, rules, (pattern) => mcpNameGlobCovers(pattern, server, tool));
|
|
94148
|
+
else if (category.startsWith(MCP_CANONICAL_PREFIX$4) && mcpNameGlobCovers(category.slice(5), server, tool)) found = firstRestrictingRule(category, rules);
|
|
94149
|
+
if (found !== null) return found;
|
|
94150
|
+
}
|
|
94151
|
+
return null;
|
|
94152
|
+
}
|
|
94153
|
+
function isMcpToolRestricted(permission, server, tool) {
|
|
94154
|
+
return findMcpToolRestriction(permission, server, tool) !== null;
|
|
94155
|
+
}
|
|
94156
|
+
/**
|
|
94157
|
+
* Translate the canonical MCP `allow` rules into Hermes trust-gate grants: a
|
|
94158
|
+
* `*` allow on `mcp__<server>__<tool>` becomes `mcp:<server>/<tool>`. Every
|
|
94159
|
+
* other MCP allow (a whole server, a wildcard, a narrower pattern) has no
|
|
94160
|
+
* Hermes spelling, and an allow a deny or ask covers is withheld.
|
|
94161
|
+
*/
|
|
94162
|
+
function collectMcpAllowlistEntries(permission) {
|
|
94163
|
+
const entries = [];
|
|
94164
|
+
const withheldCategories = [];
|
|
94165
|
+
const unexpressedRules = [];
|
|
94166
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
94167
|
+
if (!isMcpCategory$1(category)) continue;
|
|
94168
|
+
const parsed = parseMcpToolCategory(category);
|
|
94169
|
+
for (const pattern of patternsByAction(rules, "allow")) if (parsed === null || pattern !== CATCH_ALL_PATTERN$8) unexpressedRules.push(`${category}: ${pattern}`);
|
|
94170
|
+
else {
|
|
94171
|
+
const restriction = findMcpToolRestriction(permission, parsed.server, parsed.tool);
|
|
94172
|
+
if (restriction === null) entries.push(`${MCP_ALLOWLIST_PREFIX}${parsed.server}/${parsed.tool}`);
|
|
94173
|
+
else withheldCategories.push(`${category} (covered by ${restriction})`);
|
|
94174
|
+
}
|
|
94175
|
+
}
|
|
94176
|
+
return {
|
|
94177
|
+
entries,
|
|
94178
|
+
withheldCategories,
|
|
94179
|
+
unexpressedRules
|
|
94180
|
+
};
|
|
94181
|
+
}
|
|
94182
|
+
function warnAboutUnwrittenMcpAllows({ withheldCategories, unexpressedRules, logger }) {
|
|
94183
|
+
if (withheldCategories.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist was not given the MCP allow rule(s) for ${withheldCategories.join(", ")}: a deny or ask rule covers each, a Hermes grant approves every call of the tool, and Hermes has no ask tier.`);
|
|
94184
|
+
if (unexpressedRules.length > 0) warnWithFallback(logger, `Hermes Agent grants MCP tools one at a time (command_allowlist entries 'mcp:<server>/<tool>'), so only a '*' allow on an 'mcp__<server>__<tool>' category can be written; the MCP allow rule(s) ${unexpressedRules.join(", ")} were skipped and survive only in the permissions.rulesync round-trip block.`);
|
|
94185
|
+
}
|
|
94186
|
+
/**
|
|
94187
|
+
* Make the native `mcp:<server>/<tool>` grants authoritative for the canonical
|
|
94188
|
+
* single-tool MCP allows they can speak for. An allow the generator withheld
|
|
94189
|
+
* because a deny or ask covers it was never written, so its absence
|
|
94190
|
+
* is not a retraction and it is kept, as is every MCP allow with no Hermes
|
|
94191
|
+
* spelling. A grant with no canonical spelling stays only in Hermes, where
|
|
94192
|
+
* generation keeps it (see `setFileContent`).
|
|
94193
|
+
*/
|
|
94194
|
+
function reconcileMcpAllowlist({ permission, mcpEntries, logger }) {
|
|
94195
|
+
for (const [category, rules] of Object.entries(permission)) {
|
|
94196
|
+
const parsed = parseMcpToolCategory(category);
|
|
94197
|
+
if (parsed !== null && rules[CATCH_ALL_PATTERN$8] === "allow" && !isMcpToolRestricted(permission, parsed.server, parsed.tool)) delete rules[CATCH_ALL_PATTERN$8];
|
|
94198
|
+
}
|
|
94199
|
+
const keptRestrictions = [];
|
|
94200
|
+
for (const entry of mcpEntries) {
|
|
94201
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
94202
|
+
if (category === null) continue;
|
|
94203
|
+
const rules = ensureCategory(permission, category);
|
|
94204
|
+
if (isRestricting(rules[CATCH_ALL_PATTERN$8])) {
|
|
94205
|
+
keptRestrictions.push(entry);
|
|
94206
|
+
continue;
|
|
94207
|
+
}
|
|
94208
|
+
rules[CATCH_ALL_PATTERN$8] = "allow";
|
|
94209
|
+
}
|
|
94210
|
+
if (keptRestrictions.length > 0) warnWithFallback(logger, `Hermes Agent's command_allowlist grants ${keptRestrictions.join(", ")}, but the permissions.rulesync block denies or asks for those MCP tools; the canonical rule was kept and the grant was not imported.`);
|
|
94211
|
+
}
|
|
94212
|
+
/**
|
|
94213
|
+
* The `mcp:` grants in an existing `command_allowlist` that rulesync does not
|
|
94214
|
+
* manage and must keep when it rewrites the list: Hermes appends one whenever
|
|
94215
|
+
* the user answers **Always** on an untrusted MCP server, so dropping it would
|
|
94216
|
+
* silently revoke the grant. A grant is managed — and so retracted with its
|
|
94217
|
+
* canonical rule — when the previous or the new canonical block has a rule
|
|
94218
|
+
* for its `mcp__<server>__<tool>` category, or when the new block restricts
|
|
94219
|
+
* the tool in any way.
|
|
94220
|
+
*/
|
|
94221
|
+
function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermission, nextAllowlist }) {
|
|
94222
|
+
return (isStringArray(existing.command_allowlist) ? existing.command_allowlist : []).filter((entry) => {
|
|
94223
|
+
if (!isMcpAllowlistEntry(entry) || nextAllowlist.includes(entry)) return false;
|
|
94224
|
+
const parsed = parseMcpAllowlistEntry(entry);
|
|
94225
|
+
if (parsed !== null && isMcpToolRestricted(nextPermission, parsed.server, parsed.tool)) return false;
|
|
94226
|
+
const category = mcpAllowlistEntryToCategory(entry);
|
|
94227
|
+
return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
|
|
94228
|
+
});
|
|
94229
|
+
}
|
|
94230
|
+
/** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
|
|
94231
|
+
function canonicalPermissionOf(config) {
|
|
94232
|
+
const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
|
|
94233
|
+
return parsed.success ? parsed.data.permission : {};
|
|
94234
|
+
}
|
|
94235
|
+
/** The canonical `permission` block of a config's `permissions.rulesync` blob, if any. */
|
|
94236
|
+
function provenancePermissionOf(config) {
|
|
94237
|
+
const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
|
|
94238
|
+
const provenance = isRecord(permissionsRoot.rulesync) ? permissionsRoot.rulesync : {};
|
|
94239
|
+
return isRecord(provenance.permission) ? provenance.permission : {};
|
|
94240
|
+
}
|
|
93356
94241
|
/**
|
|
93357
94242
|
* Make the native `command_allowlist` authoritative for the `bash` allow rules
|
|
93358
94243
|
* it can speak for, and for those only. The allowlist is a list of shell-command
|
|
@@ -93370,8 +94255,9 @@ function reconcileCommandAllowlist({ permission, commandAllowlist }) {
|
|
|
93370
94255
|
writesAllToolsDeny: false
|
|
93371
94256
|
});
|
|
93372
94257
|
const withheld = new Set(shadowedAllowPatterns);
|
|
94258
|
+
const isWithheld = (pattern) => withheld.has(pattern) || isMcpAllowlistEntry(pattern);
|
|
93373
94259
|
const rules = ensureCategory(permission, SHELL_PERMISSION_CATEGORY);
|
|
93374
|
-
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !
|
|
94260
|
+
for (const [pattern, action] of Object.entries(rules)) if (action === "allow" && !isWithheld(pattern)) delete rules[pattern];
|
|
93375
94261
|
for (const pattern of commandAllowlist) rules[pattern] = "allow";
|
|
93376
94262
|
}
|
|
93377
94263
|
/**
|
|
@@ -93459,17 +94345,31 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93459
94345
|
return true;
|
|
93460
94346
|
}
|
|
93461
94347
|
setFileContent(fileContent) {
|
|
94348
|
+
const patch = parseSharedConfig({
|
|
94349
|
+
format: "yaml",
|
|
94350
|
+
fileContent: this.fileContent
|
|
94351
|
+
});
|
|
94352
|
+
if (isStringArray(patch.command_allowlist)) {
|
|
94353
|
+
const existing = parseSharedConfig({
|
|
94354
|
+
format: "yaml",
|
|
94355
|
+
fileContent
|
|
94356
|
+
});
|
|
94357
|
+
const kept = unmanagedMcpAllowlistEntries({
|
|
94358
|
+
existing,
|
|
94359
|
+
previousPermission: provenancePermissionOf(existing),
|
|
94360
|
+
nextPermission: canonicalPermissionOf(patch),
|
|
94361
|
+
nextAllowlist: patch.command_allowlist
|
|
94362
|
+
});
|
|
94363
|
+
if (kept.length > 0) patch.command_allowlist = [...patch.command_allowlist, ...kept];
|
|
94364
|
+
}
|
|
93462
94365
|
this.fileContent = applySharedConfigPatch({
|
|
93463
94366
|
fileKey: getHermesagentConfigSharedFileKey({ global: this.global }),
|
|
93464
94367
|
feature: "permissions",
|
|
93465
94368
|
existingContent: fileContent,
|
|
93466
|
-
patch
|
|
93467
|
-
format: "yaml",
|
|
93468
|
-
fileContent: this.fileContent
|
|
93469
|
-
})
|
|
94369
|
+
patch
|
|
93470
94370
|
});
|
|
93471
94371
|
}
|
|
93472
|
-
toRulesyncPermissions() {
|
|
94372
|
+
toRulesyncPermissions({ logger } = {}) {
|
|
93473
94373
|
const config = parseSharedConfig({
|
|
93474
94374
|
format: "yaml",
|
|
93475
94375
|
fileContent: this.getFileContent()
|
|
@@ -93481,9 +94381,15 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93481
94381
|
}) : rawProvenance);
|
|
93482
94382
|
const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
|
|
93483
94383
|
const permission = clonePermissionBlock(provenance.permission);
|
|
94384
|
+
const commandAllowlist = isStringArray(config.command_allowlist) ? config.command_allowlist : [];
|
|
93484
94385
|
reconcileCommandAllowlist({
|
|
93485
94386
|
permission,
|
|
93486
|
-
commandAllowlist:
|
|
94387
|
+
commandAllowlist: commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry))
|
|
94388
|
+
});
|
|
94389
|
+
reconcileMcpAllowlist({
|
|
94390
|
+
permission,
|
|
94391
|
+
mcpEntries: commandAllowlist.filter(isMcpAllowlistEntry),
|
|
94392
|
+
logger
|
|
93487
94393
|
});
|
|
93488
94394
|
const approvals = isRecord(config.approvals) ? config.approvals : {};
|
|
93489
94395
|
reconcileNativeDenies({
|
|
@@ -93541,8 +94447,16 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
93541
94447
|
logger
|
|
93542
94448
|
});
|
|
93543
94449
|
const webfetchDeny = patternsByAction(permissionBlock[WEBFETCH_PERMISSION_CATEGORY], "deny");
|
|
94450
|
+
const mcpAllows = collectMcpAllowlistEntries(permissionBlock);
|
|
94451
|
+
warnAboutUnwrittenMcpAllows({
|
|
94452
|
+
...mcpAllows,
|
|
94453
|
+
logger
|
|
94454
|
+
});
|
|
94455
|
+
const bashMcpAllows = commandAllowlist.filter(isMcpAllowlistEntry);
|
|
94456
|
+
if (bashMcpAllows.length > 0) warnWithFallback(logger, `Hermes Agent reads 'mcp:' entries in command_allowlist as MCP tool grants, so the bash allow rule(s) ${bashMcpAllows.join(", ")} were skipped; write them as a '*' allow on an 'mcp__<server>__<tool>' category instead.`);
|
|
94457
|
+
const allowlist = [...commandAllowlist.filter((entry) => !isMcpAllowlistEntry(entry)), ...mcpAllows.entries];
|
|
93544
94458
|
let config = {};
|
|
93545
|
-
if (
|
|
94459
|
+
if (allowlist.length > 0) config.command_allowlist = allowlist;
|
|
93546
94460
|
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
93547
94461
|
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
93548
94462
|
enabled: true,
|
|
@@ -100383,6 +101297,7 @@ const TAKT_SECURITY_POLICIES = {
|
|
|
100383
101297
|
],
|
|
100384
101298
|
workflow_runtime_prepare: ["custom_scripts"],
|
|
100385
101299
|
workflow_command_gates: ["custom_scripts"],
|
|
101300
|
+
workflow_system_commands: ["custom_scripts"],
|
|
100386
101301
|
sync_conflict_resolver: ["auto_approve_tools"],
|
|
100387
101302
|
allow_git_hooks: null,
|
|
100388
101303
|
allow_git_filters: null
|
|
@@ -107352,4 +108267,4 @@ async function importChecksCore(params) {
|
|
|
107352
108267
|
}
|
|
107353
108268
|
export { RulesyncPermissions as $, formatError as $n, isSymlink as $t, CODEBUDDY_DIR as A, MAX_FILE_SIZE as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, PACKAGING_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, DEPRECATED_FEATURE_REPLACEMENTS as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ALL_FEATURES_WITH_WILDCARD as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_CONFIG_SCHEMA_URL as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_PERMISSIONS_SCHEMA_URL as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_MCP_SCHEMA_URL as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_RELATIVE_DIR_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, CURATED_RULES_FEATURE_SUBDIR as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CONFIG_RELATIVE_FILE_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, ALL_TOOL_TARGETS_WITH_WILDCARD as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, ALL_FEATURES as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_MCP_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_USER_CONFIG_DIR_NAME as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, toPosixPath as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, readFileContent as an, stripControlCharactersKeepingLineFeeds as ar, parseJsonc as at, CommandsProcessor as b, mapWithConcurrency as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, removeDirectoryStrict as cn, RulesyncCheck as ct, shortenToWidth as d, removeFileWithoutFollowingSymlinks as dn, loadYaml as dt, listDirectoryEntryNames as en, truncateText as er, RulesyncMcp as et, McpProcessor as f, removeTempDirectory as fn, isRecord as ft, RulesProcessor as g, splitPathSegments as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, runWithDirectoryRollback as hn, ConfigResolver as ht, inspectInputRoots as i, posixRelativePathEscapesRoot as in, stripControlCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, FEATURE_SOURCE_TREE_ENTRIES as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeFile as ln, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, resolvedRelativePath as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listSubdirectoryNames as nn, hasEnclosingMarkOutsideKeycap as nr, RulesyncHooks as nt, isPackagingToolTarget as o, readFileContentOrNull as on, stripControlCharactersKeepingLineFeedsAndTabs as or, RulesyncCommand as ot, IgnoreProcessor as p, resolvePath as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_RULES_RELATIVE_DIR_PATH as qn, fileExistsStrict as qt, generate as r, pathEscapesRoot as rn, quoteForLog as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, removeDirectory as sn, stripHiddenCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listFilePathsRecursively as tn, hasDeceptiveHiddenCharacters as tr, RulesyncIgnore as tt, displayWidthOf as u, removeFileStrict as un, stringifyFrontmatter as ut, SkillsProcessor as v, writeFileBuffer as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ToolTargetSchema as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, ALL_TOOL_TARGETS as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, writeFileContent as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_IGNORE_RELATIVE_FILE_PATH as zn, assertPathResolvesInsideRoot as zt };
|
|
107354
108269
|
|
|
107355
|
-
//# sourceMappingURL=import
|
|
108270
|
+
//# sourceMappingURL=import--Y_A56qx.js.map
|