rulesync 29.0.0 → 30.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/dist/cli/index.cjs +17 -11
- package/dist/cli/index.js +17 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/{import--SCvWeDn.js → import-DBd2G6Eo.js} +1201 -193
- package/dist/{import--SCvWeDn.js.map → import-DBd2G6Eo.js.map} +1 -1
- package/dist/{import-CLfSccAk.cjs → import-XIYCKmjg.cjs} +1217 -209
- package/dist/index.cjs +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";
|
|
@@ -7883,6 +7883,26 @@ const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7883
7883
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7884
7884
|
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum(COPILOT_DEFAULT_PERMISSION_MODES))
|
|
7885
7885
|
});
|
|
7886
|
+
/**
|
|
7887
|
+
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
7888
|
+
* before prompting. `always-ask` auto-approves only read-only tools, `write`
|
|
7889
|
+
* also workspace writes, and `yolo` (oh-my-pi's default) every tier.
|
|
7890
|
+
*
|
|
7891
|
+
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
7892
|
+
*/
|
|
7893
|
+
const OMP_APPROVAL_MODES = [
|
|
7894
|
+
"always-ask",
|
|
7895
|
+
"write",
|
|
7896
|
+
"yolo"
|
|
7897
|
+
];
|
|
7898
|
+
/**
|
|
7899
|
+
* Tool-scoped override block for oh-my-pi (`omp`). `approvalMode` writes the
|
|
7900
|
+
* `tools.approvalMode` key of `config.yml`, which has no canonical category.
|
|
7901
|
+
*/
|
|
7902
|
+
const OmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7903
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7904
|
+
approvalMode: zod_mini.z.optional(zod_mini.z.enum(OMP_APPROVAL_MODES))
|
|
7905
|
+
});
|
|
7886
7906
|
const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
7887
7907
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
7888
7908
|
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -7890,6 +7910,14 @@ const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
7890
7910
|
"yolo",
|
|
7891
7911
|
"auto"
|
|
7892
7912
|
])),
|
|
7913
|
+
/**
|
|
7914
|
+
* Kimi's `[permission] dangerous_command_guard` switch (Kimi Code 0.40.0,
|
|
7915
|
+
* default `true`). `false` turns off the built-in dangerous-command policy
|
|
7916
|
+
* (no confirmation for `rm -rf`, `shutdown` and the like).
|
|
7917
|
+
*
|
|
7918
|
+
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
7919
|
+
*/
|
|
7920
|
+
dangerousCommandGuard: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7893
7921
|
rules: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
7894
7922
|
decision: PermissionActionSchema,
|
|
7895
7923
|
pattern: zod_mini.z.string(),
|
|
@@ -8105,7 +8133,10 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8105
8133
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8106
8134
|
* `todoWrite`, `workflowsEnabled`, `workflowNameOnly`, `executionSandbox`) and
|
|
8107
8135
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8108
|
-
* `allowedInsecureVoiceBaseUrls`)
|
|
8136
|
+
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8137
|
+
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
8138
|
+
* Code unions across scopes) and `general` (`outputStyle`, the output style
|
|
8139
|
+
* that reshapes how responses are written). Qwen Code strips `tools.workflowsEnabled`,
|
|
8109
8140
|
* `tools.executionSandbox`, `security.allowPrivateNetworkHooks` and
|
|
8110
8141
|
* `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
|
|
8111
8142
|
* skips those four in project scope and announces a granting value in global
|
|
@@ -8122,7 +8153,8 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8122
8153
|
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`, and `eager`, which
|
|
8123
8154
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8124
8155
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8125
|
-
* classifier config, and, because these groups are
|
|
8156
|
+
* classifier config, the skill kill-switches, and, because these groups are
|
|
8157
|
+
* loose objects, any key
|
|
8126
8158
|
* rulesync does not model. Import flags a scope-dependent key only when it read
|
|
8127
8159
|
* the project file, since that is the value a `--global` regenerate would
|
|
8128
8160
|
* promote. It also
|
|
@@ -8133,11 +8165,13 @@ const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8133
8165
|
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
8134
8166
|
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
8135
8167
|
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
8136
|
-
* `tools`/`security`/`autoMode` key can be authored.
|
|
8168
|
+
* `tools`/`security`/`skills`/`general`/`autoMode` key can be authored.
|
|
8137
8169
|
*
|
|
8138
8170
|
* @example
|
|
8139
8171
|
* {
|
|
8140
8172
|
* "tools": { "approvalMode": "auto-edit" },
|
|
8173
|
+
* "skills": { "disabled": ["pdf"], "disabledLevels": ["bundled"] },
|
|
8174
|
+
* "general": { "outputStyle": "Concise" },
|
|
8141
8175
|
* "security": { "folderTrust": { "enabled": true } },
|
|
8142
8176
|
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
8143
8177
|
* }
|
|
@@ -8146,6 +8180,8 @@ const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8146
8180
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8147
8181
|
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8148
8182
|
security: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8183
|
+
skills: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8184
|
+
general: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
8149
8185
|
autoMode: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
8150
8186
|
});
|
|
8151
8187
|
/**
|
|
@@ -8231,6 +8267,8 @@ const TabninePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8231
8267
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
8232
8268
|
* @example
|
|
8233
8269
|
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
8270
|
+
* @example
|
|
8271
|
+
* { "mode": "deny" }
|
|
8234
8272
|
*/
|
|
8235
8273
|
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8236
8274
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
@@ -8239,7 +8277,12 @@ const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8239
8277
|
rawAllow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8240
8278
|
rawAsk: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8241
8279
|
rawDeny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8242
|
-
allowDynamicBash: zod_mini.z.optional(zod_mini.z.boolean())
|
|
8280
|
+
allowDynamicBash: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8281
|
+
mode: zod_mini.z.optional(zod_mini.z.enum([
|
|
8282
|
+
"ask",
|
|
8283
|
+
"allow",
|
|
8284
|
+
"deny"
|
|
8285
|
+
]))
|
|
8243
8286
|
});
|
|
8244
8287
|
/**
|
|
8245
8288
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -8282,7 +8325,8 @@ const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8282
8325
|
* {
|
|
8283
8326
|
* "agent_mode_coding_permissions": "always_allow_reading",
|
|
8284
8327
|
* "agent_mode_execute_readonly_commands": true,
|
|
8285
|
-
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] }
|
|
8328
|
+
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] },
|
|
8329
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8286
8330
|
* }
|
|
8287
8331
|
*/
|
|
8288
8332
|
/**
|
|
@@ -8334,6 +8378,16 @@ const WarpExecutionProfileOverrideSchema = zod_mini.z.looseObject({
|
|
|
8334
8378
|
mcp_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8335
8379
|
mcp_denylist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8336
8380
|
});
|
|
8381
|
+
/**
|
|
8382
|
+
* Whether auto-approve may run commands that match the command denylist
|
|
8383
|
+
* (Warp defaults to `true`). It is not an execution-profile field: it lives in
|
|
8384
|
+
* the sibling `[agents.warp_agent.other]` table of the same `settings.toml`,
|
|
8385
|
+
* shared by the Warp app and the Warp Agent CLI.
|
|
8386
|
+
*
|
|
8387
|
+
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8388
|
+
* @see https://github.com/warpdotdev/warp/blob/main/app/src/settings/ai.rs (`AutoApproveBypassesCommandDenylist`)
|
|
8389
|
+
*/
|
|
8390
|
+
const WarpAutoApproveBypassSchema = zod_mini.z.optional(zod_mini.z.boolean());
|
|
8337
8391
|
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8338
8392
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8339
8393
|
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.enum([
|
|
@@ -8343,7 +8397,8 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8343
8397
|
])),
|
|
8344
8398
|
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8345
8399
|
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
8346
|
-
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema)
|
|
8400
|
+
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema),
|
|
8401
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8347
8402
|
});
|
|
8348
8403
|
/**
|
|
8349
8404
|
* Tool-scoped override block for the standalone Warp Agent CLI. The CLI reads
|
|
@@ -8352,16 +8407,21 @@ const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8352
8407
|
* the app's legacy `[agents.profiles]` keys), so only the nested
|
|
8353
8408
|
* `execution_profile` block is offered; it is merged into that record, and the
|
|
8354
8409
|
* shared `permission.bash` block still drives `command_allowlist` /
|
|
8355
|
-
* `command_denylist`.
|
|
8410
|
+
* `command_denylist`. `auto_approve_bypasses_command_denylist` is written to
|
|
8411
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
8356
8412
|
*
|
|
8357
8413
|
* @example
|
|
8358
|
-
* {
|
|
8414
|
+
* {
|
|
8415
|
+
* "execution_profile": { "apply_code_diffs": "always_ask", "run_agents": "never_allow" },
|
|
8416
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8417
|
+
* }
|
|
8359
8418
|
*
|
|
8360
8419
|
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8361
8420
|
*/
|
|
8362
8421
|
const WarpcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8363
8422
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8364
|
-
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema)
|
|
8423
|
+
execution_profile: zod_mini.z.optional(WarpExecutionProfileOverrideSchema),
|
|
8424
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8365
8425
|
});
|
|
8366
8426
|
/**
|
|
8367
8427
|
* deepagents-cli's approval-mode knobs under `[startup]` in
|
|
@@ -9057,7 +9117,7 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
9057
9117
|
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9058
9118
|
crush: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9059
9119
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9060
|
-
omp: zod_mini.z.optional(
|
|
9120
|
+
omp: zod_mini.z.optional(OmpPermissionsOverrideSchema),
|
|
9061
9121
|
pool: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9062
9122
|
qoder: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9063
9123
|
grokcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -12767,7 +12827,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12767
12827
|
ownedKeys: [
|
|
12768
12828
|
"permissions",
|
|
12769
12829
|
"tools",
|
|
12770
|
-
"security"
|
|
12830
|
+
"security",
|
|
12831
|
+
"skills",
|
|
12832
|
+
"general"
|
|
12771
12833
|
]
|
|
12772
12834
|
}
|
|
12773
12835
|
}
|
|
@@ -13137,7 +13199,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13137
13199
|
features: {
|
|
13138
13200
|
mcp: {
|
|
13139
13201
|
kind: "replace-owned-keys",
|
|
13140
|
-
ownedKeys: ["mcp_servers"]
|
|
13202
|
+
ownedKeys: ["mcp_servers", "disabled_mcp_tools"]
|
|
13141
13203
|
},
|
|
13142
13204
|
permissions: {
|
|
13143
13205
|
kind: "replace-owned-keys",
|
|
@@ -13272,6 +13334,14 @@ function serializeSharedConfigFile({ fileKey, document, existingContent, filePat
|
|
|
13272
13334
|
});
|
|
13273
13335
|
}
|
|
13274
13336
|
/**
|
|
13337
|
+
* A patch value for an owned key that holds a map of managed entries: the map
|
|
13338
|
+
* itself, or `undefined` when it is empty, which retracts the key instead of
|
|
13339
|
+
* leaving an empty object behind (`applySharedConfigPatch` deletes a key whose patch value is `undefined`).
|
|
13340
|
+
*/
|
|
13341
|
+
function undefinedWhenEmpty(entries) {
|
|
13342
|
+
return Object.keys(entries).length > 0 ? entries : void 0;
|
|
13343
|
+
}
|
|
13344
|
+
/**
|
|
13275
13345
|
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
13276
13346
|
* the existing content, merge the patch under the feature's declared policy,
|
|
13277
13347
|
* and serialize it back over the existing content (see
|
|
@@ -13758,14 +13828,23 @@ var FeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
13758
13828
|
async removeOrphanAiFiles(existingFiles, generatedFiles) {
|
|
13759
13829
|
const generatedPaths = new Set(generatedFiles.map((f) => caseFoldIdentity(f.getFilePath())));
|
|
13760
13830
|
const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(caseFoldIdentity(f.getFilePath())));
|
|
13831
|
+
const refusedDirs = /* @__PURE__ */ new Map();
|
|
13761
13832
|
let removedCount = 0;
|
|
13762
13833
|
for (const aiFile of orphanFiles) {
|
|
13763
13834
|
const filePath = aiFile.getFilePath();
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
|
|
13835
|
+
const rootPath = aiFile.getOutputRoot();
|
|
13836
|
+
const dirPath = (0, node_path.dirname)(filePath);
|
|
13837
|
+
const cacheKey = JSON.stringify([rootPath, dirPath]);
|
|
13838
|
+
let refused = refusedDirs.get(cacheKey);
|
|
13839
|
+
if (refused === void 0) {
|
|
13840
|
+
refused = await refusesDeleteOutsideRoot({
|
|
13841
|
+
logger: this.logger,
|
|
13842
|
+
rootPath,
|
|
13843
|
+
dirPath
|
|
13844
|
+
});
|
|
13845
|
+
refusedDirs.set(cacheKey, refused);
|
|
13846
|
+
}
|
|
13847
|
+
if (refused) continue;
|
|
13769
13848
|
const loggedPath = stripControlCharacters(filePath);
|
|
13770
13849
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would delete: ${loggedPath}`);
|
|
13771
13850
|
else {
|
|
@@ -13886,22 +13965,24 @@ async function refusesWriteOutsideRoot({ logger, rootPath, targetPath }) {
|
|
|
13886
13965
|
return true;
|
|
13887
13966
|
}
|
|
13888
13967
|
/**
|
|
13889
|
-
* Whether deleting `
|
|
13890
|
-
* the
|
|
13968
|
+
* Whether deleting the files in `dirPath` has to be refused because a link on
|
|
13969
|
+
* the way to that directory leads out of `rootPath`, warning about the refusal
|
|
13970
|
+
* once for the whole directory.
|
|
13891
13971
|
*
|
|
13892
13972
|
* Deletion candidates are enumerated without following links *inside* the
|
|
13893
13973
|
* scanned directory, but the scanned directory itself is reached through
|
|
13894
13974
|
* whatever links its path contains: a checked-out repository can carry
|
|
13895
13975
|
* `.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
|
|
13976
|
+
* an orphan. Only the directory is judged, never the file in it, because
|
|
13977
|
+
* removing a file that is itself a link unlinks the link where it is spelled
|
|
13978
|
+
* and never touches its target.
|
|
13898
13979
|
*/
|
|
13899
|
-
async function refusesDeleteOutsideRoot({ logger, rootPath,
|
|
13980
|
+
async function refusesDeleteOutsideRoot({ logger, rootPath, dirPath }) {
|
|
13900
13981
|
if (!await writablePathEscapesRoot({
|
|
13901
13982
|
rootPath,
|
|
13902
|
-
targetPath:
|
|
13983
|
+
targetPath: dirPath
|
|
13903
13984
|
})) return false;
|
|
13904
|
-
logger.warn(`Refusing to delete ${quoteForLog(
|
|
13985
|
+
logger.warn(`Refusing to delete files in ${quoteForLog(dirPath)}: it resolves outside ${quoteForLog(rootPath)} through a symbolic link`);
|
|
13905
13986
|
return true;
|
|
13906
13987
|
}
|
|
13907
13988
|
/**
|
|
@@ -17224,7 +17305,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
17224
17305
|
static getProjectRelativeDirPath() {
|
|
17225
17306
|
throw new Error("Please implement this method in the subclass.");
|
|
17226
17307
|
}
|
|
17227
|
-
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/
|
|
17308
|
+
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/config/workflows`). */
|
|
17228
17309
|
static getGlobalRelativeDirPath() {
|
|
17229
17310
|
throw new Error("Please implement this method in the subclass.");
|
|
17230
17311
|
}
|
|
@@ -17387,7 +17468,8 @@ const ANTIGRAVITY_IDE_GEMINI_DIR = ".gemini";
|
|
|
17387
17468
|
const ANTIGRAVITY_IDE_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
17388
17469
|
const ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR = "config";
|
|
17389
17470
|
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,
|
|
17471
|
+
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR, ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "workflows");
|
|
17472
|
+
const ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(ANTIGRAVITY_IDE_GEMINI_DIR, "antigravity", "global_workflows");
|
|
17391
17473
|
const ANTIGRAVITY_IDE_PERMISSIONS_DIR = ".antigravity";
|
|
17392
17474
|
const ANTIGRAVITY_IDE_PERMISSIONS_FILE_NAME = "settings.json";
|
|
17393
17475
|
//#endregion
|
|
@@ -17417,10 +17499,12 @@ var AntigravityIdeSkill = class extends AntigravitySharedSkill {
|
|
|
17417
17499
|
* Emits each command as a skill in `.agents/skills/<name>/SKILL.md` (project
|
|
17418
17500
|
* scope) and `~/.gemini/config/skills/<name>/SKILL.md` (global scope), the
|
|
17419
17501
|
* IDE's skills directories. Legacy workflows are still imported from
|
|
17420
|
-
* `.agents/workflows/` and `~/.gemini/
|
|
17421
|
-
*
|
|
17502
|
+
* `.agents/workflows/` and `~/.gemini/config/workflows/` (the global workflows
|
|
17503
|
+
* directory of Antigravity 2.0), and, in global scope only, from the pre-2.0
|
|
17504
|
+
* `~/.gemini/antigravity/global_workflows/`. All body and frontmatter handling
|
|
17505
|
+
* is shared with {@link AntigravitySharedCommand}.
|
|
17422
17506
|
*/
|
|
17423
|
-
var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
17507
|
+
var AntigravityIdeCommand = class AntigravityIdeCommand extends AntigravitySharedCommand {
|
|
17424
17508
|
static getProjectRelativeDirPath() {
|
|
17425
17509
|
return ANTIGRAVITY_IDE_COMMANDS_DIR_PATH;
|
|
17426
17510
|
}
|
|
@@ -17430,6 +17514,37 @@ var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
|
17430
17514
|
static getSkillsRelativeDirPath({ global }) {
|
|
17431
17515
|
return AntigravityIdeSkill.getSettablePaths({ global }).relativeDirPath;
|
|
17432
17516
|
}
|
|
17517
|
+
/**
|
|
17518
|
+
* Import-only, global scope: the workflows left in the pre-2.0
|
|
17519
|
+
* `~/.gemini/antigravity/global_workflows/` directory, where rulesync also
|
|
17520
|
+
* wrote IDE global commands before they were emitted as skills. The
|
|
17521
|
+
* directory is never swept by `generate --delete`; a workflow with the same
|
|
17522
|
+
* file name in `~/.gemini/config/workflows/` takes precedence.
|
|
17523
|
+
*
|
|
17524
|
+
* @see https://antigravity.google/docs/migration/workflows-to-skills
|
|
17525
|
+
*/
|
|
17526
|
+
static async loadAdditionalImportFiles({ outputRoot = process.cwd(), global = false, logger } = {}) {
|
|
17527
|
+
if (!global) return [];
|
|
17528
|
+
const filePaths = await findFilesByGlobs("*.md", { cwd: (0, node_path.join)(outputRoot, ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH) });
|
|
17529
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
17530
|
+
const fileContent = await readFileContent(filePath);
|
|
17531
|
+
const { frontmatter, body } = parseFrontmatter(fileContent, filePath);
|
|
17532
|
+
const result = AntigravityCommandFrontmatterSchema.safeParse(frontmatter);
|
|
17533
|
+
if (!result.success) {
|
|
17534
|
+
logger?.warn(`Skipping ${filePath}: invalid frontmatter (${formatError(result.error)}).`);
|
|
17535
|
+
return;
|
|
17536
|
+
}
|
|
17537
|
+
return new AntigravityIdeCommand({
|
|
17538
|
+
outputRoot,
|
|
17539
|
+
relativeDirPath: ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH,
|
|
17540
|
+
relativeFilePath: (0, node_path.basename)(filePath),
|
|
17541
|
+
frontmatter: result.data,
|
|
17542
|
+
body: body.trim(),
|
|
17543
|
+
fileContent,
|
|
17544
|
+
global
|
|
17545
|
+
});
|
|
17546
|
+
}))).filter((command) => command !== void 0);
|
|
17547
|
+
}
|
|
17433
17548
|
getToolTargetName() {
|
|
17434
17549
|
return "antigravity-ide";
|
|
17435
17550
|
}
|
|
@@ -25082,7 +25197,7 @@ function findCommandPaths({ command, scan }) {
|
|
|
25082
25197
|
} else {
|
|
25083
25198
|
if (path !== void 0) toAnchor.add(path);
|
|
25084
25199
|
state.owner = text.slice(text.lastIndexOf("/") + 1);
|
|
25085
|
-
state.expecting = afterCommandWord(state.owner);
|
|
25200
|
+
state.expecting = afterCommandWord$1(state.owner);
|
|
25086
25201
|
state.prefix = void 0;
|
|
25087
25202
|
state.shellCode = false;
|
|
25088
25203
|
}
|
|
@@ -25148,7 +25263,7 @@ function readOption({ state, text }) {
|
|
|
25148
25263
|
if (SHELL_NAME_PATTERN.test(state.owner) && SHELL_CODE_OPTION.test(text)) state.shellCode = true;
|
|
25149
25264
|
}
|
|
25150
25265
|
/** What follows a command word whose basename is `name`. */
|
|
25151
|
-
function afterCommandWord(name) {
|
|
25266
|
+
function afterCommandWord$1(name) {
|
|
25152
25267
|
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
25153
25268
|
if (PACKAGE_RUNNERS.has(name)) return "wrapped";
|
|
25154
25269
|
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
@@ -33491,6 +33606,19 @@ function isQwencodeLegacyMillisecondTimeout(timeout) {
|
|
|
33491
33606
|
return typeof timeout === "number" && Number.isFinite(timeout) && timeout >= QWENCODE_LEGACY_MILLISECOND_TIMEOUT_THRESHOLD;
|
|
33492
33607
|
}
|
|
33493
33608
|
/**
|
|
33609
|
+
* Read a `timeout` the way Qwen Code does. Settings files are not type-checked
|
|
33610
|
+
* and Qwen Code coerces a numeric string such as `"60000"` with `Number()`, so
|
|
33611
|
+
* import accepts one too; a value that is not a finite number is dropped, since
|
|
33612
|
+
* Qwen Code falls back to the hook type's default for it.
|
|
33613
|
+
* https://github.com/QwenLM/qwen-code/blob/main/packages/core/src/hooks/hook-timeout.ts
|
|
33614
|
+
*/
|
|
33615
|
+
function readQwencodeTimeout(timeout) {
|
|
33616
|
+
if (timeout === void 0) return void 0;
|
|
33617
|
+
if (typeof timeout === "string" && timeout.trim() === "") return void 0;
|
|
33618
|
+
const value = Number(timeout);
|
|
33619
|
+
return Number.isFinite(value) ? value : void 0;
|
|
33620
|
+
}
|
|
33621
|
+
/**
|
|
33494
33622
|
* Canonical `timeout` is seconds. A command hook value below the threshold is
|
|
33495
33623
|
* already read as seconds; a larger one can only be expressed in milliseconds.
|
|
33496
33624
|
*/
|
|
@@ -33601,7 +33729,7 @@ const QwencodeHookEntrySchema = zod_mini.z.looseObject({
|
|
|
33601
33729
|
type: zod_mini.z.optional(zod_mini.z.string()),
|
|
33602
33730
|
command: zod_mini.z.optional(zod_mini.z.string()),
|
|
33603
33731
|
url: zod_mini.z.optional(zod_mini.z.string()),
|
|
33604
|
-
timeout: zod_mini.z.optional(zod_mini.z.number()),
|
|
33732
|
+
timeout: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.number(), zod_mini.z.string()])),
|
|
33605
33733
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
33606
33734
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
33607
33735
|
statusMessage: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -33634,12 +33762,13 @@ function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
|
33634
33762
|
const isCommand = hookType === "command";
|
|
33635
33763
|
const isPrompt = hookType === "prompt";
|
|
33636
33764
|
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
33765
|
+
const timeout = readQwencodeTimeout(h.timeout);
|
|
33637
33766
|
return {
|
|
33638
33767
|
type: hookType,
|
|
33639
33768
|
...compact({
|
|
33640
33769
|
command: h.command,
|
|
33641
33770
|
url: h.url,
|
|
33642
|
-
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(
|
|
33771
|
+
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(timeout) : timeout,
|
|
33643
33772
|
name: h.name,
|
|
33644
33773
|
description: h.description,
|
|
33645
33774
|
statusMessage: h.statusMessage,
|
|
@@ -38141,7 +38270,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
38141
38270
|
fileKey: sharedConfigFileKey(basePaths),
|
|
38142
38271
|
feature: "mcp",
|
|
38143
38272
|
existingContent: fileContent ?? "",
|
|
38144
|
-
patch: { [AMP_MCP_SERVERS_KEY]: filterMcpServers(rulesyncMcp.getMcpServers()) },
|
|
38273
|
+
patch: { [AMP_MCP_SERVERS_KEY]: undefinedWhenEmpty(filterMcpServers(rulesyncMcp.getMcpServers())) },
|
|
38145
38274
|
filePath: (0, node_path.join)(jsonDir, relativeFilePath),
|
|
38146
38275
|
logger
|
|
38147
38276
|
}),
|
|
@@ -38439,6 +38568,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38439
38568
|
const paths = this.getSettablePaths({ global });
|
|
38440
38569
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
38441
38570
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
38571
|
+
const mcpServers = rulesyncMcp.getMcpServers();
|
|
38442
38572
|
return new AugmentcodeMcp({
|
|
38443
38573
|
outputRoot,
|
|
38444
38574
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -38447,7 +38577,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38447
38577
|
fileKey: sharedConfigFileKey(paths),
|
|
38448
38578
|
feature: "mcp",
|
|
38449
38579
|
existingContent,
|
|
38450
|
-
patch: { mcpServers:
|
|
38580
|
+
patch: { mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : void 0 },
|
|
38451
38581
|
filePath,
|
|
38452
38582
|
logger
|
|
38453
38583
|
}),
|
|
@@ -39003,6 +39133,229 @@ var ClaudecodeMcp = class ClaudecodeMcp extends ToolMcp {
|
|
|
39003
39133
|
}
|
|
39004
39134
|
};
|
|
39005
39135
|
//#endregion
|
|
39136
|
+
//#region src/features/mcp/claudecode-plugin-mcp.ts
|
|
39137
|
+
/**
|
|
39138
|
+
* The placeholder Claude Code substitutes with the installed plugin's own
|
|
39139
|
+
* directory in an MCP server's `command`, `args` and `env`.
|
|
39140
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39141
|
+
*/
|
|
39142
|
+
const PLUGIN_ROOT_PLACEHOLDER = "${CLAUDE_PLUGIN_ROOT}";
|
|
39143
|
+
const PLUGIN_ROOT_PREFIX = /^(?:\$\{CLAUDE_PLUGIN_ROOT\}|"\$CLAUDE_PLUGIN_ROOT"|\$CLAUDE_PLUGIN_ROOT)\//;
|
|
39144
|
+
/**
|
|
39145
|
+
* Whether a server `command` names a file inside the plugin rather than an
|
|
39146
|
+
* executable found on `PATH`. The command is spawned without a shell, so a
|
|
39147
|
+
* name with a slash is a path, and one without (`npx`, `node`, `uvx`) is a
|
|
39148
|
+
* `PATH` lookup. An absolute path, a home path, one that leaves the plugin
|
|
39149
|
+
* (`../`), and one that already starts with a variable are left as written.
|
|
39150
|
+
*/
|
|
39151
|
+
function isPluginRelativeCommand(command) {
|
|
39152
|
+
return /[\\/]/.test(command) && !command.startsWith("$") && !command.startsWith("%") && !command.startsWith("~") && !/^\.\.[\\/]/.test(command) && !node_path.posix.isAbsolute(command) && !node_path.win32.isAbsolute(command);
|
|
39153
|
+
}
|
|
39154
|
+
function anchorToPluginRoot(path) {
|
|
39155
|
+
return `${PLUGIN_ROOT_PLACEHOLDER}/${path.replace(/^\.[\\/]/, "")}`;
|
|
39156
|
+
}
|
|
39157
|
+
/** The runner name of a command word: its basename without a `.exe` suffix. */
|
|
39158
|
+
function runnerName(command) {
|
|
39159
|
+
return (command.split(/[\\/]/).pop() ?? command).replace(/\.exe$/i, "");
|
|
39160
|
+
}
|
|
39161
|
+
/** What follows a command word, as plugin hook commands read it. */
|
|
39162
|
+
function afterCommandWord(name) {
|
|
39163
|
+
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
39164
|
+
if (PACKAGE_RUNNERS.has(name)) return "command";
|
|
39165
|
+
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
39166
|
+
}
|
|
39167
|
+
/**
|
|
39168
|
+
* The positions in `args` that name a file the server runs, read with the
|
|
39169
|
+
* rules plugin hook commands follow (`hook-command-paths.ts`): the script of a
|
|
39170
|
+
* script runner (`node ./dist/server.js`), the command after a run subcommand
|
|
39171
|
+
* or package runner (`uv run ./server.py`, `npx tsx ./x.ts`), and the value of
|
|
39172
|
+
* an option that loads a file (`node -r ./register.js`). Only a position whose
|
|
39173
|
+
* value `isPath` accepts is returned. Everything else is data the server reads
|
|
39174
|
+
* relative to the consumer's project (`server-filesystem ./docs`), and the
|
|
39175
|
+
* scan stops at an option that runs inline code or standard input
|
|
39176
|
+
* (`node -e`, `python3 -m`, `sh -c`, `bash -s`).
|
|
39177
|
+
*/
|
|
39178
|
+
function fileArgumentIndices({ command, args, isPath }) {
|
|
39179
|
+
const indices = /* @__PURE__ */ new Set();
|
|
39180
|
+
let owner = runnerName(command);
|
|
39181
|
+
let expecting = afterCommandWord(owner);
|
|
39182
|
+
for (let index = 0; index < args.length && expecting !== "none"; index++) {
|
|
39183
|
+
const arg = args[index];
|
|
39184
|
+
if (typeof arg !== "string") break;
|
|
39185
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
39186
|
+
if (optionsFor({
|
|
39187
|
+
table: CODE_OPTIONS,
|
|
39188
|
+
name: owner
|
|
39189
|
+
})?.has(arg) || SHELL_NAME_PATTERN.test(owner) && SHELL_CODE_OPTION.test(arg)) break;
|
|
39190
|
+
if (optionsFor({
|
|
39191
|
+
table: VALUE_OPTIONS,
|
|
39192
|
+
name: owner
|
|
39193
|
+
})?.has(arg)) {
|
|
39194
|
+
const value = args[index + 1];
|
|
39195
|
+
if (typeof value === "string" && isPath(value)) indices.add(index + 1);
|
|
39196
|
+
index++;
|
|
39197
|
+
}
|
|
39198
|
+
continue;
|
|
39199
|
+
}
|
|
39200
|
+
if (expecting === "subcommand") {
|
|
39201
|
+
if (RUN_SUBCOMMANDS[owner]?.has(arg)) {
|
|
39202
|
+
expecting = "command";
|
|
39203
|
+
continue;
|
|
39204
|
+
}
|
|
39205
|
+
if (!SCRIPT_RUNNER_PATTERN.test(owner)) break;
|
|
39206
|
+
expecting = "script";
|
|
39207
|
+
}
|
|
39208
|
+
if (isPath(arg)) indices.add(index);
|
|
39209
|
+
if (expecting === "script") break;
|
|
39210
|
+
owner = runnerName(arg);
|
|
39211
|
+
expecting = afterCommandWord(owner);
|
|
39212
|
+
}
|
|
39213
|
+
return indices;
|
|
39214
|
+
}
|
|
39215
|
+
function isDotRelative(value) {
|
|
39216
|
+
return /^\.[\\/]/.test(value);
|
|
39217
|
+
}
|
|
39218
|
+
/** Turn a plugin-root path back into its `./` form, or return it unchanged. */
|
|
39219
|
+
function relativizeFromPluginRoot(value) {
|
|
39220
|
+
return PLUGIN_ROOT_PREFIX.test(value) ? value.replace(PLUGIN_ROOT_PREFIX, "./") : value;
|
|
39221
|
+
}
|
|
39222
|
+
/** The command and arguments of a stdio server, with inline array arguments split out. */
|
|
39223
|
+
function splitCommand(server) {
|
|
39224
|
+
if (Array.isArray(server.command)) {
|
|
39225
|
+
const [first, ...rest] = server.command;
|
|
39226
|
+
return {
|
|
39227
|
+
command: typeof first === "string" ? first : void 0,
|
|
39228
|
+
args: [...rest, ...Array.isArray(server.args) ? server.args : []]
|
|
39229
|
+
};
|
|
39230
|
+
}
|
|
39231
|
+
return {
|
|
39232
|
+
command: typeof server.command === "string" ? server.command : void 0,
|
|
39233
|
+
args: Array.isArray(server.args) ? server.args : []
|
|
39234
|
+
};
|
|
39235
|
+
}
|
|
39236
|
+
/**
|
|
39237
|
+
* Rewrite a server's command and the argument positions that name a file it
|
|
39238
|
+
* runs (see `fileArgumentIndices`), leaving every other argument as written.
|
|
39239
|
+
* Generate and import share this, so a position generate never anchors is
|
|
39240
|
+
* never relativized on import either, and a hand-written
|
|
39241
|
+
* `${CLAUDE_PLUGIN_ROOT}/…` data argument keeps its meaning.
|
|
39242
|
+
*/
|
|
39243
|
+
function rewriteServer({ server, rewriteCommand, isPath, rewritePath }) {
|
|
39244
|
+
const { command, args } = splitCommand(server);
|
|
39245
|
+
if (command === void 0) return server;
|
|
39246
|
+
const indices = fileArgumentIndices({
|
|
39247
|
+
command,
|
|
39248
|
+
args,
|
|
39249
|
+
isPath
|
|
39250
|
+
});
|
|
39251
|
+
const rewriteArgs = (values, offset) => values.map((value, index) => indices.has(index + offset) && typeof value === "string" ? rewritePath(value) : value);
|
|
39252
|
+
const next = { ...server };
|
|
39253
|
+
if (Array.isArray(server.command)) {
|
|
39254
|
+
const inlineArgs = server.command.slice(1);
|
|
39255
|
+
next.command = [rewriteCommand(command), ...rewriteArgs(inlineArgs, 0)];
|
|
39256
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, inlineArgs.length);
|
|
39257
|
+
} else {
|
|
39258
|
+
next.command = rewriteCommand(command);
|
|
39259
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, 0);
|
|
39260
|
+
}
|
|
39261
|
+
return next;
|
|
39262
|
+
}
|
|
39263
|
+
/** Anchor a server's plugin-relative command and the files its runner executes. */
|
|
39264
|
+
function anchorServer(server) {
|
|
39265
|
+
return rewriteServer({
|
|
39266
|
+
server,
|
|
39267
|
+
rewriteCommand: (command) => isPluginRelativeCommand(command) ? anchorToPluginRoot(command) : command,
|
|
39268
|
+
isPath: isDotRelative,
|
|
39269
|
+
rewritePath: anchorToPluginRoot
|
|
39270
|
+
});
|
|
39271
|
+
}
|
|
39272
|
+
/** Turn the plugin-root forms generate writes back into `./`. */
|
|
39273
|
+
function relativizeServer(server) {
|
|
39274
|
+
return rewriteServer({
|
|
39275
|
+
server,
|
|
39276
|
+
rewriteCommand: relativizeFromPluginRoot,
|
|
39277
|
+
isPath: (value) => PLUGIN_ROOT_PREFIX.test(value),
|
|
39278
|
+
rewritePath: relativizeFromPluginRoot
|
|
39279
|
+
});
|
|
39280
|
+
}
|
|
39281
|
+
/** Apply a per-server transform to every server entry that is an object. */
|
|
39282
|
+
function mapServers({ mcpServers, transform }) {
|
|
39283
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return mcpServers;
|
|
39284
|
+
return Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => server === null || typeof server !== "object" || Array.isArray(server) ? [name, server] : [name, transform(server)]));
|
|
39285
|
+
}
|
|
39286
|
+
/**
|
|
39287
|
+
* MCP servers bundled in a Claude Code plugin (`.mcp.json` at the plugin
|
|
39288
|
+
* root).
|
|
39289
|
+
*
|
|
39290
|
+
* Claude Code loads a plugin's `.mcp.json` with the consumer's project as the
|
|
39291
|
+
* working directory, so a relative `command` such as `./servers/db-server`
|
|
39292
|
+
* would be looked up in the consumer's repository, where the bundled binary
|
|
39293
|
+
* does not exist. Generate therefore rewrites a relative `command`, and the
|
|
39294
|
+
* `./` script a script runner executes (`node ./dist/server.js`,
|
|
39295
|
+
* `uv run ./server.py`), to `${CLAUDE_PLUGIN_ROOT}/…`, which Claude Code
|
|
39296
|
+
* substitutes with the installed plugin's directory. Other `./` arguments are
|
|
39297
|
+
* data the server reads relative to the consumer's project
|
|
39298
|
+
* (`server-filesystem ./docs`), so they are left as written, the same rule
|
|
39299
|
+
* plugin hook commands follow. Bare executables (`npx`, `node`), absolute paths
|
|
39300
|
+
* and values that already start with a variable pass through. `env` values and
|
|
39301
|
+
* the remote transports' `url` / `headers` are left as written, because
|
|
39302
|
+
* rulesync cannot tell a path from an opaque string there. Import turns the
|
|
39303
|
+
* plugin-root forms in `command` and `args` back into `./`, including the
|
|
39304
|
+
* shell-variable spellings a hand-written file may have copied from a hook
|
|
39305
|
+
* command (which Claude Code does not substitute in MCP config).
|
|
39306
|
+
*
|
|
39307
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39308
|
+
*/
|
|
39309
|
+
var ClaudecodePluginMcp = class ClaudecodePluginMcp extends ClaudecodeMcp {
|
|
39310
|
+
static async fromRulesyncMcp(params) {
|
|
39311
|
+
const base = await super.fromRulesyncMcp(params);
|
|
39312
|
+
const json = base.getJson();
|
|
39313
|
+
const mcpServers = mapServers({
|
|
39314
|
+
mcpServers: json.mcpServers,
|
|
39315
|
+
transform: anchorServer
|
|
39316
|
+
});
|
|
39317
|
+
return new ClaudecodePluginMcp({
|
|
39318
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39319
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39320
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39321
|
+
fileContent: JSON.stringify({
|
|
39322
|
+
...json,
|
|
39323
|
+
mcpServers
|
|
39324
|
+
}, null, 2),
|
|
39325
|
+
validate: params.validate ?? true,
|
|
39326
|
+
global: params.global ?? false
|
|
39327
|
+
});
|
|
39328
|
+
}
|
|
39329
|
+
static async fromFile(params) {
|
|
39330
|
+
const base = await super.fromFile(params);
|
|
39331
|
+
return new ClaudecodePluginMcp({
|
|
39332
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39333
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39334
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39335
|
+
fileContent: base.getFileContent(),
|
|
39336
|
+
validate: params.validate ?? true,
|
|
39337
|
+
global: params.global ?? false
|
|
39338
|
+
});
|
|
39339
|
+
}
|
|
39340
|
+
toRulesyncMcp() {
|
|
39341
|
+
const mcpServers = mapServers({
|
|
39342
|
+
mcpServers: this.getJson().mcpServers ?? {},
|
|
39343
|
+
transform: relativizeServer
|
|
39344
|
+
});
|
|
39345
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
39346
|
+
}
|
|
39347
|
+
static forDeletion(params) {
|
|
39348
|
+
return new ClaudecodePluginMcp({
|
|
39349
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39350
|
+
relativeDirPath: params.relativeDirPath,
|
|
39351
|
+
relativeFilePath: params.relativeFilePath,
|
|
39352
|
+
fileContent: "{}",
|
|
39353
|
+
validate: false,
|
|
39354
|
+
global: params.global ?? false
|
|
39355
|
+
});
|
|
39356
|
+
}
|
|
39357
|
+
};
|
|
39358
|
+
//#endregion
|
|
39006
39359
|
//#region src/features/mcp/cline-mcp.ts
|
|
39007
39360
|
const CLINE_GLOBAL_ONLY_MESSAGE = "Cline MCP is global-only; use --global to sync ~/.cline/data/settings/cline_mcp_settings.json";
|
|
39008
39361
|
function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
@@ -41712,7 +42065,7 @@ var CrushMcp = class CrushMcp extends ToolMcp {
|
|
|
41712
42065
|
fileKey: sharedConfigFileKey(this.getSettablePaths({ global })),
|
|
41713
42066
|
feature: "mcp",
|
|
41714
42067
|
existingContent,
|
|
41715
|
-
patch: { ["mcp"]: converted },
|
|
42068
|
+
patch: { ["mcp"]: undefinedWhenEmpty(converted) },
|
|
41716
42069
|
filePath: location.filePath,
|
|
41717
42070
|
logger
|
|
41718
42071
|
}),
|
|
@@ -43292,13 +43645,23 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
43292
43645
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
43293
43646
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
43294
43647
|
/**
|
|
43648
|
+
* Grok keeps per-server MCP tool deny lists in a top-level
|
|
43649
|
+
* `[disabled_mcp_tools]` table (`map<server, string[]>` of unqualified tool
|
|
43650
|
+
* names), not inside `[mcp_servers.<name>]`. Grok reads it from the user
|
|
43651
|
+
* `~/.grok/config.toml` only: a project `.grok/config.toml` contributes just
|
|
43652
|
+
* `[mcp_servers]`, `[plugins]`, `[permission]` and `[mcp] max_output_bytes`.
|
|
43653
|
+
* https://github.com/xai-org/grok-build/blob/2bdd1d6a/crates/codegen/xai-grok-pager/docs/user-guide/26-config-reference.md
|
|
43654
|
+
*/
|
|
43655
|
+
const GROK_DISABLED_MCP_TOOLS_KEY = "disabled_mcp_tools";
|
|
43656
|
+
/**
|
|
43295
43657
|
* Grok Build stores MCP servers in `config.toml` under a `[mcp_servers.<name>]`
|
|
43296
43658
|
* table. Verified against `grok mcp add` (grok 0.2.54): a stdio server emits
|
|
43297
43659
|
* `command`, `args`, `enabled = true`, and an `[mcp_servers.<name>.env]` table;
|
|
43298
43660
|
* 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
|
-
*
|
|
43661
|
+
* literal `env` table (not the `env_vars` passthrough list), so the only field
|
|
43662
|
+
* rename is `disabled` (rulesync) ↔ `enabled = false` (grok). `disabledTools`
|
|
43663
|
+
* never lands in the server table: it is lifted into the top-level
|
|
43664
|
+
* `[disabled_mcp_tools]` table by `buildDisabledMcpTools`.
|
|
43302
43665
|
*/
|
|
43303
43666
|
function convertToGrokFormat(mcpServers) {
|
|
43304
43667
|
const result = {};
|
|
@@ -43307,7 +43670,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
43307
43670
|
if (!isRecord$1(config)) continue;
|
|
43308
43671
|
const converted = {};
|
|
43309
43672
|
for (const [key, value] of Object.entries(config)) {
|
|
43310
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
43673
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key) || key === "disabledTools") continue;
|
|
43311
43674
|
if (key === "disabled") {
|
|
43312
43675
|
if (value === true) converted["enabled"] = false;
|
|
43313
43676
|
} else converted[key] = value;
|
|
@@ -43331,6 +43694,38 @@ function convertFromGrokFormat(grokMcp) {
|
|
|
43331
43694
|
}
|
|
43332
43695
|
return result;
|
|
43333
43696
|
}
|
|
43697
|
+
function toToolNameList(value) {
|
|
43698
|
+
if (!Array.isArray(value)) return [];
|
|
43699
|
+
return value.filter((tool) => typeof tool === "string");
|
|
43700
|
+
}
|
|
43701
|
+
/**
|
|
43702
|
+
* Recompute the global `[disabled_mcp_tools]` table: every server Rulesync
|
|
43703
|
+
* declares gets its `disabledTools` (or loses a stale entry when it has none —
|
|
43704
|
+
* Grok itself removes an entry whose list empties), while entries for other
|
|
43705
|
+
* servers — ones toggled in Grok's `/mcps` UI, plugin or compat servers, and
|
|
43706
|
+
* the `__managed_gateway_connectors` sentinel — are carried over untouched.
|
|
43707
|
+
* Returns `undefined` when nothing is left, so the key is retracted.
|
|
43708
|
+
*/
|
|
43709
|
+
function buildDisabledMcpTools({ mcpServers, existingContent, filePath }) {
|
|
43710
|
+
const existing = parseSharedConfig({
|
|
43711
|
+
format: "toml",
|
|
43712
|
+
fileContent: existingContent,
|
|
43713
|
+
filePath
|
|
43714
|
+
})[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43715
|
+
const result = isRecord$1(existing) ? { ...existing } : {};
|
|
43716
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
43717
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
43718
|
+
const tools = toToolNameList(config.disabledTools);
|
|
43719
|
+
if (tools.length > 0) result[name] = tools;
|
|
43720
|
+
else delete result[name];
|
|
43721
|
+
}
|
|
43722
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
43723
|
+
}
|
|
43724
|
+
function warnProjectDisabledTools(mcpServers, logger) {
|
|
43725
|
+
const serverNames = Object.entries(mcpServers).filter(([, config]) => isRecord$1(config) && config.disabledTools !== void 0).map(([name]) => name);
|
|
43726
|
+
if (serverNames.length === 0) return;
|
|
43727
|
+
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.`);
|
|
43728
|
+
}
|
|
43334
43729
|
var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
43335
43730
|
toml;
|
|
43336
43731
|
constructor({ ...rest }) {
|
|
@@ -43367,16 +43762,19 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43367
43762
|
relativeDirPath: paths.relativeDirPath,
|
|
43368
43763
|
relativeFilePath: paths.relativeFilePath,
|
|
43369
43764
|
fileContent,
|
|
43370
|
-
validate
|
|
43765
|
+
validate,
|
|
43766
|
+
global
|
|
43371
43767
|
});
|
|
43372
43768
|
}
|
|
43373
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
43769
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
43374
43770
|
const paths = this.getSettablePaths({ global });
|
|
43375
43771
|
const configTomlFilePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
43376
43772
|
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
43377
|
-
const
|
|
43773
|
+
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
43774
|
+
const converted = convertToGrokFormat(strippedMcpServers);
|
|
43378
43775
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
43379
|
-
|
|
43776
|
+
if (!global) warnProjectDisabledTools(strippedMcpServers, logger);
|
|
43777
|
+
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
43778
|
return new GrokcliMcp({
|
|
43381
43779
|
outputRoot,
|
|
43382
43780
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -43385,14 +43783,31 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43385
43783
|
fileKey: sharedConfigFileKey(paths),
|
|
43386
43784
|
feature: "mcp",
|
|
43387
43785
|
existingContent: configTomlFileContent,
|
|
43388
|
-
patch: {
|
|
43786
|
+
patch: {
|
|
43787
|
+
mcp_servers: Object.keys(filteredMcpServers).length > 0 ? filteredMcpServers : void 0,
|
|
43788
|
+
...global && { [GROK_DISABLED_MCP_TOOLS_KEY]: buildDisabledMcpTools({
|
|
43789
|
+
mcpServers: strippedMcpServers,
|
|
43790
|
+
existingContent: configTomlFileContent,
|
|
43791
|
+
filePath: configTomlFilePath
|
|
43792
|
+
}) }
|
|
43793
|
+
},
|
|
43389
43794
|
filePath: configTomlFilePath
|
|
43390
43795
|
}),
|
|
43391
|
-
validate
|
|
43796
|
+
validate,
|
|
43797
|
+
global
|
|
43392
43798
|
});
|
|
43393
43799
|
}
|
|
43394
43800
|
toRulesyncMcp() {
|
|
43395
43801
|
const converted = convertFromGrokFormat(this.toml.mcp_servers ?? {});
|
|
43802
|
+
const disabledMcpTools = this.toml[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43803
|
+
if (this.global && isRecord$1(disabledMcpTools)) for (const [name, config] of Object.entries(converted)) {
|
|
43804
|
+
if (!Object.hasOwn(disabledMcpTools, name)) continue;
|
|
43805
|
+
const tools = toToolNameList(disabledMcpTools[name]);
|
|
43806
|
+
if (tools.length > 0) converted[name] = {
|
|
43807
|
+
...config,
|
|
43808
|
+
disabledTools: tools
|
|
43809
|
+
};
|
|
43810
|
+
}
|
|
43396
43811
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: converted }, null, 2) });
|
|
43397
43812
|
}
|
|
43398
43813
|
validate() {
|
|
@@ -46456,7 +46871,7 @@ var PoolMcp = class PoolMcp extends ToolMcp {
|
|
|
46456
46871
|
fileKey: sharedConfigFileKey(paths),
|
|
46457
46872
|
feature: "mcp",
|
|
46458
46873
|
existingContent,
|
|
46459
|
-
patch: { [POOL_MCP_SERVERS_KEY]: converted },
|
|
46874
|
+
patch: { [POOL_MCP_SERVERS_KEY]: undefinedWhenEmpty(converted) },
|
|
46460
46875
|
filePath
|
|
46461
46876
|
}),
|
|
46462
46877
|
validate,
|
|
@@ -46586,7 +47001,7 @@ var QoderMcp = class QoderMcp extends ToolMcp {
|
|
|
46586
47001
|
fileKey: sharedConfigFileKey(paths),
|
|
46587
47002
|
feature: "mcp",
|
|
46588
47003
|
existingContent,
|
|
46589
|
-
patch: { mcpServers },
|
|
47004
|
+
patch: { mcpServers: undefinedWhenEmpty(mcpServers) },
|
|
46590
47005
|
filePath
|
|
46591
47006
|
}) : JSON.stringify({
|
|
46592
47007
|
...parseQoderMcpConfig({
|
|
@@ -46965,18 +47380,28 @@ function warnAboutRetiredFields({ name, serverRecord, logger }) {
|
|
|
46965
47380
|
* `" Serial "` counts too and is written as authored.
|
|
46966
47381
|
*/
|
|
46967
47382
|
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
47383
|
const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
47384
|
+
startup_timeout_seconds: "integer",
|
|
47385
|
+
call_timeout_seconds: "integer",
|
|
47386
|
+
tool_timeout_seconds: "integer table",
|
|
46974
47387
|
concurrency: "string",
|
|
46975
47388
|
auto_start: "boolean",
|
|
46976
47389
|
load: "string",
|
|
46977
47390
|
oauth_allow_missing_pkce_metadata: "boolean"
|
|
46978
47391
|
};
|
|
46979
47392
|
/**
|
|
47393
|
+
* Whether `value` is written as a TOML integer. smol-toml writes a JS number
|
|
47394
|
+
* outside the safe-integer range, or with a fraction, as a TOML float.
|
|
47395
|
+
*/
|
|
47396
|
+
function isTomlInteger(value) {
|
|
47397
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
47398
|
+
}
|
|
47399
|
+
function hasExpectedType(value, expected) {
|
|
47400
|
+
if (expected === "integer") return isTomlInteger(value);
|
|
47401
|
+
if (expected === "integer table") return isRecord$1(value) && Object.values(value).every(isTomlInteger);
|
|
47402
|
+
return typeof value === expected;
|
|
47403
|
+
}
|
|
47404
|
+
/**
|
|
46980
47405
|
* Why a passthrough value cannot be written, or `undefined` when it can (and
|
|
46981
47406
|
* for every field without a fixed type). A wrong type is the case that matters:
|
|
46982
47407
|
* a TOML type mismatch makes Reasonix refuse the whole config file. An unknown
|
|
@@ -46988,7 +47413,7 @@ const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
|
46988
47413
|
*/
|
|
46989
47414
|
function invalidPassthroughFieldReason(field, value) {
|
|
46990
47415
|
const expected = REASONIX_TYPED_PLUGIN_FIELDS[field];
|
|
46991
|
-
if (expected !== void 0 &&
|
|
47416
|
+
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
47417
|
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
47418
|
}
|
|
46994
47419
|
/**
|
|
@@ -48010,7 +48435,7 @@ var TabnineMcp = class TabnineMcp extends ToolMcp {
|
|
|
48010
48435
|
fileKey: sharedConfigFileKey(paths),
|
|
48011
48436
|
feature: "mcp",
|
|
48012
48437
|
existingContent,
|
|
48013
|
-
patch: { mcpServers: convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger) },
|
|
48438
|
+
patch: { mcpServers: undefinedWhenEmpty(convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger)) },
|
|
48014
48439
|
filePath
|
|
48015
48440
|
}),
|
|
48016
48441
|
validate,
|
|
@@ -49331,6 +49756,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49331
49756
|
const paths = this.getSettablePaths({ global });
|
|
49332
49757
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
49333
49758
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
49759
|
+
const contextServers = convertServersToZedFormat(rulesyncMcp.getMcpServers(), logger);
|
|
49334
49760
|
return new ZedMcp({
|
|
49335
49761
|
outputRoot,
|
|
49336
49762
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -49339,7 +49765,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49339
49765
|
fileKey: sharedConfigFileKey(paths),
|
|
49340
49766
|
feature: "mcp",
|
|
49341
49767
|
existingContent,
|
|
49342
|
-
patch: { context_servers:
|
|
49768
|
+
patch: { context_servers: Object.keys(contextServers).length > 0 ? contextServers : void 0 },
|
|
49343
49769
|
filePath
|
|
49344
49770
|
}),
|
|
49345
49771
|
validate
|
|
@@ -49472,7 +49898,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49472
49898
|
}
|
|
49473
49899
|
}],
|
|
49474
49900
|
["claudecode-plugin", {
|
|
49475
|
-
class:
|
|
49901
|
+
class: ClaudecodePluginMcp,
|
|
49476
49902
|
meta: {
|
|
49477
49903
|
supportsProject: true,
|
|
49478
49904
|
supportsGlobal: false,
|
|
@@ -49648,7 +50074,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49648
50074
|
supportsProject: true,
|
|
49649
50075
|
supportsGlobal: true,
|
|
49650
50076
|
supportsEnabledTools: false,
|
|
49651
|
-
supportsDisabledTools:
|
|
50077
|
+
supportsDisabledTools: true
|
|
49652
50078
|
}
|
|
49653
50079
|
}],
|
|
49654
50080
|
["hermesagent", {
|
|
@@ -50814,6 +51240,8 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
50814
51240
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
50815
51241
|
*/
|
|
50816
51242
|
const REGEX_PREFIX = "regex:";
|
|
51243
|
+
const TRAILING_ANCHOR = /(?<!\\)\$$/;
|
|
51244
|
+
const PLAIN_TARGET_GLOB_CHARACTERS = /[*?[{\\]/;
|
|
50817
51245
|
const REGEX_METACHARACTERS$1 = /[\\^$.|?*+()[\]{}]/;
|
|
50818
51246
|
const WHITESPACE_CODES = [
|
|
50819
51247
|
9,
|
|
@@ -50954,6 +51382,7 @@ function replacePosixClasses(source) {
|
|
|
50954
51382
|
function isInvalidRegex(source) {
|
|
50955
51383
|
const unquoted = source.replace(RE2_QUOTED, (match) => match === "\\\\" ? match : "q");
|
|
50956
51384
|
if (LOOKAROUND.test(unquoted) || hasRefusedEscape(unquoted)) return true;
|
|
51385
|
+
if (hasClassEscapeRangeEnd(unquoted)) return true;
|
|
50957
51386
|
const withoutPosixClasses = replacePosixClasses(unquoted);
|
|
50958
51387
|
if (withoutPosixClasses === void 0 || hasRefusedRepeat(withoutPosixClasses)) return true;
|
|
50959
51388
|
const asJavaScript = withoutPosixClasses.replace(/\(\?P</g, "(?<").replace(/\(\?[imsU]*-?[imsU]*\)/g, "").replace(/\(\?[imsU]*-?[imsU]*:/g, "(?:").replace(/\\A/g, "^").replace(/\\z/g, "$");
|
|
@@ -50978,6 +51407,74 @@ function hasRefusedEscape(source) {
|
|
|
50978
51407
|
}
|
|
50979
51408
|
return false;
|
|
50980
51409
|
}
|
|
51410
|
+
const CLASS_ESCAPE_LETTERS = /[dDsSwWpP]/;
|
|
51411
|
+
/** The length of the escape at `index`, braces (`\x{41}`, `\p{Greek}`) included. */
|
|
51412
|
+
function escapeLength(source, index) {
|
|
51413
|
+
const escaped = source[index + 1] ?? "";
|
|
51414
|
+
if (/[xpP]/.test(escaped) && source[index + 2] === "{") {
|
|
51415
|
+
const close = source.indexOf("}", index + 3);
|
|
51416
|
+
return close === -1 ? source.length - index : close - index + 1;
|
|
51417
|
+
}
|
|
51418
|
+
if (escaped === "p" || escaped === "P") return 3;
|
|
51419
|
+
return escaped === "x" ? 4 : 2;
|
|
51420
|
+
}
|
|
51421
|
+
/** Step past `^` and a leading `]` (a member) after the `[` that ends at `index`. */
|
|
51422
|
+
function openClass(source, start) {
|
|
51423
|
+
const index = source[start] === "^" ? start + 1 : start;
|
|
51424
|
+
return source[index] === "]" ? {
|
|
51425
|
+
index: index + 1,
|
|
51426
|
+
afterCharacter: true
|
|
51427
|
+
} : {
|
|
51428
|
+
index,
|
|
51429
|
+
afterCharacter: false
|
|
51430
|
+
};
|
|
51431
|
+
}
|
|
51432
|
+
/**
|
|
51433
|
+
* Whether a bracket range ends in a class escape (`[a-\d]`, `[--\s]`). Go
|
|
51434
|
+
* reads the end of a range as one character and refuses such a regex, while
|
|
51435
|
+
* JavaScript without the `u` flag reads it as three members. A class escape
|
|
51436
|
+
* that starts the range (`[\d-z]`) is a member followed by `-`, which both
|
|
51437
|
+
* accept. The `u` flag cannot be used instead: it also refuses RE2 spellings
|
|
51438
|
+
* such as `\x{41}` and `\-` outside a class.
|
|
51439
|
+
*/
|
|
51440
|
+
function hasClassEscapeRangeEnd(source) {
|
|
51441
|
+
let inClass = false;
|
|
51442
|
+
let afterCharacter = false;
|
|
51443
|
+
let index = 0;
|
|
51444
|
+
while (index < source.length) {
|
|
51445
|
+
const character = source[index] ?? "";
|
|
51446
|
+
if (character === "\\") {
|
|
51447
|
+
afterCharacter = inClass && !CLASS_ESCAPE_LETTERS.test(source[index + 1] ?? "");
|
|
51448
|
+
index += escapeLength(source, index);
|
|
51449
|
+
continue;
|
|
51450
|
+
}
|
|
51451
|
+
if (!inClass) {
|
|
51452
|
+
inClass = character === "[";
|
|
51453
|
+
({index, afterCharacter} = inClass ? openClass(source, index + 1) : {
|
|
51454
|
+
index: index + 1,
|
|
51455
|
+
afterCharacter: false
|
|
51456
|
+
});
|
|
51457
|
+
continue;
|
|
51458
|
+
}
|
|
51459
|
+
const posixEnd = source.startsWith("[:", index) ? source.indexOf(":]", index + 2) : -1;
|
|
51460
|
+
if (character === "]" || posixEnd !== -1) {
|
|
51461
|
+
inClass = character !== "]";
|
|
51462
|
+
afterCharacter = false;
|
|
51463
|
+
index = posixEnd === -1 ? index + 1 : posixEnd + 2;
|
|
51464
|
+
continue;
|
|
51465
|
+
}
|
|
51466
|
+
if (character === "-" && afterCharacter && source[index + 1] !== "]") {
|
|
51467
|
+
const end = index + 1;
|
|
51468
|
+
if (source[end] === "\\" && CLASS_ESCAPE_LETTERS.test(source[end + 1] ?? "")) return true;
|
|
51469
|
+
afterCharacter = false;
|
|
51470
|
+
index = end + (source[end] === "\\" ? escapeLength(source, end) : 1);
|
|
51471
|
+
continue;
|
|
51472
|
+
}
|
|
51473
|
+
afterCharacter = true;
|
|
51474
|
+
index += 1;
|
|
51475
|
+
}
|
|
51476
|
+
return false;
|
|
51477
|
+
}
|
|
50981
51478
|
/**
|
|
50982
51479
|
* Whether a repeat count is over Go's limit. Braces inside a bracket class
|
|
50983
51480
|
* (`[a{1001}]`) and in `\x{2003}` or `\p{Greek}` are not repeats, so those
|
|
@@ -51125,7 +51622,7 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
51125
51622
|
* the rule a regex.
|
|
51126
51623
|
*/
|
|
51127
51624
|
function regexWordToGlob(word) {
|
|
51128
|
-
const body = word.replace(/^\^/, "").replace(
|
|
51625
|
+
const body = word.replace(/^\^/, "").replace(TRAILING_ANCHOR, "");
|
|
51129
51626
|
const characters = Array.from(body);
|
|
51130
51627
|
if (characters.length === 0) return;
|
|
51131
51628
|
let glob = "";
|
|
@@ -51166,12 +51663,61 @@ function regexToGlob(source) {
|
|
|
51166
51663
|
function fromAntigravityCommandTarget(target) {
|
|
51167
51664
|
if (target.startsWith(REGEX_PREFIX)) return regexToGlob(target.slice(6)) ?? target;
|
|
51168
51665
|
if (target === "*") return target;
|
|
51169
|
-
if (
|
|
51666
|
+
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) {
|
|
51170
51667
|
const words = splitWords(target);
|
|
51171
51668
|
return `${REGEX_PREFIX}${words.map((word) => `^${[...word].map(escapeRegexCharacter).join("")}$`).join(" ")}`;
|
|
51172
51669
|
}
|
|
51173
51670
|
return `${target} *`;
|
|
51174
51671
|
}
|
|
51672
|
+
/**
|
|
51673
|
+
* Whether a regex word means the same anchored or not: each end is either
|
|
51674
|
+
* anchored (`^`, an unescaped `$`) or open-ended (`.*`).
|
|
51675
|
+
*/
|
|
51676
|
+
function isAnchoredWord(word) {
|
|
51677
|
+
const startAnchored = word.startsWith("^") || word.startsWith(".*");
|
|
51678
|
+
const endAnchored = TRAILING_ANCHOR.test(word) || word.endsWith(".*");
|
|
51679
|
+
return startAnchored && endAnchored;
|
|
51680
|
+
}
|
|
51681
|
+
/**
|
|
51682
|
+
* Explain what importing an Antigravity `command` target may lose, or return
|
|
51683
|
+
* `undefined` when the import is faithful.
|
|
51684
|
+
*
|
|
51685
|
+
* - A `regex:` target with a word that is not anchored is imported as a glob
|
|
51686
|
+
* that assumes anchoring. If Antigravity matches such a word unanchored,
|
|
51687
|
+
* the imported rule is narrower (`regex:rm` also matches `rmdir`).
|
|
51688
|
+
* - A plain target holding characters that are special in a glob is most
|
|
51689
|
+
* likely the verbatim output of an older rulesync (`command(rm -rf *)`).
|
|
51690
|
+
* Antigravity matches it literally, so it imports as a regex that matches a
|
|
51691
|
+
* literal `*` word and is inert in the other tools it is exported to.
|
|
51692
|
+
*/
|
|
51693
|
+
function describeAntigravityCommandImportLoss(target) {
|
|
51694
|
+
if (target.startsWith(REGEX_PREFIX)) {
|
|
51695
|
+
const source = target.slice(6);
|
|
51696
|
+
if (splitWords(source).some((word) => !isAnchoredWord(word)) && regexToGlob(source) !== void 0) return {
|
|
51697
|
+
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",
|
|
51698
|
+
denyConsequence: "The imported deny may block less than it does in Antigravity."
|
|
51699
|
+
};
|
|
51700
|
+
return;
|
|
51701
|
+
}
|
|
51702
|
+
if (target !== "*" && PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
51703
|
+
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",
|
|
51704
|
+
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
51705
|
+
};
|
|
51706
|
+
}
|
|
51707
|
+
/**
|
|
51708
|
+
* Import one Antigravity `command` target as a canonical bash pattern, warning
|
|
51709
|
+
* when the result may differ from what Antigravity enforces. Shared by the
|
|
51710
|
+
* Antigravity CLI and IDE importers.
|
|
51711
|
+
*/
|
|
51712
|
+
function importAntigravityCommandTarget({ target, entry, decision, logger, toolLabel }) {
|
|
51713
|
+
const pattern = fromAntigravityCommandTarget(target);
|
|
51714
|
+
const loss = describeAntigravityCommandImportLoss(target);
|
|
51715
|
+
if (loss !== void 0) {
|
|
51716
|
+
const consequence = decision === "deny" ? ` ${loss.denyConsequence}` : "";
|
|
51717
|
+
logger.warn(`${toolLabel} permissions: importing ${entry} as "${pattern}": ${loss.reason}.${consequence}`);
|
|
51718
|
+
}
|
|
51719
|
+
return pattern;
|
|
51720
|
+
}
|
|
51175
51721
|
//#endregion
|
|
51176
51722
|
//#region src/features/permissions/antigravity-targets.ts
|
|
51177
51723
|
/**
|
|
@@ -51202,6 +51748,7 @@ const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
|
|
|
51202
51748
|
"**/*"
|
|
51203
51749
|
]);
|
|
51204
51750
|
const DIRECTORY_CONTENTS_SUFFIX = /\/\*\*(?:\/\*)?$/;
|
|
51751
|
+
const HOME_OR_VARIABLE_PATH = /^~(?:\/|$)|^\$[A-Za-z_]|\$\{[A-Za-z_]/;
|
|
51205
51752
|
const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
|
|
51206
51753
|
const DENY_HINTS = {
|
|
51207
51754
|
read_file: "deny a directory or file path instead",
|
|
@@ -51284,6 +51831,10 @@ function buildAntigravityPermissionEntry({ action, category, pattern, decision,
|
|
|
51284
51831
|
return;
|
|
51285
51832
|
}
|
|
51286
51833
|
if (result.note !== void 0 && decision !== "deny") logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}.`);
|
|
51834
|
+
if (PATH_ACTIONS.has(action) && HOME_OR_VARIABLE_PATH.test(result.target)) {
|
|
51835
|
+
const consequence = decision === "deny" ? "this deny may block nothing" : `${toolLabel} may fall back to its default`;
|
|
51836
|
+
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.`);
|
|
51837
|
+
}
|
|
51287
51838
|
return result.target === "*" ? action : `${action}(${result.target})`;
|
|
51288
51839
|
}
|
|
51289
51840
|
//#endregion
|
|
@@ -51850,9 +52401,9 @@ function convertRulesyncToAntigravityCliPermissions({ config, logger }) {
|
|
|
51850
52401
|
}
|
|
51851
52402
|
}
|
|
51852
52403
|
return {
|
|
51853
|
-
allow,
|
|
51854
|
-
ask,
|
|
51855
|
-
deny
|
|
52404
|
+
allow: [...new Set(allow)],
|
|
52405
|
+
ask: [...new Set(ask)],
|
|
52406
|
+
deny: [...new Set(deny)]
|
|
51856
52407
|
};
|
|
51857
52408
|
}
|
|
51858
52409
|
/**
|
|
@@ -51864,7 +52415,13 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
51864
52415
|
for (const entry of entries) {
|
|
51865
52416
|
const { toolName, pattern: target } = parsePermissionEntry$1(entry);
|
|
51866
52417
|
const canonical = toCanonicalToolName$9(toolName);
|
|
51867
|
-
const pattern = toolName === "command"
|
|
52418
|
+
const pattern = toolName === "command" ? importAntigravityCommandTarget({
|
|
52419
|
+
target,
|
|
52420
|
+
entry,
|
|
52421
|
+
decision: action,
|
|
52422
|
+
logger: moduleLogger$4,
|
|
52423
|
+
toolLabel: "Antigravity CLI"
|
|
52424
|
+
}) : target;
|
|
51868
52425
|
if (!permission[canonical]) permission[canonical] = {};
|
|
51869
52426
|
permission[canonical][pattern] = action;
|
|
51870
52427
|
}
|
|
@@ -52068,9 +52625,9 @@ function convertRulesyncToAntigravityIdePermissions({ config, logger }) {
|
|
|
52068
52625
|
}
|
|
52069
52626
|
}
|
|
52070
52627
|
return {
|
|
52071
|
-
allow,
|
|
52072
|
-
ask,
|
|
52073
|
-
deny
|
|
52628
|
+
allow: [...new Set(allow)],
|
|
52629
|
+
ask: [...new Set(ask)],
|
|
52630
|
+
deny: [...new Set(deny)]
|
|
52074
52631
|
};
|
|
52075
52632
|
}
|
|
52076
52633
|
/**
|
|
@@ -52082,7 +52639,13 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
52082
52639
|
for (const entry of entries) {
|
|
52083
52640
|
const { action: ideAction, pattern: target } = parsePermissionEntry(entry);
|
|
52084
52641
|
const canonical = toCanonicalCategory$2(ideAction);
|
|
52085
|
-
const pattern = ideAction === "command"
|
|
52642
|
+
const pattern = ideAction === "command" ? importAntigravityCommandTarget({
|
|
52643
|
+
target,
|
|
52644
|
+
entry,
|
|
52645
|
+
decision: action,
|
|
52646
|
+
logger: fallbackLogger,
|
|
52647
|
+
toolLabel: "Antigravity IDE"
|
|
52648
|
+
}) : target;
|
|
52086
52649
|
if (!permission[canonical]) permission[canonical] = {};
|
|
52087
52650
|
permission[canonical][pattern] = action;
|
|
52088
52651
|
}
|
|
@@ -54064,7 +54627,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54064
54627
|
managedSourcesBehavior: "managed settings",
|
|
54065
54628
|
modelPricing: "managed settings",
|
|
54066
54629
|
parentSettingsBehavior: "managed settings",
|
|
54067
|
-
permissionExplainerEnabled: "~/.claude.json",
|
|
54068
54630
|
pluginSuggestionMarketplaces: "managed settings",
|
|
54069
54631
|
pluginTrustMessage: "managed settings",
|
|
54070
54632
|
prStatusFooterEnabled: "~/.claude.json",
|
|
@@ -54073,10 +54635,24 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54073
54635
|
sshHostAllowlist: "managed settings",
|
|
54074
54636
|
strictKnownMarketplaces: "managed settings",
|
|
54075
54637
|
strictPluginOnlyCustomization: "managed settings",
|
|
54076
|
-
teammateDefaultModel: "~/.claude.json",
|
|
54077
54638
|
wslInheritsWindowsSettings: "managed settings"
|
|
54078
54639
|
};
|
|
54079
54640
|
/**
|
|
54641
|
+
* Top-level settings keys Claude Code has removed, with the version that
|
|
54642
|
+
* removed each. Current versions ignore them wherever they are set, so they
|
|
54643
|
+
* are dropped in **both** scopes, and the warning names the version so the
|
|
54644
|
+
* author knows to delete the key rather than move it to another file.
|
|
54645
|
+
*
|
|
54646
|
+
* Derived from the "Removed in" notes of the settings reference.
|
|
54647
|
+
*
|
|
54648
|
+
* @see https://code.claude.com/docs/en/settings-reference
|
|
54649
|
+
*/
|
|
54650
|
+
const CLAUDECODE_REMOVED_KEYS = {
|
|
54651
|
+
permissionExplainerEnabled: "v2.1.257",
|
|
54652
|
+
taskOutputMaxChars: "v2.1.277",
|
|
54653
|
+
teammateDefaultModel: "v2.1.234"
|
|
54654
|
+
};
|
|
54655
|
+
/**
|
|
54080
54656
|
* Top-level settings keys whose value Claude Code **executes**. The generic
|
|
54081
54657
|
* passthrough refuses them outright rather than warning: `.rulesync/*` files
|
|
54082
54658
|
* are shareable — `rulesync fetch` copies a third party's `permissions.jsonc`
|
|
@@ -54356,6 +54932,15 @@ const CLAUDECODE_SETTINGS_KEY_ALIASES = {
|
|
|
54356
54932
|
allowedMarketplaces: "strictKnownMarketplaces"
|
|
54357
54933
|
};
|
|
54358
54934
|
/**
|
|
54935
|
+
* The warning for a key that neither scope can honor — one Claude Code has
|
|
54936
|
+
* removed, or one read only from a file rulesync does not generate — or
|
|
54937
|
+
* `undefined` when the key may be written in at least one scope.
|
|
54938
|
+
*/
|
|
54939
|
+
function neverHonoredKeyWarning({ canonicalKey, shown, relativeFilePath }) {
|
|
54940
|
+
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.`;
|
|
54941
|
+
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.`;
|
|
54942
|
+
}
|
|
54943
|
+
/**
|
|
54359
54944
|
* Copy of the authored top-level passthrough with the keys the target file
|
|
54360
54945
|
* cannot honor removed, warning once per dropped key. Like
|
|
54361
54946
|
* `stripSandboxPaths`, only the override copy is filtered — a value
|
|
@@ -54372,8 +54957,13 @@ function stripUnhonoredTopLevelKeys({ overrides, global, relativeFilePath, logge
|
|
|
54372
54957
|
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
54958
|
continue;
|
|
54374
54959
|
}
|
|
54375
|
-
|
|
54376
|
-
|
|
54960
|
+
const neverHonored = neverHonoredKeyWarning({
|
|
54961
|
+
canonicalKey,
|
|
54962
|
+
shown,
|
|
54963
|
+
relativeFilePath
|
|
54964
|
+
});
|
|
54965
|
+
if (neverHonored !== void 0) {
|
|
54966
|
+
logger?.warn(neverHonored);
|
|
54377
54967
|
continue;
|
|
54378
54968
|
}
|
|
54379
54969
|
if (!global && CLAUDECODE_USER_SCOPE_ONLY_KEYS.has(canonicalKey)) {
|
|
@@ -62064,8 +62654,10 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
62064
62654
|
* table. The gateway replaces the owned `permission` key wholesale and rulesync
|
|
62065
62655
|
* only ever authors `rules`, so a hand-written sibling such as
|
|
62066
62656
|
* `dangerous_command_guard = false` (Kimi Code 0.40.0) would otherwise be
|
|
62067
|
-
* deleted on every generate.
|
|
62068
|
-
*
|
|
62657
|
+
* deleted on every generate. Unmodeled keys pass through verbatim; `rules`
|
|
62658
|
+
* always comes from the patch, and so does `dangerous_command_guard` when the
|
|
62659
|
+
* `kimi-code.dangerousCommandGuard` override sets it. Leaving the override out
|
|
62660
|
+
* keeps whatever the file already has.
|
|
62069
62661
|
*
|
|
62070
62662
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
62071
62663
|
*/
|
|
@@ -62308,14 +62900,18 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62308
62900
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions, logger }) {
|
|
62309
62901
|
const config = rulesyncPermissions.getJson();
|
|
62310
62902
|
const defaultPermissionMode = config["kimi-code"]?.defaultPermissionMode;
|
|
62903
|
+
const dangerousCommandGuard = config["kimi-code"]?.dangerousCommandGuard;
|
|
62311
62904
|
const tools = buildKimiCodeToolsSection(config["kimi-code"]?.tools);
|
|
62312
62905
|
const document = {
|
|
62313
62906
|
...defaultPermissionMode && { default_permission_mode: defaultPermissionMode },
|
|
62314
62907
|
...tools && { tools },
|
|
62315
|
-
permission: {
|
|
62316
|
-
|
|
62317
|
-
|
|
62318
|
-
|
|
62908
|
+
permission: {
|
|
62909
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerous_command_guard: dangerousCommandGuard },
|
|
62910
|
+
rules: canonicalToKimiCodeRules({
|
|
62911
|
+
config,
|
|
62912
|
+
logger
|
|
62913
|
+
})
|
|
62914
|
+
}
|
|
62319
62915
|
};
|
|
62320
62916
|
let fileContent;
|
|
62321
62917
|
try {
|
|
@@ -62337,11 +62933,14 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62337
62933
|
format: "toml",
|
|
62338
62934
|
fileContent: this.getFileContent()
|
|
62339
62935
|
});
|
|
62340
|
-
const
|
|
62936
|
+
const permissionConfig = isRecord$1(config.permission) ? config.permission : {};
|
|
62937
|
+
const { permission, nativeRules } = preserveKimiCodeRules(permissionConfig.rules);
|
|
62341
62938
|
const defaultPermissionMode = config.default_permission_mode;
|
|
62939
|
+
const dangerousCommandGuard = permissionConfig.dangerous_command_guard;
|
|
62342
62940
|
const tools = buildKimiCodeToolsSection(config.tools);
|
|
62343
62941
|
const toolOverride = {
|
|
62344
62942
|
...defaultPermissionMode === "manual" || defaultPermissionMode === "yolo" || defaultPermissionMode === "auto" ? { defaultPermissionMode } : {},
|
|
62943
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerousCommandGuard },
|
|
62345
62944
|
...nativeRules.length > 0 && { rules: nativeRules },
|
|
62346
62945
|
...tools && { tools }
|
|
62347
62946
|
};
|
|
@@ -63223,6 +63822,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
63223
63822
|
//#endregion
|
|
63224
63823
|
//#region src/features/permissions/omp-permissions.ts
|
|
63225
63824
|
const CATCH_ALL_PATTERN$4 = "*";
|
|
63825
|
+
const APPROVAL_MODE_KEY = "approvalMode";
|
|
63226
63826
|
const ACTION_TO_OMP_APPROVAL = {
|
|
63227
63827
|
allow: "allow",
|
|
63228
63828
|
ask: "prompt",
|
|
@@ -63278,10 +63878,14 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
63278
63878
|
* commands, and its deny / ask also restrict every other tool's policy (a
|
|
63279
63879
|
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
63280
63880
|
*
|
|
63881
|
+
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
63882
|
+
* `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
63883
|
+
* import a recognized value is read back into the override.
|
|
63884
|
+
*
|
|
63281
63885
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
63282
|
-
* (the keys rulesync manages)
|
|
63283
|
-
*
|
|
63284
|
-
* never deleted.
|
|
63886
|
+
* (the keys rulesync manages), `bash.patterns` and an authored
|
|
63887
|
+
* `tools.approvalMode` are rewritten; sibling keys such as `bash.enabled` are
|
|
63888
|
+
* kept, and the file is never deleted.
|
|
63285
63889
|
*
|
|
63286
63890
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
63287
63891
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
@@ -63324,16 +63928,19 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
63324
63928
|
fileContent: existingContent,
|
|
63325
63929
|
filePath
|
|
63326
63930
|
});
|
|
63931
|
+
const config = rulesyncPermissions.getJson();
|
|
63327
63932
|
const { approval, patterns, floor } = convertRulesyncToOmp({
|
|
63328
|
-
config
|
|
63933
|
+
config,
|
|
63329
63934
|
logger
|
|
63330
63935
|
});
|
|
63936
|
+
const approvalMode = config.omp?.approvalMode;
|
|
63331
63937
|
const existingTools = isRecord$1(existing.tools) ? existing.tools : {};
|
|
63332
63938
|
const nextApproval = {
|
|
63333
63939
|
...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
63940
|
...approval
|
|
63335
63941
|
};
|
|
63336
63942
|
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
63943
|
+
if (approvalMode !== void 0) toolsSiblings[APPROVAL_MODE_KEY] = approvalMode;
|
|
63337
63944
|
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
63338
63945
|
...toolsSiblings,
|
|
63339
63946
|
approval: nextApproval
|
|
@@ -63589,7 +64196,14 @@ function convertOmpToRulesync(config) {
|
|
|
63589
64196
|
permission[category] = { [CATCH_ALL_PATTERN$4]: stricter(permission[category]?.[CATCH_ALL_PATTERN$4], action) };
|
|
63590
64197
|
}
|
|
63591
64198
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
63592
|
-
|
|
64199
|
+
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
64200
|
+
return {
|
|
64201
|
+
permission,
|
|
64202
|
+
...isOmpApprovalMode(approvalMode) && { omp: { approvalMode } }
|
|
64203
|
+
};
|
|
64204
|
+
}
|
|
64205
|
+
function isOmpApprovalMode(value) {
|
|
64206
|
+
return OMP_APPROVAL_MODES.some((mode) => mode === value);
|
|
63593
64207
|
}
|
|
63594
64208
|
//#endregion
|
|
63595
64209
|
//#region src/features/permissions/pi-permissions.ts
|
|
@@ -64629,6 +65243,13 @@ const QWEN_OVERRIDE_SECURITY_KEYS = [
|
|
|
64629
65243
|
"allowPrivateNetworkHooks",
|
|
64630
65244
|
"allowedInsecureVoiceBaseUrls"
|
|
64631
65245
|
];
|
|
65246
|
+
const QWEN_OVERRIDE_SKILLS_KEYS = [
|
|
65247
|
+
"disabled",
|
|
65248
|
+
"defaultDisabled",
|
|
65249
|
+
"enabled",
|
|
65250
|
+
"disabledLevels"
|
|
65251
|
+
];
|
|
65252
|
+
const QWEN_OVERRIDE_GENERAL_KEYS = ["outputStyle"];
|
|
64632
65253
|
/**
|
|
64633
65254
|
* What each rule means for the two directions, so a key's behavior is declared
|
|
64634
65255
|
* once beside its rule rather than spelled out at every branch.
|
|
@@ -64685,77 +65306,125 @@ const QWEN_SCOPE_RULES = {
|
|
|
64685
65306
|
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
65307
|
}
|
|
64687
65308
|
};
|
|
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
|
-
|
|
65309
|
+
const QWEN_SCOPED_TOOLS_KEYS = {
|
|
65310
|
+
workflowsEnabled: { rule: "workspace-stripped" },
|
|
65311
|
+
executionSandbox: {
|
|
65312
|
+
rule: "workspace-stripped",
|
|
65313
|
+
grants: () => true,
|
|
65314
|
+
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)."
|
|
65315
|
+
},
|
|
65316
|
+
workflowNameOnly: {
|
|
65317
|
+
rule: "global-machine-wide",
|
|
65318
|
+
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.`,
|
|
65319
|
+
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."
|
|
65320
|
+
},
|
|
65321
|
+
approvalMode: {
|
|
65322
|
+
rule: "global-machine-wide",
|
|
65323
|
+
projectNote: autonomyProjectNote
|
|
65324
|
+
},
|
|
65325
|
+
autoAccept: {
|
|
65326
|
+
rule: "global-machine-wide",
|
|
65327
|
+
projectNote: autonomyProjectNote
|
|
65328
|
+
},
|
|
65329
|
+
sandbox: {
|
|
65330
|
+
rule: "global-machine-wide",
|
|
65331
|
+
projectNote: autonomyProjectNote
|
|
65332
|
+
},
|
|
65333
|
+
sandboxImage: {
|
|
65334
|
+
rule: "global-machine-wide",
|
|
65335
|
+
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.`,
|
|
65336
|
+
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."
|
|
65337
|
+
},
|
|
65338
|
+
disabled: {
|
|
65339
|
+
rule: "global-machine-wide",
|
|
65340
|
+
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.`,
|
|
65341
|
+
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."
|
|
65342
|
+
},
|
|
65343
|
+
visible: {
|
|
65344
|
+
rule: "global-machine-wide",
|
|
65345
|
+
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.`,
|
|
65346
|
+
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."
|
|
65347
|
+
},
|
|
65348
|
+
eager: {
|
|
65349
|
+
rule: "global-machine-wide",
|
|
65350
|
+
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.`,
|
|
65351
|
+
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."
|
|
65352
|
+
},
|
|
65353
|
+
listDirectory: {
|
|
65354
|
+
rule: "global-machine-wide",
|
|
65355
|
+
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.`,
|
|
65356
|
+
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."
|
|
65357
|
+
},
|
|
65358
|
+
todoWrite: {
|
|
65359
|
+
rule: "global-machine-wide",
|
|
65360
|
+
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.`,
|
|
65361
|
+
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."
|
|
65362
|
+
}
|
|
65363
|
+
};
|
|
65364
|
+
/**
|
|
65365
|
+
* The notes for a skills list that hides something. Qwen Code unions the list
|
|
65366
|
+
* across scopes, but the override replaces the one in the file it writes, so
|
|
65367
|
+
* the consequence worth naming is what dropping an entry hands back.
|
|
65368
|
+
*/
|
|
65369
|
+
function skillsHidingListNotes(hides) {
|
|
65370
|
+
return {
|
|
65371
|
+
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.`,
|
|
65372
|
+
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.`
|
|
65373
|
+
};
|
|
65374
|
+
}
|
|
65375
|
+
const QWEN_SCOPED_SKILLS_KEYS = {
|
|
65376
|
+
disabled: {
|
|
65377
|
+
rule: "global-machine-wide",
|
|
65378
|
+
...skillsHidingListNotes("skills are hidden")
|
|
65379
|
+
},
|
|
65380
|
+
defaultDisabled: {
|
|
65381
|
+
rule: "global-machine-wide",
|
|
65382
|
+
...skillsHidingListNotes("skills start disabled until `skills.enabled` opts into them")
|
|
65383
|
+
},
|
|
65384
|
+
disabledLevels: {
|
|
65385
|
+
rule: "global-machine-wide",
|
|
65386
|
+
...skillsHidingListNotes("skill discovery levels (`project`, `user`, `extension`, `bundled`) are skipped")
|
|
65387
|
+
},
|
|
65388
|
+
enabled: {
|
|
65389
|
+
rule: "global-machine-wide",
|
|
65390
|
+
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.`,
|
|
65391
|
+
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)."
|
|
65392
|
+
}
|
|
65393
|
+
};
|
|
65394
|
+
const QWEN_OVERRIDE_GROUPS = [
|
|
65395
|
+
{
|
|
65396
|
+
groupName: "tools",
|
|
65397
|
+
overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
|
|
65398
|
+
scopedKeys: QWEN_SCOPED_TOOLS_KEYS
|
|
65399
|
+
},
|
|
65400
|
+
{
|
|
65401
|
+
groupName: "security",
|
|
65402
|
+
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
65403
|
+
scopedKeys: {
|
|
65404
|
+
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
65405
|
+
allowedInsecureVoiceBaseUrls: {
|
|
65406
|
+
rule: "workspace-stripped",
|
|
65407
|
+
grants: isNonEmptyArray
|
|
65408
|
+
},
|
|
65409
|
+
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
65410
|
+
folderTrust: { rule: "user-scope-trust-check" }
|
|
65411
|
+
}
|
|
65412
|
+
},
|
|
65413
|
+
{
|
|
65414
|
+
groupName: "skills",
|
|
65415
|
+
overrideKeys: QWEN_OVERRIDE_SKILLS_KEYS,
|
|
65416
|
+
scopedKeys: QWEN_SCOPED_SKILLS_KEYS
|
|
65417
|
+
},
|
|
65418
|
+
{
|
|
65419
|
+
groupName: "general",
|
|
65420
|
+
overrideKeys: QWEN_OVERRIDE_GENERAL_KEYS,
|
|
65421
|
+
scopedKeys: { outputStyle: {
|
|
64741
65422
|
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" }
|
|
65423
|
+
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.`,
|
|
65424
|
+
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`)."
|
|
65425
|
+
} }
|
|
64757
65426
|
}
|
|
64758
|
-
|
|
65427
|
+
];
|
|
64759
65428
|
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
64760
65429
|
const QWEN_SCOPED_PERMISSIONS_KEYS = { autoMode: {
|
|
64761
65430
|
rule: "global-machine-wide",
|
|
@@ -64886,7 +65555,7 @@ function scopeOverrideGroup(overrideGroup, { groupName, scopedKeys, existingGrou
|
|
|
64886
65555
|
return scoped;
|
|
64887
65556
|
}
|
|
64888
65557
|
/**
|
|
64889
|
-
* Build the `tools`/`security` patch groups contributed by the `qwencode`
|
|
65558
|
+
* Build the `tools`/`security`/`skills`/`general` patch groups contributed by the `qwencode`
|
|
64890
65559
|
* override. Each group is shallow-merged over what `settings.json` already has,
|
|
64891
65560
|
* after the scope gate has handled the keys Qwen Code honors in one scope only.
|
|
64892
65561
|
*/
|
|
@@ -65055,7 +65724,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
65055
65724
|
});
|
|
65056
65725
|
const overrideGroups = {
|
|
65057
65726
|
tools: pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS),
|
|
65058
|
-
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS)
|
|
65727
|
+
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS),
|
|
65728
|
+
skills: pickQwenOverrideKeys(settings.skills, QWEN_OVERRIDE_SKILLS_KEYS),
|
|
65729
|
+
general: pickQwenOverrideKeys(settings.general, QWEN_OVERRIDE_GENERAL_KEYS)
|
|
65059
65730
|
};
|
|
65060
65731
|
if (!this.global) warnAboutImportedScopedKeys(overrideGroups);
|
|
65061
65732
|
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
@@ -65358,6 +66029,37 @@ function applyAllowDynamicBash({ permissions, authored }) {
|
|
|
65358
66029
|
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
66030
|
}];
|
|
65360
66031
|
}
|
|
66032
|
+
/** The writer fallbacks `[permissions] mode` accepts. */
|
|
66033
|
+
const REASONIX_PERMISSION_MODES = /* @__PURE__ */ new Set([
|
|
66034
|
+
"ask",
|
|
66035
|
+
"allow",
|
|
66036
|
+
"deny"
|
|
66037
|
+
]);
|
|
66038
|
+
/**
|
|
66039
|
+
* Writes the override's `mode` into `[permissions]`. Reasonix honors the key
|
|
66040
|
+
* only in the user config and ignores a project value as user-scope only, so a
|
|
66041
|
+
* project-scope generate skips it with a warning and leaves whatever the file
|
|
66042
|
+
* had. A global `allow` is reported when it is newly written: it lets every
|
|
66043
|
+
* writer call that no rule matches run without a prompt.
|
|
66044
|
+
*
|
|
66045
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
66046
|
+
*/
|
|
66047
|
+
function applyPermissionsMode({ permissions, authored, global, relativeFilePath, logger }) {
|
|
66048
|
+
if (authored === void 0) return [];
|
|
66049
|
+
if (!global) {
|
|
66050
|
+
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.`);
|
|
66051
|
+
return [];
|
|
66052
|
+
}
|
|
66053
|
+
const previous = permissions.mode;
|
|
66054
|
+
permissions.mode = authored;
|
|
66055
|
+
const normalize = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
66056
|
+
const mode = normalize(authored);
|
|
66057
|
+
if (mode === "ask" || mode === "deny" || normalize(previous) === mode) return [];
|
|
66058
|
+
return [{
|
|
66059
|
+
label: "permissions.mode",
|
|
66060
|
+
reason: "lets every writer call that no rule matches run without a prompt"
|
|
66061
|
+
}];
|
|
66062
|
+
}
|
|
65361
66063
|
function parseReasonixConfig(fileContent) {
|
|
65362
66064
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
65363
66065
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -65408,7 +66110,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65408
66110
|
relativeDirPath: paths.relativeDirPath,
|
|
65409
66111
|
relativeFilePath: paths.relativeFilePath,
|
|
65410
66112
|
fileContent,
|
|
65411
|
-
validate
|
|
66113
|
+
validate,
|
|
66114
|
+
global
|
|
65412
66115
|
});
|
|
65413
66116
|
}
|
|
65414
66117
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
@@ -65463,6 +66166,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65463
66166
|
permissions: mergedPermissions,
|
|
65464
66167
|
authored: override?.allowDynamicBash
|
|
65465
66168
|
});
|
|
66169
|
+
trustAffecting.push(...applyPermissionsMode({
|
|
66170
|
+
permissions: mergedPermissions,
|
|
66171
|
+
authored: override?.mode,
|
|
66172
|
+
global,
|
|
66173
|
+
relativeFilePath: relativeFilePathForLog,
|
|
66174
|
+
logger
|
|
66175
|
+
}));
|
|
65466
66176
|
const patch = { permissions: mergedPermissions };
|
|
65467
66177
|
if (override?.sandbox !== void 0) {
|
|
65468
66178
|
const mergedSandbox = {
|
|
@@ -65504,7 +66214,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65504
66214
|
patch,
|
|
65505
66215
|
filePath
|
|
65506
66216
|
}),
|
|
65507
|
-
validate
|
|
66217
|
+
validate,
|
|
66218
|
+
global
|
|
65508
66219
|
});
|
|
65509
66220
|
}
|
|
65510
66221
|
toRulesyncPermissions() {
|
|
@@ -65529,6 +66240,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65529
66240
|
const reasonixOverride = {};
|
|
65530
66241
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
65531
66242
|
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
66243
|
+
if (this.global && typeof permissions.mode === "string" && REASONIX_PERMISSION_MODES.has(permissions.mode)) reasonixOverride.mode = permissions.mode;
|
|
65532
66244
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
65533
66245
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
65534
66246
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -69248,6 +69960,9 @@ const WARP_OVERRIDE_KEYS = [
|
|
|
69248
69960
|
"agent_mode_execute_readonly_commands"
|
|
69249
69961
|
];
|
|
69250
69962
|
const WARP_EXECUTION_PROFILE_OVERRIDE_KEY = "execution_profile";
|
|
69963
|
+
const WARP_AUTO_APPROVE_BYPASS_KEY = "auto_approve_bypasses_command_denylist";
|
|
69964
|
+
const WARP_AGENT_KEY = "warp_agent";
|
|
69965
|
+
const WARP_AGENT_OTHER_KEY = "other";
|
|
69251
69966
|
const WARP_EXECUTION_PROFILE_KEYS = [
|
|
69252
69967
|
"read_files",
|
|
69253
69968
|
"apply_code_diffs",
|
|
@@ -69396,7 +70111,7 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69396
70111
|
const override = config.warp;
|
|
69397
70112
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69398
70113
|
if (isRecord$1(override)) {
|
|
69399
|
-
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
70114
|
+
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, [WARP_AUTO_APPROVE_BYPASS_KEY]: _autoApproveBypass, ...legacyOverride } = override;
|
|
69400
70115
|
Object.assign(profiles, legacyOverride);
|
|
69401
70116
|
}
|
|
69402
70117
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
@@ -69406,9 +70121,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69406
70121
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
69407
70122
|
else delete profiles[DENYLIST_KEY];
|
|
69408
70123
|
agents.profiles = profiles;
|
|
70124
|
+
applyAutoApproveBypassOverride({
|
|
70125
|
+
agents,
|
|
70126
|
+
override
|
|
70127
|
+
});
|
|
69409
70128
|
warnAboutDenylistReplacement({
|
|
69410
70129
|
toolLabel: "Warp",
|
|
70130
|
+
overrideKey: "warp",
|
|
69411
70131
|
denyCount: mergedDeny.length,
|
|
70132
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
69412
70133
|
logger
|
|
69413
70134
|
});
|
|
69414
70135
|
mergeIntoDefaultExecutionProfile({
|
|
@@ -69447,6 +70168,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69447
70168
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
69448
70169
|
const executionProfileOverride = defaultProfile ? liftExecutionProfileOverride(defaultProfile) : void 0;
|
|
69449
70170
|
if (executionProfileOverride) warpOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70171
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70172
|
+
if (autoApproveBypass !== void 0) warpOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69450
70173
|
const result = { ...config };
|
|
69451
70174
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
69452
70175
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -69470,12 +70193,40 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69470
70193
|
};
|
|
69471
70194
|
/**
|
|
69472
70195
|
* Writing `command_denylist` at all replaces Warp's built-in default denylist,
|
|
69473
|
-
* so any non-empty list rulesync writes deserves a heads-up.
|
|
70196
|
+
* so any non-empty list rulesync writes deserves a heads-up. Unless
|
|
70197
|
+
* `auto_approve_bypasses_command_denylist` is `false`, auto-approve also runs
|
|
70198
|
+
* commands that match the list, so the warning says so.
|
|
69474
70199
|
* https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
69475
70200
|
*/
|
|
69476
|
-
function warnAboutDenylistReplacement({ toolLabel, denyCount, logger }) {
|
|
70201
|
+
function warnAboutDenylistReplacement({ toolLabel, overrideKey, denyCount, autoApproveBypassesDenylist, logger }) {
|
|
69477
70202
|
if (denyCount === 0 || !logger) return;
|
|
69478
|
-
|
|
70203
|
+
const bypassNote = autoApproveBypassesDenylist === false ? "" : ` Auto-approve also runs commands that match the denylist unless ${overrideKey}.${WARP_AUTO_APPROVE_BYPASS_KEY} is set to false.`;
|
|
70204
|
+
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}`);
|
|
70205
|
+
}
|
|
70206
|
+
/**
|
|
70207
|
+
* Write the override's `auto_approve_bypasses_command_denylist` into
|
|
70208
|
+
* `[agents.warp_agent.other]`, keeping every sibling key of `agents.warp_agent`
|
|
70209
|
+
* and of its `other` table. Nothing is written when the override omits it.
|
|
70210
|
+
*/
|
|
70211
|
+
function applyAutoApproveBypassOverride({ agents, override }) {
|
|
70212
|
+
if (!isRecord$1(override)) return;
|
|
70213
|
+
const value = override[WARP_AUTO_APPROVE_BYPASS_KEY];
|
|
70214
|
+
if (typeof value !== "boolean") return;
|
|
70215
|
+
const warpAgent = isRecord$1(agents[WARP_AGENT_KEY]) ? { ...agents[WARP_AGENT_KEY] } : {};
|
|
70216
|
+
const other = isRecord$1(warpAgent[WARP_AGENT_OTHER_KEY]) ? { ...warpAgent[WARP_AGENT_OTHER_KEY] } : {};
|
|
70217
|
+
other[WARP_AUTO_APPROVE_BYPASS_KEY] = value;
|
|
70218
|
+
warpAgent[WARP_AGENT_OTHER_KEY] = other;
|
|
70219
|
+
agents[WARP_AGENT_KEY] = warpAgent;
|
|
70220
|
+
}
|
|
70221
|
+
/**
|
|
70222
|
+
* Read `[agents.warp_agent.other] auto_approve_bypasses_command_denylist`, or
|
|
70223
|
+
* `undefined` when it is unset (Warp then defaults to `true`).
|
|
70224
|
+
*/
|
|
70225
|
+
function readAutoApproveBypass(agents) {
|
|
70226
|
+
const warpAgent = agents[WARP_AGENT_KEY];
|
|
70227
|
+
const other = isRecord$1(warpAgent) ? warpAgent[WARP_AGENT_OTHER_KEY] : void 0;
|
|
70228
|
+
const value = isRecord$1(other) ? other[WARP_AUTO_APPROVE_BYPASS_KEY] : void 0;
|
|
70229
|
+
return typeof value === "boolean" ? value : void 0;
|
|
69479
70230
|
}
|
|
69480
70231
|
/**
|
|
69481
70232
|
* On migrated installs the legacy `[agents.profiles]` keys are inert: runtime
|
|
@@ -69829,6 +70580,9 @@ const WARPCLI_GLOBAL_ONLY_MESSAGE = "Warp Agent CLI permissions are global-only;
|
|
|
69829
70580
|
* (`read_files`, `apply_code_diffs`, `run_agents`, ...), merged first so the
|
|
69830
70581
|
* rulesync-owned command lists always win.
|
|
69831
70582
|
*
|
|
70583
|
+
* The `warpcli.auto_approve_bypasses_command_denylist` override is written to
|
|
70584
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
70585
|
+
*
|
|
69832
70586
|
* Unlike the `warp` target, the CLI never read the app's legacy
|
|
69833
70587
|
* `[agents.profiles]` keys, so none are written, and there is no settings
|
|
69834
70588
|
* migration to wait for: the CLI's settings file "is created the first time you
|
|
@@ -69895,11 +70649,6 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69895
70649
|
});
|
|
69896
70650
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
69897
70651
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
69898
|
-
warnAboutDenylistReplacement({
|
|
69899
|
-
toolLabel: "Warp Agent CLI",
|
|
69900
|
-
denyCount: mergedDeny.length,
|
|
69901
|
-
logger
|
|
69902
|
-
});
|
|
69903
70652
|
const override = config.warpcli;
|
|
69904
70653
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69905
70654
|
const agents = isRecord$1(settings.agents) ? { ...settings.agents } : {};
|
|
@@ -69909,7 +70658,18 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69909
70658
|
mergedDeny,
|
|
69910
70659
|
executionProfileOverride
|
|
69911
70660
|
});
|
|
70661
|
+
applyAutoApproveBypassOverride({
|
|
70662
|
+
agents,
|
|
70663
|
+
override
|
|
70664
|
+
});
|
|
69912
70665
|
settings.agents = agents;
|
|
70666
|
+
warnAboutDenylistReplacement({
|
|
70667
|
+
toolLabel: "Warp Agent CLI",
|
|
70668
|
+
overrideKey: "warpcli",
|
|
70669
|
+
denyCount: mergedDeny.length,
|
|
70670
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
70671
|
+
logger
|
|
70672
|
+
});
|
|
69913
70673
|
return new WarpcliPermissions({
|
|
69914
70674
|
outputRoot,
|
|
69915
70675
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -69936,8 +70696,12 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69936
70696
|
deny: isStringArray$2(denylist) ? denylist : []
|
|
69937
70697
|
});
|
|
69938
70698
|
const executionProfileOverride = liftExecutionProfileOverride(defaultProfile);
|
|
70699
|
+
const warpcliOverride = {};
|
|
70700
|
+
if (executionProfileOverride) warpcliOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70701
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70702
|
+
if (autoApproveBypass !== void 0) warpcliOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69939
70703
|
const result = { ...config };
|
|
69940
|
-
if (
|
|
70704
|
+
if (Object.keys(warpcliOverride).length > 0) result.warpcli = warpcliOverride;
|
|
69941
70705
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
69942
70706
|
}
|
|
69943
70707
|
validate() {
|
|
@@ -82011,9 +82775,8 @@ var CodebuddySubagent = class CodebuddySubagent extends ToolSubagent {
|
|
|
82011
82775
|
* Keys of the rulesync `codewhale` section copied into the profile. Codewhale
|
|
82012
82776
|
* parses agent profiles with `deny_unknown_fields`, so an unknown key would
|
|
82013
82777
|
* 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.
|
|
82778
|
+
* with a warning. The `[tools]` and `[permissions]` tables are handled
|
|
82779
|
+
* separately (see `CODEWHALE_NARROWING_TABLES`).
|
|
82017
82780
|
*/
|
|
82018
82781
|
const CODEWHALE_SECTION_KEYS = [
|
|
82019
82782
|
"role_hint",
|
|
@@ -82034,6 +82797,48 @@ const CODEWHALE_SECTION_KEY_ALIASES = {
|
|
|
82034
82797
|
reasoning_effort: ["thinking", "reasoning"]
|
|
82035
82798
|
};
|
|
82036
82799
|
const CODEWHALE_SECTION_ALIAS_TO_KEY = new Map(Object.entries(CODEWHALE_SECTION_KEY_ALIASES).flatMap(([key, aliases]) => (aliases ?? []).map((alias) => [alias, key])));
|
|
82800
|
+
/**
|
|
82801
|
+
* The `[tools]` and `[permissions]` tables of an agent profile, with the only
|
|
82802
|
+
* values Codewhale accepts in each key. Codewhale rejects a profile that tries
|
|
82803
|
+
* to widen access through them (`reject_permission_expansion` in
|
|
82804
|
+
* `fleet/profile.rs`), and both tables deny unknown fields, so any other key
|
|
82805
|
+
* or value is dropped with a warning.
|
|
82806
|
+
*/
|
|
82807
|
+
const CODEWHALE_NARROWING_TABLES = {
|
|
82808
|
+
tools: { posture: (value) => typeof value === "string" && [
|
|
82809
|
+
"read-only",
|
|
82810
|
+
"readonly",
|
|
82811
|
+
"read_only"
|
|
82812
|
+
].includes(value) },
|
|
82813
|
+
permissions: {
|
|
82814
|
+
allow_shell: (value) => value === false,
|
|
82815
|
+
trust: (value) => value === false,
|
|
82816
|
+
approval_required: (value) => value === true
|
|
82817
|
+
}
|
|
82818
|
+
};
|
|
82819
|
+
/**
|
|
82820
|
+
* Keeps the narrowing-only entries of a `[tools]` / `[permissions]` table and
|
|
82821
|
+
* reports every other entry by its dotted path. A non-table value is dropped
|
|
82822
|
+
* as a whole.
|
|
82823
|
+
*/
|
|
82824
|
+
function filterNarrowingTable(tableName, value) {
|
|
82825
|
+
const allowed = CODEWHALE_NARROWING_TABLES[tableName] ?? {};
|
|
82826
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {
|
|
82827
|
+
kept: void 0,
|
|
82828
|
+
dropped: [tableName]
|
|
82829
|
+
};
|
|
82830
|
+
const kept = {};
|
|
82831
|
+
const dropped = [];
|
|
82832
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
82833
|
+
const normalized = typeof entry === "string" ? entry.trim() : entry;
|
|
82834
|
+
if (Object.hasOwn(allowed, key) && allowed[key]?.(normalized)) kept[key] = normalized;
|
|
82835
|
+
else dropped.push(`${tableName}.${key}`);
|
|
82836
|
+
}
|
|
82837
|
+
return {
|
|
82838
|
+
kept: Object.keys(kept).length > 0 ? kept : void 0,
|
|
82839
|
+
dropped
|
|
82840
|
+
};
|
|
82841
|
+
}
|
|
82037
82842
|
const CodewhaleSubagentTomlSchema = zod_mini.z.looseObject({
|
|
82038
82843
|
id: zod_mini.z.optional(zod_mini.z.string()),
|
|
82039
82844
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -82060,7 +82865,8 @@ function stringifyCodewhaleSubagentToml(fields, instructions) {
|
|
|
82060
82865
|
*
|
|
82061
82866
|
* Generation writes `id` (the file stem), `display_name` (the rulesync name),
|
|
82062
82867
|
* `description`, and the body as `[instructions] text`, plus the allowlisted
|
|
82063
|
-
* keys of the rulesync `codewhale` section
|
|
82868
|
+
* keys of the rulesync `codewhale` section and its narrowing-only `[tools]` /
|
|
82869
|
+
* `[permissions]` entries.
|
|
82064
82870
|
*
|
|
82065
82871
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/SUBAGENTS.md
|
|
82066
82872
|
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/fleet/profile.rs
|
|
@@ -82095,6 +82901,10 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82095
82901
|
const spelling = [key, ...CODEWHALE_SECTION_KEY_ALIASES[key] ?? []].find((candidate) => record[candidate] !== void 0);
|
|
82096
82902
|
if (spelling !== void 0) codewhaleSection[key] = record[spelling];
|
|
82097
82903
|
}
|
|
82904
|
+
for (const tableName of Object.keys(CODEWHALE_NARROWING_TABLES)) if (record[tableName] !== void 0) {
|
|
82905
|
+
const { kept } = filterNarrowingTable(tableName, record[tableName]);
|
|
82906
|
+
if (kept !== void 0) codewhaleSection[tableName] = kept;
|
|
82907
|
+
}
|
|
82098
82908
|
return new RulesyncSubagent({
|
|
82099
82909
|
outputRoot: ".",
|
|
82100
82910
|
frontmatter: {
|
|
@@ -82117,6 +82927,12 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82117
82927
|
const droppedKeys = [];
|
|
82118
82928
|
const sectionSpellings = /* @__PURE__ */ new Map();
|
|
82119
82929
|
for (const [rawKey, value] of Object.entries(rawSection)) {
|
|
82930
|
+
if (Object.hasOwn(CODEWHALE_NARROWING_TABLES, rawKey)) {
|
|
82931
|
+
const { kept, dropped } = filterNarrowingTable(rawKey, value);
|
|
82932
|
+
if (kept !== void 0) sectionFields[rawKey] = kept;
|
|
82933
|
+
droppedKeys.push(...dropped);
|
|
82934
|
+
continue;
|
|
82935
|
+
}
|
|
82120
82936
|
const key = CODEWHALE_SECTION_ALIAS_TO_KEY.get(rawKey) ?? rawKey;
|
|
82121
82937
|
if (CODEWHALE_SECTION_KEYS.includes(key) && typeof value === "string") {
|
|
82122
82938
|
if (key === rawKey || sectionFields[key] === void 0) {
|
|
@@ -82126,7 +82942,7 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82126
82942
|
} else droppedKeys.push(rawKey);
|
|
82127
82943
|
} else droppedKeys.push(rawKey);
|
|
82128
82944
|
}
|
|
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).`);
|
|
82945
|
+
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
82946
|
const body = stringifyCodewhaleSubagentToml({
|
|
82131
82947
|
id: toProfileId(relativeFilePath),
|
|
82132
82948
|
display_name: frontmatter.name,
|
|
@@ -88623,6 +89439,99 @@ const AntigravityRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
88623
89439
|
function toGlobalRuleFileName(relativeFilePath) {
|
|
88624
89440
|
return relativeFilePath.split(/[\\/]/).join("-");
|
|
88625
89441
|
}
|
|
89442
|
+
/** The project rules directory both Antigravity targets share, as a POSIX path. */
|
|
89443
|
+
const PROJECT_RULES_DIR = `${ANTIGRAVITY_IDE_AGENTS_DIR}/rules`;
|
|
89444
|
+
/**
|
|
89445
|
+
* The subproject directory a rule is scoped to, or `undefined` when it goes to
|
|
89446
|
+
* the project-root `.agents/rules/`. Antigravity reads a `.agents/rules/`
|
|
89447
|
+
* directory in any subdirectory and loads it for files under that directory,
|
|
89448
|
+
* so a non-root rule carrying `agentsmd.subprojectPath` is written there. Project
|
|
89449
|
+
* scope only: there is no workspace tree to nest under in global scope.
|
|
89450
|
+
*
|
|
89451
|
+
* @see https://antigravity.google/docs/rules
|
|
89452
|
+
*/
|
|
89453
|
+
function resolveAntigravitySubprojectPath({ rulesyncRule, global }) {
|
|
89454
|
+
const { root, agentsmd } = rulesyncRule.getFrontmatter();
|
|
89455
|
+
const subprojectPath = !global && root !== true ? agentsmd?.subprojectPath : void 0;
|
|
89456
|
+
return subprojectPath && ![
|
|
89457
|
+
"",
|
|
89458
|
+
".",
|
|
89459
|
+
"./"
|
|
89460
|
+
].includes(subprojectPath) ? subprojectPath : void 0;
|
|
89461
|
+
}
|
|
89462
|
+
/**
|
|
89463
|
+
* File name of a rule in a subproject's `.agents/rules/`. Antigravity reads
|
|
89464
|
+
* only the direct children of a rules directory, so a nested rulesync rule
|
|
89465
|
+
* (`frontend/style.md`) is flattened (`frontend-style.md`), and the name keeps
|
|
89466
|
+
* its case so the IDE and the CLI write the same file and an imported
|
|
89467
|
+
* `codeStyle.md` is written back to the same place.
|
|
89468
|
+
*/
|
|
89469
|
+
function toNestedRuleFileName(relativeFilePath) {
|
|
89470
|
+
return toGlobalRuleFileName(relativeFilePath);
|
|
89471
|
+
}
|
|
89472
|
+
/** The `.agents/rules/` directory of a subproject. */
|
|
89473
|
+
function toAntigravityNestedRulesDirPath(subprojectPath) {
|
|
89474
|
+
return (0, node_path.join)(subprojectPath, ANTIGRAVITY_IDE_AGENTS_DIR, "rules");
|
|
89475
|
+
}
|
|
89476
|
+
/**
|
|
89477
|
+
* Split a nested rule directory such as `packages/api/.agents/rules/sub` into
|
|
89478
|
+
* the subproject (`packages/api`) and the folder inside its rules directory
|
|
89479
|
+
* (`sub`). Returns `undefined` for the project-root `.agents/rules/`, for a
|
|
89480
|
+
* path with no `.agents/rules` segment, and for one under a hidden directory —
|
|
89481
|
+
* another tool's output or a worktree, not a subproject.
|
|
89482
|
+
*/
|
|
89483
|
+
function extractAntigravitySubprojectPath(relativeDirPath) {
|
|
89484
|
+
const segments = toPosixPath(relativeDirPath).split("/");
|
|
89485
|
+
const index = segments.findIndex((segment, i) => segment === ".agents" && segments[i + 1] === "rules");
|
|
89486
|
+
if (index <= 0) return;
|
|
89487
|
+
const subprojectSegments = segments.slice(0, index);
|
|
89488
|
+
if (subprojectSegments.some((segment) => segment === "" || segment.startsWith("."))) return;
|
|
89489
|
+
return {
|
|
89490
|
+
subprojectPath: subprojectSegments.join("/"),
|
|
89491
|
+
innerDirPath: segments.slice(index + 2).join("/")
|
|
89492
|
+
};
|
|
89493
|
+
}
|
|
89494
|
+
/**
|
|
89495
|
+
* The `.agents/rules/` folders of project subdirectories. Only their direct
|
|
89496
|
+
* `.md` children are read, because Antigravity ignores files nested in
|
|
89497
|
+
* subfolders of a rules directory. The shared nested
|
|
89498
|
+
* scan ignores every hidden directory, which would skip `.agents` itself, so
|
|
89499
|
+
* only a `.agents/rules/` below another hidden directory is ignored here
|
|
89500
|
+
* (`extractAntigravitySubprojectPath` rejects one again on import). Import-only,
|
|
89501
|
+
* like every other nested scan: these files sit outside the rulesync-owned root
|
|
89502
|
+
* `.agents/rules/`, so `--delete` never sweeps them.
|
|
89503
|
+
*/
|
|
89504
|
+
function getAntigravityNestedFilePatterns() {
|
|
89505
|
+
return {
|
|
89506
|
+
include: [`**/${PROJECT_RULES_DIR}/*.md`],
|
|
89507
|
+
ignore: [
|
|
89508
|
+
`${PROJECT_RULES_DIR}/**`,
|
|
89509
|
+
`**/.*/**/${PROJECT_RULES_DIR}/**`,
|
|
89510
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `**/${dir}/**`),
|
|
89511
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${dir}/**`)
|
|
89512
|
+
],
|
|
89513
|
+
ownedDirPath: PROJECT_RULES_DIR
|
|
89514
|
+
};
|
|
89515
|
+
}
|
|
89516
|
+
/**
|
|
89517
|
+
* Resolve where a non-root project rule file read from disk lives. A file
|
|
89518
|
+
* discovered by `getNestedFilePatterns` arrives as its own directory plus its
|
|
89519
|
+
* basename; it is re-split into the subproject's rules directory and the path
|
|
89520
|
+
* inside it, matching how the root `.agents/rules/` is read.
|
|
89521
|
+
*/
|
|
89522
|
+
function resolveAntigravityNonRootFile({ relativeDirPath, relativeFilePath, defaultDirPath }) {
|
|
89523
|
+
const nested = relativeDirPath === void 0 ? void 0 : extractAntigravitySubprojectPath(relativeDirPath);
|
|
89524
|
+
if (nested === void 0) return {
|
|
89525
|
+
relativeDirPath: defaultDirPath,
|
|
89526
|
+
relativeFilePath
|
|
89527
|
+
};
|
|
89528
|
+
return {
|
|
89529
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(nested.subprojectPath),
|
|
89530
|
+
relativeFilePath: (0, node_path.join)(nested.innerDirPath, relativeFilePath)
|
|
89531
|
+
};
|
|
89532
|
+
}
|
|
89533
|
+
/** The targets a nested rule is imported with: the two that read the shared tree. */
|
|
89534
|
+
const ANTIGRAVITY_NESTED_RULE_TARGETS = ["antigravity-ide", "antigravity-cli"];
|
|
88626
89535
|
/**
|
|
88627
89536
|
* Converts a comma-separated globs string or array to an array of globs.
|
|
88628
89537
|
* @param globs - Comma-separated globs string (e.g., "*.ts,*.js") or array of globs
|
|
@@ -88810,12 +89719,17 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88810
89719
|
});
|
|
88811
89720
|
}
|
|
88812
89721
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
88813
|
-
const
|
|
89722
|
+
const nonRoot = resolveAntigravityNonRootFile({
|
|
89723
|
+
relativeDirPath,
|
|
89724
|
+
relativeFilePath,
|
|
89725
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
89726
|
+
});
|
|
89727
|
+
const relativePath = (0, node_path.join)(nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
88814
89728
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativePath));
|
|
88815
89729
|
const rule = new AntigravityCliRule({
|
|
88816
89730
|
outputRoot,
|
|
88817
|
-
relativeDirPath:
|
|
88818
|
-
relativeFilePath,
|
|
89731
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
89732
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
88819
89733
|
fileContent,
|
|
88820
89734
|
validate,
|
|
88821
89735
|
root: false,
|
|
@@ -88847,11 +89761,21 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88847
89761
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
88848
89762
|
const frontmatter = strategy.generateFrontmatter(normalizeStoredAntigravity(storedAntigravity), rulesyncFrontmatter);
|
|
88849
89763
|
const fileContent = stringifyFrontmatter(rulesyncRule.getBody(), frontmatter);
|
|
88850
|
-
if (!global)
|
|
88851
|
-
|
|
88852
|
-
|
|
88853
|
-
|
|
88854
|
-
|
|
89764
|
+
if (!global) {
|
|
89765
|
+
const subprojectPath = resolveAntigravitySubprojectPath({
|
|
89766
|
+
rulesyncRule,
|
|
89767
|
+
global
|
|
89768
|
+
});
|
|
89769
|
+
return new AntigravityCliRule({
|
|
89770
|
+
...params,
|
|
89771
|
+
...subprojectPath !== void 0 && {
|
|
89772
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89773
|
+
relativeFilePath: toNestedRuleFileName(params.relativeFilePath)
|
|
89774
|
+
},
|
|
89775
|
+
fileContent,
|
|
89776
|
+
global
|
|
89777
|
+
});
|
|
89778
|
+
}
|
|
88855
89779
|
return new AntigravityCliRule({
|
|
88856
89780
|
...params,
|
|
88857
89781
|
relativeFilePath: toGlobalRuleFileName(params.relativeFilePath),
|
|
@@ -88867,22 +89791,32 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88867
89791
|
globs: [],
|
|
88868
89792
|
antigravity: frontmatter
|
|
88869
89793
|
};
|
|
89794
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
88870
89795
|
return new RulesyncRule({
|
|
88871
89796
|
outputRoot: process.cwd(),
|
|
88872
89797
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
88873
89798
|
relativeFilePath: this.getRelativeFilePath(),
|
|
88874
89799
|
frontmatter: {
|
|
88875
89800
|
root: false,
|
|
88876
|
-
targets: ["*"],
|
|
89801
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
88877
89802
|
...rulesyncData,
|
|
88878
89803
|
antigravity: {
|
|
88879
89804
|
...rulesyncData.antigravity,
|
|
88880
89805
|
globs: frontmatter.globs ? parseGlobsString$1(frontmatter.globs) : void 0
|
|
88881
|
-
}
|
|
89806
|
+
},
|
|
89807
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
88882
89808
|
},
|
|
88883
89809
|
body
|
|
88884
89810
|
});
|
|
88885
89811
|
}
|
|
89812
|
+
/**
|
|
89813
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
89814
|
+
* directory-scoped rules.
|
|
89815
|
+
* @see https://antigravity.google/docs/rules
|
|
89816
|
+
*/
|
|
89817
|
+
static getNestedFilePatterns() {
|
|
89818
|
+
return getAntigravityNestedFilePatterns();
|
|
89819
|
+
}
|
|
88886
89820
|
validate() {
|
|
88887
89821
|
if (this.root) return {
|
|
88888
89822
|
success: true,
|
|
@@ -89012,8 +89946,15 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89012
89946
|
global
|
|
89013
89947
|
});
|
|
89014
89948
|
}
|
|
89015
|
-
const
|
|
89016
|
-
|
|
89949
|
+
const nonRoot = this.supportsSubprojectRules() ? resolveAntigravityNonRootFile({
|
|
89950
|
+
relativeDirPath,
|
|
89951
|
+
relativeFilePath,
|
|
89952
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
89953
|
+
}) : {
|
|
89954
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
89955
|
+
relativeFilePath
|
|
89956
|
+
};
|
|
89957
|
+
const filePath = (0, node_path.join)(outputRoot, nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
89017
89958
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
89018
89959
|
let parsedFrontmatter;
|
|
89019
89960
|
if (validate) {
|
|
@@ -89023,8 +89964,8 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89023
89964
|
} else parsedFrontmatter = frontmatter;
|
|
89024
89965
|
return new this({
|
|
89025
89966
|
outputRoot,
|
|
89026
|
-
relativeDirPath:
|
|
89027
|
-
relativeFilePath,
|
|
89967
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
89968
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
89028
89969
|
body,
|
|
89029
89970
|
frontmatter: parsedFrontmatter,
|
|
89030
89971
|
validate,
|
|
@@ -89032,6 +89973,25 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89032
89973
|
global
|
|
89033
89974
|
});
|
|
89034
89975
|
}
|
|
89976
|
+
/**
|
|
89977
|
+
* Whether a rule scoped to a subproject goes to that subproject's own
|
|
89978
|
+
* `.agents/rules/`. A subclass that writes somewhere other than a project
|
|
89979
|
+
* tree (the plugin bundle) turns it off.
|
|
89980
|
+
*/
|
|
89981
|
+
static supportsSubprojectRules() {
|
|
89982
|
+
return true;
|
|
89983
|
+
}
|
|
89984
|
+
/**
|
|
89985
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
89986
|
+
* directory-scoped rules.
|
|
89987
|
+
* @see https://antigravity.google/docs/rules
|
|
89988
|
+
*/
|
|
89989
|
+
static getNestedFilePatterns() {
|
|
89990
|
+
return this.supportsSubprojectRules() ? getAntigravityNestedFilePatterns() : {
|
|
89991
|
+
include: [],
|
|
89992
|
+
ignore: []
|
|
89993
|
+
};
|
|
89994
|
+
}
|
|
89035
89995
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
89036
89996
|
const paths = this.getSettablePaths({ global });
|
|
89037
89997
|
if (rulesyncRule.getFrontmatter().root) {
|
|
@@ -89054,10 +90014,14 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89054
90014
|
const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
|
|
89055
90015
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
89056
90016
|
const frontmatter = strategy.generateFrontmatter(normalized, rulesyncFrontmatter);
|
|
89057
|
-
const
|
|
90017
|
+
const subprojectPath = this.supportsSubprojectRules() ? resolveAntigravitySubprojectPath({
|
|
90018
|
+
rulesyncRule,
|
|
90019
|
+
global
|
|
90020
|
+
}) : void 0;
|
|
90021
|
+
const relativeFilePath = global || subprojectPath !== void 0 ? toNestedRuleFileName(rulesyncRule.getRelativeFilePath()) : toKebabCaseFilename(rulesyncRule.getRelativeFilePath());
|
|
89058
90022
|
return new this({
|
|
89059
90023
|
outputRoot,
|
|
89060
|
-
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
90024
|
+
relativeDirPath: subprojectPath === void 0 ? paths.nonRoot.relativeDirPath : toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89061
90025
|
relativeFilePath,
|
|
89062
90026
|
frontmatter,
|
|
89063
90027
|
body: rulesyncRule.getBody(),
|
|
@@ -89078,15 +90042,17 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89078
90042
|
...rulesyncData.antigravity,
|
|
89079
90043
|
globs: this.frontmatter.globs ? parseGlobsString$1(this.frontmatter.globs) : void 0
|
|
89080
90044
|
};
|
|
90045
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
89081
90046
|
return new RulesyncRule({
|
|
89082
90047
|
outputRoot: process.cwd(),
|
|
89083
90048
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
89084
90049
|
relativeFilePath: this.getRelativeFilePath(),
|
|
89085
90050
|
frontmatter: {
|
|
89086
90051
|
root: false,
|
|
89087
|
-
targets: ["*"],
|
|
90052
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
89088
90053
|
...rulesyncData,
|
|
89089
|
-
antigravity: antigravityForRulesync
|
|
90054
|
+
antigravity: antigravityForRulesync,
|
|
90055
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
89090
90056
|
},
|
|
89091
90057
|
body: this.body
|
|
89092
90058
|
});
|
|
@@ -89140,6 +90106,9 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89140
90106
|
//#endregion
|
|
89141
90107
|
//#region src/features/rules/antigravity-plugin-rule.ts
|
|
89142
90108
|
var AntigravityPluginRule = class extends AntigravityIdeRule {
|
|
90109
|
+
static supportsSubprojectRules() {
|
|
90110
|
+
return false;
|
|
90111
|
+
}
|
|
89143
90112
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
89144
90113
|
const targets = rulesyncRule.getFrontmatter().targets;
|
|
89145
90114
|
return targets.includes("*") || targets.includes("antigravity-plugin");
|
|
@@ -95489,6 +96458,12 @@ var VibeRule = class extends NestedAgentsmdRule {
|
|
|
95489
96458
|
* file, so every non-root rule without `agentsmd.subprojectPath` folds into
|
|
95490
96459
|
* the root `./AGENTS.md`.
|
|
95491
96460
|
*
|
|
96461
|
+
* A legacy root `WARP.md` is read on import when the root `AGENTS.md` is
|
|
96462
|
+
* absent, so a repository that has not moved to `AGENTS.md` yet imports its
|
|
96463
|
+
* rules. It is never written or deleted. When both exist, Warp reads
|
|
96464
|
+
* `WARP.md`, so import keeps reading the `AGENTS.md` rulesync manages and warns
|
|
96465
|
+
* that Warp ignores it, as generate already does.
|
|
96466
|
+
*
|
|
95492
96467
|
* In global mode, Warp reads a third rule source from `~/.agents/AGENTS.md`
|
|
95493
96468
|
* (the cross-tool agent config directory), indexed like project rules and also
|
|
95494
96469
|
* used from remote hosts in SSH sessions. The same root-fold policy applies.
|
|
@@ -95503,6 +96478,31 @@ var WarpRule = class extends NestedAgentsmdRule {
|
|
|
95503
96478
|
toolTarget: "warp"
|
|
95504
96479
|
};
|
|
95505
96480
|
}
|
|
96481
|
+
static getSettablePaths(options = {}) {
|
|
96482
|
+
const paths = super.getSettablePaths(options);
|
|
96483
|
+
if (options.global) return paths;
|
|
96484
|
+
return {
|
|
96485
|
+
...paths,
|
|
96486
|
+
importOnlyRoots: [{
|
|
96487
|
+
relativeDirPath: ".",
|
|
96488
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96489
|
+
onlyWhenRootAbsent: true,
|
|
96490
|
+
outranksRoot: true
|
|
96491
|
+
}]
|
|
96492
|
+
};
|
|
96493
|
+
}
|
|
96494
|
+
static async fromFile(params) {
|
|
96495
|
+
const { outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true } = params;
|
|
96496
|
+
if (!params.global && relativeFilePath === "WARP.md" && (relativeDirPath === void 0 || relativeDirPath === ".")) return new this({
|
|
96497
|
+
outputRoot,
|
|
96498
|
+
relativeDirPath: ".",
|
|
96499
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96500
|
+
fileContent: await readFileContent((0, node_path.join)(outputRoot, WARP_LEGACY_RULE_FILE_NAME)),
|
|
96501
|
+
validate,
|
|
96502
|
+
root: true
|
|
96503
|
+
});
|
|
96504
|
+
return super.fromFile(params);
|
|
96505
|
+
}
|
|
95506
96506
|
};
|
|
95507
96507
|
//#endregion
|
|
95508
96508
|
//#region src/features/rules/zcode-rule.ts
|
|
@@ -96805,7 +97805,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
96805
97805
|
if (!importOnlyRoots || importOnlyRoots.length === 0) return;
|
|
96806
97806
|
const existingPaths = [];
|
|
96807
97807
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
96808
|
-
if (importOnlyRoot.onlyWhenRootAbsent !== true) continue;
|
|
97808
|
+
if (importOnlyRoot.onlyWhenRootAbsent !== true || importOnlyRoot.outranksRoot === true) continue;
|
|
96809
97809
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
96810
97810
|
cwd: this.outputRoot,
|
|
96811
97811
|
type: "file"
|
|
@@ -97506,13 +98506,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97506
98506
|
const rootFilePath = primaryRootFilePaths[0];
|
|
97507
98507
|
const scannedPaths = [];
|
|
97508
98508
|
const skippedPaths = [];
|
|
98509
|
+
const skippedOutrankingPaths = [];
|
|
97509
98510
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
97510
98511
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
97511
98512
|
cwd: this.outputRoot,
|
|
97512
98513
|
type: "file"
|
|
97513
98514
|
});
|
|
97514
98515
|
if (importOnlyRoot.onlyWhenRootAbsent === true && rootFilePath !== void 0) {
|
|
97515
|
-
skippedPaths.push(...matchedPaths);
|
|
98516
|
+
(importOnlyRoot.outranksRoot === true ? skippedOutrankingPaths : skippedPaths).push(...matchedPaths);
|
|
97516
98517
|
continue;
|
|
97517
98518
|
}
|
|
97518
98519
|
scannedPaths.push(...matchedPaths);
|
|
@@ -97523,6 +98524,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97523
98524
|
const remainingCount = skippedNames.length - listedNames.length;
|
|
97524
98525
|
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
98526
|
}
|
|
98527
|
+
if (skippedOutrankingPaths.length > 0 && rootFilePath !== void 0) {
|
|
98528
|
+
const rootName = stripControlCharacters((0, node_path.relative)(this.outputRoot, rootFilePath));
|
|
98529
|
+
const skippedNames = skippedOutrankingPaths.map((filePath) => stripControlCharacters((0, node_path.relative)(this.outputRoot, filePath)));
|
|
98530
|
+
const listedNames = skippedNames.slice(0, MAX_LISTED_SKIPPED_IMPORT_ONLY_PATHS);
|
|
98531
|
+
const remainingCount = skippedNames.length - listedNames.length;
|
|
98532
|
+
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.`);
|
|
98533
|
+
}
|
|
97526
98534
|
return await buildImportRulesFromPaths(scannedPaths);
|
|
97527
98535
|
})();
|
|
97528
98536
|
this.logger.debug(`Found ${importOnlyToolRules.length} import-only tool rule files`);
|