rulesync 29.0.0 → 31.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.cjs +17 -11
- package/dist/cli/index.js +17 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CLfSccAk.cjs → import-CBvXTNp8.cjs} +1457 -251
- package/dist/{import--SCvWeDn.js → import-ORYbMDJK.js} +1441 -235
- package/dist/{import--SCvWeDn.js.map → import-ORYbMDJK.js.map} +1 -1
- 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
|
@@ -453,36 +453,36 @@ const isFeatureValueEnabled = (value) => {
|
|
|
453
453
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
454
454
|
//#endregion
|
|
455
455
|
//#region src/constants/rulesync-paths.ts
|
|
456
|
-
const { join: join$
|
|
456
|
+
const { join: join$416 } = node_path.posix;
|
|
457
457
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
458
458
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
459
459
|
const RULESYNC_USER_CONFIG_DIR_NAME = "rulesync";
|
|
460
460
|
const RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME = ".config";
|
|
461
461
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
462
462
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
463
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
463
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$416(RULES_FEATURE_SUBDIR, ".curated");
|
|
464
464
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
465
465
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
466
466
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
467
467
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
468
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
469
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
470
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
471
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
472
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
473
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
475
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
476
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
477
|
-
join$
|
|
478
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
479
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
468
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$416(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
469
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
470
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
471
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
472
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
473
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
475
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
476
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
477
|
+
join$416(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
478
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
479
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
480
480
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
481
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
481
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
482
482
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
483
483
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
484
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
485
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
484
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
485
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$416(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
486
486
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
487
487
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
488
488
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -935,6 +935,7 @@ const checksProcessorToolTargetTuple = [
|
|
|
935
935
|
"cursor",
|
|
936
936
|
"factorydroid",
|
|
937
937
|
"gitlabduo",
|
|
938
|
+
"goose",
|
|
938
939
|
"hermesagent",
|
|
939
940
|
"kilo",
|
|
940
941
|
"qwencode",
|
|
@@ -7883,6 +7884,26 @@ const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7883
7884
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7884
7885
|
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum(COPILOT_DEFAULT_PERMISSION_MODES))
|
|
7885
7886
|
});
|
|
7887
|
+
/**
|
|
7888
|
+
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
7889
|
+
* before prompting. `always-ask` auto-approves only read-only tools, `write`
|
|
7890
|
+
* also workspace writes, and `yolo` (oh-my-pi's default) every tier.
|
|
7891
|
+
*
|
|
7892
|
+
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
7893
|
+
*/
|
|
7894
|
+
const OMP_APPROVAL_MODES = [
|
|
7895
|
+
"always-ask",
|
|
7896
|
+
"write",
|
|
7897
|
+
"yolo"
|
|
7898
|
+
];
|
|
7899
|
+
/**
|
|
7900
|
+
* Tool-scoped override block for oh-my-pi (`omp`). `approvalMode` writes the
|
|
7901
|
+
* `tools.approvalMode` key of `config.yml`, which has no canonical category.
|
|
7902
|
+
*/
|
|
7903
|
+
const OmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7904
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7905
|
+
approvalMode: zod_mini.z.optional(zod_mini.z.enum(OMP_APPROVAL_MODES))
|
|
7906
|
+
});
|
|
7886
7907
|
const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7887
7908
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7888
7909
|
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -7890,6 +7911,14 @@ const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7890
7911
|
"yolo",
|
|
7891
7912
|
"auto"
|
|
7892
7913
|
])),
|
|
7914
|
+
/**
|
|
7915
|
+
* Kimi's `[permission] dangerous_command_guard` switch (Kimi Code 0.40.0,
|
|
7916
|
+
* default `true`). `false` turns off the built-in dangerous-command policy
|
|
7917
|
+
* (no confirmation for `rm -rf`, `shutdown` and the like).
|
|
7918
|
+
*
|
|
7919
|
+
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
7920
|
+
*/
|
|
7921
|
+
dangerousCommandGuard: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7893
7922
|
rules: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
7894
7923
|
decision: PermissionActionSchema,
|
|
7895
7924
|
pattern: zod_mini.z.string(),
|
|
@@ -8105,7 +8134,10 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8105
8134
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8106
8135
|
* `todoWrite`, `workflowsEnabled`, `workflowNameOnly`, `executionSandbox`) and
|
|
8107
8136
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8108
|
-
* `allowedInsecureVoiceBaseUrls`)
|
|
8137
|
+
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8138
|
+
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
8139
|
+
* Code unions across scopes) and `general` (`outputStyle`, the output style
|
|
8140
|
+
* that reshapes how responses are written). Qwen Code strips `tools.workflowsEnabled`,
|
|
8109
8141
|
* `tools.executionSandbox`, `security.allowPrivateNetworkHooks` and
|
|
8110
8142
|
* `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
|
|
8111
8143
|
* skips those four in project scope and announces a granting value in global
|
|
@@ -8122,7 +8154,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8122
8154
|
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`, and `eager`, which
|
|
8123
8155
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8124
8156
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8125
|
-
* classifier config, and, because these groups are
|
|
8157
|
+
* classifier config, the skill kill-switches, and, because these groups are
|
|
8158
|
+
* loose objects, any key
|
|
8126
8159
|
* rulesync does not model. Import flags a scope-dependent key only when it read
|
|
8127
8160
|
* the project file, since that is the value a `--global` regenerate would
|
|
8128
8161
|
* promote. It also
|
|
@@ -8133,11 +8166,13 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8133
8166
|
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
8134
8167
|
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
8135
8168
|
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
8136
|
-
* `tools`/`security`/`autoMode` key can be authored.
|
|
8169
|
+
* `tools`/`security`/`skills`/`general`/`autoMode` key can be authored.
|
|
8137
8170
|
*
|
|
8138
8171
|
* @example
|
|
8139
8172
|
* {
|
|
8140
8173
|
* "tools": { "approvalMode": "auto-edit" },
|
|
8174
|
+
* "skills": { "disabled": ["pdf"], "disabledLevels": ["bundled"] },
|
|
8175
|
+
* "general": { "outputStyle": "Concise" },
|
|
8141
8176
|
* "security": { "folderTrust": { "enabled": true } },
|
|
8142
8177
|
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
8143
8178
|
* }
|
|
@@ -8146,6 +8181,8 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8146
8181
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8147
8182
|
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8148
8183
|
security: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8184
|
+
skills: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8185
|
+
general: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8149
8186
|
autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
8150
8187
|
});
|
|
8151
8188
|
/**
|
|
@@ -8231,6 +8268,8 @@ const TabninePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8231
8268
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
8232
8269
|
* @example
|
|
8233
8270
|
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
8271
|
+
* @example
|
|
8272
|
+
* { "mode": "deny" }
|
|
8234
8273
|
*/
|
|
8235
8274
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8236
8275
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
@@ -8239,7 +8278,12 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8239
8278
|
rawAllow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8240
8279
|
rawAsk: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8241
8280
|
rawDeny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8242
|
-
allowDynamicBash: zod_mini.z.optional(zod_mini.z.boolean())
|
|
8281
|
+
allowDynamicBash: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8282
|
+
mode: zod_mini.z.optional(zod_mini.z.enum([
|
|
8283
|
+
"ask",
|
|
8284
|
+
"allow",
|
|
8285
|
+
"deny"
|
|
8286
|
+
]))
|
|
8243
8287
|
});
|
|
8244
8288
|
/**
|
|
8245
8289
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -8282,7 +8326,8 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8282
8326
|
* {
|
|
8283
8327
|
* "agent_mode_coding_permissions": "always_allow_reading",
|
|
8284
8328
|
* "agent_mode_execute_readonly_commands": true,
|
|
8285
|
-
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] }
|
|
8329
|
+
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] },
|
|
8330
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8286
8331
|
* }
|
|
8287
8332
|
*/
|
|
8288
8333
|
/**
|
|
@@ -8334,6 +8379,16 @@ const WarpExecutionProfileOverrideSchema = zod_mini.z.looseObject({
|
|
|
8334
8379
|
mcp_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8335
8380
|
mcp_denylist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8336
8381
|
});
|
|
8382
|
+
/**
|
|
8383
|
+
* Whether auto-approve may run commands that match the command denylist
|
|
8384
|
+
* (Warp defaults to `true`). It is not an execution-profile field: it lives in
|
|
8385
|
+
* the sibling `[agents.warp_agent.other]` table of the same `settings.toml`,
|
|
8386
|
+
* shared by the Warp app and the Warp Agent CLI.
|
|
8387
|
+
*
|
|
8388
|
+
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8389
|
+
* @see https://github.com/warpdotdev/warp/blob/main/app/src/settings/ai.rs (`AutoApproveBypassesCommandDenylist`)
|
|
8390
|
+
*/
|
|
8391
|
+
const WarpAutoApproveBypassSchema = zod_mini.z.optional(zod_mini.z.boolean());
|
|
8337
8392
|
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8338
8393
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8339
8394
|
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -8343,7 +8398,8 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8343
8398
|
])),
|
|
8344
8399
|
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8345
8400
|
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8346
|
-
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema)
|
|
8401
|
+
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema),
|
|
8402
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8347
8403
|
});
|
|
8348
8404
|
/**
|
|
8349
8405
|
* Tool-scoped override block for the standalone Warp Agent CLI. The CLI reads
|
|
@@ -8352,16 +8408,21 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8352
8408
|
* the app's legacy `[agents.profiles]` keys), so only the nested
|
|
8353
8409
|
* `execution_profile` block is offered; it is merged into that record, and the
|
|
8354
8410
|
* shared `permission.bash` block still drives `command_allowlist` /
|
|
8355
|
-
* `command_denylist`.
|
|
8411
|
+
* `command_denylist`. `auto_approve_bypasses_command_denylist` is written to
|
|
8412
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
8356
8413
|
*
|
|
8357
8414
|
* @example
|
|
8358
|
-
* {
|
|
8415
|
+
* {
|
|
8416
|
+
* "execution_profile": { "apply_code_diffs": "always_ask", "run_agents": "never_allow" },
|
|
8417
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8418
|
+
* }
|
|
8359
8419
|
*
|
|
8360
8420
|
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8361
8421
|
*/
|
|
8362
8422
|
const WarpcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8363
8423
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8364
|
-
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema)
|
|
8424
|
+
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema),
|
|
8425
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8365
8426
|
});
|
|
8366
8427
|
/**
|
|
8367
8428
|
* deepagents-cli's approval-mode knobs under `[startup]` in
|
|
@@ -9057,7 +9118,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
9057
9118
|
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9058
9119
|
crush: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9059
9120
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9060
|
-
omp: zod_mini.z.optional(
|
|
9121
|
+
omp: zod_mini.z.optional(OmpPermissionsOverrideSchema),
|
|
9061
9122
|
pool: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9062
9123
|
qoder: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9063
9124
|
grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -12767,7 +12828,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12767
12828
|
ownedKeys: [
|
|
12768
12829
|
"permissions",
|
|
12769
12830
|
"tools",
|
|
12770
|
-
"security"
|
|
12831
|
+
"security",
|
|
12832
|
+
"skills",
|
|
12833
|
+
"general"
|
|
12771
12834
|
]
|
|
12772
12835
|
}
|
|
12773
12836
|
}
|
|
@@ -13137,7 +13200,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13137
13200
|
features: {
|
|
13138
13201
|
mcp: {
|
|
13139
13202
|
kind: "replace-owned-keys",
|
|
13140
|
-
ownedKeys: ["mcp_servers"]
|
|
13203
|
+
ownedKeys: ["mcp_servers", "disabled_mcp_tools"]
|
|
13141
13204
|
},
|
|
13142
13205
|
permissions: {
|
|
13143
13206
|
kind: "replace-owned-keys",
|
|
@@ -13272,6 +13335,14 @@ function serializeSharedConfigFile({ fileKey, document, existingContent, filePat
|
|
|
13272
13335
|
});
|
|
13273
13336
|
}
|
|
13274
13337
|
/**
|
|
13338
|
+
* A patch value for an owned key that holds a map of managed entries: the map
|
|
13339
|
+
* itself, or `undefined` when it is empty, which retracts the key instead of
|
|
13340
|
+
* leaving an empty object behind (`applySharedConfigPatch` deletes a key whose patch value is `undefined`).
|
|
13341
|
+
*/
|
|
13342
|
+
function undefinedWhenEmpty(entries) {
|
|
13343
|
+
return Object.keys(entries).length > 0 ? entries : void 0;
|
|
13344
|
+
}
|
|
13345
|
+
/**
|
|
13275
13346
|
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
13276
13347
|
* the existing content, merge the patch under the feature's declared policy,
|
|
13277
13348
|
* and serialize it back over the existing content (see
|
|
@@ -13758,14 +13829,23 @@ var FeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
13758
13829
|
async removeOrphanAiFiles(existingFiles, generatedFiles) {
|
|
13759
13830
|
const generatedPaths = new Set(generatedFiles.map((f) => caseFoldIdentity(f.getFilePath())));
|
|
13760
13831
|
const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(caseFoldIdentity(f.getFilePath())));
|
|
13832
|
+
const refusedDirs = /* @__PURE__ */ new Map();
|
|
13761
13833
|
let removedCount = 0;
|
|
13762
13834
|
for (const aiFile of orphanFiles) {
|
|
13763
13835
|
const filePath = aiFile.getFilePath();
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13836
|
+
const rootPath = aiFile.getOutputRoot();
|
|
13837
|
+
const dirPath = (0, node_path.dirname)(filePath);
|
|
13838
|
+
const cacheKey = JSON.stringify([rootPath, dirPath]);
|
|
13839
|
+
let refused = refusedDirs.get(cacheKey);
|
|
13840
|
+
if (refused === void 0) {
|
|
13841
|
+
refused = await refusesDeleteOutsideRoot({
|
|
13842
|
+
logger: this.logger,
|
|
13843
|
+
rootPath,
|
|
13844
|
+
dirPath
|
|
13845
|
+
});
|
|
13846
|
+
refusedDirs.set(cacheKey, refused);
|
|
13847
|
+
}
|
|
13848
|
+
if (refused) continue;
|
|
13769
13849
|
const loggedPath = stripControlCharacters(filePath);
|
|
13770
13850
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would delete: ${loggedPath}`);
|
|
13771
13851
|
else {
|
|
@@ -13886,22 +13966,24 @@ async function refusesWriteOutsideRoot({ logger, rootPath, targetPath }) {
|
|
|
13886
13966
|
return true;
|
|
13887
13967
|
}
|
|
13888
13968
|
/**
|
|
13889
|
-
* Whether deleting `
|
|
13890
|
-
* the
|
|
13969
|
+
* Whether deleting the files in `dirPath` has to be refused because a link on
|
|
13970
|
+
* the way to that directory leads out of `rootPath`, warning about the refusal
|
|
13971
|
+
* once for the whole directory.
|
|
13891
13972
|
*
|
|
13892
13973
|
* Deletion candidates are enumerated without following links *inside* the
|
|
13893
13974
|
* scanned directory, but the scanned directory itself is reached through
|
|
13894
13975
|
* whatever links its path contains: a checked-out repository can carry
|
|
13895
13976
|
* `.cursor/commands -> ~/notes`, and every file in `~/notes` would then read as
|
|
13896
|
-
* an orphan. Only the
|
|
13897
|
-
* a link unlinks the link where it is spelled
|
|
13977
|
+
* an orphan. Only the directory is judged, never the file in it, because
|
|
13978
|
+
* removing a file that is itself a link unlinks the link where it is spelled
|
|
13979
|
+
* and never touches its target.
|
|
13898
13980
|
*/
|
|
13899
|
-
async function refusesDeleteOutsideRoot({ logger, rootPath,
|
|
13981
|
+
async function refusesDeleteOutsideRoot({ logger, rootPath, dirPath }) {
|
|
13900
13982
|
if (!await writablePathEscapesRoot({
|
|
13901
13983
|
rootPath,
|
|
13902
|
-
targetPath:
|
|
13984
|
+
targetPath: dirPath
|
|
13903
13985
|
})) return false;
|
|
13904
|
-
logger.warn(`Refusing to delete ${quoteForLog(
|
|
13986
|
+
logger.warn(`Refusing to delete files in ${quoteForLog(dirPath)}: it resolves outside ${quoteForLog(rootPath)} through a symbolic link`);
|
|
13905
13987
|
return true;
|
|
13906
13988
|
}
|
|
13907
13989
|
/**
|
|
@@ -14140,12 +14222,12 @@ const AmpCheckFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
14140
14222
|
* this format continue to apply.
|
|
14141
14223
|
* @see https://ampcode.com/docs/review
|
|
14142
14224
|
*/
|
|
14143
|
-
var AmpCheck = class
|
|
14225
|
+
var AmpCheck = class extends ToolCheck {
|
|
14144
14226
|
frontmatter;
|
|
14145
14227
|
body;
|
|
14146
14228
|
constructor({ frontmatter, body, fileContent, ...rest }) {
|
|
14147
14229
|
if (rest.validate !== false) {
|
|
14148
|
-
const result =
|
|
14230
|
+
const result = new.target.getFrontmatterSchema().safeParse(frontmatter);
|
|
14149
14231
|
if (!result.success) throw new Error(`Invalid frontmatter in ${(0, node_path.join)(rest.relativeDirPath, rest.relativeFilePath)}: ${formatError(result.error)}`);
|
|
14150
14232
|
}
|
|
14151
14233
|
super({
|
|
@@ -14155,6 +14237,26 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14155
14237
|
this.frontmatter = frontmatter;
|
|
14156
14238
|
this.body = body;
|
|
14157
14239
|
}
|
|
14240
|
+
/**
|
|
14241
|
+
* The tool target this check format belongs to, which is also the name of
|
|
14242
|
+
* its tool-specific frontmatter section. Goose reads the same per-file
|
|
14243
|
+
* format with a few extra keys, so `GooseCheck` reuses this class through
|
|
14244
|
+
* these hooks.
|
|
14245
|
+
*/
|
|
14246
|
+
static getCheckToolTarget() {
|
|
14247
|
+
return "amp";
|
|
14248
|
+
}
|
|
14249
|
+
/** The frontmatter schema a check file of this tool is validated against. */
|
|
14250
|
+
static getFrontmatterSchema() {
|
|
14251
|
+
return AmpCheckFrontmatterSchema;
|
|
14252
|
+
}
|
|
14253
|
+
/**
|
|
14254
|
+
* Frontmatter keys only this tool reads. Import moves them into the tool's
|
|
14255
|
+
* own section so they are not passed on to every other checks target.
|
|
14256
|
+
*/
|
|
14257
|
+
static getToolOnlyKeys() {
|
|
14258
|
+
return [];
|
|
14259
|
+
}
|
|
14158
14260
|
static getSettablePaths({ global = false } = {}) {
|
|
14159
14261
|
return { relativeDirPath: global ? AMP_CHECKS_GLOBAL_DIR : AMP_CHECKS_PROJECT_DIR };
|
|
14160
14262
|
}
|
|
@@ -14166,6 +14268,10 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14166
14268
|
}
|
|
14167
14269
|
toRulesyncCheck() {
|
|
14168
14270
|
const { name: _name, description, "severity-default": severityDefault, tools, ...restFields } = this.frontmatter;
|
|
14271
|
+
const checkClass = this.constructor;
|
|
14272
|
+
const toolOnlyKeys = new Set(checkClass.getToolOnlyKeys());
|
|
14273
|
+
const sharedFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolOnlyKeys.has(key)));
|
|
14274
|
+
const toolSection = Object.fromEntries(Object.entries(restFields).filter(([key]) => toolOnlyKeys.has(key)));
|
|
14169
14275
|
return new RulesyncCheck({
|
|
14170
14276
|
outputRoot: ".",
|
|
14171
14277
|
frontmatter: {
|
|
@@ -14173,7 +14279,8 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14173
14279
|
...description !== void 0 && { description },
|
|
14174
14280
|
...severityDefault !== void 0 && { severity: severityDefault },
|
|
14175
14281
|
...tools !== void 0 && { tools },
|
|
14176
|
-
...
|
|
14282
|
+
...sharedFields,
|
|
14283
|
+
...Object.keys(toolSection).length > 0 && { [checkClass.getCheckToolTarget()]: toolSection }
|
|
14177
14284
|
},
|
|
14178
14285
|
body: this.body,
|
|
14179
14286
|
relativeDirPath: RULESYNC_CHECKS_RELATIVE_DIR_PATH,
|
|
@@ -14188,22 +14295,24 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14188
14295
|
const { targets: _targets, description, severity, tools, ...restFields } = rulesyncFrontmatter;
|
|
14189
14296
|
const toolTargetKeys = new Set(ALL_TOOL_TARGETS);
|
|
14190
14297
|
const passthroughFields = Object.fromEntries(Object.entries(restFields).filter(([key]) => !toolTargetKeys.has(key) && key !== "name"));
|
|
14191
|
-
const
|
|
14298
|
+
const toolTarget = this.getCheckToolTarget();
|
|
14299
|
+
const rawSection = rulesyncFrontmatter[toolTarget];
|
|
14300
|
+
const toolSection = this.filterToolSpecificSection(rawSection !== null && typeof rawSection === "object" && !Array.isArray(rawSection) ? rawSection : {}, ["name"]);
|
|
14192
14301
|
const rawAmpFrontmatter = {
|
|
14193
14302
|
name,
|
|
14194
14303
|
...description !== void 0 && { description },
|
|
14195
14304
|
...severity !== void 0 && { "severity-default": severity },
|
|
14196
14305
|
...tools !== void 0 && { tools },
|
|
14197
14306
|
...passthroughFields,
|
|
14198
|
-
...
|
|
14307
|
+
...toolSection
|
|
14199
14308
|
};
|
|
14200
|
-
const result =
|
|
14201
|
-
if (!result.success) throw new Error(`Invalid
|
|
14309
|
+
const result = this.getFrontmatterSchema().safeParse(rawAmpFrontmatter);
|
|
14310
|
+
if (!result.success) throw new Error(`Invalid ${toolTarget} check frontmatter in ${relativeFilePath}: ${formatError(result.error)}`);
|
|
14202
14311
|
const ampFrontmatter = result.data;
|
|
14203
14312
|
const body = rulesyncCheck.getBody();
|
|
14204
14313
|
const fileContent = stringifyFrontmatter(body, ampFrontmatter);
|
|
14205
14314
|
const paths = this.getSettablePaths({ global });
|
|
14206
|
-
return new
|
|
14315
|
+
return new this({
|
|
14207
14316
|
outputRoot,
|
|
14208
14317
|
frontmatter: ampFrontmatter,
|
|
14209
14318
|
body,
|
|
@@ -14218,7 +14327,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14218
14327
|
success: true,
|
|
14219
14328
|
error: null
|
|
14220
14329
|
};
|
|
14221
|
-
const result =
|
|
14330
|
+
const result = this.constructor.getFrontmatterSchema().safeParse(this.frontmatter);
|
|
14222
14331
|
if (result.success) return {
|
|
14223
14332
|
success: true,
|
|
14224
14333
|
error: null
|
|
@@ -14231,7 +14340,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14231
14340
|
static isTargetedByRulesyncCheck(rulesyncCheck) {
|
|
14232
14341
|
return this.isTargetedByRulesyncCheckDefault({
|
|
14233
14342
|
rulesyncCheck,
|
|
14234
|
-
toolTarget:
|
|
14343
|
+
toolTarget: this.getCheckToolTarget()
|
|
14235
14344
|
});
|
|
14236
14345
|
}
|
|
14237
14346
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
@@ -14239,9 +14348,9 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14239
14348
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
14240
14349
|
const fileContent = await readFileContent(filePath);
|
|
14241
14350
|
const { frontmatter, body: content } = parseFrontmatter(fileContent, filePath);
|
|
14242
|
-
const result =
|
|
14351
|
+
const result = this.getFrontmatterSchema().safeParse(frontmatter);
|
|
14243
14352
|
if (!result.success) throw new Error(`Invalid frontmatter in ${filePath}: ${formatError(result.error)}`);
|
|
14244
|
-
return new
|
|
14353
|
+
return new this({
|
|
14245
14354
|
outputRoot,
|
|
14246
14355
|
relativeDirPath: paths.relativeDirPath,
|
|
14247
14356
|
relativeFilePath,
|
|
@@ -14252,7 +14361,7 @@ var AmpCheck = class AmpCheck extends ToolCheck {
|
|
|
14252
14361
|
});
|
|
14253
14362
|
}
|
|
14254
14363
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
|
|
14255
|
-
return new
|
|
14364
|
+
return new this({
|
|
14256
14365
|
outputRoot,
|
|
14257
14366
|
relativeDirPath,
|
|
14258
14367
|
relativeFilePath,
|
|
@@ -15352,6 +15461,60 @@ var GitlabduoCheck = class GitlabduoCheck extends ToolCheck {
|
|
|
15352
15461
|
}
|
|
15353
15462
|
};
|
|
15354
15463
|
//#endregion
|
|
15464
|
+
//#region src/constants/goose-paths.ts
|
|
15465
|
+
const GOOSE_DIR = ".goose";
|
|
15466
|
+
const GOOSE_GLOBAL_DIR = (0, node_path.join)(".config", "goose");
|
|
15467
|
+
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
15468
|
+
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
15469
|
+
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
15470
|
+
const GOOSE_HOOKS_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync", "hooks");
|
|
15471
|
+
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
15472
|
+
const GOOSE_PLUGIN_MCP_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync");
|
|
15473
|
+
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
15474
|
+
const GOOSE_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "skills");
|
|
15475
|
+
const GOOSE_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "skills");
|
|
15476
|
+
const GOOSE_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "recipes");
|
|
15477
|
+
const GOOSE_GLOBAL_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "recipes");
|
|
15478
|
+
const GOOSE_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "agents");
|
|
15479
|
+
const GOOSE_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "agents");
|
|
15480
|
+
const GOOSE_CHECKS_PROJECT_DIR = (0, node_path.join)(".agents", "checks");
|
|
15481
|
+
const GOOSE_CHECKS_GLOBAL_DIR = (0, node_path.join)(GOOSE_GLOBAL_DIR, "checks");
|
|
15482
|
+
//#endregion
|
|
15483
|
+
//#region src/features/checks/goose-check.ts
|
|
15484
|
+
const GooseCheckFrontmatterSchema = zod_mini.z.extend(AmpCheckFrontmatterSchema, {
|
|
15485
|
+
model: zod_mini.z.optional(zod_mini.z.string()),
|
|
15486
|
+
"turn-limit": zod_mini.z.optional(zod_mini.z.int().check(zod_mini.z.nonnegative()))
|
|
15487
|
+
});
|
|
15488
|
+
/**
|
|
15489
|
+
* Represents a Goose review check (`goose review`), written to the user-wide
|
|
15490
|
+
* `~/.config/goose/checks/` (global scope only).
|
|
15491
|
+
*
|
|
15492
|
+
* Goose discovers repository checks in `.agents/checks/*.md`, the same
|
|
15493
|
+
* cross-tool directory the `amp` checks target already writes (Goose's
|
|
15494
|
+
* `tools` and `severity-default` fields exist for parity with Amp's), so the
|
|
15495
|
+
* project scope is left to `amp` rather than having two targets own the same
|
|
15496
|
+
* files. Its global discovery reads `~/.config/goose/checks/` first, then
|
|
15497
|
+
* `~/.config/agents/checks/`. The file format is Amp's, so this class reuses
|
|
15498
|
+
* `AmpCheck` and adds Goose's `model` and `turn-limit`, which live in the
|
|
15499
|
+
* `goose:` section of a rulesync check; `name` is derived from the file name.
|
|
15500
|
+
*
|
|
15501
|
+
* @see https://github.com/aaif-goose/goose/blob/main/crates/goose/src/checks/mod.rs
|
|
15502
|
+
*/
|
|
15503
|
+
var GooseCheck = class extends AmpCheck {
|
|
15504
|
+
static getCheckToolTarget() {
|
|
15505
|
+
return "goose";
|
|
15506
|
+
}
|
|
15507
|
+
static getFrontmatterSchema() {
|
|
15508
|
+
return GooseCheckFrontmatterSchema;
|
|
15509
|
+
}
|
|
15510
|
+
static getToolOnlyKeys() {
|
|
15511
|
+
return ["model", "turn-limit"];
|
|
15512
|
+
}
|
|
15513
|
+
static getSettablePaths({ global = false } = {}) {
|
|
15514
|
+
return { relativeDirPath: global ? GOOSE_CHECKS_GLOBAL_DIR : GOOSE_CHECKS_PROJECT_DIR };
|
|
15515
|
+
}
|
|
15516
|
+
};
|
|
15517
|
+
//#endregion
|
|
15355
15518
|
//#region src/constants/hermesagent-paths.ts
|
|
15356
15519
|
/**
|
|
15357
15520
|
* Hermes Agent configuration-layout conventions.
|
|
@@ -16080,6 +16243,14 @@ const toolCheckFactories = /* @__PURE__ */ new Map([
|
|
|
16080
16243
|
committedOutput: true
|
|
16081
16244
|
}
|
|
16082
16245
|
}],
|
|
16246
|
+
["goose", {
|
|
16247
|
+
class: GooseCheck,
|
|
16248
|
+
meta: {
|
|
16249
|
+
supportsGlobal: true,
|
|
16250
|
+
supportsProject: false,
|
|
16251
|
+
filePattern: "*.md"
|
|
16252
|
+
}
|
|
16253
|
+
}],
|
|
16083
16254
|
["hermesagent", {
|
|
16084
16255
|
class: HermesagentCheck,
|
|
16085
16256
|
meta: {
|
|
@@ -16125,7 +16296,9 @@ const defaultGetFactory$6 = (target) => {
|
|
|
16125
16296
|
return factory;
|
|
16126
16297
|
};
|
|
16127
16298
|
const allToolTargetKeys$5 = [...toolCheckFactories.keys()];
|
|
16128
|
-
const checksProcessorToolTargets = allToolTargetKeys$5
|
|
16299
|
+
const checksProcessorToolTargets = allToolTargetKeys$5.filter((target) => {
|
|
16300
|
+
return toolCheckFactories.get(target)?.meta.supportsProject !== false;
|
|
16301
|
+
});
|
|
16129
16302
|
const checksProcessorToolTargetsGlobal = allToolTargetKeys$5.filter((target) => {
|
|
16130
16303
|
return toolCheckFactories.get(target)?.meta.supportsGlobal ?? false;
|
|
16131
16304
|
});
|
|
@@ -16296,23 +16469,6 @@ var ChecksProcessor = class extends FeatureProcessor {
|
|
|
16296
16469
|
}
|
|
16297
16470
|
};
|
|
16298
16471
|
//#endregion
|
|
16299
|
-
//#region src/constants/goose-paths.ts
|
|
16300
|
-
const GOOSE_DIR = ".goose";
|
|
16301
|
-
const GOOSE_GLOBAL_DIR = (0, node_path.join)(".config", "goose");
|
|
16302
|
-
const GOOSE_RULE_FILE_NAME = ".goosehints";
|
|
16303
|
-
const GOOSE_MCP_FILE_NAME = "config.yaml";
|
|
16304
|
-
const GOOSE_PERMISSIONS_FILE_NAME = "permission.yaml";
|
|
16305
|
-
const GOOSE_HOOKS_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync", "hooks");
|
|
16306
|
-
const GOOSE_HOOKS_FILE_NAME = "hooks.json";
|
|
16307
|
-
const GOOSE_PLUGIN_MCP_DIR_PATH = (0, node_path.join)(".agents", "plugins", "rulesync");
|
|
16308
|
-
const GOOSE_PLUGIN_MCP_FILE_NAME = ".mcp.json";
|
|
16309
|
-
const GOOSE_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "skills");
|
|
16310
|
-
const GOOSE_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "skills");
|
|
16311
|
-
const GOOSE_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "recipes");
|
|
16312
|
-
const GOOSE_GLOBAL_RECIPES_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "recipes");
|
|
16313
|
-
const GOOSE_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_DIR, "agents");
|
|
16314
|
-
const GOOSE_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(GOOSE_GLOBAL_DIR, "agents");
|
|
16315
|
-
//#endregion
|
|
16316
16472
|
//#region src/utils/tool-home.ts
|
|
16317
16473
|
/**
|
|
16318
16474
|
* Where the rulesync-side source files of a tool with a home override belong.
|
|
@@ -17224,7 +17380,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
17224
17380
|
static getProjectRelativeDirPath() {
|
|
17225
17381
|
throw new Error("Please implement this method in the subclass.");
|
|
17226
17382
|
}
|
|
17227
|
-
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/
|
|
17383
|
+
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/config/workflows`). */
|
|
17228
17384
|
static getGlobalRelativeDirPath() {
|
|
17229
17385
|
throw new Error("Please implement this method in the subclass.");
|
|
17230
17386
|
}
|
|
@@ -17387,7 +17543,8 @@ const ANTIGRAVITY_IDE_GEMINI_DIR = ".gemini";
|
|
|
17387
17543
|
const ANTIGRAVITY_IDE_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
17388
17544
|
const ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR = "config";
|
|
17389
17545
|
const ANTIGRAVITY_IDE_GLOBAL_RULES_SUBDIR = (0, node_path.join)(ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "rules");
|
|
17390
|
-
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR,
|
|
17546
|
+
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR, ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "workflows");
|
|
17547
|
+
const ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR, "antigravity", "global_workflows");
|
|
17391
17548
|
const ANTIGRAVITY_IDE_PERMISSIONS_DIR = ".antigravity";
|
|
17392
17549
|
const ANTIGRAVITY_IDE_PERMISSIONS_FILE_NAME = "settings.json";
|
|
17393
17550
|
//#endregion
|
|
@@ -17417,10 +17574,12 @@ var AntigravityIdeSkill = class extends AntigravitySharedSkill {
|
|
|
17417
17574
|
* Emits each command as a skill in `.agents/skills/<name>/SKILL.md` (project
|
|
17418
17575
|
* scope) and `~/.gemini/config/skills/<name>/SKILL.md` (global scope), the
|
|
17419
17576
|
* IDE's skills directories. Legacy workflows are still imported from
|
|
17420
|
-
* `.agents/workflows/` and `~/.gemini/
|
|
17421
|
-
*
|
|
17577
|
+
* `.agents/workflows/` and `~/.gemini/config/workflows/` (the global workflows
|
|
17578
|
+
* directory of Antigravity 2.0), and, in global scope only, from the pre-2.0
|
|
17579
|
+
* `~/.gemini/antigravity/global_workflows/`. All body and frontmatter handling
|
|
17580
|
+
* is shared with {@link AntigravitySharedCommand}.
|
|
17422
17581
|
*/
|
|
17423
|
-
var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
17582
|
+
var AntigravityIdeCommand = class AntigravityIdeCommand extends AntigravitySharedCommand {
|
|
17424
17583
|
static getProjectRelativeDirPath() {
|
|
17425
17584
|
return ANTIGRAVITY_IDE_COMMANDS_DIR_PATH;
|
|
17426
17585
|
}
|
|
@@ -17430,6 +17589,37 @@ var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
|
17430
17589
|
static getSkillsRelativeDirPath({ global }) {
|
|
17431
17590
|
return AntigravityIdeSkill.getSettablePaths({ global }).relativeDirPath;
|
|
17432
17591
|
}
|
|
17592
|
+
/**
|
|
17593
|
+
* Import-only, global scope: the workflows left in the pre-2.0
|
|
17594
|
+
* `~/.gemini/antigravity/global_workflows/` directory, where rulesync also
|
|
17595
|
+
* wrote IDE global commands before they were emitted as skills. The
|
|
17596
|
+
* directory is never swept by `generate --delete`; a workflow with the same
|
|
17597
|
+
* file name in `~/.gemini/config/workflows/` takes precedence.
|
|
17598
|
+
*
|
|
17599
|
+
* @see https://antigravity.google/docs/migration/workflows-to-skills
|
|
17600
|
+
*/
|
|
17601
|
+
static async loadAdditionalImportFiles({ outputRoot = process.cwd(), global = false, logger } = {}) {
|
|
17602
|
+
if (!global) return [];
|
|
17603
|
+
const filePaths = await findFilesByGlobs("*.md", { cwd: (0, node_path.join)(outputRoot, ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH) });
|
|
17604
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
17605
|
+
const fileContent = await readFileContent(filePath);
|
|
17606
|
+
const { frontmatter, body } = parseFrontmatter(fileContent, filePath);
|
|
17607
|
+
const result = AntigravityCommandFrontmatterSchema.safeParse(frontmatter);
|
|
17608
|
+
if (!result.success) {
|
|
17609
|
+
logger?.warn(`Skipping ${filePath}: invalid frontmatter (${formatError(result.error)}).`);
|
|
17610
|
+
return;
|
|
17611
|
+
}
|
|
17612
|
+
return new AntigravityIdeCommand({
|
|
17613
|
+
outputRoot,
|
|
17614
|
+
relativeDirPath: ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH,
|
|
17615
|
+
relativeFilePath: (0, node_path.basename)(filePath),
|
|
17616
|
+
frontmatter: result.data,
|
|
17617
|
+
body: body.trim(),
|
|
17618
|
+
fileContent,
|
|
17619
|
+
global
|
|
17620
|
+
});
|
|
17621
|
+
}))).filter((command) => command !== void 0);
|
|
17622
|
+
}
|
|
17433
17623
|
getToolTargetName() {
|
|
17434
17624
|
return "antigravity-ide";
|
|
17435
17625
|
}
|
|
@@ -18000,27 +18190,71 @@ const CLINE_HOOKS_GLOBAL_DIR_PATH = (0, node_path.join)("Documents", "Cline", "H
|
|
|
18000
18190
|
const CLINE_HOOKS_MANIFEST_FILE_NAME = "rulesync-hooks.json";
|
|
18001
18191
|
//#endregion
|
|
18002
18192
|
//#region src/features/commands/cline-command.ts
|
|
18193
|
+
const CLINE_WORKFLOW_FIELD_TYPES = {
|
|
18194
|
+
name: "string",
|
|
18195
|
+
disabled: "boolean",
|
|
18196
|
+
enabled: "boolean"
|
|
18197
|
+
};
|
|
18198
|
+
function sanitizeClineWorkflowFrontmatter({ cline, relativeFilePath, logger }) {
|
|
18199
|
+
if (!isRecord$1(cline)) return {};
|
|
18200
|
+
return Object.fromEntries(Object.entries(cline).filter(([key, value]) => {
|
|
18201
|
+
const expected = Object.hasOwn(CLINE_WORKFLOW_FIELD_TYPES, key) ? CLINE_WORKFLOW_FIELD_TYPES[key] : void 0;
|
|
18202
|
+
if (expected === void 0 || typeof value === expected) return true;
|
|
18203
|
+
warnWithFallback(logger, `Cline commands: dropping cline.${key} from ${relativeFilePath}: Cline rejects the whole workflow unless it is a ${expected}.`);
|
|
18204
|
+
return false;
|
|
18205
|
+
}));
|
|
18206
|
+
}
|
|
18207
|
+
/**
|
|
18208
|
+
* Cline workflows (commands). A workflow may carry YAML frontmatter that Cline
|
|
18209
|
+
* reads: `name` (the slash-command name) and a `disabled` / `enabled` switch
|
|
18210
|
+
* (`parseWorkflowConfigFromMarkdown` in Cline's
|
|
18211
|
+
* `user-instruction-config-loader.ts`). The `cline:` section of a
|
|
18212
|
+
* `.rulesync/commands/*.md` file is written as that frontmatter, and every key
|
|
18213
|
+
* of an imported workflow's frontmatter is lifted back into `cline:`, so
|
|
18214
|
+
* hand-written workflow frontmatter survives an import → generate round trip.
|
|
18215
|
+
* Cline's own UI toggle is kept in Cline's state, not in the file. A workflow
|
|
18216
|
+
* without a `cline:` section is written as a bare body, as before.
|
|
18217
|
+
*
|
|
18218
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/extensions/config/user-instruction-config-loader.ts
|
|
18219
|
+
*/
|
|
18003
18220
|
var ClineCommand = class ClineCommand extends ToolCommand {
|
|
18004
18221
|
static getSettablePaths({ global } = {}) {
|
|
18005
18222
|
if (global) return { relativeDirPath: CLINE_COMMANDS_GLOBAL_DIR_PATH };
|
|
18006
18223
|
return { relativeDirPath: CLINE_COMMANDS_DIR_PATH };
|
|
18007
18224
|
}
|
|
18225
|
+
/** The workflow's own frontmatter, or an empty object for a bare body. */
|
|
18226
|
+
getClineFrontmatter() {
|
|
18227
|
+
const { frontmatter } = parseFrontmatter(this.getFileContent(), this.getFilePath());
|
|
18228
|
+
return isRecord$1(frontmatter) ? frontmatter : {};
|
|
18229
|
+
}
|
|
18008
18230
|
toRulesyncCommand() {
|
|
18231
|
+
const cline = this.getClineFrontmatter();
|
|
18232
|
+
const rulesyncFrontmatter = {
|
|
18233
|
+
targets: ["*"],
|
|
18234
|
+
...Object.keys(cline).length > 0 && { cline }
|
|
18235
|
+
};
|
|
18236
|
+
const body = this.getBody();
|
|
18009
18237
|
return new RulesyncCommand({
|
|
18010
18238
|
outputRoot: process.cwd(),
|
|
18011
|
-
frontmatter:
|
|
18012
|
-
body
|
|
18239
|
+
frontmatter: rulesyncFrontmatter,
|
|
18240
|
+
body,
|
|
18013
18241
|
relativeDirPath: RulesyncCommand.getSettablePaths().relativeDirPath,
|
|
18014
18242
|
relativeFilePath: this.relativeFilePath,
|
|
18015
|
-
fileContent:
|
|
18243
|
+
fileContent: stringifyFrontmatter(body, rulesyncFrontmatter),
|
|
18016
18244
|
validate: true
|
|
18017
18245
|
});
|
|
18018
18246
|
}
|
|
18019
|
-
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false }) {
|
|
18247
|
+
static fromRulesyncCommand({ outputRoot = process.cwd(), rulesyncCommand, validate = true, global = false, logger }) {
|
|
18020
18248
|
const paths = this.getSettablePaths({ global });
|
|
18249
|
+
const cline = sanitizeClineWorkflowFrontmatter({
|
|
18250
|
+
cline: rulesyncCommand.getFrontmatter().cline,
|
|
18251
|
+
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18252
|
+
logger
|
|
18253
|
+
});
|
|
18254
|
+
const body = rulesyncCommand.getBody();
|
|
18021
18255
|
return new ClineCommand({
|
|
18022
18256
|
outputRoot,
|
|
18023
|
-
fileContent:
|
|
18257
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body, cline) : body,
|
|
18024
18258
|
relativeDirPath: paths.relativeDirPath,
|
|
18025
18259
|
relativeFilePath: rulesyncCommand.getRelativeFilePath(),
|
|
18026
18260
|
validate
|
|
@@ -18033,7 +18267,7 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18033
18267
|
};
|
|
18034
18268
|
}
|
|
18035
18269
|
getBody() {
|
|
18036
|
-
return this.getFileContent();
|
|
18270
|
+
return parseFrontmatter(this.getFileContent(), this.getFilePath()).body.trim();
|
|
18037
18271
|
}
|
|
18038
18272
|
static isTargetedByRulesyncCommand(rulesyncCommand) {
|
|
18039
18273
|
return this.isTargetedByRulesyncCommandDefault({
|
|
@@ -18044,12 +18278,13 @@ var ClineCommand = class ClineCommand extends ToolCommand {
|
|
|
18044
18278
|
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, validate = true, global = false }) {
|
|
18045
18279
|
const paths = this.getSettablePaths({ global });
|
|
18046
18280
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, relativeFilePath);
|
|
18047
|
-
const { body
|
|
18281
|
+
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
18282
|
+
const cline = isRecord$1(frontmatter) ? frontmatter : {};
|
|
18048
18283
|
return new ClineCommand({
|
|
18049
18284
|
outputRoot,
|
|
18050
18285
|
relativeDirPath: paths.relativeDirPath,
|
|
18051
18286
|
relativeFilePath,
|
|
18052
|
-
fileContent:
|
|
18287
|
+
fileContent: Object.keys(cline).length > 0 ? stringifyFrontmatter(body.trim(), cline) : body.trim(),
|
|
18053
18288
|
validate
|
|
18054
18289
|
});
|
|
18055
18290
|
}
|
|
@@ -25082,7 +25317,7 @@ function findCommandPaths({ command, scan }) {
|
|
|
25082
25317
|
} else {
|
|
25083
25318
|
if (path !== void 0) toAnchor.add(path);
|
|
25084
25319
|
state.owner = text.slice(text.lastIndexOf("/") + 1);
|
|
25085
|
-
state.expecting = afterCommandWord(state.owner);
|
|
25320
|
+
state.expecting = afterCommandWord$1(state.owner);
|
|
25086
25321
|
state.prefix = void 0;
|
|
25087
25322
|
state.shellCode = false;
|
|
25088
25323
|
}
|
|
@@ -25148,7 +25383,7 @@ function readOption({ state, text }) {
|
|
|
25148
25383
|
if (SHELL_NAME_PATTERN.test(state.owner) && SHELL_CODE_OPTION.test(text)) state.shellCode = true;
|
|
25149
25384
|
}
|
|
25150
25385
|
/** What follows a command word whose basename is `name`. */
|
|
25151
|
-
function afterCommandWord(name) {
|
|
25386
|
+
function afterCommandWord$1(name) {
|
|
25152
25387
|
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
25153
25388
|
if (PACKAGE_RUNNERS.has(name)) return "wrapped";
|
|
25154
25389
|
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
@@ -33491,6 +33726,19 @@ function isQwencodeLegacyMillisecondTimeout(timeout) {
|
|
|
33491
33726
|
return typeof timeout === "number" && Number.isFinite(timeout) && timeout >= QWENCODE_LEGACY_MILLISECOND_TIMEOUT_THRESHOLD;
|
|
33492
33727
|
}
|
|
33493
33728
|
/**
|
|
33729
|
+
* Read a `timeout` the way Qwen Code does. Settings files are not type-checked
|
|
33730
|
+
* and Qwen Code coerces a numeric string such as `"60000"` with `Number()`, so
|
|
33731
|
+
* import accepts one too; a value that is not a finite number is dropped, since
|
|
33732
|
+
* Qwen Code falls back to the hook type's default for it.
|
|
33733
|
+
* https://github.com/QwenLM/qwen-code/blob/main/packages/core/src/hooks/hook-timeout.ts
|
|
33734
|
+
*/
|
|
33735
|
+
function readQwencodeTimeout(timeout) {
|
|
33736
|
+
if (timeout === void 0) return void 0;
|
|
33737
|
+
if (typeof timeout === "string" && timeout.trim() === "") return void 0;
|
|
33738
|
+
const value = Number(timeout);
|
|
33739
|
+
return Number.isFinite(value) ? value : void 0;
|
|
33740
|
+
}
|
|
33741
|
+
/**
|
|
33494
33742
|
* Canonical `timeout` is seconds. A command hook value below the threshold is
|
|
33495
33743
|
* already read as seconds; a larger one can only be expressed in milliseconds.
|
|
33496
33744
|
*/
|
|
@@ -33601,7 +33849,7 @@ const QwencodeHookEntrySchema = zod_mini.z.looseObject({
|
|
|
33601
33849
|
type: zod_mini.z.optional(zod_mini.z.string()),
|
|
33602
33850
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
33603
33851
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
33604
|
-
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
33852
|
+
timeout: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.number(), zod_mini.z.string()])),
|
|
33605
33853
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
33606
33854
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
33607
33855
|
statusMessage: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -33634,12 +33882,13 @@ function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
|
33634
33882
|
const isCommand = hookType === "command";
|
|
33635
33883
|
const isPrompt = hookType === "prompt";
|
|
33636
33884
|
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
33885
|
+
const timeout = readQwencodeTimeout(h.timeout);
|
|
33637
33886
|
return {
|
|
33638
33887
|
type: hookType,
|
|
33639
33888
|
...compact({
|
|
33640
33889
|
command: h.command,
|
|
33641
33890
|
url: h.url,
|
|
33642
|
-
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(
|
|
33891
|
+
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(timeout) : timeout,
|
|
33643
33892
|
name: h.name,
|
|
33644
33893
|
description: h.description,
|
|
33645
33894
|
statusMessage: h.statusMessage,
|
|
@@ -38141,7 +38390,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
38141
38390
|
fileKey: sharedConfigFileKey(basePaths),
|
|
38142
38391
|
feature: "mcp",
|
|
38143
38392
|
existingContent: fileContent ?? "",
|
|
38144
|
-
patch: { [AMP_MCP_SERVERS_KEY]: filterMcpServers(rulesyncMcp.getMcpServers()) },
|
|
38393
|
+
patch: { [AMP_MCP_SERVERS_KEY]: undefinedWhenEmpty(filterMcpServers(rulesyncMcp.getMcpServers())) },
|
|
38145
38394
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath),
|
|
38146
38395
|
logger
|
|
38147
38396
|
}),
|
|
@@ -38439,6 +38688,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38439
38688
|
const paths = this.getSettablePaths({ global });
|
|
38440
38689
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
38441
38690
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
38691
|
+
const mcpServers = rulesyncMcp.getMcpServers();
|
|
38442
38692
|
return new AugmentcodeMcp({
|
|
38443
38693
|
outputRoot,
|
|
38444
38694
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -38447,7 +38697,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38447
38697
|
fileKey: sharedConfigFileKey(paths),
|
|
38448
38698
|
feature: "mcp",
|
|
38449
38699
|
existingContent,
|
|
38450
|
-
patch: { mcpServers:
|
|
38700
|
+
patch: { mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : void 0 },
|
|
38451
38701
|
filePath,
|
|
38452
38702
|
logger
|
|
38453
38703
|
}),
|
|
@@ -39003,6 +39253,229 @@ var ClaudecodeMcp = class ClaudecodeMcp extends ToolMcp {
|
|
|
39003
39253
|
}
|
|
39004
39254
|
};
|
|
39005
39255
|
//#endregion
|
|
39256
|
+
//#region src/features/mcp/claudecode-plugin-mcp.ts
|
|
39257
|
+
/**
|
|
39258
|
+
* The placeholder Claude Code substitutes with the installed plugin's own
|
|
39259
|
+
* directory in an MCP server's `command`, `args` and `env`.
|
|
39260
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39261
|
+
*/
|
|
39262
|
+
const PLUGIN_ROOT_PLACEHOLDER = "${CLAUDE_PLUGIN_ROOT}";
|
|
39263
|
+
const PLUGIN_ROOT_PREFIX = /^(?:\$\{CLAUDE_PLUGIN_ROOT\}|"\$CLAUDE_PLUGIN_ROOT"|\$CLAUDE_PLUGIN_ROOT)\//;
|
|
39264
|
+
/**
|
|
39265
|
+
* Whether a server `command` names a file inside the plugin rather than an
|
|
39266
|
+
* executable found on `PATH`. The command is spawned without a shell, so a
|
|
39267
|
+
* name with a slash is a path, and one without (`npx`, `node`, `uvx`) is a
|
|
39268
|
+
* `PATH` lookup. An absolute path, a home path, one that leaves the plugin
|
|
39269
|
+
* (`../`), and one that already starts with a variable are left as written.
|
|
39270
|
+
*/
|
|
39271
|
+
function isPluginRelativeCommand(command) {
|
|
39272
|
+
return /[\\/]/.test(command) && !command.startsWith("$") && !command.startsWith("%") && !command.startsWith("~") && !/^\.\.[\\/]/.test(command) && !node_path.posix.isAbsolute(command) && !node_path.win32.isAbsolute(command);
|
|
39273
|
+
}
|
|
39274
|
+
function anchorToPluginRoot(path) {
|
|
39275
|
+
return `${PLUGIN_ROOT_PLACEHOLDER}/${path.replace(/^\.[\\/]/, "")}`;
|
|
39276
|
+
}
|
|
39277
|
+
/** The runner name of a command word: its basename without a `.exe` suffix. */
|
|
39278
|
+
function runnerName(command) {
|
|
39279
|
+
return (command.split(/[\\/]/).pop() ?? command).replace(/\.exe$/i, "");
|
|
39280
|
+
}
|
|
39281
|
+
/** What follows a command word, as plugin hook commands read it. */
|
|
39282
|
+
function afterCommandWord(name) {
|
|
39283
|
+
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
39284
|
+
if (PACKAGE_RUNNERS.has(name)) return "command";
|
|
39285
|
+
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
39286
|
+
}
|
|
39287
|
+
/**
|
|
39288
|
+
* The positions in `args` that name a file the server runs, read with the
|
|
39289
|
+
* rules plugin hook commands follow (`hook-command-paths.ts`): the script of a
|
|
39290
|
+
* script runner (`node ./dist/server.js`), the command after a run subcommand
|
|
39291
|
+
* or package runner (`uv run ./server.py`, `npx tsx ./x.ts`), and the value of
|
|
39292
|
+
* an option that loads a file (`node -r ./register.js`). Only a position whose
|
|
39293
|
+
* value `isPath` accepts is returned. Everything else is data the server reads
|
|
39294
|
+
* relative to the consumer's project (`server-filesystem ./docs`), and the
|
|
39295
|
+
* scan stops at an option that runs inline code or standard input
|
|
39296
|
+
* (`node -e`, `python3 -m`, `sh -c`, `bash -s`).
|
|
39297
|
+
*/
|
|
39298
|
+
function fileArgumentIndices({ command, args, isPath }) {
|
|
39299
|
+
const indices = /* @__PURE__ */ new Set();
|
|
39300
|
+
let owner = runnerName(command);
|
|
39301
|
+
let expecting = afterCommandWord(owner);
|
|
39302
|
+
for (let index = 0; index < args.length && expecting !== "none"; index++) {
|
|
39303
|
+
const arg = args[index];
|
|
39304
|
+
if (typeof arg !== "string") break;
|
|
39305
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
39306
|
+
if (optionsFor({
|
|
39307
|
+
table: CODE_OPTIONS,
|
|
39308
|
+
name: owner
|
|
39309
|
+
})?.has(arg) || SHELL_NAME_PATTERN.test(owner) && SHELL_CODE_OPTION.test(arg)) break;
|
|
39310
|
+
if (optionsFor({
|
|
39311
|
+
table: VALUE_OPTIONS,
|
|
39312
|
+
name: owner
|
|
39313
|
+
})?.has(arg)) {
|
|
39314
|
+
const value = args[index + 1];
|
|
39315
|
+
if (typeof value === "string" && isPath(value)) indices.add(index + 1);
|
|
39316
|
+
index++;
|
|
39317
|
+
}
|
|
39318
|
+
continue;
|
|
39319
|
+
}
|
|
39320
|
+
if (expecting === "subcommand") {
|
|
39321
|
+
if (RUN_SUBCOMMANDS[owner]?.has(arg)) {
|
|
39322
|
+
expecting = "command";
|
|
39323
|
+
continue;
|
|
39324
|
+
}
|
|
39325
|
+
if (!SCRIPT_RUNNER_PATTERN.test(owner)) break;
|
|
39326
|
+
expecting = "script";
|
|
39327
|
+
}
|
|
39328
|
+
if (isPath(arg)) indices.add(index);
|
|
39329
|
+
if (expecting === "script") break;
|
|
39330
|
+
owner = runnerName(arg);
|
|
39331
|
+
expecting = afterCommandWord(owner);
|
|
39332
|
+
}
|
|
39333
|
+
return indices;
|
|
39334
|
+
}
|
|
39335
|
+
function isDotRelative(value) {
|
|
39336
|
+
return /^\.[\\/]/.test(value);
|
|
39337
|
+
}
|
|
39338
|
+
/** Turn a plugin-root path back into its `./` form, or return it unchanged. */
|
|
39339
|
+
function relativizeFromPluginRoot(value) {
|
|
39340
|
+
return PLUGIN_ROOT_PREFIX.test(value) ? value.replace(PLUGIN_ROOT_PREFIX, "./") : value;
|
|
39341
|
+
}
|
|
39342
|
+
/** The command and arguments of a stdio server, with inline array arguments split out. */
|
|
39343
|
+
function splitCommand(server) {
|
|
39344
|
+
if (Array.isArray(server.command)) {
|
|
39345
|
+
const [first, ...rest] = server.command;
|
|
39346
|
+
return {
|
|
39347
|
+
command: typeof first === "string" ? first : void 0,
|
|
39348
|
+
args: [...rest, ...Array.isArray(server.args) ? server.args : []]
|
|
39349
|
+
};
|
|
39350
|
+
}
|
|
39351
|
+
return {
|
|
39352
|
+
command: typeof server.command === "string" ? server.command : void 0,
|
|
39353
|
+
args: Array.isArray(server.args) ? server.args : []
|
|
39354
|
+
};
|
|
39355
|
+
}
|
|
39356
|
+
/**
|
|
39357
|
+
* Rewrite a server's command and the argument positions that name a file it
|
|
39358
|
+
* runs (see `fileArgumentIndices`), leaving every other argument as written.
|
|
39359
|
+
* Generate and import share this, so a position generate never anchors is
|
|
39360
|
+
* never relativized on import either, and a hand-written
|
|
39361
|
+
* `${CLAUDE_PLUGIN_ROOT}/…` data argument keeps its meaning.
|
|
39362
|
+
*/
|
|
39363
|
+
function rewriteServer({ server, rewriteCommand, isPath, rewritePath }) {
|
|
39364
|
+
const { command, args } = splitCommand(server);
|
|
39365
|
+
if (command === void 0) return server;
|
|
39366
|
+
const indices = fileArgumentIndices({
|
|
39367
|
+
command,
|
|
39368
|
+
args,
|
|
39369
|
+
isPath
|
|
39370
|
+
});
|
|
39371
|
+
const rewriteArgs = (values, offset) => values.map((value, index) => indices.has(index + offset) && typeof value === "string" ? rewritePath(value) : value);
|
|
39372
|
+
const next = { ...server };
|
|
39373
|
+
if (Array.isArray(server.command)) {
|
|
39374
|
+
const inlineArgs = server.command.slice(1);
|
|
39375
|
+
next.command = [rewriteCommand(command), ...rewriteArgs(inlineArgs, 0)];
|
|
39376
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, inlineArgs.length);
|
|
39377
|
+
} else {
|
|
39378
|
+
next.command = rewriteCommand(command);
|
|
39379
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, 0);
|
|
39380
|
+
}
|
|
39381
|
+
return next;
|
|
39382
|
+
}
|
|
39383
|
+
/** Anchor a server's plugin-relative command and the files its runner executes. */
|
|
39384
|
+
function anchorServer(server) {
|
|
39385
|
+
return rewriteServer({
|
|
39386
|
+
server,
|
|
39387
|
+
rewriteCommand: (command) => isPluginRelativeCommand(command) ? anchorToPluginRoot(command) : command,
|
|
39388
|
+
isPath: isDotRelative,
|
|
39389
|
+
rewritePath: anchorToPluginRoot
|
|
39390
|
+
});
|
|
39391
|
+
}
|
|
39392
|
+
/** Turn the plugin-root forms generate writes back into `./`. */
|
|
39393
|
+
function relativizeServer(server) {
|
|
39394
|
+
return rewriteServer({
|
|
39395
|
+
server,
|
|
39396
|
+
rewriteCommand: relativizeFromPluginRoot,
|
|
39397
|
+
isPath: (value) => PLUGIN_ROOT_PREFIX.test(value),
|
|
39398
|
+
rewritePath: relativizeFromPluginRoot
|
|
39399
|
+
});
|
|
39400
|
+
}
|
|
39401
|
+
/** Apply a per-server transform to every server entry that is an object. */
|
|
39402
|
+
function mapServers({ mcpServers, transform }) {
|
|
39403
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return mcpServers;
|
|
39404
|
+
return Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => server === null || typeof server !== "object" || Array.isArray(server) ? [name, server] : [name, transform(server)]));
|
|
39405
|
+
}
|
|
39406
|
+
/**
|
|
39407
|
+
* MCP servers bundled in a Claude Code plugin (`.mcp.json` at the plugin
|
|
39408
|
+
* root).
|
|
39409
|
+
*
|
|
39410
|
+
* Claude Code loads a plugin's `.mcp.json` with the consumer's project as the
|
|
39411
|
+
* working directory, so a relative `command` such as `./servers/db-server`
|
|
39412
|
+
* would be looked up in the consumer's repository, where the bundled binary
|
|
39413
|
+
* does not exist. Generate therefore rewrites a relative `command`, and the
|
|
39414
|
+
* `./` script a script runner executes (`node ./dist/server.js`,
|
|
39415
|
+
* `uv run ./server.py`), to `${CLAUDE_PLUGIN_ROOT}/…`, which Claude Code
|
|
39416
|
+
* substitutes with the installed plugin's directory. Other `./` arguments are
|
|
39417
|
+
* data the server reads relative to the consumer's project
|
|
39418
|
+
* (`server-filesystem ./docs`), so they are left as written, the same rule
|
|
39419
|
+
* plugin hook commands follow. Bare executables (`npx`, `node`), absolute paths
|
|
39420
|
+
* and values that already start with a variable pass through. `env` values and
|
|
39421
|
+
* the remote transports' `url` / `headers` are left as written, because
|
|
39422
|
+
* rulesync cannot tell a path from an opaque string there. Import turns the
|
|
39423
|
+
* plugin-root forms in `command` and `args` back into `./`, including the
|
|
39424
|
+
* shell-variable spellings a hand-written file may have copied from a hook
|
|
39425
|
+
* command (which Claude Code does not substitute in MCP config).
|
|
39426
|
+
*
|
|
39427
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39428
|
+
*/
|
|
39429
|
+
var ClaudecodePluginMcp = class ClaudecodePluginMcp extends ClaudecodeMcp {
|
|
39430
|
+
static async fromRulesyncMcp(params) {
|
|
39431
|
+
const base = await super.fromRulesyncMcp(params);
|
|
39432
|
+
const json = base.getJson();
|
|
39433
|
+
const mcpServers = mapServers({
|
|
39434
|
+
mcpServers: json.mcpServers,
|
|
39435
|
+
transform: anchorServer
|
|
39436
|
+
});
|
|
39437
|
+
return new ClaudecodePluginMcp({
|
|
39438
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39439
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39440
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39441
|
+
fileContent: JSON.stringify({
|
|
39442
|
+
...json,
|
|
39443
|
+
mcpServers
|
|
39444
|
+
}, null, 2),
|
|
39445
|
+
validate: params.validate ?? true,
|
|
39446
|
+
global: params.global ?? false
|
|
39447
|
+
});
|
|
39448
|
+
}
|
|
39449
|
+
static async fromFile(params) {
|
|
39450
|
+
const base = await super.fromFile(params);
|
|
39451
|
+
return new ClaudecodePluginMcp({
|
|
39452
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39453
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39454
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39455
|
+
fileContent: base.getFileContent(),
|
|
39456
|
+
validate: params.validate ?? true,
|
|
39457
|
+
global: params.global ?? false
|
|
39458
|
+
});
|
|
39459
|
+
}
|
|
39460
|
+
toRulesyncMcp() {
|
|
39461
|
+
const mcpServers = mapServers({
|
|
39462
|
+
mcpServers: this.getJson().mcpServers ?? {},
|
|
39463
|
+
transform: relativizeServer
|
|
39464
|
+
});
|
|
39465
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
39466
|
+
}
|
|
39467
|
+
static forDeletion(params) {
|
|
39468
|
+
return new ClaudecodePluginMcp({
|
|
39469
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39470
|
+
relativeDirPath: params.relativeDirPath,
|
|
39471
|
+
relativeFilePath: params.relativeFilePath,
|
|
39472
|
+
fileContent: "{}",
|
|
39473
|
+
validate: false,
|
|
39474
|
+
global: params.global ?? false
|
|
39475
|
+
});
|
|
39476
|
+
}
|
|
39477
|
+
};
|
|
39478
|
+
//#endregion
|
|
39006
39479
|
//#region src/features/mcp/cline-mcp.ts
|
|
39007
39480
|
const CLINE_GLOBAL_ONLY_MESSAGE = "Cline MCP is global-only; use --global to sync ~/.cline/data/settings/cline_mcp_settings.json";
|
|
39008
39481
|
function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
@@ -40191,6 +40664,35 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
40191
40664
|
}
|
|
40192
40665
|
return result;
|
|
40193
40666
|
}
|
|
40667
|
+
/**
|
|
40668
|
+
* Keys Codex refuses on each transport. Its `McpServerConfig` deserializer
|
|
40669
|
+
* calls `throw_if_set` for these, and the error fails the whole `config.toml`,
|
|
40670
|
+
* not just the one server, so they are dropped with a warning instead of
|
|
40671
|
+
* written. Checked against `codex-rs/config/src/mcp_types.rs` at rust-v0.162.1.
|
|
40672
|
+
*/
|
|
40673
|
+
const CODEX_STDIO_REJECTED_KEYS = [
|
|
40674
|
+
"url",
|
|
40675
|
+
"bearer_token_env_var",
|
|
40676
|
+
"bearer_token",
|
|
40677
|
+
"http_headers_helper",
|
|
40678
|
+
"http_headers",
|
|
40679
|
+
"env_http_headers",
|
|
40680
|
+
"oauth",
|
|
40681
|
+
"oauth_resource",
|
|
40682
|
+
"auth"
|
|
40683
|
+
];
|
|
40684
|
+
const CODEX_HTTP_REJECTED_KEYS = [
|
|
40685
|
+
"args",
|
|
40686
|
+
"env",
|
|
40687
|
+
"env_vars",
|
|
40688
|
+
"cwd",
|
|
40689
|
+
"bearer_token"
|
|
40690
|
+
];
|
|
40691
|
+
function dropTransportRejectedKeys({ converted, isStdio, serverName }) {
|
|
40692
|
+
const dropped = (isStdio ? CODEX_STDIO_REJECTED_KEYS : CODEX_HTTP_REJECTED_KEYS).filter((key) => converted[key] !== void 0);
|
|
40693
|
+
for (const key of dropped) delete converted[key];
|
|
40694
|
+
if (dropped.length > 0) warnWithFallback(void 0, `[CodexCliMcp] Dropping ${dropped.map((key) => `'${key}'`).join(", ")} from ${isStdio ? "stdio" : "url-based"} MCP server "${serverName}": Codex rejects ${dropped.length === 1 ? "this key" : "these keys"} on a ${isStdio ? "stdio" : "url-based"} server and would fail to load the whole config.toml.`);
|
|
40695
|
+
}
|
|
40194
40696
|
function convertToCodexFormat(mcpServers) {
|
|
40195
40697
|
const result = {};
|
|
40196
40698
|
const originalNames = /* @__PURE__ */ new Map();
|
|
@@ -40233,6 +40735,11 @@ function convertToCodexFormat(mcpServers) {
|
|
|
40233
40735
|
converted,
|
|
40234
40736
|
serverName: name
|
|
40235
40737
|
});
|
|
40738
|
+
dropTransportRejectedKeys({
|
|
40739
|
+
converted,
|
|
40740
|
+
isStdio,
|
|
40741
|
+
serverName: name
|
|
40742
|
+
});
|
|
40236
40743
|
const previousName = originalNames.get(codexName);
|
|
40237
40744
|
if (previousName !== void 0) warnWithFallback(void 0, `Codex MCP server name collision: "${previousName}" and "${name}" both normalize to "${codexName}"; "${name}" (processed last) overwrites "${previousName}".`);
|
|
40238
40745
|
originalNames.set(codexName, name);
|
|
@@ -41712,7 +42219,7 @@ var CrushMcp = class CrushMcp extends ToolMcp {
|
|
|
41712
42219
|
fileKey: sharedConfigFileKey(this.getSettablePaths({ global })),
|
|
41713
42220
|
feature: "mcp",
|
|
41714
42221
|
existingContent,
|
|
41715
|
-
patch: { ["mcp"]: converted },
|
|
42222
|
+
patch: { ["mcp"]: undefinedWhenEmpty(converted) },
|
|
41716
42223
|
filePath: location.filePath,
|
|
41717
42224
|
logger
|
|
41718
42225
|
}),
|
|
@@ -43292,13 +43799,23 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
43292
43799
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
43293
43800
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
43294
43801
|
/**
|
|
43802
|
+
* Grok keeps per-server MCP tool deny lists in a top-level
|
|
43803
|
+
* `[disabled_mcp_tools]` table (`map<server, string[]>` of unqualified tool
|
|
43804
|
+
* names), not inside `[mcp_servers.<name>]`. Grok reads it from the user
|
|
43805
|
+
* `~/.grok/config.toml` only: a project `.grok/config.toml` contributes just
|
|
43806
|
+
* `[mcp_servers]`, `[plugins]`, `[permission]` and `[mcp] max_output_bytes`.
|
|
43807
|
+
* https://github.com/xai-org/grok-build/blob/2bdd1d6a/crates/codegen/xai-grok-pager/docs/user-guide/26-config-reference.md
|
|
43808
|
+
*/
|
|
43809
|
+
const GROK_DISABLED_MCP_TOOLS_KEY = "disabled_mcp_tools";
|
|
43810
|
+
/**
|
|
43295
43811
|
* Grok Build stores MCP servers in `config.toml` under a `[mcp_servers.<name>]`
|
|
43296
43812
|
* table. Verified against `grok mcp add` (grok 0.2.54): a stdio server emits
|
|
43297
43813
|
* `command`, `args`, `enabled = true`, and an `[mcp_servers.<name>.env]` table;
|
|
43298
43814
|
* a remote server emits `url` and `enabled`. Unlike Codex CLI, Grok uses a
|
|
43299
|
-
* literal `env` table (not the `env_vars` passthrough list)
|
|
43300
|
-
*
|
|
43301
|
-
*
|
|
43815
|
+
* literal `env` table (not the `env_vars` passthrough list), so the only field
|
|
43816
|
+
* rename is `disabled` (rulesync) ↔ `enabled = false` (grok). `disabledTools`
|
|
43817
|
+
* never lands in the server table: it is lifted into the top-level
|
|
43818
|
+
* `[disabled_mcp_tools]` table by `buildDisabledMcpTools`.
|
|
43302
43819
|
*/
|
|
43303
43820
|
function convertToGrokFormat(mcpServers) {
|
|
43304
43821
|
const result = {};
|
|
@@ -43307,7 +43824,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
43307
43824
|
if (!isRecord$1(config)) continue;
|
|
43308
43825
|
const converted = {};
|
|
43309
43826
|
for (const [key, value] of Object.entries(config)) {
|
|
43310
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
43827
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key) || key === "disabledTools") continue;
|
|
43311
43828
|
if (key === "disabled") {
|
|
43312
43829
|
if (value === true) converted["enabled"] = false;
|
|
43313
43830
|
} else converted[key] = value;
|
|
@@ -43331,6 +43848,38 @@ function convertFromGrokFormat(grokMcp) {
|
|
|
43331
43848
|
}
|
|
43332
43849
|
return result;
|
|
43333
43850
|
}
|
|
43851
|
+
function toToolNameList(value) {
|
|
43852
|
+
if (!Array.isArray(value)) return [];
|
|
43853
|
+
return value.filter((tool) => typeof tool === "string");
|
|
43854
|
+
}
|
|
43855
|
+
/**
|
|
43856
|
+
* Recompute the global `[disabled_mcp_tools]` table: every server Rulesync
|
|
43857
|
+
* declares gets its `disabledTools` (or loses a stale entry when it has none —
|
|
43858
|
+
* Grok itself removes an entry whose list empties), while entries for other
|
|
43859
|
+
* servers — ones toggled in Grok's `/mcps` UI, plugin or compat servers, and
|
|
43860
|
+
* the `__managed_gateway_connectors` sentinel — are carried over untouched.
|
|
43861
|
+
* Returns `undefined` when nothing is left, so the key is retracted.
|
|
43862
|
+
*/
|
|
43863
|
+
function buildDisabledMcpTools({ mcpServers, existingContent, filePath }) {
|
|
43864
|
+
const existing = parseSharedConfig({
|
|
43865
|
+
format: "toml",
|
|
43866
|
+
fileContent: existingContent,
|
|
43867
|
+
filePath
|
|
43868
|
+
})[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43869
|
+
const result = isRecord$1(existing) ? { ...existing } : {};
|
|
43870
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
43871
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
43872
|
+
const tools = toToolNameList(config.disabledTools);
|
|
43873
|
+
if (tools.length > 0) result[name] = tools;
|
|
43874
|
+
else delete result[name];
|
|
43875
|
+
}
|
|
43876
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
43877
|
+
}
|
|
43878
|
+
function warnProjectDisabledTools(mcpServers, logger) {
|
|
43879
|
+
const serverNames = Object.entries(mcpServers).filter(([, config]) => isRecord$1(config) && config.disabledTools !== void 0).map(([name]) => name);
|
|
43880
|
+
if (serverNames.length === 0) return;
|
|
43881
|
+
warnWithFallback(logger, `grokcli reads per-server \`disabledTools\` only from the user ~/.grok/config.toml ([disabled_mcp_tools]); dropping it from ${serverNames.join(", ")} in project mode. Generate with --global to apply it.`);
|
|
43882
|
+
}
|
|
43334
43883
|
var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
43335
43884
|
toml;
|
|
43336
43885
|
constructor({ ...rest }) {
|
|
@@ -43367,16 +43916,19 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43367
43916
|
relativeDirPath: paths.relativeDirPath,
|
|
43368
43917
|
relativeFilePath: paths.relativeFilePath,
|
|
43369
43918
|
fileContent,
|
|
43370
|
-
validate
|
|
43919
|
+
validate,
|
|
43920
|
+
global
|
|
43371
43921
|
});
|
|
43372
43922
|
}
|
|
43373
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
43923
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
43374
43924
|
const paths = this.getSettablePaths({ global });
|
|
43375
43925
|
const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
43376
43926
|
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
43377
|
-
const
|
|
43927
|
+
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
43928
|
+
const converted = convertToGrokFormat(strippedMcpServers);
|
|
43378
43929
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
43379
|
-
|
|
43930
|
+
if (!global) warnProjectDisabledTools(strippedMcpServers, logger);
|
|
43931
|
+
for (const name of Object.keys(converted)) if (!Object.hasOwn(filteredMcpServers, name)) warnWithFallback(logger, `MCP server "${name}" had no non-empty configuration and was dropped from the grok CLI config`);
|
|
43380
43932
|
return new GrokcliMcp({
|
|
43381
43933
|
outputRoot,
|
|
43382
43934
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -43385,14 +43937,31 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43385
43937
|
fileKey: sharedConfigFileKey(paths),
|
|
43386
43938
|
feature: "mcp",
|
|
43387
43939
|
existingContent: configTomlFileContent,
|
|
43388
|
-
patch: {
|
|
43940
|
+
patch: {
|
|
43941
|
+
mcp_servers: Object.keys(filteredMcpServers).length > 0 ? filteredMcpServers : void 0,
|
|
43942
|
+
...global && { [GROK_DISABLED_MCP_TOOLS_KEY]: buildDisabledMcpTools({
|
|
43943
|
+
mcpServers: strippedMcpServers,
|
|
43944
|
+
existingContent: configTomlFileContent,
|
|
43945
|
+
filePath: configTomlFilePath
|
|
43946
|
+
}) }
|
|
43947
|
+
},
|
|
43389
43948
|
filePath: configTomlFilePath
|
|
43390
43949
|
}),
|
|
43391
|
-
validate
|
|
43950
|
+
validate,
|
|
43951
|
+
global
|
|
43392
43952
|
});
|
|
43393
43953
|
}
|
|
43394
43954
|
toRulesyncMcp() {
|
|
43395
43955
|
const converted = convertFromGrokFormat(this.toml.mcp_servers ?? {});
|
|
43956
|
+
const disabledMcpTools = this.toml[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43957
|
+
if (this.global && isRecord$1(disabledMcpTools)) for (const [name, config] of Object.entries(converted)) {
|
|
43958
|
+
if (!Object.hasOwn(disabledMcpTools, name)) continue;
|
|
43959
|
+
const tools = toToolNameList(disabledMcpTools[name]);
|
|
43960
|
+
if (tools.length > 0) converted[name] = {
|
|
43961
|
+
...config,
|
|
43962
|
+
disabledTools: tools
|
|
43963
|
+
};
|
|
43964
|
+
}
|
|
43396
43965
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: converted }, null, 2) });
|
|
43397
43966
|
}
|
|
43398
43967
|
validate() {
|
|
@@ -46456,7 +47025,7 @@ var PoolMcp = class PoolMcp extends ToolMcp {
|
|
|
46456
47025
|
fileKey: sharedConfigFileKey(paths),
|
|
46457
47026
|
feature: "mcp",
|
|
46458
47027
|
existingContent,
|
|
46459
|
-
patch: { [POOL_MCP_SERVERS_KEY]: converted },
|
|
47028
|
+
patch: { [POOL_MCP_SERVERS_KEY]: undefinedWhenEmpty(converted) },
|
|
46460
47029
|
filePath
|
|
46461
47030
|
}),
|
|
46462
47031
|
validate,
|
|
@@ -46586,7 +47155,7 @@ var QoderMcp = class QoderMcp extends ToolMcp {
|
|
|
46586
47155
|
fileKey: sharedConfigFileKey(paths),
|
|
46587
47156
|
feature: "mcp",
|
|
46588
47157
|
existingContent,
|
|
46589
|
-
patch: { mcpServers },
|
|
47158
|
+
patch: { mcpServers: undefinedWhenEmpty(mcpServers) },
|
|
46590
47159
|
filePath
|
|
46591
47160
|
}) : JSON.stringify({
|
|
46592
47161
|
...parseQoderMcpConfig({
|
|
@@ -46965,18 +47534,28 @@ function warnAboutRetiredFields({ name, serverRecord, logger }) {
|
|
|
46965
47534
|
* `" Serial "` counts too and is written as authored.
|
|
46966
47535
|
*/
|
|
46967
47536
|
const REASONIX_CONCURRENCY_VALUES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
46968
|
-
/**
|
|
46969
|
-
* The type Reasonix decodes each fixed-type scalar passthrough field into
|
|
46970
|
-
* (`PluginEntry` in `internal/config/plugin_entry.go`, and
|
|
46971
|
-
* `internal/contract/config/plugin_entry.go` on the CLI v2 line).
|
|
46972
|
-
*/
|
|
46973
47537
|
const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
47538
|
+
startup_timeout_seconds: "integer",
|
|
47539
|
+
call_timeout_seconds: "integer",
|
|
47540
|
+
tool_timeout_seconds: "integer table",
|
|
46974
47541
|
concurrency: "string",
|
|
46975
47542
|
auto_start: "boolean",
|
|
46976
47543
|
load: "string",
|
|
46977
47544
|
oauth_allow_missing_pkce_metadata: "boolean"
|
|
46978
47545
|
};
|
|
46979
47546
|
/**
|
|
47547
|
+
* Whether `value` is written as a TOML integer. smol-toml writes a JS number
|
|
47548
|
+
* outside the safe-integer range, or with a fraction, as a TOML float.
|
|
47549
|
+
*/
|
|
47550
|
+
function isTomlInteger(value) {
|
|
47551
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
47552
|
+
}
|
|
47553
|
+
function hasExpectedType(value, expected) {
|
|
47554
|
+
if (expected === "integer") return isTomlInteger(value);
|
|
47555
|
+
if (expected === "integer table") return isRecord$1(value) && Object.values(value).every(isTomlInteger);
|
|
47556
|
+
return typeof value === expected;
|
|
47557
|
+
}
|
|
47558
|
+
/**
|
|
46980
47559
|
* Why a passthrough value cannot be written, or `undefined` when it can (and
|
|
46981
47560
|
* for every field without a fixed type). A wrong type is the case that matters:
|
|
46982
47561
|
* a TOML type mismatch makes Reasonix refuse the whole config file. An unknown
|
|
@@ -46988,7 +47567,7 @@ const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
|
46988
47567
|
*/
|
|
46989
47568
|
function invalidPassthroughFieldReason(field, value) {
|
|
46990
47569
|
const expected = REASONIX_TYPED_PLUGIN_FIELDS[field];
|
|
46991
|
-
if (expected !== void 0 &&
|
|
47570
|
+
if (expected !== void 0 && !hasExpectedType(value, expected)) return `Reasonix expects ${expected === "integer" ? "an" : "a"} ${expected === "integer table" ? "table of integers" : expected} and fails to load a config file holding any other type.`;
|
|
46992
47571
|
if (field === "concurrency" && typeof value === "string" && !REASONIX_CONCURRENCY_VALUES.has(value.trim().toLowerCase())) return `Reasonix accepts only "serial" or "parallel" and ignores ${JSON.stringify(value)}, falling back to the server-name default.`;
|
|
46993
47572
|
}
|
|
46994
47573
|
/**
|
|
@@ -48010,7 +48589,7 @@ var TabnineMcp = class TabnineMcp extends ToolMcp {
|
|
|
48010
48589
|
fileKey: sharedConfigFileKey(paths),
|
|
48011
48590
|
feature: "mcp",
|
|
48012
48591
|
existingContent,
|
|
48013
|
-
patch: { mcpServers: convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger) },
|
|
48592
|
+
patch: { mcpServers: undefinedWhenEmpty(convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger)) },
|
|
48014
48593
|
filePath
|
|
48015
48594
|
}),
|
|
48016
48595
|
validate,
|
|
@@ -49331,6 +49910,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49331
49910
|
const paths = this.getSettablePaths({ global });
|
|
49332
49911
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
49333
49912
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
49913
|
+
const contextServers = convertServersToZedFormat(rulesyncMcp.getMcpServers(), logger);
|
|
49334
49914
|
return new ZedMcp({
|
|
49335
49915
|
outputRoot,
|
|
49336
49916
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -49339,7 +49919,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49339
49919
|
fileKey: sharedConfigFileKey(paths),
|
|
49340
49920
|
feature: "mcp",
|
|
49341
49921
|
existingContent,
|
|
49342
|
-
patch: { context_servers:
|
|
49922
|
+
patch: { context_servers: Object.keys(contextServers).length > 0 ? contextServers : void 0 },
|
|
49343
49923
|
filePath
|
|
49344
49924
|
}),
|
|
49345
49925
|
validate
|
|
@@ -49472,7 +50052,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49472
50052
|
}
|
|
49473
50053
|
}],
|
|
49474
50054
|
["claudecode-plugin", {
|
|
49475
|
-
class:
|
|
50055
|
+
class: ClaudecodePluginMcp,
|
|
49476
50056
|
meta: {
|
|
49477
50057
|
supportsProject: true,
|
|
49478
50058
|
supportsGlobal: false,
|
|
@@ -49648,7 +50228,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49648
50228
|
supportsProject: true,
|
|
49649
50229
|
supportsGlobal: true,
|
|
49650
50230
|
supportsEnabledTools: false,
|
|
49651
|
-
supportsDisabledTools:
|
|
50231
|
+
supportsDisabledTools: true
|
|
49652
50232
|
}
|
|
49653
50233
|
}],
|
|
49654
50234
|
["hermesagent", {
|
|
@@ -50814,6 +51394,8 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
50814
51394
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
50815
51395
|
*/
|
|
50816
51396
|
const REGEX_PREFIX = "regex:";
|
|
51397
|
+
const TRAILING_ANCHOR = /(?<!\\)\$$/;
|
|
51398
|
+
const PLAIN_TARGET_GLOB_CHARACTERS = /[*?[{\\]/;
|
|
50817
51399
|
const REGEX_METACHARACTERS$1 = /[\\^$.|?*+()[\]{}]/;
|
|
50818
51400
|
const WHITESPACE_CODES = [
|
|
50819
51401
|
9,
|
|
@@ -50954,6 +51536,7 @@ function replacePosixClasses(source) {
|
|
|
50954
51536
|
function isInvalidRegex(source) {
|
|
50955
51537
|
const unquoted = source.replace(RE2_QUOTED, (match) => match === "\\\\" ? match : "q");
|
|
50956
51538
|
if (LOOKAROUND.test(unquoted) || hasRefusedEscape(unquoted)) return true;
|
|
51539
|
+
if (hasClassEscapeRangeEnd(unquoted)) return true;
|
|
50957
51540
|
const withoutPosixClasses = replacePosixClasses(unquoted);
|
|
50958
51541
|
if (withoutPosixClasses === void 0 || hasRefusedRepeat(withoutPosixClasses)) return true;
|
|
50959
51542
|
const asJavaScript = withoutPosixClasses.replace(/\(\?P</g, "(?<").replace(/\(\?[imsU]*-?[imsU]*\)/g, "").replace(/\(\?[imsU]*-?[imsU]*:/g, "(?:").replace(/\\A/g, "^").replace(/\\z/g, "$");
|
|
@@ -50978,6 +51561,74 @@ function hasRefusedEscape(source) {
|
|
|
50978
51561
|
}
|
|
50979
51562
|
return false;
|
|
50980
51563
|
}
|
|
51564
|
+
const CLASS_ESCAPE_LETTERS = /[dDsSwWpP]/;
|
|
51565
|
+
/** The length of the escape at `index`, braces (`\x{41}`, `\p{Greek}`) included. */
|
|
51566
|
+
function escapeLength(source, index) {
|
|
51567
|
+
const escaped = source[index + 1] ?? "";
|
|
51568
|
+
if (/[xpP]/.test(escaped) && source[index + 2] === "{") {
|
|
51569
|
+
const close = source.indexOf("}", index + 3);
|
|
51570
|
+
return close === -1 ? source.length - index : close - index + 1;
|
|
51571
|
+
}
|
|
51572
|
+
if (escaped === "p" || escaped === "P") return 3;
|
|
51573
|
+
return escaped === "x" ? 4 : 2;
|
|
51574
|
+
}
|
|
51575
|
+
/** Step past `^` and a leading `]` (a member) after the `[` that ends at `index`. */
|
|
51576
|
+
function openClass(source, start) {
|
|
51577
|
+
const index = source[start] === "^" ? start + 1 : start;
|
|
51578
|
+
return source[index] === "]" ? {
|
|
51579
|
+
index: index + 1,
|
|
51580
|
+
afterCharacter: true
|
|
51581
|
+
} : {
|
|
51582
|
+
index,
|
|
51583
|
+
afterCharacter: false
|
|
51584
|
+
};
|
|
51585
|
+
}
|
|
51586
|
+
/**
|
|
51587
|
+
* Whether a bracket range ends in a class escape (`[a-\d]`, `[--\s]`). Go
|
|
51588
|
+
* reads the end of a range as one character and refuses such a regex, while
|
|
51589
|
+
* JavaScript without the `u` flag reads it as three members. A class escape
|
|
51590
|
+
* that starts the range (`[\d-z]`) is a member followed by `-`, which both
|
|
51591
|
+
* accept. The `u` flag cannot be used instead: it also refuses RE2 spellings
|
|
51592
|
+
* such as `\x{41}` and `\-` outside a class.
|
|
51593
|
+
*/
|
|
51594
|
+
function hasClassEscapeRangeEnd(source) {
|
|
51595
|
+
let inClass = false;
|
|
51596
|
+
let afterCharacter = false;
|
|
51597
|
+
let index = 0;
|
|
51598
|
+
while (index < source.length) {
|
|
51599
|
+
const character = source[index] ?? "";
|
|
51600
|
+
if (character === "\\") {
|
|
51601
|
+
afterCharacter = inClass && !CLASS_ESCAPE_LETTERS.test(source[index + 1] ?? "");
|
|
51602
|
+
index += escapeLength(source, index);
|
|
51603
|
+
continue;
|
|
51604
|
+
}
|
|
51605
|
+
if (!inClass) {
|
|
51606
|
+
inClass = character === "[";
|
|
51607
|
+
({index, afterCharacter} = inClass ? openClass(source, index + 1) : {
|
|
51608
|
+
index: index + 1,
|
|
51609
|
+
afterCharacter: false
|
|
51610
|
+
});
|
|
51611
|
+
continue;
|
|
51612
|
+
}
|
|
51613
|
+
const posixEnd = source.startsWith("[:", index) ? source.indexOf(":]", index + 2) : -1;
|
|
51614
|
+
if (character === "]" || posixEnd !== -1) {
|
|
51615
|
+
inClass = character !== "]";
|
|
51616
|
+
afterCharacter = false;
|
|
51617
|
+
index = posixEnd === -1 ? index + 1 : posixEnd + 2;
|
|
51618
|
+
continue;
|
|
51619
|
+
}
|
|
51620
|
+
if (character === "-" && afterCharacter && source[index + 1] !== "]") {
|
|
51621
|
+
const end = index + 1;
|
|
51622
|
+
if (source[end] === "\\" && CLASS_ESCAPE_LETTERS.test(source[end + 1] ?? "")) return true;
|
|
51623
|
+
afterCharacter = false;
|
|
51624
|
+
index = end + (source[end] === "\\" ? escapeLength(source, end) : 1);
|
|
51625
|
+
continue;
|
|
51626
|
+
}
|
|
51627
|
+
afterCharacter = true;
|
|
51628
|
+
index += 1;
|
|
51629
|
+
}
|
|
51630
|
+
return false;
|
|
51631
|
+
}
|
|
50981
51632
|
/**
|
|
50982
51633
|
* Whether a repeat count is over Go's limit. Braces inside a bracket class
|
|
50983
51634
|
* (`[a{1001}]`) and in `\x{2003}` or `\p{Greek}` are not repeats, so those
|
|
@@ -51125,7 +51776,7 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
51125
51776
|
* the rule a regex.
|
|
51126
51777
|
*/
|
|
51127
51778
|
function regexWordToGlob(word) {
|
|
51128
|
-
const body = word.replace(/^\^/, "").replace(
|
|
51779
|
+
const body = word.replace(/^\^/, "").replace(TRAILING_ANCHOR, "");
|
|
51129
51780
|
const characters = Array.from(body);
|
|
51130
51781
|
if (characters.length === 0) return;
|
|
51131
51782
|
let glob = "";
|
|
@@ -51166,12 +51817,61 @@ function regexToGlob(source) {
|
|
|
51166
51817
|
function fromAntigravityCommandTarget(target) {
|
|
51167
51818
|
if (target.startsWith(REGEX_PREFIX)) return regexToGlob(target.slice(6)) ?? target;
|
|
51168
51819
|
if (target === "*") return target;
|
|
51169
|
-
if (
|
|
51820
|
+
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) {
|
|
51170
51821
|
const words = splitWords(target);
|
|
51171
51822
|
return `${REGEX_PREFIX}${words.map((word) => `^${[...word].map(escapeRegexCharacter).join("")}$`).join(" ")}`;
|
|
51172
51823
|
}
|
|
51173
51824
|
return `${target} *`;
|
|
51174
51825
|
}
|
|
51826
|
+
/**
|
|
51827
|
+
* Whether a regex word means the same anchored or not: each end is either
|
|
51828
|
+
* anchored (`^`, an unescaped `$`) or open-ended (`.*`).
|
|
51829
|
+
*/
|
|
51830
|
+
function isAnchoredWord(word) {
|
|
51831
|
+
const startAnchored = word.startsWith("^") || word.startsWith(".*");
|
|
51832
|
+
const endAnchored = TRAILING_ANCHOR.test(word) || word.endsWith(".*");
|
|
51833
|
+
return startAnchored && endAnchored;
|
|
51834
|
+
}
|
|
51835
|
+
/**
|
|
51836
|
+
* Explain what importing an Antigravity `command` target may lose, or return
|
|
51837
|
+
* `undefined` when the import is faithful.
|
|
51838
|
+
*
|
|
51839
|
+
* - A `regex:` target with a word that is not anchored is imported as a glob
|
|
51840
|
+
* that assumes anchoring. If Antigravity matches such a word unanchored,
|
|
51841
|
+
* the imported rule is narrower (`regex:rm` also matches `rmdir`).
|
|
51842
|
+
* - A plain target holding characters that are special in a glob is most
|
|
51843
|
+
* likely the verbatim output of an older rulesync (`command(rm -rf *)`).
|
|
51844
|
+
* Antigravity matches it literally, so it imports as a regex that matches a
|
|
51845
|
+
* literal `*` word and is inert in the other tools it is exported to.
|
|
51846
|
+
*/
|
|
51847
|
+
function describeAntigravityCommandImportLoss(target) {
|
|
51848
|
+
if (target.startsWith(REGEX_PREFIX)) {
|
|
51849
|
+
const source = target.slice(6);
|
|
51850
|
+
if (splitWords(source).some((word) => !isAnchoredWord(word)) && regexToGlob(source) !== void 0) return {
|
|
51851
|
+
reason: "it has a regex word without `^...$` anchors and is imported as a glob that assumes Antigravity anchors every word, so the imported rule may be narrower than the original",
|
|
51852
|
+
denyConsequence: "The imported deny may block less than it does in Antigravity."
|
|
51853
|
+
};
|
|
51854
|
+
return;
|
|
51855
|
+
}
|
|
51856
|
+
if (target !== "*" && PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
51857
|
+
reason: "it holds characters that are special in a glob (`*`, `?`, `[`, `{` or `\\`), which Antigravity matches literally (likely output of an older rulesync), so it is imported as a literal-match regex that no other tool enforces",
|
|
51858
|
+
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
51859
|
+
};
|
|
51860
|
+
}
|
|
51861
|
+
/**
|
|
51862
|
+
* Import one Antigravity `command` target as a canonical bash pattern, warning
|
|
51863
|
+
* when the result may differ from what Antigravity enforces. Shared by the
|
|
51864
|
+
* Antigravity CLI and IDE importers.
|
|
51865
|
+
*/
|
|
51866
|
+
function importAntigravityCommandTarget({ target, entry, decision, logger, toolLabel }) {
|
|
51867
|
+
const pattern = fromAntigravityCommandTarget(target);
|
|
51868
|
+
const loss = describeAntigravityCommandImportLoss(target);
|
|
51869
|
+
if (loss !== void 0) {
|
|
51870
|
+
const consequence = decision === "deny" ? ` ${loss.denyConsequence}` : "";
|
|
51871
|
+
logger.warn(`${toolLabel} permissions: importing ${entry} as "${pattern}": ${loss.reason}.${consequence}`);
|
|
51872
|
+
}
|
|
51873
|
+
return pattern;
|
|
51874
|
+
}
|
|
51175
51875
|
//#endregion
|
|
51176
51876
|
//#region src/features/permissions/antigravity-targets.ts
|
|
51177
51877
|
/**
|
|
@@ -51202,6 +51902,7 @@ const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
|
|
|
51202
51902
|
"**/*"
|
|
51203
51903
|
]);
|
|
51204
51904
|
const DIRECTORY_CONTENTS_SUFFIX = /\/\*\*(?:\/\*)?$/;
|
|
51905
|
+
const HOME_OR_VARIABLE_PATH = /^~(?:\/|$)|^\$[A-Za-z_]|\$\{[A-Za-z_]/;
|
|
51205
51906
|
const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
|
|
51206
51907
|
const DENY_HINTS = {
|
|
51207
51908
|
read_file: "deny a directory or file path instead",
|
|
@@ -51284,6 +51985,10 @@ function buildAntigravityPermissionEntry({ action, category, pattern, decision,
|
|
|
51284
51985
|
return;
|
|
51285
51986
|
}
|
|
51286
51987
|
if (result.note !== void 0 && decision !== "deny") logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}.`);
|
|
51988
|
+
if (PATH_ACTIONS.has(action) && HOME_OR_VARIABLE_PATH.test(result.target)) {
|
|
51989
|
+
const consequence = decision === "deny" ? "this deny may block nothing" : `${toolLabel} may fall back to its default`;
|
|
51990
|
+
logger.warn(`${toolLabel} permissions: ${rule}: \`~\` and environment variables may not be expanded in Antigravity path targets, so ${action}(${result.target}) may match nothing and ${consequence}; use an absolute path instead.`);
|
|
51991
|
+
}
|
|
51287
51992
|
return result.target === "*" ? action : `${action}(${result.target})`;
|
|
51288
51993
|
}
|
|
51289
51994
|
//#endregion
|
|
@@ -51850,9 +52555,9 @@ function convertRulesyncToAntigravityCliPermissions({ config, logger }) {
|
|
|
51850
52555
|
}
|
|
51851
52556
|
}
|
|
51852
52557
|
return {
|
|
51853
|
-
allow,
|
|
51854
|
-
ask,
|
|
51855
|
-
deny
|
|
52558
|
+
allow: [...new Set(allow)],
|
|
52559
|
+
ask: [...new Set(ask)],
|
|
52560
|
+
deny: [...new Set(deny)]
|
|
51856
52561
|
};
|
|
51857
52562
|
}
|
|
51858
52563
|
/**
|
|
@@ -51864,7 +52569,13 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
51864
52569
|
for (const entry of entries) {
|
|
51865
52570
|
const { toolName, pattern: target } = parsePermissionEntry$1(entry);
|
|
51866
52571
|
const canonical = toCanonicalToolName$9(toolName);
|
|
51867
|
-
const pattern = toolName === "command"
|
|
52572
|
+
const pattern = toolName === "command" ? importAntigravityCommandTarget({
|
|
52573
|
+
target,
|
|
52574
|
+
entry,
|
|
52575
|
+
decision: action,
|
|
52576
|
+
logger: moduleLogger$4,
|
|
52577
|
+
toolLabel: "Antigravity CLI"
|
|
52578
|
+
}) : target;
|
|
51868
52579
|
if (!permission[canonical]) permission[canonical] = {};
|
|
51869
52580
|
permission[canonical][pattern] = action;
|
|
51870
52581
|
}
|
|
@@ -52068,9 +52779,9 @@ function convertRulesyncToAntigravityIdePermissions({ config, logger }) {
|
|
|
52068
52779
|
}
|
|
52069
52780
|
}
|
|
52070
52781
|
return {
|
|
52071
|
-
allow,
|
|
52072
|
-
ask,
|
|
52073
|
-
deny
|
|
52782
|
+
allow: [...new Set(allow)],
|
|
52783
|
+
ask: [...new Set(ask)],
|
|
52784
|
+
deny: [...new Set(deny)]
|
|
52074
52785
|
};
|
|
52075
52786
|
}
|
|
52076
52787
|
/**
|
|
@@ -52082,7 +52793,13 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
52082
52793
|
for (const entry of entries) {
|
|
52083
52794
|
const { action: ideAction, pattern: target } = parsePermissionEntry(entry);
|
|
52084
52795
|
const canonical = toCanonicalCategory$2(ideAction);
|
|
52085
|
-
const pattern = ideAction === "command"
|
|
52796
|
+
const pattern = ideAction === "command" ? importAntigravityCommandTarget({
|
|
52797
|
+
target,
|
|
52798
|
+
entry,
|
|
52799
|
+
decision: action,
|
|
52800
|
+
logger: fallbackLogger,
|
|
52801
|
+
toolLabel: "Antigravity IDE"
|
|
52802
|
+
}) : target;
|
|
52086
52803
|
if (!permission[canonical]) permission[canonical] = {};
|
|
52087
52804
|
permission[canonical][pattern] = action;
|
|
52088
52805
|
}
|
|
@@ -54064,7 +54781,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54064
54781
|
managedSourcesBehavior: "managed settings",
|
|
54065
54782
|
modelPricing: "managed settings",
|
|
54066
54783
|
parentSettingsBehavior: "managed settings",
|
|
54067
|
-
permissionExplainerEnabled: "~/.claude.json",
|
|
54068
54784
|
pluginSuggestionMarketplaces: "managed settings",
|
|
54069
54785
|
pluginTrustMessage: "managed settings",
|
|
54070
54786
|
prStatusFooterEnabled: "~/.claude.json",
|
|
@@ -54073,10 +54789,24 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54073
54789
|
sshHostAllowlist: "managed settings",
|
|
54074
54790
|
strictKnownMarketplaces: "managed settings",
|
|
54075
54791
|
strictPluginOnlyCustomization: "managed settings",
|
|
54076
|
-
teammateDefaultModel: "~/.claude.json",
|
|
54077
54792
|
wslInheritsWindowsSettings: "managed settings"
|
|
54078
54793
|
};
|
|
54079
54794
|
/**
|
|
54795
|
+
* Top-level settings keys Claude Code has removed, with the version that
|
|
54796
|
+
* removed each. Current versions ignore them wherever they are set, so they
|
|
54797
|
+
* are dropped in **both** scopes, and the warning names the version so the
|
|
54798
|
+
* author knows to delete the key rather than move it to another file.
|
|
54799
|
+
*
|
|
54800
|
+
* Derived from the "Removed in" notes of the settings reference.
|
|
54801
|
+
*
|
|
54802
|
+
* @see https://code.claude.com/docs/en/settings-reference
|
|
54803
|
+
*/
|
|
54804
|
+
const CLAUDECODE_REMOVED_KEYS = {
|
|
54805
|
+
permissionExplainerEnabled: "v2.1.257",
|
|
54806
|
+
taskOutputMaxChars: "v2.1.277",
|
|
54807
|
+
teammateDefaultModel: "v2.1.234"
|
|
54808
|
+
};
|
|
54809
|
+
/**
|
|
54080
54810
|
* Top-level settings keys whose value Claude Code **executes**. The generic
|
|
54081
54811
|
* passthrough refuses them outright rather than warning: `.rulesync/*` files
|
|
54082
54812
|
* are shareable — `rulesync fetch` copies a third party's `permissions.jsonc`
|
|
@@ -54356,6 +55086,15 @@ const CLAUDECODE_SETTINGS_KEY_ALIASES = {
|
|
|
54356
55086
|
allowedMarketplaces: "strictKnownMarketplaces"
|
|
54357
55087
|
};
|
|
54358
55088
|
/**
|
|
55089
|
+
* The warning for a key that neither scope can honor — one Claude Code has
|
|
55090
|
+
* removed, or one read only from a file rulesync does not generate — or
|
|
55091
|
+
* `undefined` when the key may be written in at least one scope.
|
|
55092
|
+
*/
|
|
55093
|
+
function neverHonoredKeyWarning({ canonicalKey, shown, relativeFilePath }) {
|
|
55094
|
+
if (Object.hasOwn(CLAUDECODE_REMOVED_KEYS, canonicalKey)) return `Claude Code permissions: '${shown}' was removed in Claude Code ${CLAUDECODE_REMOVED_KEYS[canonicalKey]} and has no effect on current versions, so it is not written to ${relativeFilePath}. Delete it from .rulesync/permissions.jsonc, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`;
|
|
55095
|
+
if (Object.hasOwn(CLAUDECODE_UNHONORED_KEY_SOURCES, canonicalKey)) return `Claude Code permissions: '${shown}' is only honored in ${CLAUDECODE_UNHONORED_KEY_SOURCES[canonicalKey]}, which rulesync does not generate, so it is not written to ${relativeFilePath}. Set it in that file by hand, and check ${relativeFilePath} for a stale value an earlier generate may have left there.`;
|
|
55096
|
+
}
|
|
55097
|
+
/**
|
|
54359
55098
|
* Copy of the authored top-level passthrough with the keys the target file
|
|
54360
55099
|
* cannot honor removed, warning once per dropped key. Like
|
|
54361
55100
|
* `stripSandboxPaths`, only the override copy is filtered — a value
|
|
@@ -54372,8 +55111,13 @@ function stripUnhonoredTopLevelKeys({ overrides, global, relativeFilePath, logge
|
|
|
54372
55111
|
logger?.warn(`Claude Code permissions: '${shown}' ${CLAUDECODE_COMMAND_EXECUTING_KEYS[canonicalKey]}, so rulesync does not write it to ${relativeFilePath}. A permissions file is shareable — 'rulesync fetch' copies one into a project — and is not where a reviewer looks for a command to run; author commands in .rulesync/hooks.jsonc, or set this key in ${relativeFilePath} by hand.`);
|
|
54373
55112
|
continue;
|
|
54374
55113
|
}
|
|
54375
|
-
|
|
54376
|
-
|
|
55114
|
+
const neverHonored = neverHonoredKeyWarning({
|
|
55115
|
+
canonicalKey,
|
|
55116
|
+
shown,
|
|
55117
|
+
relativeFilePath
|
|
55118
|
+
});
|
|
55119
|
+
if (neverHonored !== void 0) {
|
|
55120
|
+
logger?.warn(neverHonored);
|
|
54377
55121
|
continue;
|
|
54378
55122
|
}
|
|
54379
55123
|
if (!global && CLAUDECODE_USER_SCOPE_ONLY_KEYS.has(canonicalKey)) {
|
|
@@ -62064,8 +62808,10 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
62064
62808
|
* table. The gateway replaces the owned `permission` key wholesale and rulesync
|
|
62065
62809
|
* only ever authors `rules`, so a hand-written sibling such as
|
|
62066
62810
|
* `dangerous_command_guard = false` (Kimi Code 0.40.0) would otherwise be
|
|
62067
|
-
* deleted on every generate.
|
|
62068
|
-
*
|
|
62811
|
+
* deleted on every generate. Unmodeled keys pass through verbatim; `rules`
|
|
62812
|
+
* always comes from the patch, and so does `dangerous_command_guard` when the
|
|
62813
|
+
* `kimi-code.dangerousCommandGuard` override sets it. Leaving the override out
|
|
62814
|
+
* keeps whatever the file already has.
|
|
62069
62815
|
*
|
|
62070
62816
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
62071
62817
|
*/
|
|
@@ -62308,14 +63054,18 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62308
63054
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions, logger }) {
|
|
62309
63055
|
const config = rulesyncPermissions.getJson();
|
|
62310
63056
|
const defaultPermissionMode = config["kimi-code"]?.defaultPermissionMode;
|
|
63057
|
+
const dangerousCommandGuard = config["kimi-code"]?.dangerousCommandGuard;
|
|
62311
63058
|
const tools = buildKimiCodeToolsSection(config["kimi-code"]?.tools);
|
|
62312
63059
|
const document = {
|
|
62313
63060
|
...defaultPermissionMode && { default_permission_mode: defaultPermissionMode },
|
|
62314
63061
|
...tools && { tools },
|
|
62315
|
-
permission: {
|
|
62316
|
-
|
|
62317
|
-
|
|
62318
|
-
|
|
63062
|
+
permission: {
|
|
63063
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerous_command_guard: dangerousCommandGuard },
|
|
63064
|
+
rules: canonicalToKimiCodeRules({
|
|
63065
|
+
config,
|
|
63066
|
+
logger
|
|
63067
|
+
})
|
|
63068
|
+
}
|
|
62319
63069
|
};
|
|
62320
63070
|
let fileContent;
|
|
62321
63071
|
try {
|
|
@@ -62337,11 +63087,14 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62337
63087
|
format: "toml",
|
|
62338
63088
|
fileContent: this.getFileContent()
|
|
62339
63089
|
});
|
|
62340
|
-
const
|
|
63090
|
+
const permissionConfig = isRecord$1(config.permission) ? config.permission : {};
|
|
63091
|
+
const { permission, nativeRules } = preserveKimiCodeRules(permissionConfig.rules);
|
|
62341
63092
|
const defaultPermissionMode = config.default_permission_mode;
|
|
63093
|
+
const dangerousCommandGuard = permissionConfig.dangerous_command_guard;
|
|
62342
63094
|
const tools = buildKimiCodeToolsSection(config.tools);
|
|
62343
63095
|
const toolOverride = {
|
|
62344
63096
|
...defaultPermissionMode === "manual" || defaultPermissionMode === "yolo" || defaultPermissionMode === "auto" ? { defaultPermissionMode } : {},
|
|
63097
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerousCommandGuard },
|
|
62345
63098
|
...nativeRules.length > 0 && { rules: nativeRules },
|
|
62346
63099
|
...tools && { tools }
|
|
62347
63100
|
};
|
|
@@ -63223,6 +63976,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
63223
63976
|
//#endregion
|
|
63224
63977
|
//#region src/features/permissions/omp-permissions.ts
|
|
63225
63978
|
const CATCH_ALL_PATTERN$4 = "*";
|
|
63979
|
+
const APPROVAL_MODE_KEY = "approvalMode";
|
|
63226
63980
|
const ACTION_TO_OMP_APPROVAL = {
|
|
63227
63981
|
allow: "allow",
|
|
63228
63982
|
ask: "prompt",
|
|
@@ -63278,10 +64032,14 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
63278
64032
|
* commands, and its deny / ask also restrict every other tool's policy (a
|
|
63279
64033
|
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
63280
64034
|
*
|
|
64035
|
+
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
64036
|
+
* `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
64037
|
+
* import a recognized value is read back into the override.
|
|
64038
|
+
*
|
|
63281
64039
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
63282
|
-
* (the keys rulesync manages)
|
|
63283
|
-
*
|
|
63284
|
-
* never deleted.
|
|
64040
|
+
* (the keys rulesync manages), `bash.patterns` and an authored
|
|
64041
|
+
* `tools.approvalMode` are rewritten; sibling keys such as `bash.enabled` are
|
|
64042
|
+
* kept, and the file is never deleted.
|
|
63285
64043
|
*
|
|
63286
64044
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
63287
64045
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
@@ -63324,16 +64082,19 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
63324
64082
|
fileContent: existingContent,
|
|
63325
64083
|
filePath
|
|
63326
64084
|
});
|
|
64085
|
+
const config = rulesyncPermissions.getJson();
|
|
63327
64086
|
const { approval, patterns, floor } = convertRulesyncToOmp({
|
|
63328
|
-
config
|
|
64087
|
+
config,
|
|
63329
64088
|
logger
|
|
63330
64089
|
});
|
|
64090
|
+
const approvalMode = config.omp?.approvalMode;
|
|
63331
64091
|
const existingTools = isRecord$1(existing.tools) ? existing.tools : {};
|
|
63332
64092
|
const nextApproval = {
|
|
63333
64093
|
...isRecord$1(existingTools.approval) ? Object.fromEntries(Object.entries(existingTools.approval).filter(([tool]) => !MANAGED_OMP_TOOLS.has(tool) && !Object.hasOwn(approval, tool)).map(([tool, value]) => [tool, raisedApproval(value, floor)])) : {},
|
|
63334
64094
|
...approval
|
|
63335
64095
|
};
|
|
63336
64096
|
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
64097
|
+
if (approvalMode !== void 0) toolsSiblings[APPROVAL_MODE_KEY] = approvalMode;
|
|
63337
64098
|
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
63338
64099
|
...toolsSiblings,
|
|
63339
64100
|
approval: nextApproval
|
|
@@ -63589,7 +64350,14 @@ function convertOmpToRulesync(config) {
|
|
|
63589
64350
|
permission[category] = { [CATCH_ALL_PATTERN$4]: stricter(permission[category]?.[CATCH_ALL_PATTERN$4], action) };
|
|
63590
64351
|
}
|
|
63591
64352
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
63592
|
-
|
|
64353
|
+
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
64354
|
+
return {
|
|
64355
|
+
permission,
|
|
64356
|
+
...isOmpApprovalMode(approvalMode) && { omp: { approvalMode } }
|
|
64357
|
+
};
|
|
64358
|
+
}
|
|
64359
|
+
function isOmpApprovalMode(value) {
|
|
64360
|
+
return OMP_APPROVAL_MODES.some((mode) => mode === value);
|
|
63593
64361
|
}
|
|
63594
64362
|
//#endregion
|
|
63595
64363
|
//#region src/features/permissions/pi-permissions.ts
|
|
@@ -64629,6 +65397,13 @@ const QWEN_OVERRIDE_SECURITY_KEYS = [
|
|
|
64629
65397
|
"allowPrivateNetworkHooks",
|
|
64630
65398
|
"allowedInsecureVoiceBaseUrls"
|
|
64631
65399
|
];
|
|
65400
|
+
const QWEN_OVERRIDE_SKILLS_KEYS = [
|
|
65401
|
+
"disabled",
|
|
65402
|
+
"defaultDisabled",
|
|
65403
|
+
"enabled",
|
|
65404
|
+
"disabledLevels"
|
|
65405
|
+
];
|
|
65406
|
+
const QWEN_OVERRIDE_GENERAL_KEYS = ["outputStyle"];
|
|
64632
65407
|
/**
|
|
64633
65408
|
* What each rule means for the two directions, so a key's behavior is declared
|
|
64634
65409
|
* once beside its rule rather than spelled out at every branch.
|
|
@@ -64685,77 +65460,125 @@ const QWEN_SCOPE_RULES = {
|
|
|
64685
65460
|
globalNote: "This is not a key rulesync models, so it cannot say what Qwen Code does with it — and some settings in these groups are potent (`tools.discoveryCommand` and `tools.callCommand` are spawned as commands when Qwen Code starts). Check what it now says for every project on this machine."
|
|
64686
65461
|
}
|
|
64687
65462
|
};
|
|
64688
|
-
const
|
|
64689
|
-
|
|
64690
|
-
|
|
64691
|
-
|
|
64692
|
-
|
|
64693
|
-
|
|
64694
|
-
|
|
64695
|
-
|
|
64696
|
-
|
|
64697
|
-
},
|
|
64698
|
-
|
|
64699
|
-
|
|
64700
|
-
|
|
64701
|
-
|
|
64702
|
-
|
|
64703
|
-
|
|
64704
|
-
|
|
64705
|
-
|
|
64706
|
-
|
|
64707
|
-
|
|
64708
|
-
|
|
64709
|
-
|
|
64710
|
-
|
|
64711
|
-
|
|
64712
|
-
|
|
64713
|
-
|
|
64714
|
-
},
|
|
64715
|
-
|
|
64716
|
-
|
|
64717
|
-
|
|
64718
|
-
|
|
64719
|
-
},
|
|
64720
|
-
|
|
64721
|
-
|
|
64722
|
-
|
|
64723
|
-
|
|
64724
|
-
},
|
|
64725
|
-
|
|
64726
|
-
|
|
64727
|
-
|
|
64728
|
-
|
|
64729
|
-
},
|
|
64730
|
-
|
|
64731
|
-
|
|
64732
|
-
|
|
64733
|
-
|
|
64734
|
-
},
|
|
64735
|
-
|
|
64736
|
-
|
|
64737
|
-
|
|
64738
|
-
|
|
64739
|
-
},
|
|
64740
|
-
|
|
65463
|
+
const QWEN_SCOPED_TOOLS_KEYS = {
|
|
65464
|
+
workflowsEnabled: { rule: "workspace-stripped" },
|
|
65465
|
+
executionSandbox: {
|
|
65466
|
+
rule: "workspace-stripped",
|
|
65467
|
+
grants: () => true,
|
|
65468
|
+
globalNote: "Qwen Code ignores this key in workspace settings so a repository cannot choose its own sandbox; in the global scope it sets the filesystem and network policy every tool command runs under for every project on this machine (a System value still replaces it, and Qwen Code needs a restart to pick it up)."
|
|
65469
|
+
},
|
|
65470
|
+
workflowNameOnly: {
|
|
65471
|
+
rule: "global-machine-wide",
|
|
65472
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}. Qwen Code lets a workspace only turn this key on: \`true\` restricts the model to named workflows in this repository, while any other value cannot lift a restriction set in user or system settings.`,
|
|
65473
|
+
globalNote: "Qwen Code honors this key in the global scope for every project on this machine, and a repository can only add the restriction, never lift it, so this decides whether the model may run inline or path-based workflow scripts anywhere a workspace does not turn the restriction on itself."
|
|
65474
|
+
},
|
|
65475
|
+
approvalMode: {
|
|
65476
|
+
rule: "global-machine-wide",
|
|
65477
|
+
projectNote: autonomyProjectNote
|
|
65478
|
+
},
|
|
65479
|
+
autoAccept: {
|
|
65480
|
+
rule: "global-machine-wide",
|
|
65481
|
+
projectNote: autonomyProjectNote
|
|
65482
|
+
},
|
|
65483
|
+
sandbox: {
|
|
65484
|
+
rule: "global-machine-wide",
|
|
65485
|
+
projectNote: autonomyProjectNote
|
|
65486
|
+
},
|
|
65487
|
+
sandboxImage: {
|
|
65488
|
+
rule: "global-machine-wide",
|
|
65489
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so a sandboxed run in this repository executes inside the named image — check that it is one you trust to run your code.`,
|
|
65490
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope every sandboxed run on this machine executes inside the named image — check that it is one you trust to run your code."
|
|
65491
|
+
},
|
|
65492
|
+
disabled: {
|
|
65493
|
+
rule: "global-machine-wide",
|
|
65494
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the list in that file rather than adding to it, so a name that is gone is a tool the model can call again in this repository unless another scope still disables it.`,
|
|
65495
|
+
globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the list in this file rather than adding to it, so in the global scope this decides which tools stay out of the registry for every project on this machine — dropping a name hands that tool back to the model unless another scope still disables it."
|
|
65496
|
+
},
|
|
65497
|
+
visible: {
|
|
65498
|
+
rule: "global-machine-wide",
|
|
65499
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides which deferred tools are visible at startup in this repository.`,
|
|
65500
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides which deferred tools are visible at startup for every project on this machine."
|
|
65501
|
+
},
|
|
65502
|
+
eager: {
|
|
65503
|
+
rule: "global-machine-wide",
|
|
65504
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the list in that file rather than adding to it, so in this repository every non-exempt eager-by-default tool it does not name is demoted to deferred — still callable, but only after \`tool_search\` loads it, and out of reach for the session if \`tool_search\` is not registered. A name added back is a tool whose schema is in the initial request again.`,
|
|
65505
|
+
globalNote: "Qwen Code honors this key wherever it is written, and the override replaces the list in this file rather than adding to it, so in the global scope it decides which eager-by-default tools keep their schemas in the initial request for every project on this machine — the ones it does not name are demoted to deferred, reachable only through `tool_search`, and out of reach entirely for a session where `tool_search` is not registered, while a name it adds back is a tool declared up front again."
|
|
65506
|
+
},
|
|
65507
|
+
listDirectory: {
|
|
65508
|
+
rule: "global-machine-wide",
|
|
65509
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`list_directory\` tool is registered in this repository.`,
|
|
65510
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `list_directory` tool is registered for every project on this machine."
|
|
65511
|
+
},
|
|
65512
|
+
todoWrite: {
|
|
65513
|
+
rule: "global-machine-wide",
|
|
65514
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it decides whether the built-in \`todo_write\` tool is registered in this repository.`,
|
|
65515
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope this decides whether the built-in `todo_write` tool is registered for every project on this machine."
|
|
65516
|
+
}
|
|
65517
|
+
};
|
|
65518
|
+
/**
|
|
65519
|
+
* The notes for a skills list that hides something. Qwen Code unions the list
|
|
65520
|
+
* across scopes, but the override replaces the one in the file it writes, so
|
|
65521
|
+
* the consequence worth naming is what dropping an entry hands back.
|
|
65522
|
+
*/
|
|
65523
|
+
function skillsHidingListNotes(hides) {
|
|
65524
|
+
return {
|
|
65525
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, replacing the list in that file rather than adding to it, so it decides which ${hides} in this repository — an entry that is gone is one the model can load again unless another scope still names it.`,
|
|
65526
|
+
globalNote: `Qwen Code honors this key wherever it is written and unions it across scopes, but the override replaces the list in this file rather than adding to it, so in the global scope this decides which ${hides} for every project on this machine — an entry that is gone is one the model can load again unless another scope still names it.`
|
|
65527
|
+
};
|
|
65528
|
+
}
|
|
65529
|
+
const QWEN_SCOPED_SKILLS_KEYS = {
|
|
65530
|
+
disabled: {
|
|
65531
|
+
rule: "global-machine-wide",
|
|
65532
|
+
...skillsHidingListNotes("skills are hidden")
|
|
65533
|
+
},
|
|
65534
|
+
defaultDisabled: {
|
|
65535
|
+
rule: "global-machine-wide",
|
|
65536
|
+
...skillsHidingListNotes("skills start disabled until `skills.enabled` opts into them")
|
|
65537
|
+
},
|
|
65538
|
+
disabledLevels: {
|
|
65539
|
+
rule: "global-machine-wide",
|
|
65540
|
+
...skillsHidingListNotes("skill discovery levels (`project`, `user`, `extension`, `bundled`) are skipped")
|
|
65541
|
+
},
|
|
65542
|
+
enabled: {
|
|
65543
|
+
rule: "global-machine-wide",
|
|
65544
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so in this repository it opts the model into the skills it names that \`skills.defaultDisabled\` would otherwise keep off in any scope (it cannot override \`skills.disabled\` or a skipped \`skills.disabledLevels\` level). Check it if the override arrived with a repository you cloned.`,
|
|
65545
|
+
globalNote: "Qwen Code honors this key wherever it is written and unions it across scopes, so in the global scope it opts the model into the skills it names that `skills.defaultDisabled` would otherwise keep off, for every project on this machine (it cannot override `skills.disabled` or a skipped `skills.disabledLevels` level)."
|
|
65546
|
+
}
|
|
65547
|
+
};
|
|
65548
|
+
const QWEN_OVERRIDE_GROUPS = [
|
|
65549
|
+
{
|
|
65550
|
+
groupName: "tools",
|
|
65551
|
+
overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
|
|
65552
|
+
scopedKeys: QWEN_SCOPED_TOOLS_KEYS
|
|
65553
|
+
},
|
|
65554
|
+
{
|
|
65555
|
+
groupName: "security",
|
|
65556
|
+
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
65557
|
+
scopedKeys: {
|
|
65558
|
+
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
65559
|
+
allowedInsecureVoiceBaseUrls: {
|
|
65560
|
+
rule: "workspace-stripped",
|
|
65561
|
+
grants: isNonEmptyArray
|
|
65562
|
+
},
|
|
65563
|
+
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
65564
|
+
folderTrust: { rule: "user-scope-trust-check" }
|
|
65565
|
+
}
|
|
65566
|
+
},
|
|
65567
|
+
{
|
|
65568
|
+
groupName: "skills",
|
|
65569
|
+
overrideKeys: QWEN_OVERRIDE_SKILLS_KEYS,
|
|
65570
|
+
scopedKeys: QWEN_SCOPED_SKILLS_KEYS
|
|
65571
|
+
},
|
|
65572
|
+
{
|
|
65573
|
+
groupName: "general",
|
|
65574
|
+
overrideKeys: QWEN_OVERRIDE_GENERAL_KEYS,
|
|
65575
|
+
scopedKeys: { outputStyle: {
|
|
64741
65576
|
rule: "global-machine-wide",
|
|
64742
|
-
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it
|
|
64743
|
-
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope
|
|
64744
|
-
}
|
|
64745
|
-
}
|
|
64746
|
-
}, {
|
|
64747
|
-
groupName: "security",
|
|
64748
|
-
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
64749
|
-
scopedKeys: {
|
|
64750
|
-
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
64751
|
-
allowedInsecureVoiceBaseUrls: {
|
|
64752
|
-
rule: "workspace-stripped",
|
|
64753
|
-
grants: isNonEmptyArray
|
|
64754
|
-
},
|
|
64755
|
-
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
64756
|
-
folderTrust: { rule: "user-scope-trust-check" }
|
|
65577
|
+
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it picks the output style for this repository. A custom style adds the instructions in its style file to the system prompt and can drop the built-in coding guidance (\`keep-coding-instructions\`) — a project style file is read only when the workspace is trusted. Check it if the override arrived with a repository you cloned.`,
|
|
65578
|
+
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope it picks the output style for every project on this machine that does not choose its own. A custom style adds the instructions in its style file to the system prompt and can drop the built-in coding guidance (`keep-coding-instructions`)."
|
|
65579
|
+
} }
|
|
64757
65580
|
}
|
|
64758
|
-
|
|
65581
|
+
];
|
|
64759
65582
|
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
64760
65583
|
const QWEN_SCOPED_PERMISSIONS_KEYS = { autoMode: {
|
|
64761
65584
|
rule: "global-machine-wide",
|
|
@@ -64886,7 +65709,7 @@ function scopeOverrideGroup(overrideGroup, { groupName, scopedKeys, existingGrou
|
|
|
64886
65709
|
return scoped;
|
|
64887
65710
|
}
|
|
64888
65711
|
/**
|
|
64889
|
-
* Build the `tools`/`security` patch groups contributed by the `qwencode`
|
|
65712
|
+
* Build the `tools`/`security`/`skills`/`general` patch groups contributed by the `qwencode`
|
|
64890
65713
|
* override. Each group is shallow-merged over what `settings.json` already has,
|
|
64891
65714
|
* after the scope gate has handled the keys Qwen Code honors in one scope only.
|
|
64892
65715
|
*/
|
|
@@ -65055,7 +65878,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
65055
65878
|
});
|
|
65056
65879
|
const overrideGroups = {
|
|
65057
65880
|
tools: pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS),
|
|
65058
|
-
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS)
|
|
65881
|
+
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS),
|
|
65882
|
+
skills: pickQwenOverrideKeys(settings.skills, QWEN_OVERRIDE_SKILLS_KEYS),
|
|
65883
|
+
general: pickQwenOverrideKeys(settings.general, QWEN_OVERRIDE_GENERAL_KEYS)
|
|
65059
65884
|
};
|
|
65060
65885
|
if (!this.global) warnAboutImportedScopedKeys(overrideGroups);
|
|
65061
65886
|
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
@@ -65358,6 +66183,37 @@ function applyAllowDynamicBash({ permissions, authored }) {
|
|
|
65358
66183
|
reason: "lets an Allow fallback, Auto included, run the nested and indirect Bash that otherwise needs a human or an exact-literal grant — command and process substitution, a dynamic command name, 'eval', 'source', 'sh -c' and their kind"
|
|
65359
66184
|
}];
|
|
65360
66185
|
}
|
|
66186
|
+
/** The writer fallbacks `[permissions] mode` accepts. */
|
|
66187
|
+
const REASONIX_PERMISSION_MODES = /* @__PURE__ */ new Set([
|
|
66188
|
+
"ask",
|
|
66189
|
+
"allow",
|
|
66190
|
+
"deny"
|
|
66191
|
+
]);
|
|
66192
|
+
/**
|
|
66193
|
+
* Writes the override's `mode` into `[permissions]`. Reasonix honors the key
|
|
66194
|
+
* only in the user config and ignores a project value as user-scope only, so a
|
|
66195
|
+
* project-scope generate skips it with a warning and leaves whatever the file
|
|
66196
|
+
* had. A global `allow` is reported when it is newly written: it lets every
|
|
66197
|
+
* writer call that no rule matches run without a prompt.
|
|
66198
|
+
*
|
|
66199
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
66200
|
+
*/
|
|
66201
|
+
function applyPermissionsMode({ permissions, authored, global, relativeFilePath, logger }) {
|
|
66202
|
+
if (authored === void 0) return [];
|
|
66203
|
+
if (!global) {
|
|
66204
|
+
logger?.warn(`Reasonix permissions: skipping reasonix.mode = ${quoteValueForWarning(authored)} for the project-scoped ${relativeFilePath}; Reasonix honors [permissions] mode only in the user config and ignores a project value, so generate it with --global instead.`);
|
|
66205
|
+
return [];
|
|
66206
|
+
}
|
|
66207
|
+
const previous = permissions.mode;
|
|
66208
|
+
permissions.mode = authored;
|
|
66209
|
+
const normalize = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
66210
|
+
const mode = normalize(authored);
|
|
66211
|
+
if (mode === "ask" || mode === "deny" || normalize(previous) === mode) return [];
|
|
66212
|
+
return [{
|
|
66213
|
+
label: "permissions.mode",
|
|
66214
|
+
reason: "lets every writer call that no rule matches run without a prompt"
|
|
66215
|
+
}];
|
|
66216
|
+
}
|
|
65361
66217
|
function parseReasonixConfig(fileContent) {
|
|
65362
66218
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
65363
66219
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -65408,7 +66264,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65408
66264
|
relativeDirPath: paths.relativeDirPath,
|
|
65409
66265
|
relativeFilePath: paths.relativeFilePath,
|
|
65410
66266
|
fileContent,
|
|
65411
|
-
validate
|
|
66267
|
+
validate,
|
|
66268
|
+
global
|
|
65412
66269
|
});
|
|
65413
66270
|
}
|
|
65414
66271
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
@@ -65463,6 +66320,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65463
66320
|
permissions: mergedPermissions,
|
|
65464
66321
|
authored: override?.allowDynamicBash
|
|
65465
66322
|
});
|
|
66323
|
+
trustAffecting.push(...applyPermissionsMode({
|
|
66324
|
+
permissions: mergedPermissions,
|
|
66325
|
+
authored: override?.mode,
|
|
66326
|
+
global,
|
|
66327
|
+
relativeFilePath: relativeFilePathForLog,
|
|
66328
|
+
logger
|
|
66329
|
+
}));
|
|
65466
66330
|
const patch = { permissions: mergedPermissions };
|
|
65467
66331
|
if (override?.sandbox !== void 0) {
|
|
65468
66332
|
const mergedSandbox = {
|
|
@@ -65504,7 +66368,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65504
66368
|
patch,
|
|
65505
66369
|
filePath
|
|
65506
66370
|
}),
|
|
65507
|
-
validate
|
|
66371
|
+
validate,
|
|
66372
|
+
global
|
|
65508
66373
|
});
|
|
65509
66374
|
}
|
|
65510
66375
|
toRulesyncPermissions() {
|
|
@@ -65529,6 +66394,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65529
66394
|
const reasonixOverride = {};
|
|
65530
66395
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
65531
66396
|
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
66397
|
+
if (this.global && typeof permissions.mode === "string" && REASONIX_PERMISSION_MODES.has(permissions.mode)) reasonixOverride.mode = permissions.mode;
|
|
65532
66398
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
65533
66399
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
65534
66400
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -67094,10 +67960,15 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67094
67960
|
outputRoot,
|
|
67095
67961
|
logger
|
|
67096
67962
|
});
|
|
67963
|
+
const inheritedProvider = global ? void 0 : await readGlobalTaktProvider({
|
|
67964
|
+
projectFilePath: filePath,
|
|
67965
|
+
logger
|
|
67966
|
+
});
|
|
67097
67967
|
const rulesyncJson = rulesyncPermissions.getJson();
|
|
67098
67968
|
const provider = resolveActiveProvider({
|
|
67099
67969
|
config,
|
|
67100
|
-
runtime: runtime.active ? runtime.config : void 0
|
|
67970
|
+
runtime: runtime.active ? runtime.config : void 0,
|
|
67971
|
+
inheritedProvider
|
|
67101
67972
|
});
|
|
67102
67973
|
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
67103
67974
|
const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
@@ -67207,10 +68078,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
67207
68078
|
* `provider_profiles` (unless that key is the pre-0.68.0 `claude` fallback),
|
|
67208
68079
|
* else the `claude-sdk` default (Takt 0.68.0+).
|
|
67209
68080
|
*/
|
|
67210
|
-
function resolveActiveProvider({ config, runtime }) {
|
|
68081
|
+
function resolveActiveProvider({ config, runtime, inheritedProvider }) {
|
|
67211
68082
|
const fromRuntime = runtime === void 0 ? void 0 : resolveRuntimeProvider(runtime);
|
|
67212
68083
|
if (fromRuntime !== void 0) return fromRuntime;
|
|
67213
|
-
|
|
68084
|
+
const local = readProviderName(config);
|
|
68085
|
+
if (local !== void 0) return local;
|
|
68086
|
+
if (inheritedProvider !== void 0) return inheritedProvider;
|
|
67214
68087
|
const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
|
|
67215
68088
|
if (isPlainObject$1(profiles)) {
|
|
67216
68089
|
const keys = Object.keys(profiles);
|
|
@@ -67218,6 +68091,43 @@ function resolveActiveProvider({ config, runtime }) {
|
|
|
67218
68091
|
}
|
|
67219
68092
|
return TAKT_DEFAULT_PROVIDER;
|
|
67220
68093
|
}
|
|
68094
|
+
/**
|
|
68095
|
+
* The top-level `provider:` of a Takt `config.yaml`, when it names one: either
|
|
68096
|
+
* the plain name or the block form `provider: { type: <name>, ... }` Takt also
|
|
68097
|
+
* accepts (`ConfigProviderReference`).
|
|
68098
|
+
*/
|
|
68099
|
+
function readProviderName(config) {
|
|
68100
|
+
const raw = config[TAKT_PROVIDER_KEY];
|
|
68101
|
+
const provider = isPlainObject$1(raw) ? raw.type : raw;
|
|
68102
|
+
return typeof provider === "string" && provider.trim() !== "" ? provider : void 0;
|
|
68103
|
+
}
|
|
68104
|
+
/**
|
|
68105
|
+
* The top-level `provider:` of the global `~/.takt/config.yaml`, read for a
|
|
68106
|
+
* project-scope generate. An unresolvable home directory, a missing or
|
|
68107
|
+
* unparsable file, or one that names no provider all mean "nothing inherited".
|
|
68108
|
+
*/
|
|
68109
|
+
async function readGlobalTaktProvider({ projectFilePath, logger }) {
|
|
68110
|
+
let globalPath;
|
|
68111
|
+
try {
|
|
68112
|
+
globalPath = (0, node_path.join)(getHomeDirectory(), TAKT_DIR, TAKT_CONFIG_FILE_NAME);
|
|
68113
|
+
} catch {
|
|
68114
|
+
return;
|
|
68115
|
+
}
|
|
68116
|
+
if (globalPath === projectFilePath) return;
|
|
68117
|
+
try {
|
|
68118
|
+
const content = await readFileContentOrNull(globalPath);
|
|
68119
|
+
if (content === null) return;
|
|
68120
|
+
return readProviderName(parseSharedConfig({
|
|
68121
|
+
format: "yaml",
|
|
68122
|
+
fileContent: content,
|
|
68123
|
+
filePath: globalPath,
|
|
68124
|
+
invalidRootPolicy: "error"
|
|
68125
|
+
}));
|
|
68126
|
+
} catch (error) {
|
|
68127
|
+
logger?.warn(`Takt permissions: could not read the provider from ${globalPath} (${formatError(error)}); falling back to the project config alone.`);
|
|
68128
|
+
return;
|
|
68129
|
+
}
|
|
68130
|
+
}
|
|
67221
68131
|
/** A plain object carrying at least one entry (Takt's "assignment" test). */
|
|
67222
68132
|
function hasEntries(value) {
|
|
67223
68133
|
return isPlainObject$1(value) && Object.keys(value).length > 0;
|
|
@@ -69248,6 +70158,9 @@ const WARP_OVERRIDE_KEYS = [
|
|
|
69248
70158
|
"agent_mode_execute_readonly_commands"
|
|
69249
70159
|
];
|
|
69250
70160
|
const WARP_EXECUTION_PROFILE_OVERRIDE_KEY = "execution_profile";
|
|
70161
|
+
const WARP_AUTO_APPROVE_BYPASS_KEY = "auto_approve_bypasses_command_denylist";
|
|
70162
|
+
const WARP_AGENT_KEY = "warp_agent";
|
|
70163
|
+
const WARP_AGENT_OTHER_KEY = "other";
|
|
69251
70164
|
const WARP_EXECUTION_PROFILE_KEYS = [
|
|
69252
70165
|
"read_files",
|
|
69253
70166
|
"apply_code_diffs",
|
|
@@ -69396,7 +70309,7 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69396
70309
|
const override = config.warp;
|
|
69397
70310
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69398
70311
|
if (isRecord$1(override)) {
|
|
69399
|
-
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
70312
|
+
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, [WARP_AUTO_APPROVE_BYPASS_KEY]: _autoApproveBypass, ...legacyOverride } = override;
|
|
69400
70313
|
Object.assign(profiles, legacyOverride);
|
|
69401
70314
|
}
|
|
69402
70315
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
@@ -69406,9 +70319,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69406
70319
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
69407
70320
|
else delete profiles[DENYLIST_KEY];
|
|
69408
70321
|
agents.profiles = profiles;
|
|
70322
|
+
applyAutoApproveBypassOverride({
|
|
70323
|
+
agents,
|
|
70324
|
+
override
|
|
70325
|
+
});
|
|
69409
70326
|
warnAboutDenylistReplacement({
|
|
69410
70327
|
toolLabel: "Warp",
|
|
70328
|
+
overrideKey: "warp",
|
|
69411
70329
|
denyCount: mergedDeny.length,
|
|
70330
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
69412
70331
|
logger
|
|
69413
70332
|
});
|
|
69414
70333
|
mergeIntoDefaultExecutionProfile({
|
|
@@ -69447,6 +70366,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69447
70366
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
69448
70367
|
const executionProfileOverride = defaultProfile ? liftExecutionProfileOverride(defaultProfile) : void 0;
|
|
69449
70368
|
if (executionProfileOverride) warpOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70369
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70370
|
+
if (autoApproveBypass !== void 0) warpOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69450
70371
|
const result = { ...config };
|
|
69451
70372
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
69452
70373
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -69470,12 +70391,40 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69470
70391
|
};
|
|
69471
70392
|
/**
|
|
69472
70393
|
* Writing `command_denylist` at all replaces Warp's built-in default denylist,
|
|
69473
|
-
* so any non-empty list rulesync writes deserves a heads-up.
|
|
70394
|
+
* so any non-empty list rulesync writes deserves a heads-up. Unless
|
|
70395
|
+
* `auto_approve_bypasses_command_denylist` is `false`, auto-approve also runs
|
|
70396
|
+
* commands that match the list, so the warning says so.
|
|
69474
70397
|
* https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
69475
70398
|
*/
|
|
69476
|
-
function warnAboutDenylistReplacement({ toolLabel, denyCount, logger }) {
|
|
70399
|
+
function warnAboutDenylistReplacement({ toolLabel, overrideKey, denyCount, autoApproveBypassesDenylist, logger }) {
|
|
69477
70400
|
if (denyCount === 0 || !logger) return;
|
|
69478
|
-
|
|
70401
|
+
const bypassNote = autoApproveBypassesDenylist === false ? "" : ` Auto-approve also runs commands that match the denylist unless ${overrideKey}.${WARP_AUTO_APPROVE_BYPASS_KEY} is set to false.`;
|
|
70402
|
+
logger.warn(`${toolLabel}'s command_denylist replaces its built-in default denylist, which covers rm, curl, wget, eval, ssh, shells, and other risky command patterns. The ${denyCount} deny rule(s) from .rulesync/permissions.jsonc are now the whole denylist — add equivalents for the built-in patterns you want to keep.${bypassNote}`);
|
|
70403
|
+
}
|
|
70404
|
+
/**
|
|
70405
|
+
* Write the override's `auto_approve_bypasses_command_denylist` into
|
|
70406
|
+
* `[agents.warp_agent.other]`, keeping every sibling key of `agents.warp_agent`
|
|
70407
|
+
* and of its `other` table. Nothing is written when the override omits it.
|
|
70408
|
+
*/
|
|
70409
|
+
function applyAutoApproveBypassOverride({ agents, override }) {
|
|
70410
|
+
if (!isRecord$1(override)) return;
|
|
70411
|
+
const value = override[WARP_AUTO_APPROVE_BYPASS_KEY];
|
|
70412
|
+
if (typeof value !== "boolean") return;
|
|
70413
|
+
const warpAgent = isRecord$1(agents[WARP_AGENT_KEY]) ? { ...agents[WARP_AGENT_KEY] } : {};
|
|
70414
|
+
const other = isRecord$1(warpAgent[WARP_AGENT_OTHER_KEY]) ? { ...warpAgent[WARP_AGENT_OTHER_KEY] } : {};
|
|
70415
|
+
other[WARP_AUTO_APPROVE_BYPASS_KEY] = value;
|
|
70416
|
+
warpAgent[WARP_AGENT_OTHER_KEY] = other;
|
|
70417
|
+
agents[WARP_AGENT_KEY] = warpAgent;
|
|
70418
|
+
}
|
|
70419
|
+
/**
|
|
70420
|
+
* Read `[agents.warp_agent.other] auto_approve_bypasses_command_denylist`, or
|
|
70421
|
+
* `undefined` when it is unset (Warp then defaults to `true`).
|
|
70422
|
+
*/
|
|
70423
|
+
function readAutoApproveBypass(agents) {
|
|
70424
|
+
const warpAgent = agents[WARP_AGENT_KEY];
|
|
70425
|
+
const other = isRecord$1(warpAgent) ? warpAgent[WARP_AGENT_OTHER_KEY] : void 0;
|
|
70426
|
+
const value = isRecord$1(other) ? other[WARP_AUTO_APPROVE_BYPASS_KEY] : void 0;
|
|
70427
|
+
return typeof value === "boolean" ? value : void 0;
|
|
69479
70428
|
}
|
|
69480
70429
|
/**
|
|
69481
70430
|
* On migrated installs the legacy `[agents.profiles]` keys are inert: runtime
|
|
@@ -69829,6 +70778,9 @@ const WARPCLI_GLOBAL_ONLY_MESSAGE = "Warp Agent CLI permissions are global-only;
|
|
|
69829
70778
|
* (`read_files`, `apply_code_diffs`, `run_agents`, ...), merged first so the
|
|
69830
70779
|
* rulesync-owned command lists always win.
|
|
69831
70780
|
*
|
|
70781
|
+
* The `warpcli.auto_approve_bypasses_command_denylist` override is written to
|
|
70782
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
70783
|
+
*
|
|
69832
70784
|
* Unlike the `warp` target, the CLI never read the app's legacy
|
|
69833
70785
|
* `[agents.profiles]` keys, so none are written, and there is no settings
|
|
69834
70786
|
* migration to wait for: the CLI's settings file "is created the first time you
|
|
@@ -69895,11 +70847,6 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69895
70847
|
});
|
|
69896
70848
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
69897
70849
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
69898
|
-
warnAboutDenylistReplacement({
|
|
69899
|
-
toolLabel: "Warp Agent CLI",
|
|
69900
|
-
denyCount: mergedDeny.length,
|
|
69901
|
-
logger
|
|
69902
|
-
});
|
|
69903
70850
|
const override = config.warpcli;
|
|
69904
70851
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69905
70852
|
const agents = isRecord$1(settings.agents) ? { ...settings.agents } : {};
|
|
@@ -69909,7 +70856,18 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69909
70856
|
mergedDeny,
|
|
69910
70857
|
executionProfileOverride
|
|
69911
70858
|
});
|
|
70859
|
+
applyAutoApproveBypassOverride({
|
|
70860
|
+
agents,
|
|
70861
|
+
override
|
|
70862
|
+
});
|
|
69912
70863
|
settings.agents = agents;
|
|
70864
|
+
warnAboutDenylistReplacement({
|
|
70865
|
+
toolLabel: "Warp Agent CLI",
|
|
70866
|
+
overrideKey: "warpcli",
|
|
70867
|
+
denyCount: mergedDeny.length,
|
|
70868
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
70869
|
+
logger
|
|
70870
|
+
});
|
|
69913
70871
|
return new WarpcliPermissions({
|
|
69914
70872
|
outputRoot,
|
|
69915
70873
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -69936,8 +70894,12 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69936
70894
|
deny: isStringArray$2(denylist) ? denylist : []
|
|
69937
70895
|
});
|
|
69938
70896
|
const executionProfileOverride = liftExecutionProfileOverride(defaultProfile);
|
|
70897
|
+
const warpcliOverride = {};
|
|
70898
|
+
if (executionProfileOverride) warpcliOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70899
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70900
|
+
if (autoApproveBypass !== void 0) warpcliOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69939
70901
|
const result = { ...config };
|
|
69940
|
-
if (
|
|
70902
|
+
if (Object.keys(warpcliOverride).length > 0) result.warpcli = warpcliOverride;
|
|
69941
70903
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
69942
70904
|
}
|
|
69943
70905
|
validate() {
|
|
@@ -82011,9 +82973,8 @@ var CodebuddySubagent = class CodebuddySubagent extends ToolSubagent {
|
|
|
82011
82973
|
* Keys of the rulesync `codewhale` section copied into the profile. Codewhale
|
|
82012
82974
|
* parses agent profiles with `deny_unknown_fields`, so an unknown key would
|
|
82013
82975
|
* make the whole profile fail to load; anything else in the section is dropped
|
|
82014
|
-
* with a warning. `[tools]` and `[permissions]` are
|
|
82015
|
-
*
|
|
82016
|
-
* profile that tries to widen them.
|
|
82976
|
+
* with a warning. The `[tools]` and `[permissions]` tables are handled
|
|
82977
|
+
* separately (see `CODEWHALE_NARROWING_TABLES`).
|
|
82017
82978
|
*/
|
|
82018
82979
|
const CODEWHALE_SECTION_KEYS = [
|
|
82019
82980
|
"role_hint",
|
|
@@ -82034,6 +82995,48 @@ const CODEWHALE_SECTION_KEY_ALIASES = {
|
|
|
82034
82995
|
reasoning_effort: ["thinking", "reasoning"]
|
|
82035
82996
|
};
|
|
82036
82997
|
const CODEWHALE_SECTION_ALIAS_TO_KEY = new Map(Object.entries(CODEWHALE_SECTION_KEY_ALIASES).flatMap(([key, aliases]) => (aliases ?? []).map((alias) => [alias, key])));
|
|
82998
|
+
/**
|
|
82999
|
+
* The `[tools]` and `[permissions]` tables of an agent profile, with the only
|
|
83000
|
+
* values Codewhale accepts in each key. Codewhale rejects a profile that tries
|
|
83001
|
+
* to widen access through them (`reject_permission_expansion` in
|
|
83002
|
+
* `fleet/profile.rs`), and both tables deny unknown fields, so any other key
|
|
83003
|
+
* or value is dropped with a warning.
|
|
83004
|
+
*/
|
|
83005
|
+
const CODEWHALE_NARROWING_TABLES = {
|
|
83006
|
+
tools: { posture: (value) => typeof value === "string" && [
|
|
83007
|
+
"read-only",
|
|
83008
|
+
"readonly",
|
|
83009
|
+
"read_only"
|
|
83010
|
+
].includes(value) },
|
|
83011
|
+
permissions: {
|
|
83012
|
+
allow_shell: (value) => value === false,
|
|
83013
|
+
trust: (value) => value === false,
|
|
83014
|
+
approval_required: (value) => value === true
|
|
83015
|
+
}
|
|
83016
|
+
};
|
|
83017
|
+
/**
|
|
83018
|
+
* Keeps the narrowing-only entries of a `[tools]` / `[permissions]` table and
|
|
83019
|
+
* reports every other entry by its dotted path. A non-table value is dropped
|
|
83020
|
+
* as a whole.
|
|
83021
|
+
*/
|
|
83022
|
+
function filterNarrowingTable(tableName, value) {
|
|
83023
|
+
const allowed = CODEWHALE_NARROWING_TABLES[tableName] ?? {};
|
|
83024
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {
|
|
83025
|
+
kept: void 0,
|
|
83026
|
+
dropped: [tableName]
|
|
83027
|
+
};
|
|
83028
|
+
const kept = {};
|
|
83029
|
+
const dropped = [];
|
|
83030
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
83031
|
+
const normalized = typeof entry === "string" ? entry.trim() : entry;
|
|
83032
|
+
if (Object.hasOwn(allowed, key) && allowed[key]?.(normalized)) kept[key] = normalized;
|
|
83033
|
+
else dropped.push(`${tableName}.${key}`);
|
|
83034
|
+
}
|
|
83035
|
+
return {
|
|
83036
|
+
kept: Object.keys(kept).length > 0 ? kept : void 0,
|
|
83037
|
+
dropped
|
|
83038
|
+
};
|
|
83039
|
+
}
|
|
82037
83040
|
const CodewhaleSubagentTomlSchema = zod_mini.z.looseObject({
|
|
82038
83041
|
id: zod_mini.z.optional(zod_mini.z.string()),
|
|
82039
83042
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -82060,7 +83063,8 @@ function stringifyCodewhaleSubagentToml(fields, instructions) {
|
|
|
82060
83063
|
*
|
|
82061
83064
|
* Generation writes `id` (the file stem), `display_name` (the rulesync name),
|
|
82062
83065
|
* `description`, and the body as `[instructions] text`, plus the allowlisted
|
|
82063
|
-
* keys of the rulesync `codewhale` section
|
|
83066
|
+
* keys of the rulesync `codewhale` section and its narrowing-only `[tools]` /
|
|
83067
|
+
* `[permissions]` entries.
|
|
82064
83068
|
*
|
|
82065
83069
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/SUBAGENTS.md
|
|
82066
83070
|
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/fleet/profile.rs
|
|
@@ -82095,6 +83099,10 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82095
83099
|
const spelling = [key, ...CODEWHALE_SECTION_KEY_ALIASES[key] ?? []].find((candidate) => record[candidate] !== void 0);
|
|
82096
83100
|
if (spelling !== void 0) codewhaleSection[key] = record[spelling];
|
|
82097
83101
|
}
|
|
83102
|
+
for (const tableName of Object.keys(CODEWHALE_NARROWING_TABLES)) if (record[tableName] !== void 0) {
|
|
83103
|
+
const { kept } = filterNarrowingTable(tableName, record[tableName]);
|
|
83104
|
+
if (kept !== void 0) codewhaleSection[tableName] = kept;
|
|
83105
|
+
}
|
|
82098
83106
|
return new RulesyncSubagent({
|
|
82099
83107
|
outputRoot: ".",
|
|
82100
83108
|
frontmatter: {
|
|
@@ -82117,6 +83125,12 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82117
83125
|
const droppedKeys = [];
|
|
82118
83126
|
const sectionSpellings = /* @__PURE__ */ new Map();
|
|
82119
83127
|
for (const [rawKey, value] of Object.entries(rawSection)) {
|
|
83128
|
+
if (Object.hasOwn(CODEWHALE_NARROWING_TABLES, rawKey)) {
|
|
83129
|
+
const { kept, dropped } = filterNarrowingTable(rawKey, value);
|
|
83130
|
+
if (kept !== void 0) sectionFields[rawKey] = kept;
|
|
83131
|
+
droppedKeys.push(...dropped);
|
|
83132
|
+
continue;
|
|
83133
|
+
}
|
|
82120
83134
|
const key = CODEWHALE_SECTION_ALIAS_TO_KEY.get(rawKey) ?? rawKey;
|
|
82121
83135
|
if (CODEWHALE_SECTION_KEYS.includes(key) && typeof value === "string") {
|
|
82122
83136
|
if (key === rawKey || sectionFields[key] === void 0) {
|
|
@@ -82126,7 +83140,7 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82126
83140
|
} else droppedKeys.push(rawKey);
|
|
82127
83141
|
} else droppedKeys.push(rawKey);
|
|
82128
83142
|
}
|
|
82129
|
-
if (droppedKeys.length > 0) logger?.warn(`Dropping unsupported or duplicate codewhale subagent keys in ${rulesyncSubagent.getRelativeFilePath()}: ${droppedKeys.join(", ")}. Codewhale rejects agent profiles with unknown fields, non-string values there, or two spellings of one key; supported keys are ${CODEWHALE_SECTION_KEYS.join(", ")}, each a string (the canonical spelling wins over an alias).`);
|
|
83143
|
+
if (droppedKeys.length > 0) logger?.warn(`Dropping unsupported or duplicate codewhale subagent keys in ${rulesyncSubagent.getRelativeFilePath()}: ${droppedKeys.join(", ")}. Codewhale rejects agent profiles with unknown fields, non-string values there, [tools] / [permissions] values that would widen access, or two spellings of one key; supported keys are ${CODEWHALE_SECTION_KEYS.join(", ")}, each a string (the canonical spelling wins over an alias), plus the narrowing-only tools.posture = "read-only" and permissions.allow_shell = false, permissions.trust = false, permissions.approval_required = true.`);
|
|
82130
83144
|
const body = stringifyCodewhaleSubagentToml({
|
|
82131
83145
|
id: toProfileId(relativeFilePath),
|
|
82132
83146
|
display_name: frontmatter.name,
|
|
@@ -88623,6 +89637,99 @@ const AntigravityRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
88623
89637
|
function toGlobalRuleFileName(relativeFilePath) {
|
|
88624
89638
|
return relativeFilePath.split(/[\\/]/).join("-");
|
|
88625
89639
|
}
|
|
89640
|
+
/** The project rules directory both Antigravity targets share, as a POSIX path. */
|
|
89641
|
+
const PROJECT_RULES_DIR = `${ANTIGRAVITY_IDE_AGENTS_DIR}/rules`;
|
|
89642
|
+
/**
|
|
89643
|
+
* The subproject directory a rule is scoped to, or `undefined` when it goes to
|
|
89644
|
+
* the project-root `.agents/rules/`. Antigravity reads a `.agents/rules/`
|
|
89645
|
+
* directory in any subdirectory and loads it for files under that directory,
|
|
89646
|
+
* so a non-root rule carrying `agentsmd.subprojectPath` is written there. Project
|
|
89647
|
+
* scope only: there is no workspace tree to nest under in global scope.
|
|
89648
|
+
*
|
|
89649
|
+
* @see https://antigravity.google/docs/rules
|
|
89650
|
+
*/
|
|
89651
|
+
function resolveAntigravitySubprojectPath({ rulesyncRule, global }) {
|
|
89652
|
+
const { root, agentsmd } = rulesyncRule.getFrontmatter();
|
|
89653
|
+
const subprojectPath = !global && root !== true ? agentsmd?.subprojectPath : void 0;
|
|
89654
|
+
return subprojectPath && ![
|
|
89655
|
+
"",
|
|
89656
|
+
".",
|
|
89657
|
+
"./"
|
|
89658
|
+
].includes(subprojectPath) ? subprojectPath : void 0;
|
|
89659
|
+
}
|
|
89660
|
+
/**
|
|
89661
|
+
* File name of a rule in a subproject's `.agents/rules/`. Antigravity reads
|
|
89662
|
+
* only the direct children of a rules directory, so a nested rulesync rule
|
|
89663
|
+
* (`frontend/style.md`) is flattened (`frontend-style.md`), and the name keeps
|
|
89664
|
+
* its case so the IDE and the CLI write the same file and an imported
|
|
89665
|
+
* `codeStyle.md` is written back to the same place.
|
|
89666
|
+
*/
|
|
89667
|
+
function toNestedRuleFileName(relativeFilePath) {
|
|
89668
|
+
return toGlobalRuleFileName(relativeFilePath);
|
|
89669
|
+
}
|
|
89670
|
+
/** The `.agents/rules/` directory of a subproject. */
|
|
89671
|
+
function toAntigravityNestedRulesDirPath(subprojectPath) {
|
|
89672
|
+
return (0, node_path.join)(subprojectPath, ANTIGRAVITY_IDE_AGENTS_DIR, "rules");
|
|
89673
|
+
}
|
|
89674
|
+
/**
|
|
89675
|
+
* Split a nested rule directory such as `packages/api/.agents/rules/sub` into
|
|
89676
|
+
* the subproject (`packages/api`) and the folder inside its rules directory
|
|
89677
|
+
* (`sub`). Returns `undefined` for the project-root `.agents/rules/`, for a
|
|
89678
|
+
* path with no `.agents/rules` segment, and for one under a hidden directory —
|
|
89679
|
+
* another tool's output or a worktree, not a subproject.
|
|
89680
|
+
*/
|
|
89681
|
+
function extractAntigravitySubprojectPath(relativeDirPath) {
|
|
89682
|
+
const segments = toPosixPath(relativeDirPath).split("/");
|
|
89683
|
+
const index = segments.findIndex((segment, i) => segment === ".agents" && segments[i + 1] === "rules");
|
|
89684
|
+
if (index <= 0) return;
|
|
89685
|
+
const subprojectSegments = segments.slice(0, index);
|
|
89686
|
+
if (subprojectSegments.some((segment) => segment === "" || segment.startsWith("."))) return;
|
|
89687
|
+
return {
|
|
89688
|
+
subprojectPath: subprojectSegments.join("/"),
|
|
89689
|
+
innerDirPath: segments.slice(index + 2).join("/")
|
|
89690
|
+
};
|
|
89691
|
+
}
|
|
89692
|
+
/**
|
|
89693
|
+
* The `.agents/rules/` folders of project subdirectories. Only their direct
|
|
89694
|
+
* `.md` children are read, because Antigravity ignores files nested in
|
|
89695
|
+
* subfolders of a rules directory. The shared nested
|
|
89696
|
+
* scan ignores every hidden directory, which would skip `.agents` itself, so
|
|
89697
|
+
* only a `.agents/rules/` below another hidden directory is ignored here
|
|
89698
|
+
* (`extractAntigravitySubprojectPath` rejects one again on import). Import-only,
|
|
89699
|
+
* like every other nested scan: these files sit outside the rulesync-owned root
|
|
89700
|
+
* `.agents/rules/`, so `--delete` never sweeps them.
|
|
89701
|
+
*/
|
|
89702
|
+
function getAntigravityNestedFilePatterns() {
|
|
89703
|
+
return {
|
|
89704
|
+
include: [`**/${PROJECT_RULES_DIR}/*.md`],
|
|
89705
|
+
ignore: [
|
|
89706
|
+
`${PROJECT_RULES_DIR}/**`,
|
|
89707
|
+
`**/.*/**/${PROJECT_RULES_DIR}/**`,
|
|
89708
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `**/${dir}/**`),
|
|
89709
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${dir}/**`)
|
|
89710
|
+
],
|
|
89711
|
+
ownedDirPath: PROJECT_RULES_DIR
|
|
89712
|
+
};
|
|
89713
|
+
}
|
|
89714
|
+
/**
|
|
89715
|
+
* Resolve where a non-root project rule file read from disk lives. A file
|
|
89716
|
+
* discovered by `getNestedFilePatterns` arrives as its own directory plus its
|
|
89717
|
+
* basename; it is re-split into the subproject's rules directory and the path
|
|
89718
|
+
* inside it, matching how the root `.agents/rules/` is read.
|
|
89719
|
+
*/
|
|
89720
|
+
function resolveAntigravityNonRootFile({ relativeDirPath, relativeFilePath, defaultDirPath }) {
|
|
89721
|
+
const nested = relativeDirPath === void 0 ? void 0 : extractAntigravitySubprojectPath(relativeDirPath);
|
|
89722
|
+
if (nested === void 0) return {
|
|
89723
|
+
relativeDirPath: defaultDirPath,
|
|
89724
|
+
relativeFilePath
|
|
89725
|
+
};
|
|
89726
|
+
return {
|
|
89727
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(nested.subprojectPath),
|
|
89728
|
+
relativeFilePath: (0, node_path.join)(nested.innerDirPath, relativeFilePath)
|
|
89729
|
+
};
|
|
89730
|
+
}
|
|
89731
|
+
/** The targets a nested rule is imported with: the two that read the shared tree. */
|
|
89732
|
+
const ANTIGRAVITY_NESTED_RULE_TARGETS = ["antigravity-ide", "antigravity-cli"];
|
|
88626
89733
|
/**
|
|
88627
89734
|
* Converts a comma-separated globs string or array to an array of globs.
|
|
88628
89735
|
* @param globs - Comma-separated globs string (e.g., "*.ts,*.js") or array of globs
|
|
@@ -88810,12 +89917,17 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88810
89917
|
});
|
|
88811
89918
|
}
|
|
88812
89919
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
88813
|
-
const
|
|
89920
|
+
const nonRoot = resolveAntigravityNonRootFile({
|
|
89921
|
+
relativeDirPath,
|
|
89922
|
+
relativeFilePath,
|
|
89923
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
89924
|
+
});
|
|
89925
|
+
const relativePath = (0, node_path.join)(nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
88814
89926
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
88815
89927
|
const rule = new AntigravityCliRule({
|
|
88816
89928
|
outputRoot,
|
|
88817
|
-
relativeDirPath:
|
|
88818
|
-
relativeFilePath,
|
|
89929
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
89930
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
88819
89931
|
fileContent,
|
|
88820
89932
|
validate,
|
|
88821
89933
|
root: false,
|
|
@@ -88847,11 +89959,21 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88847
89959
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
88848
89960
|
const frontmatter = strategy.generateFrontmatter(normalizeStoredAntigravity(storedAntigravity), rulesyncFrontmatter);
|
|
88849
89961
|
const fileContent = stringifyFrontmatter(rulesyncRule.getBody(), frontmatter);
|
|
88850
|
-
if (!global)
|
|
88851
|
-
|
|
88852
|
-
|
|
88853
|
-
|
|
88854
|
-
|
|
89962
|
+
if (!global) {
|
|
89963
|
+
const subprojectPath = resolveAntigravitySubprojectPath({
|
|
89964
|
+
rulesyncRule,
|
|
89965
|
+
global
|
|
89966
|
+
});
|
|
89967
|
+
return new AntigravityCliRule({
|
|
89968
|
+
...params,
|
|
89969
|
+
...subprojectPath !== void 0 && {
|
|
89970
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89971
|
+
relativeFilePath: toNestedRuleFileName(params.relativeFilePath)
|
|
89972
|
+
},
|
|
89973
|
+
fileContent,
|
|
89974
|
+
global
|
|
89975
|
+
});
|
|
89976
|
+
}
|
|
88855
89977
|
return new AntigravityCliRule({
|
|
88856
89978
|
...params,
|
|
88857
89979
|
relativeFilePath: toGlobalRuleFileName(params.relativeFilePath),
|
|
@@ -88867,22 +89989,32 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88867
89989
|
globs: [],
|
|
88868
89990
|
antigravity: frontmatter
|
|
88869
89991
|
};
|
|
89992
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
88870
89993
|
return new RulesyncRule({
|
|
88871
89994
|
outputRoot: process.cwd(),
|
|
88872
89995
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
88873
89996
|
relativeFilePath: this.getRelativeFilePath(),
|
|
88874
89997
|
frontmatter: {
|
|
88875
89998
|
root: false,
|
|
88876
|
-
targets: ["*"],
|
|
89999
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
88877
90000
|
...rulesyncData,
|
|
88878
90001
|
antigravity: {
|
|
88879
90002
|
...rulesyncData.antigravity,
|
|
88880
90003
|
globs: frontmatter.globs ? parseGlobsString$1(frontmatter.globs) : void 0
|
|
88881
|
-
}
|
|
90004
|
+
},
|
|
90005
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
88882
90006
|
},
|
|
88883
90007
|
body
|
|
88884
90008
|
});
|
|
88885
90009
|
}
|
|
90010
|
+
/**
|
|
90011
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
90012
|
+
* directory-scoped rules.
|
|
90013
|
+
* @see https://antigravity.google/docs/rules
|
|
90014
|
+
*/
|
|
90015
|
+
static getNestedFilePatterns() {
|
|
90016
|
+
return getAntigravityNestedFilePatterns();
|
|
90017
|
+
}
|
|
88886
90018
|
validate() {
|
|
88887
90019
|
if (this.root) return {
|
|
88888
90020
|
success: true,
|
|
@@ -89012,8 +90144,15 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89012
90144
|
global
|
|
89013
90145
|
});
|
|
89014
90146
|
}
|
|
89015
|
-
const
|
|
89016
|
-
|
|
90147
|
+
const nonRoot = this.supportsSubprojectRules() ? resolveAntigravityNonRootFile({
|
|
90148
|
+
relativeDirPath,
|
|
90149
|
+
relativeFilePath,
|
|
90150
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
90151
|
+
}) : {
|
|
90152
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
90153
|
+
relativeFilePath
|
|
90154
|
+
};
|
|
90155
|
+
const filePath = (0, node_path.join)(outputRoot, nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
89017
90156
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
89018
90157
|
let parsedFrontmatter;
|
|
89019
90158
|
if (validate) {
|
|
@@ -89023,8 +90162,8 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89023
90162
|
} else parsedFrontmatter = frontmatter;
|
|
89024
90163
|
return new this({
|
|
89025
90164
|
outputRoot,
|
|
89026
|
-
relativeDirPath:
|
|
89027
|
-
relativeFilePath,
|
|
90165
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
90166
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
89028
90167
|
body,
|
|
89029
90168
|
frontmatter: parsedFrontmatter,
|
|
89030
90169
|
validate,
|
|
@@ -89032,6 +90171,25 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89032
90171
|
global
|
|
89033
90172
|
});
|
|
89034
90173
|
}
|
|
90174
|
+
/**
|
|
90175
|
+
* Whether a rule scoped to a subproject goes to that subproject's own
|
|
90176
|
+
* `.agents/rules/`. A subclass that writes somewhere other than a project
|
|
90177
|
+
* tree (the plugin bundle) turns it off.
|
|
90178
|
+
*/
|
|
90179
|
+
static supportsSubprojectRules() {
|
|
90180
|
+
return true;
|
|
90181
|
+
}
|
|
90182
|
+
/**
|
|
90183
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
90184
|
+
* directory-scoped rules.
|
|
90185
|
+
* @see https://antigravity.google/docs/rules
|
|
90186
|
+
*/
|
|
90187
|
+
static getNestedFilePatterns() {
|
|
90188
|
+
return this.supportsSubprojectRules() ? getAntigravityNestedFilePatterns() : {
|
|
90189
|
+
include: [],
|
|
90190
|
+
ignore: []
|
|
90191
|
+
};
|
|
90192
|
+
}
|
|
89035
90193
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
89036
90194
|
const paths = this.getSettablePaths({ global });
|
|
89037
90195
|
if (rulesyncRule.getFrontmatter().root) {
|
|
@@ -89054,10 +90212,14 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89054
90212
|
const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
|
|
89055
90213
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
89056
90214
|
const frontmatter = strategy.generateFrontmatter(normalized, rulesyncFrontmatter);
|
|
89057
|
-
const
|
|
90215
|
+
const subprojectPath = this.supportsSubprojectRules() ? resolveAntigravitySubprojectPath({
|
|
90216
|
+
rulesyncRule,
|
|
90217
|
+
global
|
|
90218
|
+
}) : void 0;
|
|
90219
|
+
const relativeFilePath = global || subprojectPath !== void 0 ? toNestedRuleFileName(rulesyncRule.getRelativeFilePath()) : toKebabCaseFilename(rulesyncRule.getRelativeFilePath());
|
|
89058
90220
|
return new this({
|
|
89059
90221
|
outputRoot,
|
|
89060
|
-
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
90222
|
+
relativeDirPath: subprojectPath === void 0 ? paths.nonRoot.relativeDirPath : toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89061
90223
|
relativeFilePath,
|
|
89062
90224
|
frontmatter,
|
|
89063
90225
|
body: rulesyncRule.getBody(),
|
|
@@ -89078,15 +90240,17 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89078
90240
|
...rulesyncData.antigravity,
|
|
89079
90241
|
globs: this.frontmatter.globs ? parseGlobsString$1(this.frontmatter.globs) : void 0
|
|
89080
90242
|
};
|
|
90243
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
89081
90244
|
return new RulesyncRule({
|
|
89082
90245
|
outputRoot: process.cwd(),
|
|
89083
90246
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
89084
90247
|
relativeFilePath: this.getRelativeFilePath(),
|
|
89085
90248
|
frontmatter: {
|
|
89086
90249
|
root: false,
|
|
89087
|
-
targets: ["*"],
|
|
90250
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
89088
90251
|
...rulesyncData,
|
|
89089
|
-
antigravity: antigravityForRulesync
|
|
90252
|
+
antigravity: antigravityForRulesync,
|
|
90253
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
89090
90254
|
},
|
|
89091
90255
|
body: this.body
|
|
89092
90256
|
});
|
|
@@ -89140,6 +90304,9 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89140
90304
|
//#endregion
|
|
89141
90305
|
//#region src/features/rules/antigravity-plugin-rule.ts
|
|
89142
90306
|
var AntigravityPluginRule = class extends AntigravityIdeRule {
|
|
90307
|
+
static supportsSubprojectRules() {
|
|
90308
|
+
return false;
|
|
90309
|
+
}
|
|
89143
90310
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
89144
90311
|
const targets = rulesyncRule.getFrontmatter().targets;
|
|
89145
90312
|
return targets.includes("*") || targets.includes("antigravity-plugin");
|
|
@@ -95489,6 +96656,12 @@ var VibeRule = class extends NestedAgentsmdRule {
|
|
|
95489
96656
|
* file, so every non-root rule without `agentsmd.subprojectPath` folds into
|
|
95490
96657
|
* the root `./AGENTS.md`.
|
|
95491
96658
|
*
|
|
96659
|
+
* A legacy root `WARP.md` is read on import when the root `AGENTS.md` is
|
|
96660
|
+
* absent, so a repository that has not moved to `AGENTS.md` yet imports its
|
|
96661
|
+
* rules. It is never written or deleted. When both exist, Warp reads
|
|
96662
|
+
* `WARP.md`, so import keeps reading the `AGENTS.md` rulesync manages and warns
|
|
96663
|
+
* that Warp ignores it, as generate already does.
|
|
96664
|
+
*
|
|
95492
96665
|
* In global mode, Warp reads a third rule source from `~/.agents/AGENTS.md`
|
|
95493
96666
|
* (the cross-tool agent config directory), indexed like project rules and also
|
|
95494
96667
|
* used from remote hosts in SSH sessions. The same root-fold policy applies.
|
|
@@ -95503,6 +96676,31 @@ var WarpRule = class extends NestedAgentsmdRule {
|
|
|
95503
96676
|
toolTarget: "warp"
|
|
95504
96677
|
};
|
|
95505
96678
|
}
|
|
96679
|
+
static getSettablePaths(options = {}) {
|
|
96680
|
+
const paths = super.getSettablePaths(options);
|
|
96681
|
+
if (options.global) return paths;
|
|
96682
|
+
return {
|
|
96683
|
+
...paths,
|
|
96684
|
+
importOnlyRoots: [{
|
|
96685
|
+
relativeDirPath: ".",
|
|
96686
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96687
|
+
onlyWhenRootAbsent: true,
|
|
96688
|
+
outranksRoot: true
|
|
96689
|
+
}]
|
|
96690
|
+
};
|
|
96691
|
+
}
|
|
96692
|
+
static async fromFile(params) {
|
|
96693
|
+
const { outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true } = params;
|
|
96694
|
+
if (!params.global && relativeFilePath === "WARP.md" && (relativeDirPath === void 0 || relativeDirPath === ".")) return new this({
|
|
96695
|
+
outputRoot,
|
|
96696
|
+
relativeDirPath: ".",
|
|
96697
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96698
|
+
fileContent: await readFileContent((0, node_path.join)(outputRoot, WARP_LEGACY_RULE_FILE_NAME)),
|
|
96699
|
+
validate,
|
|
96700
|
+
root: true
|
|
96701
|
+
});
|
|
96702
|
+
return super.fromFile(params);
|
|
96703
|
+
}
|
|
95506
96704
|
};
|
|
95507
96705
|
//#endregion
|
|
95508
96706
|
//#region src/features/rules/zcode-rule.ts
|
|
@@ -96805,7 +98003,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
96805
98003
|
if (!importOnlyRoots || importOnlyRoots.length === 0) return;
|
|
96806
98004
|
const existingPaths = [];
|
|
96807
98005
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
96808
|
-
if (importOnlyRoot.onlyWhenRootAbsent !== true) continue;
|
|
98006
|
+
if (importOnlyRoot.onlyWhenRootAbsent !== true || importOnlyRoot.outranksRoot === true) continue;
|
|
96809
98007
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
96810
98008
|
cwd: this.outputRoot,
|
|
96811
98009
|
type: "file"
|
|
@@ -97506,13 +98704,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97506
98704
|
const rootFilePath = primaryRootFilePaths[0];
|
|
97507
98705
|
const scannedPaths = [];
|
|
97508
98706
|
const skippedPaths = [];
|
|
98707
|
+
const skippedOutrankingPaths = [];
|
|
97509
98708
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
97510
98709
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
97511
98710
|
cwd: this.outputRoot,
|
|
97512
98711
|
type: "file"
|
|
97513
98712
|
});
|
|
97514
98713
|
if (importOnlyRoot.onlyWhenRootAbsent === true && rootFilePath !== void 0) {
|
|
97515
|
-
skippedPaths.push(...matchedPaths);
|
|
98714
|
+
(importOnlyRoot.outranksRoot === true ? skippedOutrankingPaths : skippedPaths).push(...matchedPaths);
|
|
97516
98715
|
continue;
|
|
97517
98716
|
}
|
|
97518
98717
|
scannedPaths.push(...matchedPaths);
|
|
@@ -97523,6 +98722,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97523
98722
|
const remainingCount = skippedNames.length - listedNames.length;
|
|
97524
98723
|
this.logger.warn(`Not importing ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} for ${this.toolTarget}: ${stripControlCharacters((0, node_path.relative)(this.outputRoot, rootFilePath))} exists, and the tool reads that file exclusively. Delete them once you have checked that content is in the root file, or move it into ${RULESYNC_RULES_RELATIVE_DIR_PATH} if it is not.`);
|
|
97525
98724
|
}
|
|
98725
|
+
if (skippedOutrankingPaths.length > 0 && rootFilePath !== void 0) {
|
|
98726
|
+
const rootName = stripControlCharacters((0, node_path.relative)(this.outputRoot, rootFilePath));
|
|
98727
|
+
const skippedNames = skippedOutrankingPaths.map((filePath) => stripControlCharacters((0, node_path.relative)(this.outputRoot, filePath)));
|
|
98728
|
+
const listedNames = skippedNames.slice(0, MAX_LISTED_SKIPPED_IMPORT_ONLY_PATHS);
|
|
98729
|
+
const remainingCount = skippedNames.length - listedNames.length;
|
|
98730
|
+
this.logger.warn(`Not importing ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} for ${this.toolTarget}: ${rootName} is imported instead, but ${this.toolTarget} reads ${listedNames.length === 1 ? "that file" : "those files"} in place of ${rootName} when both exist. Move any content only it has into ${RULESYNC_RULES_RELATIVE_DIR_PATH} and delete it, so the generated ${rootName} takes effect.`);
|
|
98731
|
+
}
|
|
97526
98732
|
return await buildImportRulesFromPaths(scannedPaths);
|
|
97527
98733
|
})();
|
|
97528
98734
|
this.logger.debug(`Found ${importOnlyToolRules.length} import-only tool rule files`);
|