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
|
@@ -7861,6 +7861,26 @@ const CopilotPermissionsOverrideSchema = z.looseObject({
|
|
|
7861
7861
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
7862
7862
|
defaultPermissionMode: z.optional(z.enum(COPILOT_DEFAULT_PERMISSION_MODES))
|
|
7863
7863
|
});
|
|
7864
|
+
/**
|
|
7865
|
+
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
7866
|
+
* before prompting. `always-ask` auto-approves only read-only tools, `write`
|
|
7867
|
+
* also workspace writes, and `yolo` (oh-my-pi's default) every tier.
|
|
7868
|
+
*
|
|
7869
|
+
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
7870
|
+
*/
|
|
7871
|
+
const OMP_APPROVAL_MODES = [
|
|
7872
|
+
"always-ask",
|
|
7873
|
+
"write",
|
|
7874
|
+
"yolo"
|
|
7875
|
+
];
|
|
7876
|
+
/**
|
|
7877
|
+
* Tool-scoped override block for oh-my-pi (`omp`). `approvalMode` writes the
|
|
7878
|
+
* `tools.approvalMode` key of `config.yml`, which has no canonical category.
|
|
7879
|
+
*/
|
|
7880
|
+
const OmpPermissionsOverrideSchema = z.looseObject({
|
|
7881
|
+
permission: z.optional(ToolScopedPermissionSchema),
|
|
7882
|
+
approvalMode: z.optional(z.enum(OMP_APPROVAL_MODES))
|
|
7883
|
+
});
|
|
7864
7884
|
const KimiCodePermissionsOverrideSchema = z.looseObject({
|
|
7865
7885
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
7866
7886
|
defaultPermissionMode: z.optional(z.enum([
|
|
@@ -7868,6 +7888,14 @@ const KimiCodePermissionsOverrideSchema = z.looseObject({
|
|
|
7868
7888
|
"yolo",
|
|
7869
7889
|
"auto"
|
|
7870
7890
|
])),
|
|
7891
|
+
/**
|
|
7892
|
+
* Kimi's `[permission] dangerous_command_guard` switch (Kimi Code 0.40.0,
|
|
7893
|
+
* default `true`). `false` turns off the built-in dangerous-command policy
|
|
7894
|
+
* (no confirmation for `rm -rf`, `shutdown` and the like).
|
|
7895
|
+
*
|
|
7896
|
+
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
7897
|
+
*/
|
|
7898
|
+
dangerousCommandGuard: z.optional(z.boolean()),
|
|
7871
7899
|
rules: z.optional(z.array(z.looseObject({
|
|
7872
7900
|
decision: PermissionActionSchema,
|
|
7873
7901
|
pattern: z.string(),
|
|
@@ -8083,7 +8111,10 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8083
8111
|
* `sandbox`, `sandboxImage`, `disabled`, `visible`, `eager`, `listDirectory`,
|
|
8084
8112
|
* `todoWrite`, `workflowsEnabled`, `workflowNameOnly`, `executionSandbox`) and
|
|
8085
8113
|
* `security` (`folderTrust`, `allowedHttpHookUrls`, `allowPrivateNetworkHooks`,
|
|
8086
|
-
* `allowedInsecureVoiceBaseUrls`)
|
|
8114
|
+
* `allowedInsecureVoiceBaseUrls`) and `skills` (the skill kill-switches
|
|
8115
|
+
* `disabled`, `defaultDisabled`, `enabled`, `disabledLevels`, each a list Qwen
|
|
8116
|
+
* Code unions across scopes) and `general` (`outputStyle`, the output style
|
|
8117
|
+
* that reshapes how responses are written). Qwen Code strips `tools.workflowsEnabled`,
|
|
8087
8118
|
* `tools.executionSandbox`, `security.allowPrivateNetworkHooks` and
|
|
8088
8119
|
* `security.allowedInsecureVoiceBaseUrls` out of workspace settings, so generate
|
|
8089
8120
|
* skips those four in project scope and announces a granting value in global
|
|
@@ -8100,7 +8131,8 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8100
8131
|
* registry controls (`disabled`, `visible`, `listDirectory`, `todoWrite`, and `eager`, which
|
|
8101
8132
|
* demotes an omitted tool to deferred rather than removing it, replacing rather
|
|
8102
8133
|
* than merging with the list a higher scope set), the Auto Mode
|
|
8103
|
-
* classifier config, and, because these groups are
|
|
8134
|
+
* classifier config, the skill kill-switches, and, because these groups are
|
|
8135
|
+
* loose objects, any key
|
|
8104
8136
|
* rulesync does not model. Import flags a scope-dependent key only when it read
|
|
8105
8137
|
* the project file, since that is the value a `--global` regenerate would
|
|
8106
8138
|
* promote. It also
|
|
@@ -8111,11 +8143,13 @@ const CursorPermissionsOverrideSchema = z.looseObject({
|
|
|
8111
8143
|
* matching `settings.json` group and emitted only for Qwen, while the shared
|
|
8112
8144
|
* `permission` block continues to drive the `permissions.allow`/`ask`/`deny`
|
|
8113
8145
|
* arrays. Kept `looseObject` (verbatim passthrough) so any current or future
|
|
8114
|
-
* `tools`/`security`/`autoMode` key can be authored.
|
|
8146
|
+
* `tools`/`security`/`skills`/`general`/`autoMode` key can be authored.
|
|
8115
8147
|
*
|
|
8116
8148
|
* @example
|
|
8117
8149
|
* {
|
|
8118
8150
|
* "tools": { "approvalMode": "auto-edit" },
|
|
8151
|
+
* "skills": { "disabled": ["pdf"], "disabledLevels": ["bundled"] },
|
|
8152
|
+
* "general": { "outputStyle": "Concise" },
|
|
8119
8153
|
* "security": { "folderTrust": { "enabled": true } },
|
|
8120
8154
|
* "autoMode": { "hints": { "allow": ["Running tests"] }, "classifyAllShell": true }
|
|
8121
8155
|
* }
|
|
@@ -8124,6 +8158,8 @@ const QwencodePermissionsOverrideSchema = z.looseObject({
|
|
|
8124
8158
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
8125
8159
|
tools: z.optional(z.looseObject({})),
|
|
8126
8160
|
security: z.optional(z.looseObject({})),
|
|
8161
|
+
skills: z.optional(z.looseObject({})),
|
|
8162
|
+
general: z.optional(z.looseObject({})),
|
|
8127
8163
|
autoMode: z.optional(z.looseObject({}))
|
|
8128
8164
|
});
|
|
8129
8165
|
/**
|
|
@@ -8209,6 +8245,8 @@ const TabninePermissionsOverrideSchema = z.looseObject({
|
|
|
8209
8245
|
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
8210
8246
|
* @example
|
|
8211
8247
|
* { "allowDynamicBash": true, "rawAllow": ["Bash=pnpm test"] }
|
|
8248
|
+
* @example
|
|
8249
|
+
* { "mode": "deny" }
|
|
8212
8250
|
*/
|
|
8213
8251
|
const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
8214
8252
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
@@ -8217,7 +8255,12 @@ const ReasonixPermissionsOverrideSchema = z.looseObject({
|
|
|
8217
8255
|
rawAllow: z.optional(z.array(z.string())),
|
|
8218
8256
|
rawAsk: z.optional(z.array(z.string())),
|
|
8219
8257
|
rawDeny: z.optional(z.array(z.string())),
|
|
8220
|
-
allowDynamicBash: z.optional(z.boolean())
|
|
8258
|
+
allowDynamicBash: z.optional(z.boolean()),
|
|
8259
|
+
mode: z.optional(z.enum([
|
|
8260
|
+
"ask",
|
|
8261
|
+
"allow",
|
|
8262
|
+
"deny"
|
|
8263
|
+
]))
|
|
8221
8264
|
});
|
|
8222
8265
|
/**
|
|
8223
8266
|
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
@@ -8260,7 +8303,8 @@ const FactorydroidPermissionsOverrideSchema = z.looseObject({
|
|
|
8260
8303
|
* {
|
|
8261
8304
|
* "agent_mode_coding_permissions": "always_allow_reading",
|
|
8262
8305
|
* "agent_mode_execute_readonly_commands": true,
|
|
8263
|
-
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] }
|
|
8306
|
+
* "execution_profile": { "read_files": "always_allow", "mcp_denylist": ["untrusted-server"] },
|
|
8307
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8264
8308
|
* }
|
|
8265
8309
|
*/
|
|
8266
8310
|
/**
|
|
@@ -8312,6 +8356,16 @@ const WarpExecutionProfileOverrideSchema = z.looseObject({
|
|
|
8312
8356
|
mcp_allowlist: z.optional(z.array(z.string())),
|
|
8313
8357
|
mcp_denylist: z.optional(z.array(z.string()))
|
|
8314
8358
|
});
|
|
8359
|
+
/**
|
|
8360
|
+
* Whether auto-approve may run commands that match the command denylist
|
|
8361
|
+
* (Warp defaults to `true`). It is not an execution-profile field: it lives in
|
|
8362
|
+
* the sibling `[agents.warp_agent.other]` table of the same `settings.toml`,
|
|
8363
|
+
* shared by the Warp app and the Warp Agent CLI.
|
|
8364
|
+
*
|
|
8365
|
+
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8366
|
+
* @see https://github.com/warpdotdev/warp/blob/main/app/src/settings/ai.rs (`AutoApproveBypassesCommandDenylist`)
|
|
8367
|
+
*/
|
|
8368
|
+
const WarpAutoApproveBypassSchema = z.optional(z.boolean());
|
|
8315
8369
|
const WarpPermissionsOverrideSchema = z.looseObject({
|
|
8316
8370
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
8317
8371
|
agent_mode_coding_permissions: z.optional(z.enum([
|
|
@@ -8321,7 +8375,8 @@ const WarpPermissionsOverrideSchema = z.looseObject({
|
|
|
8321
8375
|
])),
|
|
8322
8376
|
agent_mode_coding_file_read_allowlist: z.optional(z.array(z.string())),
|
|
8323
8377
|
agent_mode_execute_readonly_commands: z.optional(z.boolean()),
|
|
8324
|
-
execution_profile: z.optional(WarpExecutionProfileOverrideSchema)
|
|
8378
|
+
execution_profile: z.optional(WarpExecutionProfileOverrideSchema),
|
|
8379
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8325
8380
|
});
|
|
8326
8381
|
/**
|
|
8327
8382
|
* Tool-scoped override block for the standalone Warp Agent CLI. The CLI reads
|
|
@@ -8330,16 +8385,21 @@ const WarpPermissionsOverrideSchema = z.looseObject({
|
|
|
8330
8385
|
* the app's legacy `[agents.profiles]` keys), so only the nested
|
|
8331
8386
|
* `execution_profile` block is offered; it is merged into that record, and the
|
|
8332
8387
|
* shared `permission.bash` block still drives `command_allowlist` /
|
|
8333
|
-
* `command_denylist`.
|
|
8388
|
+
* `command_denylist`. `auto_approve_bypasses_command_denylist` is written to
|
|
8389
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
8334
8390
|
*
|
|
8335
8391
|
* @example
|
|
8336
|
-
* {
|
|
8392
|
+
* {
|
|
8393
|
+
* "execution_profile": { "apply_code_diffs": "always_ask", "run_agents": "never_allow" },
|
|
8394
|
+
* "auto_approve_bypasses_command_denylist": false
|
|
8395
|
+
* }
|
|
8337
8396
|
*
|
|
8338
8397
|
* @see https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
8339
8398
|
*/
|
|
8340
8399
|
const WarpcliPermissionsOverrideSchema = z.looseObject({
|
|
8341
8400
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
8342
|
-
execution_profile: z.optional(WarpExecutionProfileOverrideSchema)
|
|
8401
|
+
execution_profile: z.optional(WarpExecutionProfileOverrideSchema),
|
|
8402
|
+
auto_approve_bypasses_command_denylist: WarpAutoApproveBypassSchema
|
|
8343
8403
|
});
|
|
8344
8404
|
/**
|
|
8345
8405
|
* deepagents-cli's approval-mode knobs under `[startup]` in
|
|
@@ -9035,7 +9095,7 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
9035
9095
|
copilotcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9036
9096
|
crush: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9037
9097
|
goose: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9038
|
-
omp: z.optional(
|
|
9098
|
+
omp: z.optional(OmpPermissionsOverrideSchema),
|
|
9039
9099
|
pool: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9040
9100
|
qoder: z.optional(CanonicalPermissionsOverrideSchema),
|
|
9041
9101
|
grokcli: z.optional(CanonicalPermissionsOverrideSchema),
|
|
@@ -12745,7 +12805,9 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12745
12805
|
ownedKeys: [
|
|
12746
12806
|
"permissions",
|
|
12747
12807
|
"tools",
|
|
12748
|
-
"security"
|
|
12808
|
+
"security",
|
|
12809
|
+
"skills",
|
|
12810
|
+
"general"
|
|
12749
12811
|
]
|
|
12750
12812
|
}
|
|
12751
12813
|
}
|
|
@@ -13115,7 +13177,7 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13115
13177
|
features: {
|
|
13116
13178
|
mcp: {
|
|
13117
13179
|
kind: "replace-owned-keys",
|
|
13118
|
-
ownedKeys: ["mcp_servers"]
|
|
13180
|
+
ownedKeys: ["mcp_servers", "disabled_mcp_tools"]
|
|
13119
13181
|
},
|
|
13120
13182
|
permissions: {
|
|
13121
13183
|
kind: "replace-owned-keys",
|
|
@@ -13250,6 +13312,14 @@ function serializeSharedConfigFile({ fileKey, document, existingContent, filePat
|
|
|
13250
13312
|
});
|
|
13251
13313
|
}
|
|
13252
13314
|
/**
|
|
13315
|
+
* A patch value for an owned key that holds a map of managed entries: the map
|
|
13316
|
+
* itself, or `undefined` when it is empty, which retracts the key instead of
|
|
13317
|
+
* leaving an empty object behind (`applySharedConfigPatch` deletes a key whose patch value is `undefined`).
|
|
13318
|
+
*/
|
|
13319
|
+
function undefinedWhenEmpty(entries) {
|
|
13320
|
+
return Object.keys(entries).length > 0 ? entries : void 0;
|
|
13321
|
+
}
|
|
13322
|
+
/**
|
|
13253
13323
|
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
13254
13324
|
* the existing content, merge the patch under the feature's declared policy,
|
|
13255
13325
|
* and serialize it back over the existing content (see
|
|
@@ -13736,14 +13806,23 @@ var FeatureProcessor = class extends RulesyncSourceConsumer {
|
|
|
13736
13806
|
async removeOrphanAiFiles(existingFiles, generatedFiles) {
|
|
13737
13807
|
const generatedPaths = new Set(generatedFiles.map((f) => caseFoldIdentity(f.getFilePath())));
|
|
13738
13808
|
const orphanFiles = existingFiles.filter((f) => !generatedPaths.has(caseFoldIdentity(f.getFilePath())));
|
|
13809
|
+
const refusedDirs = /* @__PURE__ */ new Map();
|
|
13739
13810
|
let removedCount = 0;
|
|
13740
13811
|
for (const aiFile of orphanFiles) {
|
|
13741
13812
|
const filePath = aiFile.getFilePath();
|
|
13742
|
-
|
|
13743
|
-
|
|
13744
|
-
|
|
13745
|
-
|
|
13746
|
-
|
|
13813
|
+
const rootPath = aiFile.getOutputRoot();
|
|
13814
|
+
const dirPath = dirname(filePath);
|
|
13815
|
+
const cacheKey = JSON.stringify([rootPath, dirPath]);
|
|
13816
|
+
let refused = refusedDirs.get(cacheKey);
|
|
13817
|
+
if (refused === void 0) {
|
|
13818
|
+
refused = await refusesDeleteOutsideRoot({
|
|
13819
|
+
logger: this.logger,
|
|
13820
|
+
rootPath,
|
|
13821
|
+
dirPath
|
|
13822
|
+
});
|
|
13823
|
+
refusedDirs.set(cacheKey, refused);
|
|
13824
|
+
}
|
|
13825
|
+
if (refused) continue;
|
|
13747
13826
|
const loggedPath = stripControlCharacters(filePath);
|
|
13748
13827
|
if (this.dryRun) this.logger.info(`[DRY RUN] Would delete: ${loggedPath}`);
|
|
13749
13828
|
else {
|
|
@@ -13864,22 +13943,24 @@ async function refusesWriteOutsideRoot({ logger, rootPath, targetPath }) {
|
|
|
13864
13943
|
return true;
|
|
13865
13944
|
}
|
|
13866
13945
|
/**
|
|
13867
|
-
* Whether deleting `
|
|
13868
|
-
* the
|
|
13946
|
+
* Whether deleting the files in `dirPath` has to be refused because a link on
|
|
13947
|
+
* the way to that directory leads out of `rootPath`, warning about the refusal
|
|
13948
|
+
* once for the whole directory.
|
|
13869
13949
|
*
|
|
13870
13950
|
* Deletion candidates are enumerated without following links *inside* the
|
|
13871
13951
|
* scanned directory, but the scanned directory itself is reached through
|
|
13872
13952
|
* whatever links its path contains: a checked-out repository can carry
|
|
13873
13953
|
* `.cursor/commands -> ~/notes`, and every file in `~/notes` would then read as
|
|
13874
|
-
* an orphan. Only the
|
|
13875
|
-
* a link unlinks the link where it is spelled
|
|
13954
|
+
* an orphan. Only the directory is judged, never the file in it, because
|
|
13955
|
+
* removing a file that is itself a link unlinks the link where it is spelled
|
|
13956
|
+
* and never touches its target.
|
|
13876
13957
|
*/
|
|
13877
|
-
async function refusesDeleteOutsideRoot({ logger, rootPath,
|
|
13958
|
+
async function refusesDeleteOutsideRoot({ logger, rootPath, dirPath }) {
|
|
13878
13959
|
if (!await writablePathEscapesRoot({
|
|
13879
13960
|
rootPath,
|
|
13880
|
-
targetPath:
|
|
13961
|
+
targetPath: dirPath
|
|
13881
13962
|
})) return false;
|
|
13882
|
-
logger.warn(`Refusing to delete ${quoteForLog(
|
|
13963
|
+
logger.warn(`Refusing to delete files in ${quoteForLog(dirPath)}: it resolves outside ${quoteForLog(rootPath)} through a symbolic link`);
|
|
13883
13964
|
return true;
|
|
13884
13965
|
}
|
|
13885
13966
|
/**
|
|
@@ -17202,7 +17283,7 @@ var AntigravitySharedCommand = class extends ToolCommand {
|
|
|
17202
17283
|
static getProjectRelativeDirPath() {
|
|
17203
17284
|
throw new Error("Please implement this method in the subclass.");
|
|
17204
17285
|
}
|
|
17205
|
-
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/
|
|
17286
|
+
/** Global-scope workflows directory under `~/.gemini/` (e.g. `.gemini/config/workflows`). */
|
|
17206
17287
|
static getGlobalRelativeDirPath() {
|
|
17207
17288
|
throw new Error("Please implement this method in the subclass.");
|
|
17208
17289
|
}
|
|
@@ -17365,7 +17446,8 @@ const ANTIGRAVITY_IDE_GEMINI_DIR = ".gemini";
|
|
|
17365
17446
|
const ANTIGRAVITY_IDE_GLOBAL_RULE_FILE_NAME = "GEMINI.md";
|
|
17366
17447
|
const ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR = "config";
|
|
17367
17448
|
const ANTIGRAVITY_IDE_GLOBAL_RULES_SUBDIR = join(ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "rules");
|
|
17368
|
-
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_IDE_GEMINI_DIR,
|
|
17449
|
+
const ANTIGRAVITY_IDE_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_IDE_GEMINI_DIR, ANTIGRAVITY_IDE_GLOBAL_CONFIG_SUBDIR, "workflows");
|
|
17450
|
+
const ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH = join(ANTIGRAVITY_IDE_GEMINI_DIR, "antigravity", "global_workflows");
|
|
17369
17451
|
const ANTIGRAVITY_IDE_PERMISSIONS_DIR = ".antigravity";
|
|
17370
17452
|
const ANTIGRAVITY_IDE_PERMISSIONS_FILE_NAME = "settings.json";
|
|
17371
17453
|
//#endregion
|
|
@@ -17395,10 +17477,12 @@ var AntigravityIdeSkill = class extends AntigravitySharedSkill {
|
|
|
17395
17477
|
* Emits each command as a skill in `.agents/skills/<name>/SKILL.md` (project
|
|
17396
17478
|
* scope) and `~/.gemini/config/skills/<name>/SKILL.md` (global scope), the
|
|
17397
17479
|
* IDE's skills directories. Legacy workflows are still imported from
|
|
17398
|
-
* `.agents/workflows/` and `~/.gemini/
|
|
17399
|
-
*
|
|
17480
|
+
* `.agents/workflows/` and `~/.gemini/config/workflows/` (the global workflows
|
|
17481
|
+
* directory of Antigravity 2.0), and, in global scope only, from the pre-2.0
|
|
17482
|
+
* `~/.gemini/antigravity/global_workflows/`. All body and frontmatter handling
|
|
17483
|
+
* is shared with {@link AntigravitySharedCommand}.
|
|
17400
17484
|
*/
|
|
17401
|
-
var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
17485
|
+
var AntigravityIdeCommand = class AntigravityIdeCommand extends AntigravitySharedCommand {
|
|
17402
17486
|
static getProjectRelativeDirPath() {
|
|
17403
17487
|
return ANTIGRAVITY_IDE_COMMANDS_DIR_PATH;
|
|
17404
17488
|
}
|
|
@@ -17408,6 +17492,37 @@ var AntigravityIdeCommand = class extends AntigravitySharedCommand {
|
|
|
17408
17492
|
static getSkillsRelativeDirPath({ global }) {
|
|
17409
17493
|
return AntigravityIdeSkill.getSettablePaths({ global }).relativeDirPath;
|
|
17410
17494
|
}
|
|
17495
|
+
/**
|
|
17496
|
+
* Import-only, global scope: the workflows left in the pre-2.0
|
|
17497
|
+
* `~/.gemini/antigravity/global_workflows/` directory, where rulesync also
|
|
17498
|
+
* wrote IDE global commands before they were emitted as skills. The
|
|
17499
|
+
* directory is never swept by `generate --delete`; a workflow with the same
|
|
17500
|
+
* file name in `~/.gemini/config/workflows/` takes precedence.
|
|
17501
|
+
*
|
|
17502
|
+
* @see https://antigravity.google/docs/migration/workflows-to-skills
|
|
17503
|
+
*/
|
|
17504
|
+
static async loadAdditionalImportFiles({ outputRoot = process.cwd(), global = false, logger } = {}) {
|
|
17505
|
+
if (!global) return [];
|
|
17506
|
+
const filePaths = await findFilesByGlobs("*.md", { cwd: join(outputRoot, ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH) });
|
|
17507
|
+
return (await Promise.all(filePaths.map(async (filePath) => {
|
|
17508
|
+
const fileContent = await readFileContent(filePath);
|
|
17509
|
+
const { frontmatter, body } = parseFrontmatter(fileContent, filePath);
|
|
17510
|
+
const result = AntigravityCommandFrontmatterSchema.safeParse(frontmatter);
|
|
17511
|
+
if (!result.success) {
|
|
17512
|
+
logger?.warn(`Skipping ${filePath}: invalid frontmatter (${formatError(result.error)}).`);
|
|
17513
|
+
return;
|
|
17514
|
+
}
|
|
17515
|
+
return new AntigravityIdeCommand({
|
|
17516
|
+
outputRoot,
|
|
17517
|
+
relativeDirPath: ANTIGRAVITY_IDE_PRE_V2_GLOBAL_WORKFLOWS_DIR_PATH,
|
|
17518
|
+
relativeFilePath: basename(filePath),
|
|
17519
|
+
frontmatter: result.data,
|
|
17520
|
+
body: body.trim(),
|
|
17521
|
+
fileContent,
|
|
17522
|
+
global
|
|
17523
|
+
});
|
|
17524
|
+
}))).filter((command) => command !== void 0);
|
|
17525
|
+
}
|
|
17411
17526
|
getToolTargetName() {
|
|
17412
17527
|
return "antigravity-ide";
|
|
17413
17528
|
}
|
|
@@ -25060,7 +25175,7 @@ function findCommandPaths({ command, scan }) {
|
|
|
25060
25175
|
} else {
|
|
25061
25176
|
if (path !== void 0) toAnchor.add(path);
|
|
25062
25177
|
state.owner = text.slice(text.lastIndexOf("/") + 1);
|
|
25063
|
-
state.expecting = afterCommandWord(state.owner);
|
|
25178
|
+
state.expecting = afterCommandWord$1(state.owner);
|
|
25064
25179
|
state.prefix = void 0;
|
|
25065
25180
|
state.shellCode = false;
|
|
25066
25181
|
}
|
|
@@ -25126,7 +25241,7 @@ function readOption({ state, text }) {
|
|
|
25126
25241
|
if (SHELL_NAME_PATTERN.test(state.owner) && SHELL_CODE_OPTION.test(text)) state.shellCode = true;
|
|
25127
25242
|
}
|
|
25128
25243
|
/** What follows a command word whose basename is `name`. */
|
|
25129
|
-
function afterCommandWord(name) {
|
|
25244
|
+
function afterCommandWord$1(name) {
|
|
25130
25245
|
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
25131
25246
|
if (PACKAGE_RUNNERS.has(name)) return "wrapped";
|
|
25132
25247
|
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
@@ -33469,6 +33584,19 @@ function isQwencodeLegacyMillisecondTimeout(timeout) {
|
|
|
33469
33584
|
return typeof timeout === "number" && Number.isFinite(timeout) && timeout >= QWENCODE_LEGACY_MILLISECOND_TIMEOUT_THRESHOLD;
|
|
33470
33585
|
}
|
|
33471
33586
|
/**
|
|
33587
|
+
* Read a `timeout` the way Qwen Code does. Settings files are not type-checked
|
|
33588
|
+
* and Qwen Code coerces a numeric string such as `"60000"` with `Number()`, so
|
|
33589
|
+
* import accepts one too; a value that is not a finite number is dropped, since
|
|
33590
|
+
* Qwen Code falls back to the hook type's default for it.
|
|
33591
|
+
* https://github.com/QwenLM/qwen-code/blob/main/packages/core/src/hooks/hook-timeout.ts
|
|
33592
|
+
*/
|
|
33593
|
+
function readQwencodeTimeout(timeout) {
|
|
33594
|
+
if (timeout === void 0) return void 0;
|
|
33595
|
+
if (typeof timeout === "string" && timeout.trim() === "") return void 0;
|
|
33596
|
+
const value = Number(timeout);
|
|
33597
|
+
return Number.isFinite(value) ? value : void 0;
|
|
33598
|
+
}
|
|
33599
|
+
/**
|
|
33472
33600
|
* Canonical `timeout` is seconds. A command hook value below the threshold is
|
|
33473
33601
|
* already read as seconds; a larger one can only be expressed in milliseconds.
|
|
33474
33602
|
*/
|
|
@@ -33579,7 +33707,7 @@ const QwencodeHookEntrySchema = z.looseObject({
|
|
|
33579
33707
|
type: z.optional(z.string()),
|
|
33580
33708
|
command: z.optional(z.string()),
|
|
33581
33709
|
url: z.optional(z.string()),
|
|
33582
|
-
timeout: z.optional(z.number()),
|
|
33710
|
+
timeout: z.optional(z.union([z.number(), z.string()])),
|
|
33583
33711
|
name: z.optional(z.string()),
|
|
33584
33712
|
description: z.optional(z.string()),
|
|
33585
33713
|
statusMessage: z.optional(z.string()),
|
|
@@ -33612,12 +33740,13 @@ function qwencodeHookEntryToCanonical({ hook, sequential, matcher }) {
|
|
|
33612
33740
|
const isCommand = hookType === "command";
|
|
33613
33741
|
const isPrompt = hookType === "prompt";
|
|
33614
33742
|
const shell = h.shell === "bash" || h.shell === "powershell" ? h.shell : void 0;
|
|
33743
|
+
const timeout = readQwencodeTimeout(h.timeout);
|
|
33615
33744
|
return {
|
|
33616
33745
|
type: hookType,
|
|
33617
33746
|
...compact({
|
|
33618
33747
|
command: h.command,
|
|
33619
33748
|
url: h.url,
|
|
33620
|
-
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(
|
|
33749
|
+
timeout: isCommand ? qwencodeCommandTimeoutToCanonicalTimeout(timeout) : timeout,
|
|
33621
33750
|
name: h.name,
|
|
33622
33751
|
description: h.description,
|
|
33623
33752
|
statusMessage: h.statusMessage,
|
|
@@ -38119,7 +38248,7 @@ var AmpMcp = class AmpMcp extends ToolMcp {
|
|
|
38119
38248
|
fileKey: sharedConfigFileKey(basePaths),
|
|
38120
38249
|
feature: "mcp",
|
|
38121
38250
|
existingContent: fileContent ?? "",
|
|
38122
|
-
patch: { [AMP_MCP_SERVERS_KEY]: filterMcpServers(rulesyncMcp.getMcpServers()) },
|
|
38251
|
+
patch: { [AMP_MCP_SERVERS_KEY]: undefinedWhenEmpty(filterMcpServers(rulesyncMcp.getMcpServers())) },
|
|
38123
38252
|
filePath: join(jsonDir, relativeFilePath),
|
|
38124
38253
|
logger
|
|
38125
38254
|
}),
|
|
@@ -38417,6 +38546,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38417
38546
|
const paths = this.getSettablePaths({ global });
|
|
38418
38547
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
38419
38548
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
38549
|
+
const mcpServers = rulesyncMcp.getMcpServers();
|
|
38420
38550
|
return new AugmentcodeMcp({
|
|
38421
38551
|
outputRoot,
|
|
38422
38552
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -38425,7 +38555,7 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
38425
38555
|
fileKey: sharedConfigFileKey(paths),
|
|
38426
38556
|
feature: "mcp",
|
|
38427
38557
|
existingContent,
|
|
38428
|
-
patch: { mcpServers:
|
|
38558
|
+
patch: { mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : void 0 },
|
|
38429
38559
|
filePath,
|
|
38430
38560
|
logger
|
|
38431
38561
|
}),
|
|
@@ -38981,6 +39111,229 @@ var ClaudecodeMcp = class ClaudecodeMcp extends ToolMcp {
|
|
|
38981
39111
|
}
|
|
38982
39112
|
};
|
|
38983
39113
|
//#endregion
|
|
39114
|
+
//#region src/features/mcp/claudecode-plugin-mcp.ts
|
|
39115
|
+
/**
|
|
39116
|
+
* The placeholder Claude Code substitutes with the installed plugin's own
|
|
39117
|
+
* directory in an MCP server's `command`, `args` and `env`.
|
|
39118
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39119
|
+
*/
|
|
39120
|
+
const PLUGIN_ROOT_PLACEHOLDER = "${CLAUDE_PLUGIN_ROOT}";
|
|
39121
|
+
const PLUGIN_ROOT_PREFIX = /^(?:\$\{CLAUDE_PLUGIN_ROOT\}|"\$CLAUDE_PLUGIN_ROOT"|\$CLAUDE_PLUGIN_ROOT)\//;
|
|
39122
|
+
/**
|
|
39123
|
+
* Whether a server `command` names a file inside the plugin rather than an
|
|
39124
|
+
* executable found on `PATH`. The command is spawned without a shell, so a
|
|
39125
|
+
* name with a slash is a path, and one without (`npx`, `node`, `uvx`) is a
|
|
39126
|
+
* `PATH` lookup. An absolute path, a home path, one that leaves the plugin
|
|
39127
|
+
* (`../`), and one that already starts with a variable are left as written.
|
|
39128
|
+
*/
|
|
39129
|
+
function isPluginRelativeCommand(command) {
|
|
39130
|
+
return /[\\/]/.test(command) && !command.startsWith("$") && !command.startsWith("%") && !command.startsWith("~") && !/^\.\.[\\/]/.test(command) && !posix.isAbsolute(command) && !win32.isAbsolute(command);
|
|
39131
|
+
}
|
|
39132
|
+
function anchorToPluginRoot(path) {
|
|
39133
|
+
return `${PLUGIN_ROOT_PLACEHOLDER}/${path.replace(/^\.[\\/]/, "")}`;
|
|
39134
|
+
}
|
|
39135
|
+
/** The runner name of a command word: its basename without a `.exe` suffix. */
|
|
39136
|
+
function runnerName(command) {
|
|
39137
|
+
return (command.split(/[\\/]/).pop() ?? command).replace(/\.exe$/i, "");
|
|
39138
|
+
}
|
|
39139
|
+
/** What follows a command word, as plugin hook commands read it. */
|
|
39140
|
+
function afterCommandWord(name) {
|
|
39141
|
+
if (Object.hasOwn(RUN_SUBCOMMANDS, name)) return "subcommand";
|
|
39142
|
+
if (PACKAGE_RUNNERS.has(name)) return "command";
|
|
39143
|
+
return SCRIPT_RUNNER_PATTERN.test(name) ? "script" : "none";
|
|
39144
|
+
}
|
|
39145
|
+
/**
|
|
39146
|
+
* The positions in `args` that name a file the server runs, read with the
|
|
39147
|
+
* rules plugin hook commands follow (`hook-command-paths.ts`): the script of a
|
|
39148
|
+
* script runner (`node ./dist/server.js`), the command after a run subcommand
|
|
39149
|
+
* or package runner (`uv run ./server.py`, `npx tsx ./x.ts`), and the value of
|
|
39150
|
+
* an option that loads a file (`node -r ./register.js`). Only a position whose
|
|
39151
|
+
* value `isPath` accepts is returned. Everything else is data the server reads
|
|
39152
|
+
* relative to the consumer's project (`server-filesystem ./docs`), and the
|
|
39153
|
+
* scan stops at an option that runs inline code or standard input
|
|
39154
|
+
* (`node -e`, `python3 -m`, `sh -c`, `bash -s`).
|
|
39155
|
+
*/
|
|
39156
|
+
function fileArgumentIndices({ command, args, isPath }) {
|
|
39157
|
+
const indices = /* @__PURE__ */ new Set();
|
|
39158
|
+
let owner = runnerName(command);
|
|
39159
|
+
let expecting = afterCommandWord(owner);
|
|
39160
|
+
for (let index = 0; index < args.length && expecting !== "none"; index++) {
|
|
39161
|
+
const arg = args[index];
|
|
39162
|
+
if (typeof arg !== "string") break;
|
|
39163
|
+
if (arg.startsWith("-") && arg !== "-") {
|
|
39164
|
+
if (optionsFor({
|
|
39165
|
+
table: CODE_OPTIONS,
|
|
39166
|
+
name: owner
|
|
39167
|
+
})?.has(arg) || SHELL_NAME_PATTERN.test(owner) && SHELL_CODE_OPTION.test(arg)) break;
|
|
39168
|
+
if (optionsFor({
|
|
39169
|
+
table: VALUE_OPTIONS,
|
|
39170
|
+
name: owner
|
|
39171
|
+
})?.has(arg)) {
|
|
39172
|
+
const value = args[index + 1];
|
|
39173
|
+
if (typeof value === "string" && isPath(value)) indices.add(index + 1);
|
|
39174
|
+
index++;
|
|
39175
|
+
}
|
|
39176
|
+
continue;
|
|
39177
|
+
}
|
|
39178
|
+
if (expecting === "subcommand") {
|
|
39179
|
+
if (RUN_SUBCOMMANDS[owner]?.has(arg)) {
|
|
39180
|
+
expecting = "command";
|
|
39181
|
+
continue;
|
|
39182
|
+
}
|
|
39183
|
+
if (!SCRIPT_RUNNER_PATTERN.test(owner)) break;
|
|
39184
|
+
expecting = "script";
|
|
39185
|
+
}
|
|
39186
|
+
if (isPath(arg)) indices.add(index);
|
|
39187
|
+
if (expecting === "script") break;
|
|
39188
|
+
owner = runnerName(arg);
|
|
39189
|
+
expecting = afterCommandWord(owner);
|
|
39190
|
+
}
|
|
39191
|
+
return indices;
|
|
39192
|
+
}
|
|
39193
|
+
function isDotRelative(value) {
|
|
39194
|
+
return /^\.[\\/]/.test(value);
|
|
39195
|
+
}
|
|
39196
|
+
/** Turn a plugin-root path back into its `./` form, or return it unchanged. */
|
|
39197
|
+
function relativizeFromPluginRoot(value) {
|
|
39198
|
+
return PLUGIN_ROOT_PREFIX.test(value) ? value.replace(PLUGIN_ROOT_PREFIX, "./") : value;
|
|
39199
|
+
}
|
|
39200
|
+
/** The command and arguments of a stdio server, with inline array arguments split out. */
|
|
39201
|
+
function splitCommand(server) {
|
|
39202
|
+
if (Array.isArray(server.command)) {
|
|
39203
|
+
const [first, ...rest] = server.command;
|
|
39204
|
+
return {
|
|
39205
|
+
command: typeof first === "string" ? first : void 0,
|
|
39206
|
+
args: [...rest, ...Array.isArray(server.args) ? server.args : []]
|
|
39207
|
+
};
|
|
39208
|
+
}
|
|
39209
|
+
return {
|
|
39210
|
+
command: typeof server.command === "string" ? server.command : void 0,
|
|
39211
|
+
args: Array.isArray(server.args) ? server.args : []
|
|
39212
|
+
};
|
|
39213
|
+
}
|
|
39214
|
+
/**
|
|
39215
|
+
* Rewrite a server's command and the argument positions that name a file it
|
|
39216
|
+
* runs (see `fileArgumentIndices`), leaving every other argument as written.
|
|
39217
|
+
* Generate and import share this, so a position generate never anchors is
|
|
39218
|
+
* never relativized on import either, and a hand-written
|
|
39219
|
+
* `${CLAUDE_PLUGIN_ROOT}/…` data argument keeps its meaning.
|
|
39220
|
+
*/
|
|
39221
|
+
function rewriteServer({ server, rewriteCommand, isPath, rewritePath }) {
|
|
39222
|
+
const { command, args } = splitCommand(server);
|
|
39223
|
+
if (command === void 0) return server;
|
|
39224
|
+
const indices = fileArgumentIndices({
|
|
39225
|
+
command,
|
|
39226
|
+
args,
|
|
39227
|
+
isPath
|
|
39228
|
+
});
|
|
39229
|
+
const rewriteArgs = (values, offset) => values.map((value, index) => indices.has(index + offset) && typeof value === "string" ? rewritePath(value) : value);
|
|
39230
|
+
const next = { ...server };
|
|
39231
|
+
if (Array.isArray(server.command)) {
|
|
39232
|
+
const inlineArgs = server.command.slice(1);
|
|
39233
|
+
next.command = [rewriteCommand(command), ...rewriteArgs(inlineArgs, 0)];
|
|
39234
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, inlineArgs.length);
|
|
39235
|
+
} else {
|
|
39236
|
+
next.command = rewriteCommand(command);
|
|
39237
|
+
if (Array.isArray(server.args)) next.args = rewriteArgs(server.args, 0);
|
|
39238
|
+
}
|
|
39239
|
+
return next;
|
|
39240
|
+
}
|
|
39241
|
+
/** Anchor a server's plugin-relative command and the files its runner executes. */
|
|
39242
|
+
function anchorServer(server) {
|
|
39243
|
+
return rewriteServer({
|
|
39244
|
+
server,
|
|
39245
|
+
rewriteCommand: (command) => isPluginRelativeCommand(command) ? anchorToPluginRoot(command) : command,
|
|
39246
|
+
isPath: isDotRelative,
|
|
39247
|
+
rewritePath: anchorToPluginRoot
|
|
39248
|
+
});
|
|
39249
|
+
}
|
|
39250
|
+
/** Turn the plugin-root forms generate writes back into `./`. */
|
|
39251
|
+
function relativizeServer(server) {
|
|
39252
|
+
return rewriteServer({
|
|
39253
|
+
server,
|
|
39254
|
+
rewriteCommand: relativizeFromPluginRoot,
|
|
39255
|
+
isPath: (value) => PLUGIN_ROOT_PREFIX.test(value),
|
|
39256
|
+
rewritePath: relativizeFromPluginRoot
|
|
39257
|
+
});
|
|
39258
|
+
}
|
|
39259
|
+
/** Apply a per-server transform to every server entry that is an object. */
|
|
39260
|
+
function mapServers({ mcpServers, transform }) {
|
|
39261
|
+
if (mcpServers === null || typeof mcpServers !== "object" || Array.isArray(mcpServers)) return mcpServers;
|
|
39262
|
+
return Object.fromEntries(Object.entries(mcpServers).map(([name, server]) => server === null || typeof server !== "object" || Array.isArray(server) ? [name, server] : [name, transform(server)]));
|
|
39263
|
+
}
|
|
39264
|
+
/**
|
|
39265
|
+
* MCP servers bundled in a Claude Code plugin (`.mcp.json` at the plugin
|
|
39266
|
+
* root).
|
|
39267
|
+
*
|
|
39268
|
+
* Claude Code loads a plugin's `.mcp.json` with the consumer's project as the
|
|
39269
|
+
* working directory, so a relative `command` such as `./servers/db-server`
|
|
39270
|
+
* would be looked up in the consumer's repository, where the bundled binary
|
|
39271
|
+
* does not exist. Generate therefore rewrites a relative `command`, and the
|
|
39272
|
+
* `./` script a script runner executes (`node ./dist/server.js`,
|
|
39273
|
+
* `uv run ./server.py`), to `${CLAUDE_PLUGIN_ROOT}/…`, which Claude Code
|
|
39274
|
+
* substitutes with the installed plugin's directory. Other `./` arguments are
|
|
39275
|
+
* data the server reads relative to the consumer's project
|
|
39276
|
+
* (`server-filesystem ./docs`), so they are left as written, the same rule
|
|
39277
|
+
* plugin hook commands follow. Bare executables (`npx`, `node`), absolute paths
|
|
39278
|
+
* and values that already start with a variable pass through. `env` values and
|
|
39279
|
+
* the remote transports' `url` / `headers` are left as written, because
|
|
39280
|
+
* rulesync cannot tell a path from an opaque string there. Import turns the
|
|
39281
|
+
* plugin-root forms in `command` and `args` back into `./`, including the
|
|
39282
|
+
* shell-variable spellings a hand-written file may have copied from a hook
|
|
39283
|
+
* command (which Claude Code does not substitute in MCP config).
|
|
39284
|
+
*
|
|
39285
|
+
* @see https://code.claude.com/docs/en/plugins-reference
|
|
39286
|
+
*/
|
|
39287
|
+
var ClaudecodePluginMcp = class ClaudecodePluginMcp extends ClaudecodeMcp {
|
|
39288
|
+
static async fromRulesyncMcp(params) {
|
|
39289
|
+
const base = await super.fromRulesyncMcp(params);
|
|
39290
|
+
const json = base.getJson();
|
|
39291
|
+
const mcpServers = mapServers({
|
|
39292
|
+
mcpServers: json.mcpServers,
|
|
39293
|
+
transform: anchorServer
|
|
39294
|
+
});
|
|
39295
|
+
return new ClaudecodePluginMcp({
|
|
39296
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39297
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39298
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39299
|
+
fileContent: JSON.stringify({
|
|
39300
|
+
...json,
|
|
39301
|
+
mcpServers
|
|
39302
|
+
}, null, 2),
|
|
39303
|
+
validate: params.validate ?? true,
|
|
39304
|
+
global: params.global ?? false
|
|
39305
|
+
});
|
|
39306
|
+
}
|
|
39307
|
+
static async fromFile(params) {
|
|
39308
|
+
const base = await super.fromFile(params);
|
|
39309
|
+
return new ClaudecodePluginMcp({
|
|
39310
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39311
|
+
relativeDirPath: base.getRelativeDirPath(),
|
|
39312
|
+
relativeFilePath: base.getRelativeFilePath(),
|
|
39313
|
+
fileContent: base.getFileContent(),
|
|
39314
|
+
validate: params.validate ?? true,
|
|
39315
|
+
global: params.global ?? false
|
|
39316
|
+
});
|
|
39317
|
+
}
|
|
39318
|
+
toRulesyncMcp() {
|
|
39319
|
+
const mcpServers = mapServers({
|
|
39320
|
+
mcpServers: this.getJson().mcpServers ?? {},
|
|
39321
|
+
transform: relativizeServer
|
|
39322
|
+
});
|
|
39323
|
+
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers }, null, 2) });
|
|
39324
|
+
}
|
|
39325
|
+
static forDeletion(params) {
|
|
39326
|
+
return new ClaudecodePluginMcp({
|
|
39327
|
+
outputRoot: params.outputRoot ?? process.cwd(),
|
|
39328
|
+
relativeDirPath: params.relativeDirPath,
|
|
39329
|
+
relativeFilePath: params.relativeFilePath,
|
|
39330
|
+
fileContent: "{}",
|
|
39331
|
+
validate: false,
|
|
39332
|
+
global: params.global ?? false
|
|
39333
|
+
});
|
|
39334
|
+
}
|
|
39335
|
+
};
|
|
39336
|
+
//#endregion
|
|
38984
39337
|
//#region src/features/mcp/cline-mcp.ts
|
|
38985
39338
|
const CLINE_GLOBAL_ONLY_MESSAGE = "Cline MCP is global-only; use --global to sync ~/.cline/data/settings/cline_mcp_settings.json";
|
|
38986
39339
|
function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
@@ -41690,7 +42043,7 @@ var CrushMcp = class CrushMcp extends ToolMcp {
|
|
|
41690
42043
|
fileKey: sharedConfigFileKey(this.getSettablePaths({ global })),
|
|
41691
42044
|
feature: "mcp",
|
|
41692
42045
|
existingContent,
|
|
41693
|
-
patch: { ["mcp"]: converted },
|
|
42046
|
+
patch: { ["mcp"]: undefinedWhenEmpty(converted) },
|
|
41694
42047
|
filePath: location.filePath,
|
|
41695
42048
|
logger
|
|
41696
42049
|
}),
|
|
@@ -43270,13 +43623,23 @@ var GooseMcp = class GooseMcp extends ToolMcp {
|
|
|
43270
43623
|
//#region src/features/mcp/grokcli-mcp.ts
|
|
43271
43624
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH = 32;
|
|
43272
43625
|
/**
|
|
43626
|
+
* Grok keeps per-server MCP tool deny lists in a top-level
|
|
43627
|
+
* `[disabled_mcp_tools]` table (`map<server, string[]>` of unqualified tool
|
|
43628
|
+
* names), not inside `[mcp_servers.<name>]`. Grok reads it from the user
|
|
43629
|
+
* `~/.grok/config.toml` only: a project `.grok/config.toml` contributes just
|
|
43630
|
+
* `[mcp_servers]`, `[plugins]`, `[permission]` and `[mcp] max_output_bytes`.
|
|
43631
|
+
* https://github.com/xai-org/grok-build/blob/2bdd1d6a/crates/codegen/xai-grok-pager/docs/user-guide/26-config-reference.md
|
|
43632
|
+
*/
|
|
43633
|
+
const GROK_DISABLED_MCP_TOOLS_KEY = "disabled_mcp_tools";
|
|
43634
|
+
/**
|
|
43273
43635
|
* Grok Build stores MCP servers in `config.toml` under a `[mcp_servers.<name>]`
|
|
43274
43636
|
* table. Verified against `grok mcp add` (grok 0.2.54): a stdio server emits
|
|
43275
43637
|
* `command`, `args`, `enabled = true`, and an `[mcp_servers.<name>.env]` table;
|
|
43276
43638
|
* a remote server emits `url` and `enabled`. Unlike Codex CLI, Grok uses a
|
|
43277
|
-
* literal `env` table (not the `env_vars` passthrough list)
|
|
43278
|
-
*
|
|
43279
|
-
*
|
|
43639
|
+
* literal `env` table (not the `env_vars` passthrough list), so the only field
|
|
43640
|
+
* rename is `disabled` (rulesync) ↔ `enabled = false` (grok). `disabledTools`
|
|
43641
|
+
* never lands in the server table: it is lifted into the top-level
|
|
43642
|
+
* `[disabled_mcp_tools]` table by `buildDisabledMcpTools`.
|
|
43280
43643
|
*/
|
|
43281
43644
|
function convertToGrokFormat(mcpServers) {
|
|
43282
43645
|
const result = {};
|
|
@@ -43285,7 +43648,7 @@ function convertToGrokFormat(mcpServers) {
|
|
|
43285
43648
|
if (!isRecord$1(config)) continue;
|
|
43286
43649
|
const converted = {};
|
|
43287
43650
|
for (const [key, value] of Object.entries(config)) {
|
|
43288
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
43651
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key) || key === "disabledTools") continue;
|
|
43289
43652
|
if (key === "disabled") {
|
|
43290
43653
|
if (value === true) converted["enabled"] = false;
|
|
43291
43654
|
} else converted[key] = value;
|
|
@@ -43309,6 +43672,38 @@ function convertFromGrokFormat(grokMcp) {
|
|
|
43309
43672
|
}
|
|
43310
43673
|
return result;
|
|
43311
43674
|
}
|
|
43675
|
+
function toToolNameList(value) {
|
|
43676
|
+
if (!Array.isArray(value)) return [];
|
|
43677
|
+
return value.filter((tool) => typeof tool === "string");
|
|
43678
|
+
}
|
|
43679
|
+
/**
|
|
43680
|
+
* Recompute the global `[disabled_mcp_tools]` table: every server Rulesync
|
|
43681
|
+
* declares gets its `disabledTools` (or loses a stale entry when it has none —
|
|
43682
|
+
* Grok itself removes an entry whose list empties), while entries for other
|
|
43683
|
+
* servers — ones toggled in Grok's `/mcps` UI, plugin or compat servers, and
|
|
43684
|
+
* the `__managed_gateway_connectors` sentinel — are carried over untouched.
|
|
43685
|
+
* Returns `undefined` when nothing is left, so the key is retracted.
|
|
43686
|
+
*/
|
|
43687
|
+
function buildDisabledMcpTools({ mcpServers, existingContent, filePath }) {
|
|
43688
|
+
const existing = parseSharedConfig({
|
|
43689
|
+
format: "toml",
|
|
43690
|
+
fileContent: existingContent,
|
|
43691
|
+
filePath
|
|
43692
|
+
})[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43693
|
+
const result = isRecord$1(existing) ? { ...existing } : {};
|
|
43694
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
43695
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord$1(config)) continue;
|
|
43696
|
+
const tools = toToolNameList(config.disabledTools);
|
|
43697
|
+
if (tools.length > 0) result[name] = tools;
|
|
43698
|
+
else delete result[name];
|
|
43699
|
+
}
|
|
43700
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
43701
|
+
}
|
|
43702
|
+
function warnProjectDisabledTools(mcpServers, logger) {
|
|
43703
|
+
const serverNames = Object.entries(mcpServers).filter(([, config]) => isRecord$1(config) && config.disabledTools !== void 0).map(([name]) => name);
|
|
43704
|
+
if (serverNames.length === 0) return;
|
|
43705
|
+
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.`);
|
|
43706
|
+
}
|
|
43312
43707
|
var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
43313
43708
|
toml;
|
|
43314
43709
|
constructor({ ...rest }) {
|
|
@@ -43345,16 +43740,19 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43345
43740
|
relativeDirPath: paths.relativeDirPath,
|
|
43346
43741
|
relativeFilePath: paths.relativeFilePath,
|
|
43347
43742
|
fileContent,
|
|
43348
|
-
validate
|
|
43743
|
+
validate,
|
|
43744
|
+
global
|
|
43349
43745
|
});
|
|
43350
43746
|
}
|
|
43351
|
-
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
43747
|
+
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false, logger }) {
|
|
43352
43748
|
const paths = this.getSettablePaths({ global });
|
|
43353
43749
|
const configTomlFilePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
43354
43750
|
const configTomlFileContent = await readFileContentOrNull(configTomlFilePath) ?? "";
|
|
43355
|
-
const
|
|
43751
|
+
const strippedMcpServers = rulesyncMcp.getMcpServers();
|
|
43752
|
+
const converted = convertToGrokFormat(strippedMcpServers);
|
|
43356
43753
|
const filteredMcpServers = this.removeEmptyEntries(converted);
|
|
43357
|
-
|
|
43754
|
+
if (!global) warnProjectDisabledTools(strippedMcpServers, logger);
|
|
43755
|
+
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`);
|
|
43358
43756
|
return new GrokcliMcp({
|
|
43359
43757
|
outputRoot,
|
|
43360
43758
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -43363,14 +43761,31 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
|
|
|
43363
43761
|
fileKey: sharedConfigFileKey(paths),
|
|
43364
43762
|
feature: "mcp",
|
|
43365
43763
|
existingContent: configTomlFileContent,
|
|
43366
|
-
patch: {
|
|
43764
|
+
patch: {
|
|
43765
|
+
mcp_servers: Object.keys(filteredMcpServers).length > 0 ? filteredMcpServers : void 0,
|
|
43766
|
+
...global && { [GROK_DISABLED_MCP_TOOLS_KEY]: buildDisabledMcpTools({
|
|
43767
|
+
mcpServers: strippedMcpServers,
|
|
43768
|
+
existingContent: configTomlFileContent,
|
|
43769
|
+
filePath: configTomlFilePath
|
|
43770
|
+
}) }
|
|
43771
|
+
},
|
|
43367
43772
|
filePath: configTomlFilePath
|
|
43368
43773
|
}),
|
|
43369
|
-
validate
|
|
43774
|
+
validate,
|
|
43775
|
+
global
|
|
43370
43776
|
});
|
|
43371
43777
|
}
|
|
43372
43778
|
toRulesyncMcp() {
|
|
43373
43779
|
const converted = convertFromGrokFormat(this.toml.mcp_servers ?? {});
|
|
43780
|
+
const disabledMcpTools = this.toml[GROK_DISABLED_MCP_TOOLS_KEY];
|
|
43781
|
+
if (this.global && isRecord$1(disabledMcpTools)) for (const [name, config] of Object.entries(converted)) {
|
|
43782
|
+
if (!Object.hasOwn(disabledMcpTools, name)) continue;
|
|
43783
|
+
const tools = toToolNameList(disabledMcpTools[name]);
|
|
43784
|
+
if (tools.length > 0) converted[name] = {
|
|
43785
|
+
...config,
|
|
43786
|
+
disabledTools: tools
|
|
43787
|
+
};
|
|
43788
|
+
}
|
|
43374
43789
|
return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: converted }, null, 2) });
|
|
43375
43790
|
}
|
|
43376
43791
|
validate() {
|
|
@@ -46434,7 +46849,7 @@ var PoolMcp = class PoolMcp extends ToolMcp {
|
|
|
46434
46849
|
fileKey: sharedConfigFileKey(paths),
|
|
46435
46850
|
feature: "mcp",
|
|
46436
46851
|
existingContent,
|
|
46437
|
-
patch: { [POOL_MCP_SERVERS_KEY]: converted },
|
|
46852
|
+
patch: { [POOL_MCP_SERVERS_KEY]: undefinedWhenEmpty(converted) },
|
|
46438
46853
|
filePath
|
|
46439
46854
|
}),
|
|
46440
46855
|
validate,
|
|
@@ -46564,7 +46979,7 @@ var QoderMcp = class QoderMcp extends ToolMcp {
|
|
|
46564
46979
|
fileKey: sharedConfigFileKey(paths),
|
|
46565
46980
|
feature: "mcp",
|
|
46566
46981
|
existingContent,
|
|
46567
|
-
patch: { mcpServers },
|
|
46982
|
+
patch: { mcpServers: undefinedWhenEmpty(mcpServers) },
|
|
46568
46983
|
filePath
|
|
46569
46984
|
}) : JSON.stringify({
|
|
46570
46985
|
...parseQoderMcpConfig({
|
|
@@ -46943,18 +47358,28 @@ function warnAboutRetiredFields({ name, serverRecord, logger }) {
|
|
|
46943
47358
|
* `" Serial "` counts too and is written as authored.
|
|
46944
47359
|
*/
|
|
46945
47360
|
const REASONIX_CONCURRENCY_VALUES = /* @__PURE__ */ new Set(["serial", "parallel"]);
|
|
46946
|
-
/**
|
|
46947
|
-
* The type Reasonix decodes each fixed-type scalar passthrough field into
|
|
46948
|
-
* (`PluginEntry` in `internal/config/plugin_entry.go`, and
|
|
46949
|
-
* `internal/contract/config/plugin_entry.go` on the CLI v2 line).
|
|
46950
|
-
*/
|
|
46951
47361
|
const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
47362
|
+
startup_timeout_seconds: "integer",
|
|
47363
|
+
call_timeout_seconds: "integer",
|
|
47364
|
+
tool_timeout_seconds: "integer table",
|
|
46952
47365
|
concurrency: "string",
|
|
46953
47366
|
auto_start: "boolean",
|
|
46954
47367
|
load: "string",
|
|
46955
47368
|
oauth_allow_missing_pkce_metadata: "boolean"
|
|
46956
47369
|
};
|
|
46957
47370
|
/**
|
|
47371
|
+
* Whether `value` is written as a TOML integer. smol-toml writes a JS number
|
|
47372
|
+
* outside the safe-integer range, or with a fraction, as a TOML float.
|
|
47373
|
+
*/
|
|
47374
|
+
function isTomlInteger(value) {
|
|
47375
|
+
return typeof value === "number" && Number.isSafeInteger(value);
|
|
47376
|
+
}
|
|
47377
|
+
function hasExpectedType(value, expected) {
|
|
47378
|
+
if (expected === "integer") return isTomlInteger(value);
|
|
47379
|
+
if (expected === "integer table") return isRecord$1(value) && Object.values(value).every(isTomlInteger);
|
|
47380
|
+
return typeof value === expected;
|
|
47381
|
+
}
|
|
47382
|
+
/**
|
|
46958
47383
|
* Why a passthrough value cannot be written, or `undefined` when it can (and
|
|
46959
47384
|
* for every field without a fixed type). A wrong type is the case that matters:
|
|
46960
47385
|
* a TOML type mismatch makes Reasonix refuse the whole config file. An unknown
|
|
@@ -46966,7 +47391,7 @@ const REASONIX_TYPED_PLUGIN_FIELDS = {
|
|
|
46966
47391
|
*/
|
|
46967
47392
|
function invalidPassthroughFieldReason(field, value) {
|
|
46968
47393
|
const expected = REASONIX_TYPED_PLUGIN_FIELDS[field];
|
|
46969
|
-
if (expected !== void 0 &&
|
|
47394
|
+
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.`;
|
|
46970
47395
|
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.`;
|
|
46971
47396
|
}
|
|
46972
47397
|
/**
|
|
@@ -47988,7 +48413,7 @@ var TabnineMcp = class TabnineMcp extends ToolMcp {
|
|
|
47988
48413
|
fileKey: sharedConfigFileKey(paths),
|
|
47989
48414
|
feature: "mcp",
|
|
47990
48415
|
existingContent,
|
|
47991
|
-
patch: { mcpServers: convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger) },
|
|
48416
|
+
patch: { mcpServers: undefinedWhenEmpty(convertToTabnineFormat(rulesyncMcp.getMcpServers(), logger)) },
|
|
47992
48417
|
filePath
|
|
47993
48418
|
}),
|
|
47994
48419
|
validate,
|
|
@@ -49309,6 +49734,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49309
49734
|
const paths = this.getSettablePaths({ global });
|
|
49310
49735
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
49311
49736
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
49737
|
+
const contextServers = convertServersToZedFormat(rulesyncMcp.getMcpServers(), logger);
|
|
49312
49738
|
return new ZedMcp({
|
|
49313
49739
|
outputRoot,
|
|
49314
49740
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -49317,7 +49743,7 @@ var ZedMcp = class ZedMcp extends ToolMcp {
|
|
|
49317
49743
|
fileKey: sharedConfigFileKey(paths),
|
|
49318
49744
|
feature: "mcp",
|
|
49319
49745
|
existingContent,
|
|
49320
|
-
patch: { context_servers:
|
|
49746
|
+
patch: { context_servers: Object.keys(contextServers).length > 0 ? contextServers : void 0 },
|
|
49321
49747
|
filePath
|
|
49322
49748
|
}),
|
|
49323
49749
|
validate
|
|
@@ -49450,7 +49876,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49450
49876
|
}
|
|
49451
49877
|
}],
|
|
49452
49878
|
["claudecode-plugin", {
|
|
49453
|
-
class:
|
|
49879
|
+
class: ClaudecodePluginMcp,
|
|
49454
49880
|
meta: {
|
|
49455
49881
|
supportsProject: true,
|
|
49456
49882
|
supportsGlobal: false,
|
|
@@ -49626,7 +50052,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
49626
50052
|
supportsProject: true,
|
|
49627
50053
|
supportsGlobal: true,
|
|
49628
50054
|
supportsEnabledTools: false,
|
|
49629
|
-
supportsDisabledTools:
|
|
50055
|
+
supportsDisabledTools: true
|
|
49630
50056
|
}
|
|
49631
50057
|
}],
|
|
49632
50058
|
["hermesagent", {
|
|
@@ -50792,6 +51218,8 @@ function parsedGlobsIntersect(left, right, budget) {
|
|
|
50792
51218
|
* @see https://antigravity.google/docs/permissions?tab=cli
|
|
50793
51219
|
*/
|
|
50794
51220
|
const REGEX_PREFIX = "regex:";
|
|
51221
|
+
const TRAILING_ANCHOR = /(?<!\\)\$$/;
|
|
51222
|
+
const PLAIN_TARGET_GLOB_CHARACTERS = /[*?[{\\]/;
|
|
50795
51223
|
const REGEX_METACHARACTERS$1 = /[\\^$.|?*+()[\]{}]/;
|
|
50796
51224
|
const WHITESPACE_CODES = [
|
|
50797
51225
|
9,
|
|
@@ -50932,6 +51360,7 @@ function replacePosixClasses(source) {
|
|
|
50932
51360
|
function isInvalidRegex(source) {
|
|
50933
51361
|
const unquoted = source.replace(RE2_QUOTED, (match) => match === "\\\\" ? match : "q");
|
|
50934
51362
|
if (LOOKAROUND.test(unquoted) || hasRefusedEscape(unquoted)) return true;
|
|
51363
|
+
if (hasClassEscapeRangeEnd(unquoted)) return true;
|
|
50935
51364
|
const withoutPosixClasses = replacePosixClasses(unquoted);
|
|
50936
51365
|
if (withoutPosixClasses === void 0 || hasRefusedRepeat(withoutPosixClasses)) return true;
|
|
50937
51366
|
const asJavaScript = withoutPosixClasses.replace(/\(\?P</g, "(?<").replace(/\(\?[imsU]*-?[imsU]*\)/g, "").replace(/\(\?[imsU]*-?[imsU]*:/g, "(?:").replace(/\\A/g, "^").replace(/\\z/g, "$");
|
|
@@ -50956,6 +51385,74 @@ function hasRefusedEscape(source) {
|
|
|
50956
51385
|
}
|
|
50957
51386
|
return false;
|
|
50958
51387
|
}
|
|
51388
|
+
const CLASS_ESCAPE_LETTERS = /[dDsSwWpP]/;
|
|
51389
|
+
/** The length of the escape at `index`, braces (`\x{41}`, `\p{Greek}`) included. */
|
|
51390
|
+
function escapeLength(source, index) {
|
|
51391
|
+
const escaped = source[index + 1] ?? "";
|
|
51392
|
+
if (/[xpP]/.test(escaped) && source[index + 2] === "{") {
|
|
51393
|
+
const close = source.indexOf("}", index + 3);
|
|
51394
|
+
return close === -1 ? source.length - index : close - index + 1;
|
|
51395
|
+
}
|
|
51396
|
+
if (escaped === "p" || escaped === "P") return 3;
|
|
51397
|
+
return escaped === "x" ? 4 : 2;
|
|
51398
|
+
}
|
|
51399
|
+
/** Step past `^` and a leading `]` (a member) after the `[` that ends at `index`. */
|
|
51400
|
+
function openClass(source, start) {
|
|
51401
|
+
const index = source[start] === "^" ? start + 1 : start;
|
|
51402
|
+
return source[index] === "]" ? {
|
|
51403
|
+
index: index + 1,
|
|
51404
|
+
afterCharacter: true
|
|
51405
|
+
} : {
|
|
51406
|
+
index,
|
|
51407
|
+
afterCharacter: false
|
|
51408
|
+
};
|
|
51409
|
+
}
|
|
51410
|
+
/**
|
|
51411
|
+
* Whether a bracket range ends in a class escape (`[a-\d]`, `[--\s]`). Go
|
|
51412
|
+
* reads the end of a range as one character and refuses such a regex, while
|
|
51413
|
+
* JavaScript without the `u` flag reads it as three members. A class escape
|
|
51414
|
+
* that starts the range (`[\d-z]`) is a member followed by `-`, which both
|
|
51415
|
+
* accept. The `u` flag cannot be used instead: it also refuses RE2 spellings
|
|
51416
|
+
* such as `\x{41}` and `\-` outside a class.
|
|
51417
|
+
*/
|
|
51418
|
+
function hasClassEscapeRangeEnd(source) {
|
|
51419
|
+
let inClass = false;
|
|
51420
|
+
let afterCharacter = false;
|
|
51421
|
+
let index = 0;
|
|
51422
|
+
while (index < source.length) {
|
|
51423
|
+
const character = source[index] ?? "";
|
|
51424
|
+
if (character === "\\") {
|
|
51425
|
+
afterCharacter = inClass && !CLASS_ESCAPE_LETTERS.test(source[index + 1] ?? "");
|
|
51426
|
+
index += escapeLength(source, index);
|
|
51427
|
+
continue;
|
|
51428
|
+
}
|
|
51429
|
+
if (!inClass) {
|
|
51430
|
+
inClass = character === "[";
|
|
51431
|
+
({index, afterCharacter} = inClass ? openClass(source, index + 1) : {
|
|
51432
|
+
index: index + 1,
|
|
51433
|
+
afterCharacter: false
|
|
51434
|
+
});
|
|
51435
|
+
continue;
|
|
51436
|
+
}
|
|
51437
|
+
const posixEnd = source.startsWith("[:", index) ? source.indexOf(":]", index + 2) : -1;
|
|
51438
|
+
if (character === "]" || posixEnd !== -1) {
|
|
51439
|
+
inClass = character !== "]";
|
|
51440
|
+
afterCharacter = false;
|
|
51441
|
+
index = posixEnd === -1 ? index + 1 : posixEnd + 2;
|
|
51442
|
+
continue;
|
|
51443
|
+
}
|
|
51444
|
+
if (character === "-" && afterCharacter && source[index + 1] !== "]") {
|
|
51445
|
+
const end = index + 1;
|
|
51446
|
+
if (source[end] === "\\" && CLASS_ESCAPE_LETTERS.test(source[end + 1] ?? "")) return true;
|
|
51447
|
+
afterCharacter = false;
|
|
51448
|
+
index = end + (source[end] === "\\" ? escapeLength(source, end) : 1);
|
|
51449
|
+
continue;
|
|
51450
|
+
}
|
|
51451
|
+
afterCharacter = true;
|
|
51452
|
+
index += 1;
|
|
51453
|
+
}
|
|
51454
|
+
return false;
|
|
51455
|
+
}
|
|
50959
51456
|
/**
|
|
50960
51457
|
* Whether a repeat count is over Go's limit. Braces inside a bracket class
|
|
50961
51458
|
* (`[a{1001}]`) and in `\x{2003}` or `\p{Greek}` are not repeats, so those
|
|
@@ -51103,7 +51600,7 @@ function toAntigravityCommandLiteralPrefix(pattern) {
|
|
|
51103
51600
|
* the rule a regex.
|
|
51104
51601
|
*/
|
|
51105
51602
|
function regexWordToGlob(word) {
|
|
51106
|
-
const body = word.replace(/^\^/, "").replace(
|
|
51603
|
+
const body = word.replace(/^\^/, "").replace(TRAILING_ANCHOR, "");
|
|
51107
51604
|
const characters = Array.from(body);
|
|
51108
51605
|
if (characters.length === 0) return;
|
|
51109
51606
|
let glob = "";
|
|
@@ -51144,12 +51641,61 @@ function regexToGlob(source) {
|
|
|
51144
51641
|
function fromAntigravityCommandTarget(target) {
|
|
51145
51642
|
if (target.startsWith(REGEX_PREFIX)) return regexToGlob(target.slice(6)) ?? target;
|
|
51146
51643
|
if (target === "*") return target;
|
|
51147
|
-
if (
|
|
51644
|
+
if (PLAIN_TARGET_GLOB_CHARACTERS.test(target)) {
|
|
51148
51645
|
const words = splitWords(target);
|
|
51149
51646
|
return `${REGEX_PREFIX}${words.map((word) => `^${[...word].map(escapeRegexCharacter).join("")}$`).join(" ")}`;
|
|
51150
51647
|
}
|
|
51151
51648
|
return `${target} *`;
|
|
51152
51649
|
}
|
|
51650
|
+
/**
|
|
51651
|
+
* Whether a regex word means the same anchored or not: each end is either
|
|
51652
|
+
* anchored (`^`, an unescaped `$`) or open-ended (`.*`).
|
|
51653
|
+
*/
|
|
51654
|
+
function isAnchoredWord(word) {
|
|
51655
|
+
const startAnchored = word.startsWith("^") || word.startsWith(".*");
|
|
51656
|
+
const endAnchored = TRAILING_ANCHOR.test(word) || word.endsWith(".*");
|
|
51657
|
+
return startAnchored && endAnchored;
|
|
51658
|
+
}
|
|
51659
|
+
/**
|
|
51660
|
+
* Explain what importing an Antigravity `command` target may lose, or return
|
|
51661
|
+
* `undefined` when the import is faithful.
|
|
51662
|
+
*
|
|
51663
|
+
* - A `regex:` target with a word that is not anchored is imported as a glob
|
|
51664
|
+
* that assumes anchoring. If Antigravity matches such a word unanchored,
|
|
51665
|
+
* the imported rule is narrower (`regex:rm` also matches `rmdir`).
|
|
51666
|
+
* - A plain target holding characters that are special in a glob is most
|
|
51667
|
+
* likely the verbatim output of an older rulesync (`command(rm -rf *)`).
|
|
51668
|
+
* Antigravity matches it literally, so it imports as a regex that matches a
|
|
51669
|
+
* literal `*` word and is inert in the other tools it is exported to.
|
|
51670
|
+
*/
|
|
51671
|
+
function describeAntigravityCommandImportLoss(target) {
|
|
51672
|
+
if (target.startsWith(REGEX_PREFIX)) {
|
|
51673
|
+
const source = target.slice(6);
|
|
51674
|
+
if (splitWords(source).some((word) => !isAnchoredWord(word)) && regexToGlob(source) !== void 0) return {
|
|
51675
|
+
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",
|
|
51676
|
+
denyConsequence: "The imported deny may block less than it does in Antigravity."
|
|
51677
|
+
};
|
|
51678
|
+
return;
|
|
51679
|
+
}
|
|
51680
|
+
if (target !== "*" && PLAIN_TARGET_GLOB_CHARACTERS.test(target)) return {
|
|
51681
|
+
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",
|
|
51682
|
+
denyConsequence: "This deny is NOT enforced by the other tools it is generated for."
|
|
51683
|
+
};
|
|
51684
|
+
}
|
|
51685
|
+
/**
|
|
51686
|
+
* Import one Antigravity `command` target as a canonical bash pattern, warning
|
|
51687
|
+
* when the result may differ from what Antigravity enforces. Shared by the
|
|
51688
|
+
* Antigravity CLI and IDE importers.
|
|
51689
|
+
*/
|
|
51690
|
+
function importAntigravityCommandTarget({ target, entry, decision, logger, toolLabel }) {
|
|
51691
|
+
const pattern = fromAntigravityCommandTarget(target);
|
|
51692
|
+
const loss = describeAntigravityCommandImportLoss(target);
|
|
51693
|
+
if (loss !== void 0) {
|
|
51694
|
+
const consequence = decision === "deny" ? ` ${loss.denyConsequence}` : "";
|
|
51695
|
+
logger.warn(`${toolLabel} permissions: importing ${entry} as "${pattern}": ${loss.reason}.${consequence}`);
|
|
51696
|
+
}
|
|
51697
|
+
return pattern;
|
|
51698
|
+
}
|
|
51153
51699
|
//#endregion
|
|
51154
51700
|
//#region src/features/permissions/antigravity-targets.ts
|
|
51155
51701
|
/**
|
|
@@ -51180,6 +51726,7 @@ const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
|
|
|
51180
51726
|
"**/*"
|
|
51181
51727
|
]);
|
|
51182
51728
|
const DIRECTORY_CONTENTS_SUFFIX = /\/\*\*(?:\/\*)?$/;
|
|
51729
|
+
const HOME_OR_VARIABLE_PATH = /^~(?:\/|$)|^\$[A-Za-z_]|\$\{[A-Za-z_]/;
|
|
51183
51730
|
const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
|
|
51184
51731
|
const DENY_HINTS = {
|
|
51185
51732
|
read_file: "deny a directory or file path instead",
|
|
@@ -51262,6 +51809,10 @@ function buildAntigravityPermissionEntry({ action, category, pattern, decision,
|
|
|
51262
51809
|
return;
|
|
51263
51810
|
}
|
|
51264
51811
|
if (result.note !== void 0 && decision !== "deny") logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}.`);
|
|
51812
|
+
if (PATH_ACTIONS.has(action) && HOME_OR_VARIABLE_PATH.test(result.target)) {
|
|
51813
|
+
const consequence = decision === "deny" ? "this deny may block nothing" : `${toolLabel} may fall back to its default`;
|
|
51814
|
+
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.`);
|
|
51815
|
+
}
|
|
51265
51816
|
return result.target === "*" ? action : `${action}(${result.target})`;
|
|
51266
51817
|
}
|
|
51267
51818
|
//#endregion
|
|
@@ -51828,9 +52379,9 @@ function convertRulesyncToAntigravityCliPermissions({ config, logger }) {
|
|
|
51828
52379
|
}
|
|
51829
52380
|
}
|
|
51830
52381
|
return {
|
|
51831
|
-
allow,
|
|
51832
|
-
ask,
|
|
51833
|
-
deny
|
|
52382
|
+
allow: [...new Set(allow)],
|
|
52383
|
+
ask: [...new Set(ask)],
|
|
52384
|
+
deny: [...new Set(deny)]
|
|
51834
52385
|
};
|
|
51835
52386
|
}
|
|
51836
52387
|
/**
|
|
@@ -51842,7 +52393,13 @@ function convertAntigravityCliToRulesyncPermissions(params) {
|
|
|
51842
52393
|
for (const entry of entries) {
|
|
51843
52394
|
const { toolName, pattern: target } = parsePermissionEntry$1(entry);
|
|
51844
52395
|
const canonical = toCanonicalToolName$9(toolName);
|
|
51845
|
-
const pattern = toolName === "command"
|
|
52396
|
+
const pattern = toolName === "command" ? importAntigravityCommandTarget({
|
|
52397
|
+
target,
|
|
52398
|
+
entry,
|
|
52399
|
+
decision: action,
|
|
52400
|
+
logger: moduleLogger$4,
|
|
52401
|
+
toolLabel: "Antigravity CLI"
|
|
52402
|
+
}) : target;
|
|
51846
52403
|
if (!permission[canonical]) permission[canonical] = {};
|
|
51847
52404
|
permission[canonical][pattern] = action;
|
|
51848
52405
|
}
|
|
@@ -52046,9 +52603,9 @@ function convertRulesyncToAntigravityIdePermissions({ config, logger }) {
|
|
|
52046
52603
|
}
|
|
52047
52604
|
}
|
|
52048
52605
|
return {
|
|
52049
|
-
allow,
|
|
52050
|
-
ask,
|
|
52051
|
-
deny
|
|
52606
|
+
allow: [...new Set(allow)],
|
|
52607
|
+
ask: [...new Set(ask)],
|
|
52608
|
+
deny: [...new Set(deny)]
|
|
52052
52609
|
};
|
|
52053
52610
|
}
|
|
52054
52611
|
/**
|
|
@@ -52060,7 +52617,13 @@ function convertAntigravityIdeToRulesyncPermissions(params) {
|
|
|
52060
52617
|
for (const entry of entries) {
|
|
52061
52618
|
const { action: ideAction, pattern: target } = parsePermissionEntry(entry);
|
|
52062
52619
|
const canonical = toCanonicalCategory$2(ideAction);
|
|
52063
|
-
const pattern = ideAction === "command"
|
|
52620
|
+
const pattern = ideAction === "command" ? importAntigravityCommandTarget({
|
|
52621
|
+
target,
|
|
52622
|
+
entry,
|
|
52623
|
+
decision: action,
|
|
52624
|
+
logger: fallbackLogger,
|
|
52625
|
+
toolLabel: "Antigravity IDE"
|
|
52626
|
+
}) : target;
|
|
52064
52627
|
if (!permission[canonical]) permission[canonical] = {};
|
|
52065
52628
|
permission[canonical][pattern] = action;
|
|
52066
52629
|
}
|
|
@@ -54042,7 +54605,6 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54042
54605
|
managedSourcesBehavior: "managed settings",
|
|
54043
54606
|
modelPricing: "managed settings",
|
|
54044
54607
|
parentSettingsBehavior: "managed settings",
|
|
54045
|
-
permissionExplainerEnabled: "~/.claude.json",
|
|
54046
54608
|
pluginSuggestionMarketplaces: "managed settings",
|
|
54047
54609
|
pluginTrustMessage: "managed settings",
|
|
54048
54610
|
prStatusFooterEnabled: "~/.claude.json",
|
|
@@ -54051,10 +54613,24 @@ const CLAUDECODE_UNHONORED_KEY_SOURCES = {
|
|
|
54051
54613
|
sshHostAllowlist: "managed settings",
|
|
54052
54614
|
strictKnownMarketplaces: "managed settings",
|
|
54053
54615
|
strictPluginOnlyCustomization: "managed settings",
|
|
54054
|
-
teammateDefaultModel: "~/.claude.json",
|
|
54055
54616
|
wslInheritsWindowsSettings: "managed settings"
|
|
54056
54617
|
};
|
|
54057
54618
|
/**
|
|
54619
|
+
* Top-level settings keys Claude Code has removed, with the version that
|
|
54620
|
+
* removed each. Current versions ignore them wherever they are set, so they
|
|
54621
|
+
* are dropped in **both** scopes, and the warning names the version so the
|
|
54622
|
+
* author knows to delete the key rather than move it to another file.
|
|
54623
|
+
*
|
|
54624
|
+
* Derived from the "Removed in" notes of the settings reference.
|
|
54625
|
+
*
|
|
54626
|
+
* @see https://code.claude.com/docs/en/settings-reference
|
|
54627
|
+
*/
|
|
54628
|
+
const CLAUDECODE_REMOVED_KEYS = {
|
|
54629
|
+
permissionExplainerEnabled: "v2.1.257",
|
|
54630
|
+
taskOutputMaxChars: "v2.1.277",
|
|
54631
|
+
teammateDefaultModel: "v2.1.234"
|
|
54632
|
+
};
|
|
54633
|
+
/**
|
|
54058
54634
|
* Top-level settings keys whose value Claude Code **executes**. The generic
|
|
54059
54635
|
* passthrough refuses them outright rather than warning: `.rulesync/*` files
|
|
54060
54636
|
* are shareable — `rulesync fetch` copies a third party's `permissions.jsonc`
|
|
@@ -54334,6 +54910,15 @@ const CLAUDECODE_SETTINGS_KEY_ALIASES = {
|
|
|
54334
54910
|
allowedMarketplaces: "strictKnownMarketplaces"
|
|
54335
54911
|
};
|
|
54336
54912
|
/**
|
|
54913
|
+
* The warning for a key that neither scope can honor — one Claude Code has
|
|
54914
|
+
* removed, or one read only from a file rulesync does not generate — or
|
|
54915
|
+
* `undefined` when the key may be written in at least one scope.
|
|
54916
|
+
*/
|
|
54917
|
+
function neverHonoredKeyWarning({ canonicalKey, shown, relativeFilePath }) {
|
|
54918
|
+
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.`;
|
|
54919
|
+
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.`;
|
|
54920
|
+
}
|
|
54921
|
+
/**
|
|
54337
54922
|
* Copy of the authored top-level passthrough with the keys the target file
|
|
54338
54923
|
* cannot honor removed, warning once per dropped key. Like
|
|
54339
54924
|
* `stripSandboxPaths`, only the override copy is filtered — a value
|
|
@@ -54350,8 +54935,13 @@ function stripUnhonoredTopLevelKeys({ overrides, global, relativeFilePath, logge
|
|
|
54350
54935
|
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.`);
|
|
54351
54936
|
continue;
|
|
54352
54937
|
}
|
|
54353
|
-
|
|
54354
|
-
|
|
54938
|
+
const neverHonored = neverHonoredKeyWarning({
|
|
54939
|
+
canonicalKey,
|
|
54940
|
+
shown,
|
|
54941
|
+
relativeFilePath
|
|
54942
|
+
});
|
|
54943
|
+
if (neverHonored !== void 0) {
|
|
54944
|
+
logger?.warn(neverHonored);
|
|
54355
54945
|
continue;
|
|
54356
54946
|
}
|
|
54357
54947
|
if (!global && CLAUDECODE_USER_SCOPE_ONLY_KEYS.has(canonicalKey)) {
|
|
@@ -62042,8 +62632,10 @@ function mergeKimiCodeToolsSection({ existingContent, patch }) {
|
|
|
62042
62632
|
* table. The gateway replaces the owned `permission` key wholesale and rulesync
|
|
62043
62633
|
* only ever authors `rules`, so a hand-written sibling such as
|
|
62044
62634
|
* `dangerous_command_guard = false` (Kimi Code 0.40.0) would otherwise be
|
|
62045
|
-
* deleted on every generate.
|
|
62046
|
-
*
|
|
62635
|
+
* deleted on every generate. Unmodeled keys pass through verbatim; `rules`
|
|
62636
|
+
* always comes from the patch, and so does `dangerous_command_guard` when the
|
|
62637
|
+
* `kimi-code.dangerousCommandGuard` override sets it. Leaving the override out
|
|
62638
|
+
* keeps whatever the file already has.
|
|
62047
62639
|
*
|
|
62048
62640
|
* @see https://moonshotai.github.io/kimi-code/en/configuration/config-files.html#permission
|
|
62049
62641
|
*/
|
|
@@ -62286,14 +62878,18 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62286
62878
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions, logger }) {
|
|
62287
62879
|
const config = rulesyncPermissions.getJson();
|
|
62288
62880
|
const defaultPermissionMode = config["kimi-code"]?.defaultPermissionMode;
|
|
62881
|
+
const dangerousCommandGuard = config["kimi-code"]?.dangerousCommandGuard;
|
|
62289
62882
|
const tools = buildKimiCodeToolsSection(config["kimi-code"]?.tools);
|
|
62290
62883
|
const document = {
|
|
62291
62884
|
...defaultPermissionMode && { default_permission_mode: defaultPermissionMode },
|
|
62292
62885
|
...tools && { tools },
|
|
62293
|
-
permission: {
|
|
62294
|
-
|
|
62295
|
-
|
|
62296
|
-
|
|
62886
|
+
permission: {
|
|
62887
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerous_command_guard: dangerousCommandGuard },
|
|
62888
|
+
rules: canonicalToKimiCodeRules({
|
|
62889
|
+
config,
|
|
62890
|
+
logger
|
|
62891
|
+
})
|
|
62892
|
+
}
|
|
62297
62893
|
};
|
|
62298
62894
|
let fileContent;
|
|
62299
62895
|
try {
|
|
@@ -62315,11 +62911,14 @@ var KimiCodePermissions = class KimiCodePermissions extends ToolPermissions {
|
|
|
62315
62911
|
format: "toml",
|
|
62316
62912
|
fileContent: this.getFileContent()
|
|
62317
62913
|
});
|
|
62318
|
-
const
|
|
62914
|
+
const permissionConfig = isRecord$1(config.permission) ? config.permission : {};
|
|
62915
|
+
const { permission, nativeRules } = preserveKimiCodeRules(permissionConfig.rules);
|
|
62319
62916
|
const defaultPermissionMode = config.default_permission_mode;
|
|
62917
|
+
const dangerousCommandGuard = permissionConfig.dangerous_command_guard;
|
|
62320
62918
|
const tools = buildKimiCodeToolsSection(config.tools);
|
|
62321
62919
|
const toolOverride = {
|
|
62322
62920
|
...defaultPermissionMode === "manual" || defaultPermissionMode === "yolo" || defaultPermissionMode === "auto" ? { defaultPermissionMode } : {},
|
|
62921
|
+
...typeof dangerousCommandGuard === "boolean" && { dangerousCommandGuard },
|
|
62323
62922
|
...nativeRules.length > 0 && { rules: nativeRules },
|
|
62324
62923
|
...tools && { tools }
|
|
62325
62924
|
};
|
|
@@ -63201,6 +63800,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
63201
63800
|
//#endregion
|
|
63202
63801
|
//#region src/features/permissions/omp-permissions.ts
|
|
63203
63802
|
const CATCH_ALL_PATTERN$4 = "*";
|
|
63803
|
+
const APPROVAL_MODE_KEY = "approvalMode";
|
|
63204
63804
|
const ACTION_TO_OMP_APPROVAL = {
|
|
63205
63805
|
allow: "allow",
|
|
63206
63806
|
ask: "prompt",
|
|
@@ -63256,10 +63856,14 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
63256
63856
|
* commands, and its deny / ask also restrict every other tool's policy (a
|
|
63257
63857
|
* catch-all one at its own strictness, a pattern-specific one as `prompt`).
|
|
63258
63858
|
*
|
|
63859
|
+
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
63860
|
+
* `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
63861
|
+
* import a recognized value is read back into the override.
|
|
63862
|
+
*
|
|
63259
63863
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
63260
|
-
* (the keys rulesync manages)
|
|
63261
|
-
*
|
|
63262
|
-
* never deleted.
|
|
63864
|
+
* (the keys rulesync manages), `bash.patterns` and an authored
|
|
63865
|
+
* `tools.approvalMode` are rewritten; sibling keys such as `bash.enabled` are
|
|
63866
|
+
* kept, and the file is never deleted.
|
|
63263
63867
|
*
|
|
63264
63868
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
63265
63869
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
@@ -63302,16 +63906,19 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
63302
63906
|
fileContent: existingContent,
|
|
63303
63907
|
filePath
|
|
63304
63908
|
});
|
|
63909
|
+
const config = rulesyncPermissions.getJson();
|
|
63305
63910
|
const { approval, patterns, floor } = convertRulesyncToOmp({
|
|
63306
|
-
config
|
|
63911
|
+
config,
|
|
63307
63912
|
logger
|
|
63308
63913
|
});
|
|
63914
|
+
const approvalMode = config.omp?.approvalMode;
|
|
63309
63915
|
const existingTools = isRecord$1(existing.tools) ? existing.tools : {};
|
|
63310
63916
|
const nextApproval = {
|
|
63311
63917
|
...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)])) : {},
|
|
63312
63918
|
...approval
|
|
63313
63919
|
};
|
|
63314
63920
|
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
63921
|
+
if (approvalMode !== void 0) toolsSiblings[APPROVAL_MODE_KEY] = approvalMode;
|
|
63315
63922
|
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
63316
63923
|
...toolsSiblings,
|
|
63317
63924
|
approval: nextApproval
|
|
@@ -63567,7 +64174,14 @@ function convertOmpToRulesync(config) {
|
|
|
63567
64174
|
permission[category] = { [CATCH_ALL_PATTERN$4]: stricter(permission[category]?.[CATCH_ALL_PATTERN$4], action) };
|
|
63568
64175
|
}
|
|
63569
64176
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
63570
|
-
|
|
64177
|
+
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
64178
|
+
return {
|
|
64179
|
+
permission,
|
|
64180
|
+
...isOmpApprovalMode(approvalMode) && { omp: { approvalMode } }
|
|
64181
|
+
};
|
|
64182
|
+
}
|
|
64183
|
+
function isOmpApprovalMode(value) {
|
|
64184
|
+
return OMP_APPROVAL_MODES.some((mode) => mode === value);
|
|
63571
64185
|
}
|
|
63572
64186
|
//#endregion
|
|
63573
64187
|
//#region src/features/permissions/pi-permissions.ts
|
|
@@ -64607,6 +65221,13 @@ const QWEN_OVERRIDE_SECURITY_KEYS = [
|
|
|
64607
65221
|
"allowPrivateNetworkHooks",
|
|
64608
65222
|
"allowedInsecureVoiceBaseUrls"
|
|
64609
65223
|
];
|
|
65224
|
+
const QWEN_OVERRIDE_SKILLS_KEYS = [
|
|
65225
|
+
"disabled",
|
|
65226
|
+
"defaultDisabled",
|
|
65227
|
+
"enabled",
|
|
65228
|
+
"disabledLevels"
|
|
65229
|
+
];
|
|
65230
|
+
const QWEN_OVERRIDE_GENERAL_KEYS = ["outputStyle"];
|
|
64610
65231
|
/**
|
|
64611
65232
|
* What each rule means for the two directions, so a key's behavior is declared
|
|
64612
65233
|
* once beside its rule rather than spelled out at every branch.
|
|
@@ -64663,77 +65284,125 @@ const QWEN_SCOPE_RULES = {
|
|
|
64663
65284
|
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."
|
|
64664
65285
|
}
|
|
64665
65286
|
};
|
|
64666
|
-
const
|
|
64667
|
-
|
|
64668
|
-
|
|
64669
|
-
|
|
64670
|
-
|
|
64671
|
-
|
|
64672
|
-
|
|
64673
|
-
|
|
64674
|
-
|
|
64675
|
-
},
|
|
64676
|
-
|
|
64677
|
-
|
|
64678
|
-
|
|
64679
|
-
|
|
64680
|
-
|
|
64681
|
-
|
|
64682
|
-
|
|
64683
|
-
|
|
64684
|
-
|
|
64685
|
-
|
|
64686
|
-
|
|
64687
|
-
|
|
64688
|
-
|
|
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
|
-
|
|
65287
|
+
const QWEN_SCOPED_TOOLS_KEYS = {
|
|
65288
|
+
workflowsEnabled: { rule: "workspace-stripped" },
|
|
65289
|
+
executionSandbox: {
|
|
65290
|
+
rule: "workspace-stripped",
|
|
65291
|
+
grants: () => true,
|
|
65292
|
+
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)."
|
|
65293
|
+
},
|
|
65294
|
+
workflowNameOnly: {
|
|
65295
|
+
rule: "global-machine-wide",
|
|
65296
|
+
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.`,
|
|
65297
|
+
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."
|
|
65298
|
+
},
|
|
65299
|
+
approvalMode: {
|
|
65300
|
+
rule: "global-machine-wide",
|
|
65301
|
+
projectNote: autonomyProjectNote
|
|
65302
|
+
},
|
|
65303
|
+
autoAccept: {
|
|
65304
|
+
rule: "global-machine-wide",
|
|
65305
|
+
projectNote: autonomyProjectNote
|
|
65306
|
+
},
|
|
65307
|
+
sandbox: {
|
|
65308
|
+
rule: "global-machine-wide",
|
|
65309
|
+
projectNote: autonomyProjectNote
|
|
65310
|
+
},
|
|
65311
|
+
sandboxImage: {
|
|
65312
|
+
rule: "global-machine-wide",
|
|
65313
|
+
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.`,
|
|
65314
|
+
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."
|
|
65315
|
+
},
|
|
65316
|
+
disabled: {
|
|
65317
|
+
rule: "global-machine-wide",
|
|
65318
|
+
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.`,
|
|
65319
|
+
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."
|
|
65320
|
+
},
|
|
65321
|
+
visible: {
|
|
65322
|
+
rule: "global-machine-wide",
|
|
65323
|
+
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.`,
|
|
65324
|
+
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."
|
|
65325
|
+
},
|
|
65326
|
+
eager: {
|
|
65327
|
+
rule: "global-machine-wide",
|
|
65328
|
+
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.`,
|
|
65329
|
+
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."
|
|
65330
|
+
},
|
|
65331
|
+
listDirectory: {
|
|
65332
|
+
rule: "global-machine-wide",
|
|
65333
|
+
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.`,
|
|
65334
|
+
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."
|
|
65335
|
+
},
|
|
65336
|
+
todoWrite: {
|
|
65337
|
+
rule: "global-machine-wide",
|
|
65338
|
+
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.`,
|
|
65339
|
+
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."
|
|
65340
|
+
}
|
|
65341
|
+
};
|
|
65342
|
+
/**
|
|
65343
|
+
* The notes for a skills list that hides something. Qwen Code unions the list
|
|
65344
|
+
* across scopes, but the override replaces the one in the file it writes, so
|
|
65345
|
+
* the consequence worth naming is what dropping an entry hands back.
|
|
65346
|
+
*/
|
|
65347
|
+
function skillsHidingListNotes(hides) {
|
|
65348
|
+
return {
|
|
65349
|
+
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.`,
|
|
65350
|
+
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.`
|
|
65351
|
+
};
|
|
65352
|
+
}
|
|
65353
|
+
const QWEN_SCOPED_SKILLS_KEYS = {
|
|
65354
|
+
disabled: {
|
|
65355
|
+
rule: "global-machine-wide",
|
|
65356
|
+
...skillsHidingListNotes("skills are hidden")
|
|
65357
|
+
},
|
|
65358
|
+
defaultDisabled: {
|
|
65359
|
+
rule: "global-machine-wide",
|
|
65360
|
+
...skillsHidingListNotes("skills start disabled until `skills.enabled` opts into them")
|
|
65361
|
+
},
|
|
65362
|
+
disabledLevels: {
|
|
65363
|
+
rule: "global-machine-wide",
|
|
65364
|
+
...skillsHidingListNotes("skill discovery levels (`project`, `user`, `extension`, `bundled`) are skipped")
|
|
65365
|
+
},
|
|
65366
|
+
enabled: {
|
|
65367
|
+
rule: "global-machine-wide",
|
|
65368
|
+
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.`,
|
|
65369
|
+
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)."
|
|
65370
|
+
}
|
|
65371
|
+
};
|
|
65372
|
+
const QWEN_OVERRIDE_GROUPS = [
|
|
65373
|
+
{
|
|
65374
|
+
groupName: "tools",
|
|
65375
|
+
overrideKeys: QWEN_OVERRIDE_TOOLS_KEYS,
|
|
65376
|
+
scopedKeys: QWEN_SCOPED_TOOLS_KEYS
|
|
65377
|
+
},
|
|
65378
|
+
{
|
|
65379
|
+
groupName: "security",
|
|
65380
|
+
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
65381
|
+
scopedKeys: {
|
|
65382
|
+
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
65383
|
+
allowedInsecureVoiceBaseUrls: {
|
|
65384
|
+
rule: "workspace-stripped",
|
|
65385
|
+
grants: isNonEmptyArray
|
|
65386
|
+
},
|
|
65387
|
+
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
65388
|
+
folderTrust: { rule: "user-scope-trust-check" }
|
|
65389
|
+
}
|
|
65390
|
+
},
|
|
65391
|
+
{
|
|
65392
|
+
groupName: "skills",
|
|
65393
|
+
overrideKeys: QWEN_OVERRIDE_SKILLS_KEYS,
|
|
65394
|
+
scopedKeys: QWEN_SCOPED_SKILLS_KEYS
|
|
65395
|
+
},
|
|
65396
|
+
{
|
|
65397
|
+
groupName: "general",
|
|
65398
|
+
overrideKeys: QWEN_OVERRIDE_GENERAL_KEYS,
|
|
65399
|
+
scopedKeys: { outputStyle: {
|
|
64719
65400
|
rule: "global-machine-wide",
|
|
64720
|
-
projectNote: ({ qualifiedKey, quotedValue, filePath }) => `${qualifiedKey} = ${quotedValue} was written to the project-scoped ${filePath}, so it
|
|
64721
|
-
globalNote: "Qwen Code honors this key wherever it is written, so in the global scope
|
|
64722
|
-
}
|
|
64723
|
-
}
|
|
64724
|
-
}, {
|
|
64725
|
-
groupName: "security",
|
|
64726
|
-
overrideKeys: QWEN_OVERRIDE_SECURITY_KEYS,
|
|
64727
|
-
scopedKeys: {
|
|
64728
|
-
allowPrivateNetworkHooks: { rule: "workspace-stripped" },
|
|
64729
|
-
allowedInsecureVoiceBaseUrls: {
|
|
64730
|
-
rule: "workspace-stripped",
|
|
64731
|
-
grants: isNonEmptyArray
|
|
64732
|
-
},
|
|
64733
|
-
allowedHttpHookUrls: { rule: "workspace-non-overriding" },
|
|
64734
|
-
folderTrust: { rule: "user-scope-trust-check" }
|
|
65401
|
+
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.`,
|
|
65402
|
+
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`)."
|
|
65403
|
+
} }
|
|
64735
65404
|
}
|
|
64736
|
-
|
|
65405
|
+
];
|
|
64737
65406
|
const QWEN_OVERRIDE_PERMISSIONS_KEYS = ["autoMode"];
|
|
64738
65407
|
const QWEN_SCOPED_PERMISSIONS_KEYS = { autoMode: {
|
|
64739
65408
|
rule: "global-machine-wide",
|
|
@@ -64864,7 +65533,7 @@ function scopeOverrideGroup(overrideGroup, { groupName, scopedKeys, existingGrou
|
|
|
64864
65533
|
return scoped;
|
|
64865
65534
|
}
|
|
64866
65535
|
/**
|
|
64867
|
-
* Build the `tools`/`security` patch groups contributed by the `qwencode`
|
|
65536
|
+
* Build the `tools`/`security`/`skills`/`general` patch groups contributed by the `qwencode`
|
|
64868
65537
|
* override. Each group is shallow-merged over what `settings.json` already has,
|
|
64869
65538
|
* after the scope gate has handled the keys Qwen Code honors in one scope only.
|
|
64870
65539
|
*/
|
|
@@ -65033,7 +65702,9 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
65033
65702
|
});
|
|
65034
65703
|
const overrideGroups = {
|
|
65035
65704
|
tools: pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS),
|
|
65036
|
-
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS)
|
|
65705
|
+
security: pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS),
|
|
65706
|
+
skills: pickQwenOverrideKeys(settings.skills, QWEN_OVERRIDE_SKILLS_KEYS),
|
|
65707
|
+
general: pickQwenOverrideKeys(settings.general, QWEN_OVERRIDE_GENERAL_KEYS)
|
|
65037
65708
|
};
|
|
65038
65709
|
if (!this.global) warnAboutImportedScopedKeys(overrideGroups);
|
|
65039
65710
|
const overridePermissions = pickQwenOverrideKeys(settings.permissions, QWEN_OVERRIDE_PERMISSIONS_KEYS);
|
|
@@ -65336,6 +66007,37 @@ function applyAllowDynamicBash({ permissions, authored }) {
|
|
|
65336
66007
|
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"
|
|
65337
66008
|
}];
|
|
65338
66009
|
}
|
|
66010
|
+
/** The writer fallbacks `[permissions] mode` accepts. */
|
|
66011
|
+
const REASONIX_PERMISSION_MODES = /* @__PURE__ */ new Set([
|
|
66012
|
+
"ask",
|
|
66013
|
+
"allow",
|
|
66014
|
+
"deny"
|
|
66015
|
+
]);
|
|
66016
|
+
/**
|
|
66017
|
+
* Writes the override's `mode` into `[permissions]`. Reasonix honors the key
|
|
66018
|
+
* only in the user config and ignores a project value as user-scope only, so a
|
|
66019
|
+
* project-scope generate skips it with a warning and leaves whatever the file
|
|
66020
|
+
* had. A global `allow` is reported when it is newly written: it lets every
|
|
66021
|
+
* writer call that no rule matches run without a prompt.
|
|
66022
|
+
*
|
|
66023
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/SPEC.md
|
|
66024
|
+
*/
|
|
66025
|
+
function applyPermissionsMode({ permissions, authored, global, relativeFilePath, logger }) {
|
|
66026
|
+
if (authored === void 0) return [];
|
|
66027
|
+
if (!global) {
|
|
66028
|
+
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.`);
|
|
66029
|
+
return [];
|
|
66030
|
+
}
|
|
66031
|
+
const previous = permissions.mode;
|
|
66032
|
+
permissions.mode = authored;
|
|
66033
|
+
const normalize = (value) => typeof value === "string" ? value.trim().toLowerCase() : void 0;
|
|
66034
|
+
const mode = normalize(authored);
|
|
66035
|
+
if (mode === "ask" || mode === "deny" || normalize(previous) === mode) return [];
|
|
66036
|
+
return [{
|
|
66037
|
+
label: "permissions.mode",
|
|
66038
|
+
reason: "lets every writer call that no rule matches run without a prompt"
|
|
66039
|
+
}];
|
|
66040
|
+
}
|
|
65339
66041
|
function parseReasonixConfig(fileContent) {
|
|
65340
66042
|
const parsed = smolToml.parse(fileContent || smolToml.stringify({}));
|
|
65341
66043
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -65386,7 +66088,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65386
66088
|
relativeDirPath: paths.relativeDirPath,
|
|
65387
66089
|
relativeFilePath: paths.relativeFilePath,
|
|
65388
66090
|
fileContent,
|
|
65389
|
-
validate
|
|
66091
|
+
validate,
|
|
66092
|
+
global
|
|
65390
66093
|
});
|
|
65391
66094
|
}
|
|
65392
66095
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, validate = true, logger, global = false }) {
|
|
@@ -65441,6 +66144,13 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65441
66144
|
permissions: mergedPermissions,
|
|
65442
66145
|
authored: override?.allowDynamicBash
|
|
65443
66146
|
});
|
|
66147
|
+
trustAffecting.push(...applyPermissionsMode({
|
|
66148
|
+
permissions: mergedPermissions,
|
|
66149
|
+
authored: override?.mode,
|
|
66150
|
+
global,
|
|
66151
|
+
relativeFilePath: relativeFilePathForLog,
|
|
66152
|
+
logger
|
|
66153
|
+
}));
|
|
65444
66154
|
const patch = { permissions: mergedPermissions };
|
|
65445
66155
|
if (override?.sandbox !== void 0) {
|
|
65446
66156
|
const mergedSandbox = {
|
|
@@ -65482,7 +66192,8 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65482
66192
|
patch,
|
|
65483
66193
|
filePath
|
|
65484
66194
|
}),
|
|
65485
|
-
validate
|
|
66195
|
+
validate,
|
|
66196
|
+
global
|
|
65486
66197
|
});
|
|
65487
66198
|
}
|
|
65488
66199
|
toRulesyncPermissions() {
|
|
@@ -65507,6 +66218,7 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
65507
66218
|
const reasonixOverride = {};
|
|
65508
66219
|
const allowDynamicBash = permissions[REASONIX_ALLOW_DYNAMIC_BASH_KEY];
|
|
65509
66220
|
if (typeof allowDynamicBash === "boolean") reasonixOverride.allowDynamicBash = allowDynamicBash;
|
|
66221
|
+
if (this.global && typeof permissions.mode === "string" && REASONIX_PERMISSION_MODES.has(permissions.mode)) reasonixOverride.mode = permissions.mode;
|
|
65510
66222
|
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
65511
66223
|
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
65512
66224
|
if (allowSplit.exact.length > 0) reasonixOverride.rawAllow = allowSplit.exact;
|
|
@@ -69226,6 +69938,9 @@ const WARP_OVERRIDE_KEYS = [
|
|
|
69226
69938
|
"agent_mode_execute_readonly_commands"
|
|
69227
69939
|
];
|
|
69228
69940
|
const WARP_EXECUTION_PROFILE_OVERRIDE_KEY = "execution_profile";
|
|
69941
|
+
const WARP_AUTO_APPROVE_BYPASS_KEY = "auto_approve_bypasses_command_denylist";
|
|
69942
|
+
const WARP_AGENT_KEY = "warp_agent";
|
|
69943
|
+
const WARP_AGENT_OTHER_KEY = "other";
|
|
69229
69944
|
const WARP_EXECUTION_PROFILE_KEYS = [
|
|
69230
69945
|
"read_files",
|
|
69231
69946
|
"apply_code_diffs",
|
|
@@ -69374,7 +70089,7 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69374
70089
|
const override = config.warp;
|
|
69375
70090
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69376
70091
|
if (isRecord$1(override)) {
|
|
69377
|
-
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, ...legacyOverride } = override;
|
|
70092
|
+
const { [WARP_EXECUTION_PROFILE_OVERRIDE_KEY]: _executionProfile, [WARP_AUTO_APPROVE_BYPASS_KEY]: _autoApproveBypass, ...legacyOverride } = override;
|
|
69378
70093
|
Object.assign(profiles, legacyOverride);
|
|
69379
70094
|
}
|
|
69380
70095
|
const mergedAllow = uniq(allow.toSorted());
|
|
@@ -69384,9 +70099,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69384
70099
|
if (mergedDeny.length > 0) profiles[DENYLIST_KEY] = mergedDeny;
|
|
69385
70100
|
else delete profiles[DENYLIST_KEY];
|
|
69386
70101
|
agents.profiles = profiles;
|
|
70102
|
+
applyAutoApproveBypassOverride({
|
|
70103
|
+
agents,
|
|
70104
|
+
override
|
|
70105
|
+
});
|
|
69387
70106
|
warnAboutDenylistReplacement({
|
|
69388
70107
|
toolLabel: "Warp",
|
|
70108
|
+
overrideKey: "warp",
|
|
69389
70109
|
denyCount: mergedDeny.length,
|
|
70110
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
69390
70111
|
logger
|
|
69391
70112
|
});
|
|
69392
70113
|
mergeIntoDefaultExecutionProfile({
|
|
@@ -69425,6 +70146,8 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69425
70146
|
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
69426
70147
|
const executionProfileOverride = defaultProfile ? liftExecutionProfileOverride(defaultProfile) : void 0;
|
|
69427
70148
|
if (executionProfileOverride) warpOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70149
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70150
|
+
if (autoApproveBypass !== void 0) warpOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69428
70151
|
const result = { ...config };
|
|
69429
70152
|
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
69430
70153
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
@@ -69448,12 +70171,40 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
69448
70171
|
};
|
|
69449
70172
|
/**
|
|
69450
70173
|
* Writing `command_denylist` at all replaces Warp's built-in default denylist,
|
|
69451
|
-
* so any non-empty list rulesync writes deserves a heads-up.
|
|
70174
|
+
* so any non-empty list rulesync writes deserves a heads-up. Unless
|
|
70175
|
+
* `auto_approve_bypasses_command_denylist` is `false`, auto-approve also runs
|
|
70176
|
+
* commands that match the list, so the warning says so.
|
|
69452
70177
|
* https://docs.warp.dev/agents/cli/permissions-and-profiles/
|
|
69453
70178
|
*/
|
|
69454
|
-
function warnAboutDenylistReplacement({ toolLabel, denyCount, logger }) {
|
|
70179
|
+
function warnAboutDenylistReplacement({ toolLabel, overrideKey, denyCount, autoApproveBypassesDenylist, logger }) {
|
|
69455
70180
|
if (denyCount === 0 || !logger) return;
|
|
69456
|
-
|
|
70181
|
+
const bypassNote = autoApproveBypassesDenylist === false ? "" : ` Auto-approve also runs commands that match the denylist unless ${overrideKey}.${WARP_AUTO_APPROVE_BYPASS_KEY} is set to false.`;
|
|
70182
|
+
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}`);
|
|
70183
|
+
}
|
|
70184
|
+
/**
|
|
70185
|
+
* Write the override's `auto_approve_bypasses_command_denylist` into
|
|
70186
|
+
* `[agents.warp_agent.other]`, keeping every sibling key of `agents.warp_agent`
|
|
70187
|
+
* and of its `other` table. Nothing is written when the override omits it.
|
|
70188
|
+
*/
|
|
70189
|
+
function applyAutoApproveBypassOverride({ agents, override }) {
|
|
70190
|
+
if (!isRecord$1(override)) return;
|
|
70191
|
+
const value = override[WARP_AUTO_APPROVE_BYPASS_KEY];
|
|
70192
|
+
if (typeof value !== "boolean") return;
|
|
70193
|
+
const warpAgent = isRecord$1(agents[WARP_AGENT_KEY]) ? { ...agents[WARP_AGENT_KEY] } : {};
|
|
70194
|
+
const other = isRecord$1(warpAgent[WARP_AGENT_OTHER_KEY]) ? { ...warpAgent[WARP_AGENT_OTHER_KEY] } : {};
|
|
70195
|
+
other[WARP_AUTO_APPROVE_BYPASS_KEY] = value;
|
|
70196
|
+
warpAgent[WARP_AGENT_OTHER_KEY] = other;
|
|
70197
|
+
agents[WARP_AGENT_KEY] = warpAgent;
|
|
70198
|
+
}
|
|
70199
|
+
/**
|
|
70200
|
+
* Read `[agents.warp_agent.other] auto_approve_bypasses_command_denylist`, or
|
|
70201
|
+
* `undefined` when it is unset (Warp then defaults to `true`).
|
|
70202
|
+
*/
|
|
70203
|
+
function readAutoApproveBypass(agents) {
|
|
70204
|
+
const warpAgent = agents[WARP_AGENT_KEY];
|
|
70205
|
+
const other = isRecord$1(warpAgent) ? warpAgent[WARP_AGENT_OTHER_KEY] : void 0;
|
|
70206
|
+
const value = isRecord$1(other) ? other[WARP_AUTO_APPROVE_BYPASS_KEY] : void 0;
|
|
70207
|
+
return typeof value === "boolean" ? value : void 0;
|
|
69457
70208
|
}
|
|
69458
70209
|
/**
|
|
69459
70210
|
* On migrated installs the legacy `[agents.profiles]` keys are inert: runtime
|
|
@@ -69807,6 +70558,9 @@ const WARPCLI_GLOBAL_ONLY_MESSAGE = "Warp Agent CLI permissions are global-only;
|
|
|
69807
70558
|
* (`read_files`, `apply_code_diffs`, `run_agents`, ...), merged first so the
|
|
69808
70559
|
* rulesync-owned command lists always win.
|
|
69809
70560
|
*
|
|
70561
|
+
* The `warpcli.auto_approve_bypasses_command_denylist` override is written to
|
|
70562
|
+
* the sibling `[agents.warp_agent.other]` table.
|
|
70563
|
+
*
|
|
69810
70564
|
* Unlike the `warp` target, the CLI never read the app's legacy
|
|
69811
70565
|
* `[agents.profiles]` keys, so none are written, and there is no settings
|
|
69812
70566
|
* migration to wait for: the CLI's settings file "is created the first time you
|
|
@@ -69873,11 +70627,6 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69873
70627
|
});
|
|
69874
70628
|
const mergedAllow = uniq(allow.toSorted());
|
|
69875
70629
|
const mergedDeny = uniq(deny.toSorted());
|
|
69876
|
-
warnAboutDenylistReplacement({
|
|
69877
|
-
toolLabel: "Warp Agent CLI",
|
|
69878
|
-
denyCount: mergedDeny.length,
|
|
69879
|
-
logger
|
|
69880
|
-
});
|
|
69881
70630
|
const override = config.warpcli;
|
|
69882
70631
|
const executionProfileOverride = isRecord$1(override) && isRecord$1(override["execution_profile"]) ? override[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] : void 0;
|
|
69883
70632
|
const agents = isRecord$1(settings.agents) ? { ...settings.agents } : {};
|
|
@@ -69887,7 +70636,18 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69887
70636
|
mergedDeny,
|
|
69888
70637
|
executionProfileOverride
|
|
69889
70638
|
});
|
|
70639
|
+
applyAutoApproveBypassOverride({
|
|
70640
|
+
agents,
|
|
70641
|
+
override
|
|
70642
|
+
});
|
|
69890
70643
|
settings.agents = agents;
|
|
70644
|
+
warnAboutDenylistReplacement({
|
|
70645
|
+
toolLabel: "Warp Agent CLI",
|
|
70646
|
+
overrideKey: "warpcli",
|
|
70647
|
+
denyCount: mergedDeny.length,
|
|
70648
|
+
autoApproveBypassesDenylist: readAutoApproveBypass(agents),
|
|
70649
|
+
logger
|
|
70650
|
+
});
|
|
69891
70651
|
return new WarpcliPermissions({
|
|
69892
70652
|
outputRoot,
|
|
69893
70653
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -69914,8 +70674,12 @@ var WarpcliPermissions = class WarpcliPermissions extends ToolPermissions {
|
|
|
69914
70674
|
deny: isStringArray$2(denylist) ? denylist : []
|
|
69915
70675
|
});
|
|
69916
70676
|
const executionProfileOverride = liftExecutionProfileOverride(defaultProfile);
|
|
70677
|
+
const warpcliOverride = {};
|
|
70678
|
+
if (executionProfileOverride) warpcliOverride[WARP_EXECUTION_PROFILE_OVERRIDE_KEY] = executionProfileOverride;
|
|
70679
|
+
const autoApproveBypass = readAutoApproveBypass(agents);
|
|
70680
|
+
if (autoApproveBypass !== void 0) warpcliOverride[WARP_AUTO_APPROVE_BYPASS_KEY] = autoApproveBypass;
|
|
69917
70681
|
const result = { ...config };
|
|
69918
|
-
if (
|
|
70682
|
+
if (Object.keys(warpcliOverride).length > 0) result.warpcli = warpcliOverride;
|
|
69919
70683
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
69920
70684
|
}
|
|
69921
70685
|
validate() {
|
|
@@ -81989,9 +82753,8 @@ var CodebuddySubagent = class CodebuddySubagent extends ToolSubagent {
|
|
|
81989
82753
|
* Keys of the rulesync `codewhale` section copied into the profile. Codewhale
|
|
81990
82754
|
* parses agent profiles with `deny_unknown_fields`, so an unknown key would
|
|
81991
82755
|
* make the whole profile fail to load; anything else in the section is dropped
|
|
81992
|
-
* with a warning. `[tools]` and `[permissions]` are
|
|
81993
|
-
*
|
|
81994
|
-
* profile that tries to widen them.
|
|
82756
|
+
* with a warning. The `[tools]` and `[permissions]` tables are handled
|
|
82757
|
+
* separately (see `CODEWHALE_NARROWING_TABLES`).
|
|
81995
82758
|
*/
|
|
81996
82759
|
const CODEWHALE_SECTION_KEYS = [
|
|
81997
82760
|
"role_hint",
|
|
@@ -82012,6 +82775,48 @@ const CODEWHALE_SECTION_KEY_ALIASES = {
|
|
|
82012
82775
|
reasoning_effort: ["thinking", "reasoning"]
|
|
82013
82776
|
};
|
|
82014
82777
|
const CODEWHALE_SECTION_ALIAS_TO_KEY = new Map(Object.entries(CODEWHALE_SECTION_KEY_ALIASES).flatMap(([key, aliases]) => (aliases ?? []).map((alias) => [alias, key])));
|
|
82778
|
+
/**
|
|
82779
|
+
* The `[tools]` and `[permissions]` tables of an agent profile, with the only
|
|
82780
|
+
* values Codewhale accepts in each key. Codewhale rejects a profile that tries
|
|
82781
|
+
* to widen access through them (`reject_permission_expansion` in
|
|
82782
|
+
* `fleet/profile.rs`), and both tables deny unknown fields, so any other key
|
|
82783
|
+
* or value is dropped with a warning.
|
|
82784
|
+
*/
|
|
82785
|
+
const CODEWHALE_NARROWING_TABLES = {
|
|
82786
|
+
tools: { posture: (value) => typeof value === "string" && [
|
|
82787
|
+
"read-only",
|
|
82788
|
+
"readonly",
|
|
82789
|
+
"read_only"
|
|
82790
|
+
].includes(value) },
|
|
82791
|
+
permissions: {
|
|
82792
|
+
allow_shell: (value) => value === false,
|
|
82793
|
+
trust: (value) => value === false,
|
|
82794
|
+
approval_required: (value) => value === true
|
|
82795
|
+
}
|
|
82796
|
+
};
|
|
82797
|
+
/**
|
|
82798
|
+
* Keeps the narrowing-only entries of a `[tools]` / `[permissions]` table and
|
|
82799
|
+
* reports every other entry by its dotted path. A non-table value is dropped
|
|
82800
|
+
* as a whole.
|
|
82801
|
+
*/
|
|
82802
|
+
function filterNarrowingTable(tableName, value) {
|
|
82803
|
+
const allowed = CODEWHALE_NARROWING_TABLES[tableName] ?? {};
|
|
82804
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {
|
|
82805
|
+
kept: void 0,
|
|
82806
|
+
dropped: [tableName]
|
|
82807
|
+
};
|
|
82808
|
+
const kept = {};
|
|
82809
|
+
const dropped = [];
|
|
82810
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
82811
|
+
const normalized = typeof entry === "string" ? entry.trim() : entry;
|
|
82812
|
+
if (Object.hasOwn(allowed, key) && allowed[key]?.(normalized)) kept[key] = normalized;
|
|
82813
|
+
else dropped.push(`${tableName}.${key}`);
|
|
82814
|
+
}
|
|
82815
|
+
return {
|
|
82816
|
+
kept: Object.keys(kept).length > 0 ? kept : void 0,
|
|
82817
|
+
dropped
|
|
82818
|
+
};
|
|
82819
|
+
}
|
|
82015
82820
|
const CodewhaleSubagentTomlSchema = z.looseObject({
|
|
82016
82821
|
id: z.optional(z.string()),
|
|
82017
82822
|
name: z.optional(z.string()),
|
|
@@ -82038,7 +82843,8 @@ function stringifyCodewhaleSubagentToml(fields, instructions) {
|
|
|
82038
82843
|
*
|
|
82039
82844
|
* Generation writes `id` (the file stem), `display_name` (the rulesync name),
|
|
82040
82845
|
* `description`, and the body as `[instructions] text`, plus the allowlisted
|
|
82041
|
-
* keys of the rulesync `codewhale` section
|
|
82846
|
+
* keys of the rulesync `codewhale` section and its narrowing-only `[tools]` /
|
|
82847
|
+
* `[permissions]` entries.
|
|
82042
82848
|
*
|
|
82043
82849
|
* @see https://github.com/Hmbown/Codewhale/blob/main/docs/SUBAGENTS.md
|
|
82044
82850
|
* @see https://github.com/Hmbown/Codewhale/blob/main/crates/tui/src/fleet/profile.rs
|
|
@@ -82073,6 +82879,10 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82073
82879
|
const spelling = [key, ...CODEWHALE_SECTION_KEY_ALIASES[key] ?? []].find((candidate) => record[candidate] !== void 0);
|
|
82074
82880
|
if (spelling !== void 0) codewhaleSection[key] = record[spelling];
|
|
82075
82881
|
}
|
|
82882
|
+
for (const tableName of Object.keys(CODEWHALE_NARROWING_TABLES)) if (record[tableName] !== void 0) {
|
|
82883
|
+
const { kept } = filterNarrowingTable(tableName, record[tableName]);
|
|
82884
|
+
if (kept !== void 0) codewhaleSection[tableName] = kept;
|
|
82885
|
+
}
|
|
82076
82886
|
return new RulesyncSubagent({
|
|
82077
82887
|
outputRoot: ".",
|
|
82078
82888
|
frontmatter: {
|
|
@@ -82095,6 +82905,12 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82095
82905
|
const droppedKeys = [];
|
|
82096
82906
|
const sectionSpellings = /* @__PURE__ */ new Map();
|
|
82097
82907
|
for (const [rawKey, value] of Object.entries(rawSection)) {
|
|
82908
|
+
if (Object.hasOwn(CODEWHALE_NARROWING_TABLES, rawKey)) {
|
|
82909
|
+
const { kept, dropped } = filterNarrowingTable(rawKey, value);
|
|
82910
|
+
if (kept !== void 0) sectionFields[rawKey] = kept;
|
|
82911
|
+
droppedKeys.push(...dropped);
|
|
82912
|
+
continue;
|
|
82913
|
+
}
|
|
82098
82914
|
const key = CODEWHALE_SECTION_ALIAS_TO_KEY.get(rawKey) ?? rawKey;
|
|
82099
82915
|
if (CODEWHALE_SECTION_KEYS.includes(key) && typeof value === "string") {
|
|
82100
82916
|
if (key === rawKey || sectionFields[key] === void 0) {
|
|
@@ -82104,7 +82920,7 @@ var CodewhaleSubagent = class CodewhaleSubagent extends ToolSubagent {
|
|
|
82104
82920
|
} else droppedKeys.push(rawKey);
|
|
82105
82921
|
} else droppedKeys.push(rawKey);
|
|
82106
82922
|
}
|
|
82107
|
-
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).`);
|
|
82923
|
+
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.`);
|
|
82108
82924
|
const body = stringifyCodewhaleSubagentToml({
|
|
82109
82925
|
id: toProfileId(relativeFilePath),
|
|
82110
82926
|
display_name: frontmatter.name,
|
|
@@ -88601,6 +89417,99 @@ const AntigravityRuleFrontmatterSchema = z.looseObject({
|
|
|
88601
89417
|
function toGlobalRuleFileName(relativeFilePath) {
|
|
88602
89418
|
return relativeFilePath.split(/[\\/]/).join("-");
|
|
88603
89419
|
}
|
|
89420
|
+
/** The project rules directory both Antigravity targets share, as a POSIX path. */
|
|
89421
|
+
const PROJECT_RULES_DIR = `${ANTIGRAVITY_IDE_AGENTS_DIR}/rules`;
|
|
89422
|
+
/**
|
|
89423
|
+
* The subproject directory a rule is scoped to, or `undefined` when it goes to
|
|
89424
|
+
* the project-root `.agents/rules/`. Antigravity reads a `.agents/rules/`
|
|
89425
|
+
* directory in any subdirectory and loads it for files under that directory,
|
|
89426
|
+
* so a non-root rule carrying `agentsmd.subprojectPath` is written there. Project
|
|
89427
|
+
* scope only: there is no workspace tree to nest under in global scope.
|
|
89428
|
+
*
|
|
89429
|
+
* @see https://antigravity.google/docs/rules
|
|
89430
|
+
*/
|
|
89431
|
+
function resolveAntigravitySubprojectPath({ rulesyncRule, global }) {
|
|
89432
|
+
const { root, agentsmd } = rulesyncRule.getFrontmatter();
|
|
89433
|
+
const subprojectPath = !global && root !== true ? agentsmd?.subprojectPath : void 0;
|
|
89434
|
+
return subprojectPath && ![
|
|
89435
|
+
"",
|
|
89436
|
+
".",
|
|
89437
|
+
"./"
|
|
89438
|
+
].includes(subprojectPath) ? subprojectPath : void 0;
|
|
89439
|
+
}
|
|
89440
|
+
/**
|
|
89441
|
+
* File name of a rule in a subproject's `.agents/rules/`. Antigravity reads
|
|
89442
|
+
* only the direct children of a rules directory, so a nested rulesync rule
|
|
89443
|
+
* (`frontend/style.md`) is flattened (`frontend-style.md`), and the name keeps
|
|
89444
|
+
* its case so the IDE and the CLI write the same file and an imported
|
|
89445
|
+
* `codeStyle.md` is written back to the same place.
|
|
89446
|
+
*/
|
|
89447
|
+
function toNestedRuleFileName(relativeFilePath) {
|
|
89448
|
+
return toGlobalRuleFileName(relativeFilePath);
|
|
89449
|
+
}
|
|
89450
|
+
/** The `.agents/rules/` directory of a subproject. */
|
|
89451
|
+
function toAntigravityNestedRulesDirPath(subprojectPath) {
|
|
89452
|
+
return join(subprojectPath, ANTIGRAVITY_IDE_AGENTS_DIR, "rules");
|
|
89453
|
+
}
|
|
89454
|
+
/**
|
|
89455
|
+
* Split a nested rule directory such as `packages/api/.agents/rules/sub` into
|
|
89456
|
+
* the subproject (`packages/api`) and the folder inside its rules directory
|
|
89457
|
+
* (`sub`). Returns `undefined` for the project-root `.agents/rules/`, for a
|
|
89458
|
+
* path with no `.agents/rules` segment, and for one under a hidden directory —
|
|
89459
|
+
* another tool's output or a worktree, not a subproject.
|
|
89460
|
+
*/
|
|
89461
|
+
function extractAntigravitySubprojectPath(relativeDirPath) {
|
|
89462
|
+
const segments = toPosixPath(relativeDirPath).split("/");
|
|
89463
|
+
const index = segments.findIndex((segment, i) => segment === ".agents" && segments[i + 1] === "rules");
|
|
89464
|
+
if (index <= 0) return;
|
|
89465
|
+
const subprojectSegments = segments.slice(0, index);
|
|
89466
|
+
if (subprojectSegments.some((segment) => segment === "" || segment.startsWith("."))) return;
|
|
89467
|
+
return {
|
|
89468
|
+
subprojectPath: subprojectSegments.join("/"),
|
|
89469
|
+
innerDirPath: segments.slice(index + 2).join("/")
|
|
89470
|
+
};
|
|
89471
|
+
}
|
|
89472
|
+
/**
|
|
89473
|
+
* The `.agents/rules/` folders of project subdirectories. Only their direct
|
|
89474
|
+
* `.md` children are read, because Antigravity ignores files nested in
|
|
89475
|
+
* subfolders of a rules directory. The shared nested
|
|
89476
|
+
* scan ignores every hidden directory, which would skip `.agents` itself, so
|
|
89477
|
+
* only a `.agents/rules/` below another hidden directory is ignored here
|
|
89478
|
+
* (`extractAntigravitySubprojectPath` rejects one again on import). Import-only,
|
|
89479
|
+
* like every other nested scan: these files sit outside the rulesync-owned root
|
|
89480
|
+
* `.agents/rules/`, so `--delete` never sweeps them.
|
|
89481
|
+
*/
|
|
89482
|
+
function getAntigravityNestedFilePatterns() {
|
|
89483
|
+
return {
|
|
89484
|
+
include: [`**/${PROJECT_RULES_DIR}/*.md`],
|
|
89485
|
+
ignore: [
|
|
89486
|
+
`${PROJECT_RULES_DIR}/**`,
|
|
89487
|
+
`**/.*/**/${PROJECT_RULES_DIR}/**`,
|
|
89488
|
+
...NESTED_SCAN_EXCLUDED_DIRS_ANY_DEPTH.map((dir) => `**/${dir}/**`),
|
|
89489
|
+
...NESTED_SCAN_EXCLUDED_ROOT_DIRS.map((dir) => `${dir}/**`)
|
|
89490
|
+
],
|
|
89491
|
+
ownedDirPath: PROJECT_RULES_DIR
|
|
89492
|
+
};
|
|
89493
|
+
}
|
|
89494
|
+
/**
|
|
89495
|
+
* Resolve where a non-root project rule file read from disk lives. A file
|
|
89496
|
+
* discovered by `getNestedFilePatterns` arrives as its own directory plus its
|
|
89497
|
+
* basename; it is re-split into the subproject's rules directory and the path
|
|
89498
|
+
* inside it, matching how the root `.agents/rules/` is read.
|
|
89499
|
+
*/
|
|
89500
|
+
function resolveAntigravityNonRootFile({ relativeDirPath, relativeFilePath, defaultDirPath }) {
|
|
89501
|
+
const nested = relativeDirPath === void 0 ? void 0 : extractAntigravitySubprojectPath(relativeDirPath);
|
|
89502
|
+
if (nested === void 0) return {
|
|
89503
|
+
relativeDirPath: defaultDirPath,
|
|
89504
|
+
relativeFilePath
|
|
89505
|
+
};
|
|
89506
|
+
return {
|
|
89507
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(nested.subprojectPath),
|
|
89508
|
+
relativeFilePath: join(nested.innerDirPath, relativeFilePath)
|
|
89509
|
+
};
|
|
89510
|
+
}
|
|
89511
|
+
/** The targets a nested rule is imported with: the two that read the shared tree. */
|
|
89512
|
+
const ANTIGRAVITY_NESTED_RULE_TARGETS = ["antigravity-ide", "antigravity-cli"];
|
|
88604
89513
|
/**
|
|
88605
89514
|
* Converts a comma-separated globs string or array to an array of globs.
|
|
88606
89515
|
* @param globs - Comma-separated globs string (e.g., "*.ts,*.js") or array of globs
|
|
@@ -88788,12 +89697,17 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88788
89697
|
});
|
|
88789
89698
|
}
|
|
88790
89699
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${relativeFilePath}`);
|
|
88791
|
-
const
|
|
89700
|
+
const nonRoot = resolveAntigravityNonRootFile({
|
|
89701
|
+
relativeDirPath,
|
|
89702
|
+
relativeFilePath,
|
|
89703
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
89704
|
+
});
|
|
89705
|
+
const relativePath = join(nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
88792
89706
|
const fileContent = await readFileContent(join(outputRoot, relativePath));
|
|
88793
89707
|
const rule = new AntigravityCliRule({
|
|
88794
89708
|
outputRoot,
|
|
88795
|
-
relativeDirPath:
|
|
88796
|
-
relativeFilePath,
|
|
89709
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
89710
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
88797
89711
|
fileContent,
|
|
88798
89712
|
validate,
|
|
88799
89713
|
root: false,
|
|
@@ -88825,11 +89739,21 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88825
89739
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
88826
89740
|
const frontmatter = strategy.generateFrontmatter(normalizeStoredAntigravity(storedAntigravity), rulesyncFrontmatter);
|
|
88827
89741
|
const fileContent = stringifyFrontmatter(rulesyncRule.getBody(), frontmatter);
|
|
88828
|
-
if (!global)
|
|
88829
|
-
|
|
88830
|
-
|
|
88831
|
-
|
|
88832
|
-
|
|
89742
|
+
if (!global) {
|
|
89743
|
+
const subprojectPath = resolveAntigravitySubprojectPath({
|
|
89744
|
+
rulesyncRule,
|
|
89745
|
+
global
|
|
89746
|
+
});
|
|
89747
|
+
return new AntigravityCliRule({
|
|
89748
|
+
...params,
|
|
89749
|
+
...subprojectPath !== void 0 && {
|
|
89750
|
+
relativeDirPath: toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89751
|
+
relativeFilePath: toNestedRuleFileName(params.relativeFilePath)
|
|
89752
|
+
},
|
|
89753
|
+
fileContent,
|
|
89754
|
+
global
|
|
89755
|
+
});
|
|
89756
|
+
}
|
|
88833
89757
|
return new AntigravityCliRule({
|
|
88834
89758
|
...params,
|
|
88835
89759
|
relativeFilePath: toGlobalRuleFileName(params.relativeFilePath),
|
|
@@ -88845,22 +89769,32 @@ var AntigravityCliRule = class AntigravityCliRule extends ToolRule {
|
|
|
88845
89769
|
globs: [],
|
|
88846
89770
|
antigravity: frontmatter
|
|
88847
89771
|
};
|
|
89772
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
88848
89773
|
return new RulesyncRule({
|
|
88849
89774
|
outputRoot: process.cwd(),
|
|
88850
89775
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
88851
89776
|
relativeFilePath: this.getRelativeFilePath(),
|
|
88852
89777
|
frontmatter: {
|
|
88853
89778
|
root: false,
|
|
88854
|
-
targets: ["*"],
|
|
89779
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
88855
89780
|
...rulesyncData,
|
|
88856
89781
|
antigravity: {
|
|
88857
89782
|
...rulesyncData.antigravity,
|
|
88858
89783
|
globs: frontmatter.globs ? parseGlobsString$1(frontmatter.globs) : void 0
|
|
88859
|
-
}
|
|
89784
|
+
},
|
|
89785
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
88860
89786
|
},
|
|
88861
89787
|
body
|
|
88862
89788
|
});
|
|
88863
89789
|
}
|
|
89790
|
+
/**
|
|
89791
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
89792
|
+
* directory-scoped rules.
|
|
89793
|
+
* @see https://antigravity.google/docs/rules
|
|
89794
|
+
*/
|
|
89795
|
+
static getNestedFilePatterns() {
|
|
89796
|
+
return getAntigravityNestedFilePatterns();
|
|
89797
|
+
}
|
|
88864
89798
|
validate() {
|
|
88865
89799
|
if (this.root) return {
|
|
88866
89800
|
success: true,
|
|
@@ -88990,8 +89924,15 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
88990
89924
|
global
|
|
88991
89925
|
});
|
|
88992
89926
|
}
|
|
88993
|
-
const
|
|
88994
|
-
|
|
89927
|
+
const nonRoot = this.supportsSubprojectRules() ? resolveAntigravityNonRootFile({
|
|
89928
|
+
relativeDirPath,
|
|
89929
|
+
relativeFilePath,
|
|
89930
|
+
defaultDirPath: paths.nonRoot.relativeDirPath
|
|
89931
|
+
}) : {
|
|
89932
|
+
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
89933
|
+
relativeFilePath
|
|
89934
|
+
};
|
|
89935
|
+
const filePath = join(outputRoot, nonRoot.relativeDirPath, nonRoot.relativeFilePath);
|
|
88995
89936
|
const { frontmatter, body } = parseFrontmatter(await readFileContent(filePath), filePath);
|
|
88996
89937
|
let parsedFrontmatter;
|
|
88997
89938
|
if (validate) {
|
|
@@ -89001,8 +89942,8 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89001
89942
|
} else parsedFrontmatter = frontmatter;
|
|
89002
89943
|
return new this({
|
|
89003
89944
|
outputRoot,
|
|
89004
|
-
relativeDirPath:
|
|
89005
|
-
relativeFilePath,
|
|
89945
|
+
relativeDirPath: nonRoot.relativeDirPath,
|
|
89946
|
+
relativeFilePath: nonRoot.relativeFilePath,
|
|
89006
89947
|
body,
|
|
89007
89948
|
frontmatter: parsedFrontmatter,
|
|
89008
89949
|
validate,
|
|
@@ -89010,6 +89951,25 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89010
89951
|
global
|
|
89011
89952
|
});
|
|
89012
89953
|
}
|
|
89954
|
+
/**
|
|
89955
|
+
* Whether a rule scoped to a subproject goes to that subproject's own
|
|
89956
|
+
* `.agents/rules/`. A subclass that writes somewhere other than a project
|
|
89957
|
+
* tree (the plugin bundle) turns it off.
|
|
89958
|
+
*/
|
|
89959
|
+
static supportsSubprojectRules() {
|
|
89960
|
+
return true;
|
|
89961
|
+
}
|
|
89962
|
+
/**
|
|
89963
|
+
* The `.agents/rules/` folders of project subdirectories, imported back as
|
|
89964
|
+
* directory-scoped rules.
|
|
89965
|
+
* @see https://antigravity.google/docs/rules
|
|
89966
|
+
*/
|
|
89967
|
+
static getNestedFilePatterns() {
|
|
89968
|
+
return this.supportsSubprojectRules() ? getAntigravityNestedFilePatterns() : {
|
|
89969
|
+
include: [],
|
|
89970
|
+
ignore: []
|
|
89971
|
+
};
|
|
89972
|
+
}
|
|
89013
89973
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
89014
89974
|
const paths = this.getSettablePaths({ global });
|
|
89015
89975
|
if (rulesyncRule.getFrontmatter().root) {
|
|
@@ -89032,10 +89992,14 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89032
89992
|
const strategy = STRATEGIES$1.find((s) => s.canHandle(storedTrigger));
|
|
89033
89993
|
if (!strategy) throw new Error(`No strategy found for trigger: ${storedTrigger}`);
|
|
89034
89994
|
const frontmatter = strategy.generateFrontmatter(normalized, rulesyncFrontmatter);
|
|
89035
|
-
const
|
|
89995
|
+
const subprojectPath = this.supportsSubprojectRules() ? resolveAntigravitySubprojectPath({
|
|
89996
|
+
rulesyncRule,
|
|
89997
|
+
global
|
|
89998
|
+
}) : void 0;
|
|
89999
|
+
const relativeFilePath = global || subprojectPath !== void 0 ? toNestedRuleFileName(rulesyncRule.getRelativeFilePath()) : toKebabCaseFilename(rulesyncRule.getRelativeFilePath());
|
|
89036
90000
|
return new this({
|
|
89037
90001
|
outputRoot,
|
|
89038
|
-
relativeDirPath: paths.nonRoot.relativeDirPath,
|
|
90002
|
+
relativeDirPath: subprojectPath === void 0 ? paths.nonRoot.relativeDirPath : toAntigravityNestedRulesDirPath(subprojectPath),
|
|
89039
90003
|
relativeFilePath,
|
|
89040
90004
|
frontmatter,
|
|
89041
90005
|
body: rulesyncRule.getBody(),
|
|
@@ -89056,15 +90020,17 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89056
90020
|
...rulesyncData.antigravity,
|
|
89057
90021
|
globs: this.frontmatter.globs ? parseGlobsString$1(this.frontmatter.globs) : void 0
|
|
89058
90022
|
};
|
|
90023
|
+
const subprojectPath = this.global ? void 0 : extractAntigravitySubprojectPath(this.getRelativeDirPath())?.subprojectPath;
|
|
89059
90024
|
return new RulesyncRule({
|
|
89060
90025
|
outputRoot: process.cwd(),
|
|
89061
90026
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
89062
90027
|
relativeFilePath: this.getRelativeFilePath(),
|
|
89063
90028
|
frontmatter: {
|
|
89064
90029
|
root: false,
|
|
89065
|
-
targets: ["*"],
|
|
90030
|
+
targets: subprojectPath === void 0 ? ["*"] : [...ANTIGRAVITY_NESTED_RULE_TARGETS],
|
|
89066
90031
|
...rulesyncData,
|
|
89067
|
-
antigravity: antigravityForRulesync
|
|
90032
|
+
antigravity: antigravityForRulesync,
|
|
90033
|
+
...subprojectPath !== void 0 && { agentsmd: { subprojectPath } }
|
|
89068
90034
|
},
|
|
89069
90035
|
body: this.body
|
|
89070
90036
|
});
|
|
@@ -89118,6 +90084,9 @@ var AntigravityIdeRule = class extends ToolRule {
|
|
|
89118
90084
|
//#endregion
|
|
89119
90085
|
//#region src/features/rules/antigravity-plugin-rule.ts
|
|
89120
90086
|
var AntigravityPluginRule = class extends AntigravityIdeRule {
|
|
90087
|
+
static supportsSubprojectRules() {
|
|
90088
|
+
return false;
|
|
90089
|
+
}
|
|
89121
90090
|
static isTargetedByRulesyncRule(rulesyncRule) {
|
|
89122
90091
|
const targets = rulesyncRule.getFrontmatter().targets;
|
|
89123
90092
|
return targets.includes("*") || targets.includes("antigravity-plugin");
|
|
@@ -95467,6 +96436,12 @@ var VibeRule = class extends NestedAgentsmdRule {
|
|
|
95467
96436
|
* file, so every non-root rule without `agentsmd.subprojectPath` folds into
|
|
95468
96437
|
* the root `./AGENTS.md`.
|
|
95469
96438
|
*
|
|
96439
|
+
* A legacy root `WARP.md` is read on import when the root `AGENTS.md` is
|
|
96440
|
+
* absent, so a repository that has not moved to `AGENTS.md` yet imports its
|
|
96441
|
+
* rules. It is never written or deleted. When both exist, Warp reads
|
|
96442
|
+
* `WARP.md`, so import keeps reading the `AGENTS.md` rulesync manages and warns
|
|
96443
|
+
* that Warp ignores it, as generate already does.
|
|
96444
|
+
*
|
|
95470
96445
|
* In global mode, Warp reads a third rule source from `~/.agents/AGENTS.md`
|
|
95471
96446
|
* (the cross-tool agent config directory), indexed like project rules and also
|
|
95472
96447
|
* used from remote hosts in SSH sessions. The same root-fold policy applies.
|
|
@@ -95481,6 +96456,31 @@ var WarpRule = class extends NestedAgentsmdRule {
|
|
|
95481
96456
|
toolTarget: "warp"
|
|
95482
96457
|
};
|
|
95483
96458
|
}
|
|
96459
|
+
static getSettablePaths(options = {}) {
|
|
96460
|
+
const paths = super.getSettablePaths(options);
|
|
96461
|
+
if (options.global) return paths;
|
|
96462
|
+
return {
|
|
96463
|
+
...paths,
|
|
96464
|
+
importOnlyRoots: [{
|
|
96465
|
+
relativeDirPath: ".",
|
|
96466
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96467
|
+
onlyWhenRootAbsent: true,
|
|
96468
|
+
outranksRoot: true
|
|
96469
|
+
}]
|
|
96470
|
+
};
|
|
96471
|
+
}
|
|
96472
|
+
static async fromFile(params) {
|
|
96473
|
+
const { outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true } = params;
|
|
96474
|
+
if (!params.global && relativeFilePath === "WARP.md" && (relativeDirPath === void 0 || relativeDirPath === ".")) return new this({
|
|
96475
|
+
outputRoot,
|
|
96476
|
+
relativeDirPath: ".",
|
|
96477
|
+
relativeFilePath: WARP_LEGACY_RULE_FILE_NAME,
|
|
96478
|
+
fileContent: await readFileContent(join(outputRoot, WARP_LEGACY_RULE_FILE_NAME)),
|
|
96479
|
+
validate,
|
|
96480
|
+
root: true
|
|
96481
|
+
});
|
|
96482
|
+
return super.fromFile(params);
|
|
96483
|
+
}
|
|
95484
96484
|
};
|
|
95485
96485
|
//#endregion
|
|
95486
96486
|
//#region src/features/rules/zcode-rule.ts
|
|
@@ -96783,7 +97783,7 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
96783
97783
|
if (!importOnlyRoots || importOnlyRoots.length === 0) return;
|
|
96784
97784
|
const existingPaths = [];
|
|
96785
97785
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
96786
|
-
if (importOnlyRoot.onlyWhenRootAbsent !== true) continue;
|
|
97786
|
+
if (importOnlyRoot.onlyWhenRootAbsent !== true || importOnlyRoot.outranksRoot === true) continue;
|
|
96787
97787
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
96788
97788
|
cwd: this.outputRoot,
|
|
96789
97789
|
type: "file"
|
|
@@ -97484,13 +98484,14 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97484
98484
|
const rootFilePath = primaryRootFilePaths[0];
|
|
97485
98485
|
const scannedPaths = [];
|
|
97486
98486
|
const skippedPaths = [];
|
|
98487
|
+
const skippedOutrankingPaths = [];
|
|
97487
98488
|
for (const importOnlyRoot of importOnlyRoots) {
|
|
97488
98489
|
const matchedPaths = await findFilesByGlobs(rootRelativeGlob(importOnlyRoot.relativeDirPath, importOnlyRoot.relativeFilePath ?? `*.${factory.meta.extension}`), {
|
|
97489
98490
|
cwd: this.outputRoot,
|
|
97490
98491
|
type: "file"
|
|
97491
98492
|
});
|
|
97492
98493
|
if (importOnlyRoot.onlyWhenRootAbsent === true && rootFilePath !== void 0) {
|
|
97493
|
-
skippedPaths.push(...matchedPaths);
|
|
98494
|
+
(importOnlyRoot.outranksRoot === true ? skippedOutrankingPaths : skippedPaths).push(...matchedPaths);
|
|
97494
98495
|
continue;
|
|
97495
98496
|
}
|
|
97496
98497
|
scannedPaths.push(...matchedPaths);
|
|
@@ -97501,6 +98502,13 @@ As this project's AI coding tool, you must follow the additional conventions bel
|
|
|
97501
98502
|
const remainingCount = skippedNames.length - listedNames.length;
|
|
97502
98503
|
this.logger.warn(`Not importing ${listedNames.join(", ")}${remainingCount > 0 ? ` and ${remainingCount} more` : ""} for ${this.toolTarget}: ${stripControlCharacters(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.`);
|
|
97503
98504
|
}
|
|
98505
|
+
if (skippedOutrankingPaths.length > 0 && rootFilePath !== void 0) {
|
|
98506
|
+
const rootName = stripControlCharacters(relative(this.outputRoot, rootFilePath));
|
|
98507
|
+
const skippedNames = skippedOutrankingPaths.map((filePath) => stripControlCharacters(relative(this.outputRoot, filePath)));
|
|
98508
|
+
const listedNames = skippedNames.slice(0, MAX_LISTED_SKIPPED_IMPORT_ONLY_PATHS);
|
|
98509
|
+
const remainingCount = skippedNames.length - listedNames.length;
|
|
98510
|
+
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.`);
|
|
98511
|
+
}
|
|
97504
98512
|
return await buildImportRulesFromPaths(scannedPaths);
|
|
97505
98513
|
})();
|
|
97506
98514
|
this.logger.debug(`Found ${importOnlyToolRules.length} import-only tool rule files`);
|
|
@@ -100321,4 +101329,4 @@ async function importChecksCore(params) {
|
|
|
100321
101329
|
//#endregion
|
|
100322
101330
|
export { RulesyncHooks as $, removeFile as $t, groupSpellingsByCaseFoldedIdentity as A, RULESYNC_RELATIVE_DIR_PATH as An, applyFileMode as At, QWENCODE_LOCAL_RULE_FILE_NAME as B, DEPRECATED_FEATURE_REPLACEMENTS as Bn, getFileSize as Bt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as C, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Cn, fallbackLogger as Ct, FACTORYDROID_DIR as D, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Dn, withWarnOnceScope as Dt, ChecksProcessor as E, RULESYNC_MCP_SCHEMA_URL as En, resetRunWarningState as Et, CLAUDECODE_LOCAL_RULE_FILE_NAME as F, RULESYNC_USER_CONFIG_DIR_NAME as Fn, createTempDirectory as Ft, RulesyncSubagentFrontmatterSchema as G, quoteForLog as Gn, listDirectoryEntryNames as Gt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as H, truncateText as Hn, isFileNotFoundError as Ht, CLAUDECODE_MEMORIES_DIR_NAME as I, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as In, directoryExists as It, RulesyncRule as J, stripHiddenCharacters as Jn, pathEscapesRoot as Jt, RulesyncSkill as K, stripControlCharacters as Kn, listFilePathsRecursively as Kt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as L, parseCommaSeparatedList as Ln, ensureDir as Lt, CODEXCLI_DIR as M, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Mn, assertTreeContainsNoSymlinks as Mt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as N, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Nn, assertWritablePathInsideRoot as Nt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as On, CLIError as Ot, CLAUDECODE_DIR as P, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Pn, checkPathTraversal as Pt, RulesyncIgnore as Q, removeDirectoryStrict as Qt, CLAUDECODE_SKILLS_DIR_PATH as R, ALL_FEATURES as Rn, fileExists as Rt, COMMANDCODE_DIR as S, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Sn, WarningCollectingLogger as St, CODEBUDDY_LOCAL_RULE_FILE_NAME as T, RULESYNC_MCP_RELATIVE_FILE_PATH as Tn, withFallbackLoggerTarget as Tt, getLocalSkillDirNames as U, hasDeceptiveHiddenCharacters as Un, isFileSystemError as Ut, AUGMENTCODE_DIR as V, formatError as Vn, getHomeDirectory as Vt, RulesyncSubagent as W, hasEnclosingMarkOutsideKeycap as Wn, isSymlink as Wt, RulesyncPermissions as X, readFileContentOrNull as Xt, RulesyncRuleFrontmatterSchema as Y, readFileContent as Yt, RulesyncMcp as Z, removeDirectory as Zt, LETTACODE_DIR as _, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as _n, SourceEntrySchema as _t, getProcessorRegistryEntry as a, toPosixPath as an, RulesyncCheck as at, CommandsProcessor as b, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as bn, ConsoleLogger as bt, RulesProcessor as c, ALL_TOOL_TARGETS as cn, loadYaml as ct, ELLIPSIS_WIDTH as d, ToolTargetSchema as dn, ConfigResolver as dt, removeFileStrict as en, getRulesyncSourceCandidates as et, displayWidthOf as f, CURATED_RULES_FEATURE_SUBDIR as fn, mergeInputRootConfigs as ft, HooksProcessor as g, RULESYNC_CHECKS_RELATIVE_DIR_PATH as gn, GITIGNORE_DESTINATION_KEY as gt, IgnoreProcessor as h, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as hn, ConfigFileSchema as ht, inspectInputRoots as i, splitPathSegments as in, RulesyncCommandFrontmatterSchema as it, CODEXCLI_BASH_RULES_FILE_NAME as j, RULESYNC_RULES_RELATIVE_DIR_PATH as jn, assertDirectoryIfExists as jt, caseFoldIdentity as k, RULESYNC_PERMISSIONS_SCHEMA_URL as kn, ErrorCodes as kt, SubagentsProcessor as l, ALL_TOOL_TARGETS_WITH_WILDCARD as ln, SHARED_USER_MANAGED_CONFIG_PATHS as lt, McpProcessor as m, MAX_FILE_SIZE as mn, CONFLICTING_TARGET_PAIRS as mt, formatSourceLoadFailure as n, resolvePath as nn, parseJsonc as nt, convertFromTool as o, writeFileBuffer as on, RulesyncCheckFrontmatterSchema as ot, shortenToWidth as p, FEATURE_SOURCE_TREE_ENTRIES as pn, resolveEffectiveInputRoots as pt, RulesyncSkillFrontmatterSchema as q, stripControlCharactersKeepingLineFeeds as qn, listSubdirectoryNames as qt, generate as r, runWithDirectoryRollback as rn, RulesyncCommand as rt, isPackagingToolTarget as s, writeFileContent as sn, stringifyFrontmatter as st, importFromTool as t, removeTempDirectory as tn, resolveRulesyncSourceWritePath as tt, SkillsProcessor as u, PACKAGING_TOOL_TARGETS as un, SKILL_FILE_NAME as ut, LETTACODE_SETTINGS_LOCAL_FILE_NAME as v, RULESYNC_CONFIG_RELATIVE_FILE_PATH as vn, assertTargetsFeaturesExclusive as vt, CODEBUDDY_DIR as w, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as wn, warnOnConflictingFlags as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as xn, JsonLogger as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, RULESYNC_CONFIG_SCHEMA_URL as yn, findControlCharacter as yt, QWENCODE_DIR as z, ALL_FEATURES_WITH_WILDCARD as zn, findFilesByGlobs as zt };
|
|
100323
101331
|
|
|
100324
|
-
//# sourceMappingURL=import
|
|
101332
|
+
//# sourceMappingURL=import-DBd2G6Eo.js.map
|