rulesync 43.1.2 → 43.3.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 +273 -65
- package/dist/cli/index.js +273 -65
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-D2uYjAdc.js → import-BQNLyW5s.js} +646 -81
- package/dist/{import-D2uYjAdc.js.map → import-BQNLyW5s.js.map} +1 -1
- package/dist/{import-BTqYh7nP.cjs → import-CedO1sFP.cjs} +645 -80
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -5544,24 +5544,27 @@ const AUGMENTCODE_HOOK_EVENTS = [
|
|
|
5544
5544
|
* Hook events supported by Mistral Vibe (mistral-vibe).
|
|
5545
5545
|
*
|
|
5546
5546
|
* Vibe exposes three hook events in `.vibe/hooks.toml`: `pre_tool` ←
|
|
5547
|
-
* `preToolUse`, `post_tool` ← `postToolUse`, and `post_agent` ← `stop`
|
|
5548
|
-
* after every assistant turn that ends
|
|
5549
|
-
*
|
|
5550
|
-
*
|
|
5551
|
-
*
|
|
5552
|
-
*
|
|
5553
|
-
* `
|
|
5547
|
+
* `preToolUse`, `post_tool` ← `postToolUse`, and `post_agent` ← `stop` /
|
|
5548
|
+
* `subagentStop`. `post_agent` fires after every assistant turn that ends
|
|
5549
|
+
* without pending tool calls, for the main agent and (since v3.0.2) for every
|
|
5550
|
+
* subagent. v3.0.3 lets a `post_agent` hook take a `match` on the agent that
|
|
5551
|
+
* finished its turn: the reserved alias `main` selects the main agent, so
|
|
5552
|
+
* canonical `stop` becomes `post_agent` with `match = "main"`, and a canonical
|
|
5553
|
+
* `subagentStop` with a matcher becomes `post_agent` with that agent name as
|
|
5554
|
+
* `match`. Only the tool events (`pre_tool`/`post_tool`) carry the `strict`
|
|
5555
|
+
* flag. Only `type: "command"` hooks are relevant.
|
|
5554
5556
|
*
|
|
5555
5557
|
* v2.21.0 graduated hooks from experimental and renamed all three types
|
|
5556
5558
|
* (`before_tool` → `pre_tool`, `after_tool` → `post_tool`, `post_agent_turn` →
|
|
5557
5559
|
* `post_agent`). `HookType` is a strict enum, so an entry using an old name is
|
|
5558
5560
|
* rejected outright and reported as a `HookConfigIssue`.
|
|
5559
|
-
* @see https://github.com/mistralai/mistral-vibe/blob/
|
|
5561
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
5560
5562
|
*/
|
|
5561
5563
|
const VIBE_HOOK_EVENTS = [
|
|
5562
5564
|
"preToolUse",
|
|
5563
5565
|
"postToolUse",
|
|
5564
|
-
"stop"
|
|
5566
|
+
"stop",
|
|
5567
|
+
"subagentStop"
|
|
5565
5568
|
];
|
|
5566
5569
|
/**
|
|
5567
5570
|
* Hook events supported by Codewhale.
|
|
@@ -7029,9 +7032,10 @@ const JUNIE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_JUNIE_EVENT
|
|
|
7029
7032
|
/**
|
|
7030
7033
|
* Map canonical camelCase event names to Mistral Vibe snake_case.
|
|
7031
7034
|
*
|
|
7032
|
-
* Vibe documents three hook events. The canonical `stop`
|
|
7033
|
-
* `post_agent` (fires after every assistant turn
|
|
7034
|
-
* calls)
|
|
7035
|
+
* Vibe documents three hook events. The canonical `stop` and `subagentStop`
|
|
7036
|
+
* events both map to Vibe's `post_agent` (fires after every assistant turn
|
|
7037
|
+
* ending without pending tool calls); the adapter tells them apart with the
|
|
7038
|
+
* hook's `match` on the agent name (`main` for the main agent).
|
|
7035
7039
|
*
|
|
7036
7040
|
* These are the v2.21.0 names; the pre-2.21.0 spellings (`before_tool`,
|
|
7037
7041
|
* `after_tool`, `post_agent_turn`) are rejected by Vibe's strict `HookType`
|
|
@@ -7041,7 +7045,8 @@ const JUNIE_TO_CANONICAL_EVENT_NAMES = invertEventNames(CANONICAL_TO_JUNIE_EVENT
|
|
|
7041
7045
|
const CANONICAL_TO_VIBE_EVENT_NAMES = {
|
|
7042
7046
|
preToolUse: "pre_tool",
|
|
7043
7047
|
postToolUse: "post_tool",
|
|
7044
|
-
stop: "post_agent"
|
|
7048
|
+
stop: "post_agent",
|
|
7049
|
+
subagentStop: "post_agent"
|
|
7045
7050
|
};
|
|
7046
7051
|
/**
|
|
7047
7052
|
* Map Mistral Vibe snake_case event names to canonical camelCase.
|
|
@@ -7054,6 +7059,7 @@ const CANONICAL_TO_VIBE_EVENT_NAMES = {
|
|
|
7054
7059
|
*/
|
|
7055
7060
|
const VIBE_TO_CANONICAL_EVENT_NAMES = {
|
|
7056
7061
|
...invertEventNames(CANONICAL_TO_VIBE_EVENT_NAMES),
|
|
7062
|
+
post_agent: "stop",
|
|
7057
7063
|
before_tool: "preToolUse",
|
|
7058
7064
|
after_tool: "postToolUse",
|
|
7059
7065
|
post_agent_turn: "stop"
|
|
@@ -8404,6 +8410,32 @@ const CopilotPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8404
8410
|
sandbox: zod_mini.z.optional(CopilotSandboxSchema)
|
|
8405
8411
|
});
|
|
8406
8412
|
/**
|
|
8413
|
+
* The permission mode the GitHub Copilot CLI starts new interactive sessions
|
|
8414
|
+
* in (`defaultPermissionMode` of `~/.copilot/settings.json`): `manual` prompts
|
|
8415
|
+
* for every request not already allowed, `assisted` lets a model review
|
|
8416
|
+
* eligible requests (Assisted Permissions, non-experimental since v1.0.104),
|
|
8417
|
+
* and `allow-all` is equivalent to `--allow-all`. These are the CLI's own
|
|
8418
|
+
* values and differ from the VS Code `copilot` target's
|
|
8419
|
+
* {@link COPILOT_DEFAULT_PERMISSION_MODES}.
|
|
8420
|
+
*
|
|
8421
|
+
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
8422
|
+
*/
|
|
8423
|
+
const COPILOTCLI_DEFAULT_PERMISSION_MODES = [
|
|
8424
|
+
"manual",
|
|
8425
|
+
"assisted",
|
|
8426
|
+
"allow-all"
|
|
8427
|
+
];
|
|
8428
|
+
/**
|
|
8429
|
+
* Tool-scoped override block for the GitHub Copilot CLI.
|
|
8430
|
+
* `defaultPermissionMode` writes the user-scope `defaultPermissionMode` key of
|
|
8431
|
+
* `~/.copilot/settings.json`; it has no canonical category, and the repository
|
|
8432
|
+
* settings file does not accept it.
|
|
8433
|
+
*/
|
|
8434
|
+
const CopilotcliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8435
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8436
|
+
defaultPermissionMode: zod_mini.z.optional(zod_mini.z.enum(COPILOTCLI_DEFAULT_PERMISSION_MODES))
|
|
8437
|
+
});
|
|
8438
|
+
/**
|
|
8407
8439
|
* oh-my-pi's `tools.approvalMode` values: the tool tiers it auto-approves
|
|
8408
8440
|
* before prompting. `always-ask` auto-approves only read-only tools,
|
|
8409
8441
|
* `always-ask-cached` (Ask and remember) also prompts before file reads and
|
|
@@ -8420,11 +8452,14 @@ const OMP_APPROVAL_MODES = [
|
|
|
8420
8452
|
];
|
|
8421
8453
|
/**
|
|
8422
8454
|
* Tool-scoped override block for oh-my-pi (`omp`). `approvalMode` writes the
|
|
8423
|
-
* `tools.approvalMode` key of `config.yml`,
|
|
8455
|
+
* `tools.approvalMode` key of `config.yml`, and `restrictedFolders` the
|
|
8456
|
+
* `tools.restrictedFolders` list (folders where every action prompts in every
|
|
8457
|
+
* approval mode, `yolo` included); neither has a canonical category.
|
|
8424
8458
|
*/
|
|
8425
8459
|
const OmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8426
8460
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8427
|
-
approvalMode: zod_mini.z.optional(zod_mini.z.enum(OMP_APPROVAL_MODES))
|
|
8461
|
+
approvalMode: zod_mini.z.optional(zod_mini.z.enum(OMP_APPROVAL_MODES)),
|
|
8462
|
+
restrictedFolders: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8428
8463
|
});
|
|
8429
8464
|
const KimiCodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8430
8465
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
@@ -8876,6 +8911,48 @@ const QoderPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
8876
8911
|
agentsMdExcludes: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8877
8912
|
});
|
|
8878
8913
|
/**
|
|
8914
|
+
* Tool-scoped override block for CodeBuddy Code. The shared `permission` block
|
|
8915
|
+
* continues to drive `permissions.allow`/`ask`/`deny` of
|
|
8916
|
+
* `.codebuddy/settings.json` (project) / `~/.codebuddy/settings.json` (global).
|
|
8917
|
+
*
|
|
8918
|
+
* The other documented settings with no canonical category are authored here
|
|
8919
|
+
* and merged into the same file, key by key, over what the file already
|
|
8920
|
+
* holds:
|
|
8921
|
+
*
|
|
8922
|
+
* - `autoReview` (`policy` replaces the built-in security policy of the `auto`
|
|
8923
|
+
* permission mode, `extraPolicy` is appended to it) and the legacy
|
|
8924
|
+
* `autoMode` rules (`environment` / `allow` / `soft_deny` / `hard_deny`).
|
|
8925
|
+
* CodeBuddy reads both from the user and project-local settings only, never
|
|
8926
|
+
* from the shared project file, so they are written by `--global` generate
|
|
8927
|
+
* alone.
|
|
8928
|
+
* - `memory.parentDiscovery`: whether project instruction files
|
|
8929
|
+
* (`CODEBUDDY.md` / `AGENTS.md`) are also discovered in parent directories.
|
|
8930
|
+
*
|
|
8931
|
+
* Only the keys listed here are written; any other key of these groups is
|
|
8932
|
+
* skipped with a warning. Removing a key from the override leaves the
|
|
8933
|
+
* previously written value in place.
|
|
8934
|
+
*
|
|
8935
|
+
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
8936
|
+
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
8937
|
+
* @example
|
|
8938
|
+
* { "autoReview": { "extraPolicy": "Allow dev namespace releases" },
|
|
8939
|
+
* "memory": { "parentDiscovery": false } }
|
|
8940
|
+
*/
|
|
8941
|
+
const CodebuddyPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
8942
|
+
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
8943
|
+
autoReview: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8944
|
+
policy: zod_mini.z.optional(zod_mini.z.string()),
|
|
8945
|
+
extraPolicy: zod_mini.z.optional(zod_mini.z.string())
|
|
8946
|
+
})),
|
|
8947
|
+
autoMode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
8948
|
+
environment: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8949
|
+
allow: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8950
|
+
soft_deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
8951
|
+
hard_deny: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
|
|
8952
|
+
})),
|
|
8953
|
+
memory: zod_mini.z.optional(zod_mini.z.looseObject({ parentDiscovery: zod_mini.z.optional(zod_mini.z.boolean()) }))
|
|
8954
|
+
});
|
|
8955
|
+
/**
|
|
8879
8956
|
* One native Factory Droid permission rule. `match.prefix` positions are either
|
|
8880
8957
|
* a single token or a non-empty list of alternative tokens.
|
|
8881
8958
|
*
|
|
@@ -9758,13 +9835,13 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
9758
9835
|
zed: zod_mini.z.optional(ZedPermissionsOverrideSchema),
|
|
9759
9836
|
devin: zod_mini.z.optional(DevinPermissionsOverrideSchema),
|
|
9760
9837
|
qoder: zod_mini.z.optional(QoderPermissionsOverrideSchema),
|
|
9838
|
+
copilotcli: zod_mini.z.optional(CopilotcliPermissionsOverrideSchema),
|
|
9761
9839
|
"antigravity-ide": zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9762
9840
|
continue: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9763
9841
|
copilot: zod_mini.z.optional(CopilotPermissionsOverrideSchema),
|
|
9764
|
-
codebuddy: zod_mini.z.optional(
|
|
9842
|
+
codebuddy: zod_mini.z.optional(CodebuddyPermissionsOverrideSchema),
|
|
9765
9843
|
commandcode: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9766
9844
|
lettacode: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9767
|
-
copilotcli: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9768
9845
|
crush: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9769
9846
|
goose: zod_mini.z.optional(CanonicalPermissionsOverrideSchema),
|
|
9770
9847
|
omp: zod_mini.z.optional(OmpPermissionsOverrideSchema),
|
|
@@ -10251,6 +10328,10 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
10251
10328
|
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
10252
10329
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
10253
10330
|
})),
|
|
10331
|
+
cline: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10332
|
+
disabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
10333
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
10334
|
+
})),
|
|
10254
10335
|
continue: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
10255
10336
|
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
10256
10337
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -11587,7 +11668,8 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
11587
11668
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
11588
11669
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
11589
11670
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
11590
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
11671
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
11672
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
11591
11673
|
})),
|
|
11592
11674
|
hermesagent: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
11593
11675
|
vibe: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -13730,7 +13812,11 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13730
13812
|
},
|
|
13731
13813
|
permissions: {
|
|
13732
13814
|
kind: "replace-owned-keys",
|
|
13733
|
-
ownedKeys: [
|
|
13815
|
+
ownedKeys: [
|
|
13816
|
+
"allowedUrls",
|
|
13817
|
+
"deniedUrls",
|
|
13818
|
+
"defaultPermissionMode"
|
|
13819
|
+
]
|
|
13734
13820
|
}
|
|
13735
13821
|
}
|
|
13736
13822
|
},
|
|
@@ -13909,7 +13995,12 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13909
13995
|
},
|
|
13910
13996
|
permissions: {
|
|
13911
13997
|
kind: "replace-owned-keys",
|
|
13912
|
-
ownedKeys: [
|
|
13998
|
+
ownedKeys: [
|
|
13999
|
+
"permissions",
|
|
14000
|
+
"autoReview",
|
|
14001
|
+
"autoMode",
|
|
14002
|
+
"memory"
|
|
14003
|
+
]
|
|
13913
14004
|
}
|
|
13914
14005
|
}
|
|
13915
14006
|
},
|
|
@@ -14219,7 +14310,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
14219
14310
|
"tools",
|
|
14220
14311
|
"enabled_tools",
|
|
14221
14312
|
"disabled_tools",
|
|
14222
|
-
"enable_subagents"
|
|
14313
|
+
"enable_subagents",
|
|
14314
|
+
"outside_workdir"
|
|
14223
14315
|
]
|
|
14224
14316
|
}
|
|
14225
14317
|
}
|
|
@@ -19531,7 +19623,8 @@ const AgentsSkillsSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
19531
19623
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
19532
19624
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
19533
19625
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
19534
|
-
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
19626
|
+
"allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
19627
|
+
"disable-model-invocation": zod_mini.z.optional(zod_mini.z.boolean())
|
|
19535
19628
|
});
|
|
19536
19629
|
const COMPATIBILITY_MAX_LENGTH = 500;
|
|
19537
19630
|
const AGENT_SKILLS_SPEC = "the Agent Skills spec";
|
|
@@ -19672,6 +19765,38 @@ function toSpecConformantAgentSkillFields(section, { coerceMetadata = true } = {
|
|
|
19672
19765
|
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
|
|
19673
19766
|
};
|
|
19674
19767
|
}
|
|
19768
|
+
/**
|
|
19769
|
+
* The opt-in `disable-model-invocation` flag of the `agentsskills` block.
|
|
19770
|
+
*
|
|
19771
|
+
* The key is not part of the Agent Skills specification, so `skills-ref
|
|
19772
|
+
* validate` rejects a `SKILL.md` that carries it, but several clients reading
|
|
19773
|
+
* `.agents/skills/` (e.g. pi) honor it. To keep the default output
|
|
19774
|
+
* spec-conformant, only an explicit `agentsskills.disable-model-invocation`
|
|
19775
|
+
* value is written; the root-level rulesync flag is deliberately not inherited.
|
|
19776
|
+
*
|
|
19777
|
+
* Kept apart from {@link toSpecConformantAgentSkillFields} because that helper
|
|
19778
|
+
* is shared with writers of other tools' own locations, which have their own
|
|
19779
|
+
* sections for tool-specific keys. Only writers of the shared `.agents/skills/`
|
|
19780
|
+
* root (see {@link isSharedAgentSkillsRoot}) — the native target, GitLab Duo's
|
|
19781
|
+
* project scope and the simulated `agentsmd` — spread this, so they all emit
|
|
19782
|
+
* the same frontmatter for that file.
|
|
19783
|
+
*/
|
|
19784
|
+
function toAgentsSkillsInvocationField(section) {
|
|
19785
|
+
const value = section?.["disable-model-invocation"];
|
|
19786
|
+
return value === void 0 ? {} : { "disable-model-invocation": value };
|
|
19787
|
+
}
|
|
19788
|
+
/**
|
|
19789
|
+
* Whether a skill directory lives in the cross-client `.agents/skills/` root
|
|
19790
|
+
* (project, or `~/.agents/skills/` in global scope). The
|
|
19791
|
+
* `agentsskills.disable-model-invocation` opt-in exists for the clients that
|
|
19792
|
+
* read that root, so it is written and imported only there — not by the
|
|
19793
|
+
* subclasses that reuse this class for a tool's own location (Trae, Qoder, Xum,
|
|
19794
|
+
* …), where the key would otherwise leak into the `agentsskills` section on
|
|
19795
|
+
* import and from there into every `.agents/skills/` output.
|
|
19796
|
+
*/
|
|
19797
|
+
function isSharedAgentSkillsRoot(relativeDirPath) {
|
|
19798
|
+
return toPosixPath(relativeDirPath) === toPosixPath(AGENTSMD_SKILLS_DIR_PATH);
|
|
19799
|
+
}
|
|
19675
19800
|
/** The `SKILL.md` a diagnostic should point at, in the scope it is written to. */
|
|
19676
19801
|
function agentSkillFilePath({ outputRoot, relativeDirPath, dirName }) {
|
|
19677
19802
|
return (0, node_path.join)(outputRoot, relativeDirPath, dirName, SKILL_FILE_NAME);
|
|
@@ -19753,7 +19878,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
19753
19878
|
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
19754
19879
|
...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
|
|
19755
19880
|
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata },
|
|
19756
|
-
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools }
|
|
19881
|
+
...allowedTools !== void 0 && allowedTools.length > 0 && { "allowed-tools": allowedTools },
|
|
19882
|
+
...isSharedAgentSkillsRoot(this.getRelativeDirPath()) && frontmatter["disable-model-invocation"] !== void 0 && { "disable-model-invocation": frontmatter["disable-model-invocation"] }
|
|
19757
19883
|
};
|
|
19758
19884
|
const rulesyncFrontmatter = {
|
|
19759
19885
|
name: frontmatter.name,
|
|
@@ -19779,7 +19905,8 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
|
|
|
19779
19905
|
const agentsSkillsFrontmatter = {
|
|
19780
19906
|
name: rulesyncFrontmatter.name,
|
|
19781
19907
|
description: rulesyncFrontmatter.description,
|
|
19782
|
-
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter))
|
|
19908
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(rulesyncFrontmatter)),
|
|
19909
|
+
...isSharedAgentSkillsRoot(settablePaths.relativeDirPath) && toAgentsSkillsInvocationField(rulesyncFrontmatter.agentsskills)
|
|
19783
19910
|
};
|
|
19784
19911
|
AgentsSkillsSkill.reportSpecViolations({
|
|
19785
19912
|
outputRoot,
|
|
@@ -24853,7 +24980,8 @@ var AgentsmdSkill = class AgentsmdSkill extends SimulatedSkill {
|
|
|
24853
24980
|
const relativeDirPath = this.getSettablePaths().relativeDirPath;
|
|
24854
24981
|
const frontmatter = {
|
|
24855
24982
|
...defaults.frontmatter,
|
|
24856
|
-
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter()))
|
|
24983
|
+
...toSpecConformantAgentSkillFields(resolveAgentsSkillsSection(params.rulesyncSkill.getFrontmatter())),
|
|
24984
|
+
...toAgentsSkillsInvocationField(params.rulesyncSkill.getFrontmatter().agentsskills)
|
|
24857
24985
|
};
|
|
24858
24986
|
AgentsSkillsSkill.reportSpecViolations({
|
|
24859
24987
|
outputRoot: params.outputRoot ?? process.cwd(),
|
|
@@ -30572,7 +30700,10 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
|
|
|
30572
30700
|
}
|
|
30573
30701
|
}
|
|
30574
30702
|
static getSettablePaths({ global: _global = false } = {}) {
|
|
30575
|
-
return {
|
|
30703
|
+
return {
|
|
30704
|
+
relativeDirPath: QWENCODE_SKILLS_DIR_PATH,
|
|
30705
|
+
importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH]
|
|
30706
|
+
};
|
|
30576
30707
|
}
|
|
30577
30708
|
getFrontmatter() {
|
|
30578
30709
|
return QwencodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
@@ -39593,6 +39724,16 @@ var VibePluginSubagent = class VibePluginSubagent extends ToolSubagent {
|
|
|
39593
39724
|
*/
|
|
39594
39725
|
const VIBE_PROMPTS_DIR_PATH = (0, node_path.join)(".vibe", "prompts");
|
|
39595
39726
|
/**
|
|
39727
|
+
* The agent name Vibe reserves for `post_agent` hook matching (v3.0.3+).
|
|
39728
|
+
* `AgentProfile.from_toml` refuses a file whose stem matches it case-insensitively, so an
|
|
39729
|
+
* agent written as `main.toml` would never load.
|
|
39730
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/agents/models.py
|
|
39731
|
+
*/
|
|
39732
|
+
const VIBE_RESERVED_AGENT_NAME = "main";
|
|
39733
|
+
function isReservedVibeAgentFileName(relativeFilePath) {
|
|
39734
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.(md|toml)$/, "").toLowerCase() === VIBE_RESERVED_AGENT_NAME;
|
|
39735
|
+
}
|
|
39736
|
+
/**
|
|
39596
39737
|
* Vibe resolves a `system_prompt_id` by file name, and
|
|
39597
39738
|
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
39598
39739
|
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
@@ -39707,9 +39848,17 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
39707
39848
|
* evaluates the property during validation, so an unresolvable
|
|
39708
39849
|
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
39709
39850
|
* with a warning.
|
|
39851
|
+
*
|
|
39852
|
+
* A subagent named `main` is skipped with a warning: Vibe reserves that name
|
|
39853
|
+
* for hook matching and does not load an agent file called `main.toml`.
|
|
39710
39854
|
*/
|
|
39711
|
-
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
39855
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false, logger }) {
|
|
39712
39856
|
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
39857
|
+
const sourcePath = rulesyncSubagent.getRelativeFilePath();
|
|
39858
|
+
if (isReservedVibeAgentFileName(sourcePath)) {
|
|
39859
|
+
logger?.warn(`Skipping Vibe subagent ${sourcePath}: Vibe reserves the agent name '${VIBE_RESERVED_AGENT_NAME}' for hook matching and does not load an agent file named '${VIBE_RESERVED_AGENT_NAME}'. Rename the subagent file.`);
|
|
39860
|
+
return [];
|
|
39861
|
+
}
|
|
39713
39862
|
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
39714
39863
|
outputRoot,
|
|
39715
39864
|
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -44996,17 +45145,49 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
44996
45145
|
* - `alwaysApply: true`: force the rule to always load (equivalent to a file
|
|
44997
45146
|
* with no frontmatter at all).
|
|
44998
45147
|
* - `description`: label surfaced in the rule toggle UI.
|
|
45148
|
+
* - `disabled: true` (or `enabled: false`): the rule is switched off. Since
|
|
45149
|
+
* Cline v4.1.26 (cline/cline#14605) the rule toggle writes this flag into the
|
|
45150
|
+
* rule file itself instead of keeping it only in extension state.
|
|
44999
45151
|
*
|
|
45000
45152
|
* Rules without frontmatter are always active. Uses `z.looseObject()` so
|
|
45001
|
-
* forward-compatible fields added upstream are preserved on round-trip
|
|
45153
|
+
* forward-compatible fields added upstream are preserved on round-trip: every
|
|
45154
|
+
* key other than `paths` / `alwaysApply` / `description` is carried in the
|
|
45155
|
+
* rulesync `cline:` section.
|
|
45002
45156
|
*
|
|
45003
45157
|
* @see https://docs.cline.bot/customization/cline-rules
|
|
45158
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/extensions/config/user-instruction-config-loader.ts
|
|
45004
45159
|
*/
|
|
45005
45160
|
const ClineRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
45006
45161
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.array(zod_mini.z.string()), zod_mini.z.string()])),
|
|
45007
45162
|
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
45008
45163
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
45009
45164
|
});
|
|
45165
|
+
const CLINE_RULE_FIELD_TYPES = {
|
|
45166
|
+
disabled: "boolean",
|
|
45167
|
+
enabled: "boolean"
|
|
45168
|
+
};
|
|
45169
|
+
const CANONICAL_CLINE_RULE_KEYS = [
|
|
45170
|
+
"paths",
|
|
45171
|
+
"alwaysApply",
|
|
45172
|
+
"description"
|
|
45173
|
+
];
|
|
45174
|
+
/** Returns a copy of `record` without the canonical `paths` / `alwaysApply` / `description` keys. */
|
|
45175
|
+
function omitCanonicalClineRuleKeys(record) {
|
|
45176
|
+
return Object.fromEntries(Object.entries(record ?? {}).filter(([key]) => !CANONICAL_CLINE_RULE_KEYS.includes(key)));
|
|
45177
|
+
}
|
|
45178
|
+
/**
|
|
45179
|
+
* Splits a Cline rule frontmatter into the keys that have canonical rulesync
|
|
45180
|
+
* homes (`paths` / `alwaysApply` / `description`) and the rest, which are
|
|
45181
|
+
* lifted into the rulesync `cline:` section.
|
|
45182
|
+
*/
|
|
45183
|
+
function extractClineSection({ frontmatter, relativeFilePath }) {
|
|
45184
|
+
return Object.fromEntries(Object.entries(omitCanonicalClineRuleKeys(frontmatter)).filter(([key, value]) => {
|
|
45185
|
+
const expected = Object.hasOwn(CLINE_RULE_FIELD_TYPES, key) ? CLINE_RULE_FIELD_TYPES[key] : void 0;
|
|
45186
|
+
if (expected === void 0 || value === void 0 || typeof value === expected) return true;
|
|
45187
|
+
warnWithFallback(void 0, `Cline rules: dropping ${key} from ${relativeFilePath}: Cline rejects the whole rule unless it is a ${expected}.`);
|
|
45188
|
+
return false;
|
|
45189
|
+
}));
|
|
45190
|
+
}
|
|
45010
45191
|
function isNonRootParams$1(params) {
|
|
45011
45192
|
return "frontmatter" in params && "body" in params;
|
|
45012
45193
|
}
|
|
@@ -45081,6 +45262,10 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45081
45262
|
toRulesyncRule() {
|
|
45082
45263
|
if (this.isRoot()) return this.toRulesyncRuleDefault();
|
|
45083
45264
|
const globs = this.frontmatter?.alwaysApply === true ? ["**/*"] : normalizePaths$1(this.frontmatter?.paths);
|
|
45265
|
+
const cline = extractClineSection({
|
|
45266
|
+
frontmatter: this.frontmatter,
|
|
45267
|
+
relativeFilePath: this.getRelativeFilePath()
|
|
45268
|
+
});
|
|
45084
45269
|
return new RulesyncRule({
|
|
45085
45270
|
outputRoot: process.cwd(),
|
|
45086
45271
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -45089,7 +45274,8 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45089
45274
|
root: false,
|
|
45090
45275
|
targets: ["*"],
|
|
45091
45276
|
description: this.frontmatter?.description,
|
|
45092
|
-
globs
|
|
45277
|
+
globs,
|
|
45278
|
+
...Object.keys(cline).length > 0 && { cline }
|
|
45093
45279
|
},
|
|
45094
45280
|
body: this.body ?? this.getFileContent()
|
|
45095
45281
|
});
|
|
@@ -45134,16 +45320,22 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45134
45320
|
* universal match-everything globs mean "always", so they map to
|
|
45135
45321
|
* `alwaysApply: true` instead of a `paths` list; specific globs become
|
|
45136
45322
|
* conditional `paths`; an empty globs list leaves the rule always-active with
|
|
45137
|
-
* no `paths`/`alwaysApply`.
|
|
45323
|
+
* no `paths`/`alwaysApply`. Every key of the rulesync `cline:` section
|
|
45324
|
+
* (e.g. `disabled: true`) is written into the frontmatter too; the canonical
|
|
45325
|
+
* `paths` / `alwaysApply` / `description` win over same-named keys there.
|
|
45138
45326
|
*/
|
|
45139
45327
|
static buildNonRootRule({ outputRoot, rulesyncRule, validate, relativeDirPath }) {
|
|
45140
45328
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
45141
45329
|
const globs = normalizePaths$1(rulesyncFrontmatter.globs);
|
|
45142
45330
|
const isUniversal = globs.length > 0 && globs.every((glob) => glob === "**/*" || glob === "*");
|
|
45331
|
+
const rawClineSection = rulesyncFrontmatter.cline ?? {};
|
|
45332
|
+
for (const key of CANONICAL_CLINE_RULE_KEYS) if (Object.hasOwn(rawClineSection, key)) warnWithFallback(void 0, `Cline rules: ignoring cline.${key} in ${rulesyncRule.getRelativeFilePath()}: set the canonical ${key === "description" ? "description" : "globs"} instead.`);
|
|
45333
|
+
const clineSection = omitCanonicalClineRuleKeys(rawClineSection);
|
|
45143
45334
|
const frontmatter = {
|
|
45144
45335
|
paths: globs.length > 0 && !isUniversal ? globs : void 0,
|
|
45145
45336
|
alwaysApply: isUniversal ? true : void 0,
|
|
45146
|
-
description: rulesyncFrontmatter.description || void 0
|
|
45337
|
+
description: rulesyncFrontmatter.description || void 0,
|
|
45338
|
+
...clineSection
|
|
45147
45339
|
};
|
|
45148
45340
|
return new ClineRule({
|
|
45149
45341
|
outputRoot,
|
|
@@ -55830,6 +56022,14 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
55830
56022
|
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
55831
56023
|
return anchorInline(`"${converterConfig.projectDirVar}"/${relativeCommand}`);
|
|
55832
56024
|
}
|
|
56025
|
+
/** Whether a field restricted to some events applies on this one. */
|
|
56026
|
+
function isEventApplicable({ events, eventName }) {
|
|
56027
|
+
return events === void 0 || events.has(eventName);
|
|
56028
|
+
}
|
|
56029
|
+
/** Quote and comma-join a set of names (events, hook types) for a warning. */
|
|
56030
|
+
function describeEvents(names) {
|
|
56031
|
+
return [...names].map((name) => `"${name}"`).join(", ");
|
|
56032
|
+
}
|
|
55833
56033
|
/**
|
|
55834
56034
|
* Whether a field registered for `command` hooks only applies to this hook.
|
|
55835
56035
|
* Applied on both export and import: a value imported into a canonical field
|
|
@@ -55851,7 +56051,7 @@ function isFieldApplicable({ commandOnly, hookType }) {
|
|
|
55851
56051
|
* silence — the mirror of the import side.
|
|
55852
56052
|
*/
|
|
55853
56053
|
function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
|
|
55854
|
-
for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength, onlyWithFlag } of fields) {
|
|
56054
|
+
for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength, onlyWithFlag, events } of fields) {
|
|
55855
56055
|
const value = def[canonical];
|
|
55856
56056
|
if (value === void 0) continue;
|
|
55857
56057
|
if (!isFieldApplicable({
|
|
@@ -55861,6 +56061,13 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55861
56061
|
warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not generated.`);
|
|
55862
56062
|
continue;
|
|
55863
56063
|
}
|
|
56064
|
+
if (events !== void 0 && !isEventApplicable({
|
|
56065
|
+
events,
|
|
56066
|
+
eventName
|
|
56067
|
+
})) {
|
|
56068
|
+
if (value !== false) warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${describeEvents(events)} only, so it is not generated.`);
|
|
56069
|
+
continue;
|
|
56070
|
+
}
|
|
55864
56071
|
if (!isValid({
|
|
55865
56072
|
value,
|
|
55866
56073
|
canonical
|
|
@@ -55874,9 +56081,12 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55874
56081
|
if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long${collapseWhitespaceForLength ? " after collapsing whitespace" : ""}; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
|
|
55875
56082
|
}
|
|
55876
56083
|
}
|
|
55877
|
-
return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
|
|
56084
|
+
return Object.fromEntries(fields.filter(({ canonical, commandOnly, events }) => isFieldApplicable({
|
|
55878
56085
|
commandOnly,
|
|
55879
56086
|
hookType
|
|
56087
|
+
}) && isEventApplicable({
|
|
56088
|
+
events,
|
|
56089
|
+
eventName
|
|
55880
56090
|
}) && isValid({
|
|
55881
56091
|
value: def[canonical],
|
|
55882
56092
|
canonical
|
|
@@ -55889,16 +56099,24 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55889
56099
|
* kind has a rule an authored file can plausibly violate — turns a rejected
|
|
55890
56100
|
* value into a warning instead of a silent drop.
|
|
55891
56101
|
*/
|
|
55892
|
-
function importPassthroughFields({ h, hookType, fields, isValid, describeInvalid, warn }) {
|
|
55893
|
-
const
|
|
56102
|
+
function importPassthroughFields({ h, hookType, eventName, fields, isValid, describeInvalid, warn }) {
|
|
56103
|
+
const typeApplicable = fields.filter(({ commandOnly }) => isFieldApplicable({
|
|
55894
56104
|
commandOnly,
|
|
55895
56105
|
hookType
|
|
55896
56106
|
}));
|
|
56107
|
+
const applicable = typeApplicable.filter(({ events }) => isEventApplicable({
|
|
56108
|
+
events,
|
|
56109
|
+
eventName
|
|
56110
|
+
}));
|
|
55897
56111
|
const skipped = fields.filter(({ commandOnly }) => !isFieldApplicable({
|
|
55898
56112
|
commandOnly,
|
|
55899
56113
|
hookType
|
|
55900
56114
|
}));
|
|
55901
56115
|
for (const { tool } of skipped) if (h[tool] !== void 0) warn?.(`Dropping "${tool}" from an imported "${hookType}" hook: this tool documents it on "command" hooks only, so it is not imported.`);
|
|
56116
|
+
for (const { tool, events } of typeApplicable) if (events !== void 0 && !isEventApplicable({
|
|
56117
|
+
events,
|
|
56118
|
+
eventName
|
|
56119
|
+
}) && h[tool] !== void 0 && h[tool] !== false) warn?.(`Dropping "${tool}" from an imported "${hookType}" hook on "${eventName}": this tool honours it on ${describeEvents(events)} only, so it is not imported.`);
|
|
55902
56120
|
for (const { tool, canonical } of applicable) if (describeInvalid !== void 0 && h[tool] !== void 0 && !isValid({
|
|
55903
56121
|
value: h[tool],
|
|
55904
56122
|
canonical
|
|
@@ -56066,11 +56284,11 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction,
|
|
|
56066
56284
|
return false;
|
|
56067
56285
|
}
|
|
56068
56286
|
if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
|
|
56069
|
-
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${
|
|
56287
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${describeEvents(hookTypes)} hooks only, so it is not ${direction}.`);
|
|
56070
56288
|
return false;
|
|
56071
56289
|
}
|
|
56072
56290
|
if (events !== void 0 && !events.has(eventName)) {
|
|
56073
|
-
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${
|
|
56291
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${describeEvents(events)} only, so it is not ${direction}.`);
|
|
56074
56292
|
return false;
|
|
56075
56293
|
}
|
|
56076
56294
|
if (ignoredEvents?.has(eventName)) {
|
|
@@ -56516,6 +56734,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56516
56734
|
...importPassthroughFields({
|
|
56517
56735
|
h,
|
|
56518
56736
|
hookType,
|
|
56737
|
+
eventName,
|
|
56519
56738
|
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
56520
56739
|
isValid: isBooleanValue,
|
|
56521
56740
|
warn
|
|
@@ -56530,6 +56749,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56530
56749
|
...importPassthroughFields({
|
|
56531
56750
|
h,
|
|
56532
56751
|
hookType,
|
|
56752
|
+
eventName,
|
|
56533
56753
|
fields: converterConfig.numberPassthroughFields ?? [],
|
|
56534
56754
|
isValid: isImportableNumber,
|
|
56535
56755
|
describeInvalid: describeInvalidScalar,
|
|
@@ -56538,6 +56758,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56538
56758
|
...importPassthroughFields({
|
|
56539
56759
|
h,
|
|
56540
56760
|
hookType,
|
|
56761
|
+
eventName,
|
|
56541
56762
|
fields: converterConfig.stringPassthroughFields ?? [],
|
|
56542
56763
|
isValid: isImportableString,
|
|
56543
56764
|
describeInvalid: describeInvalidScalar,
|
|
@@ -56546,6 +56767,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56546
56767
|
...importPassthroughFields({
|
|
56547
56768
|
h,
|
|
56548
56769
|
hookType,
|
|
56770
|
+
eventName,
|
|
56549
56771
|
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
56550
56772
|
isValid: isImportableArray,
|
|
56551
56773
|
describeInvalid: describeInvalidArray,
|
|
@@ -56554,6 +56776,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56554
56776
|
...importPassthroughFields({
|
|
56555
56777
|
h,
|
|
56556
56778
|
hookType,
|
|
56779
|
+
eventName,
|
|
56557
56780
|
fields: converterConfig.recordPassthroughFields ?? [],
|
|
56558
56781
|
isValid: isEmittableRecord,
|
|
56559
56782
|
describeInvalid: describeInvalidRecord,
|
|
@@ -56562,6 +56785,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56562
56785
|
...importPassthroughFields({
|
|
56563
56786
|
h,
|
|
56564
56787
|
hookType,
|
|
56788
|
+
eventName,
|
|
56565
56789
|
fields: converterConfig.objectPassthroughFields ?? [],
|
|
56566
56790
|
isValid: isImportableObject,
|
|
56567
56791
|
describeInvalid: describeInvalidObject,
|
|
@@ -61394,6 +61618,11 @@ const GROKCLI_CONVERTER_CONFIG = {
|
|
|
61394
61618
|
tool: "env",
|
|
61395
61619
|
commandOnly: true
|
|
61396
61620
|
}],
|
|
61621
|
+
booleanPassthroughFields: [{
|
|
61622
|
+
canonical: "failClosed",
|
|
61623
|
+
tool: "failClosed",
|
|
61624
|
+
events: /* @__PURE__ */ new Set(["preToolUse", "beforeSubmitPrompt"])
|
|
61625
|
+
}],
|
|
61397
61626
|
arrayPassthroughFields: [{
|
|
61398
61627
|
canonical: "args",
|
|
61399
61628
|
tool: "args",
|
|
@@ -63043,6 +63272,15 @@ const LETTACODE_CONVERTER_CONFIG = {
|
|
|
63043
63272
|
timeoutUnit: "milliseconds"
|
|
63044
63273
|
};
|
|
63045
63274
|
/**
|
|
63275
|
+
* Letta Code documents settings-file hooks as deprecated in favor of Mods
|
|
63276
|
+
* (https://docs.letta.com/reference/deprecated/hooks) but still ships the
|
|
63277
|
+
* loader, so rulesync keeps emitting them and tells the user once per run.
|
|
63278
|
+
* Mods are not modeled: they are JS/TS code modules loaded only from
|
|
63279
|
+
* `~/.letta/mods/` or an agent's memory directory, with no declarative or
|
|
63280
|
+
* project-local form a canonical hook could be translated into.
|
|
63281
|
+
*/
|
|
63282
|
+
const LETTACODE_HOOKS_DEPRECATION_WARNING = "Letta Code documents settings.json hooks as deprecated in favor of Mods and may remove them in a future release; rulesync still writes them while Letta Code loads them. See https://docs.letta.com/reference/deprecated/hooks";
|
|
63283
|
+
/**
|
|
63046
63284
|
* Letta Code keeps a boolean `disabled` switch beside the event arrays inside
|
|
63047
63285
|
* `hooks`. rulesync owns the whole `hooks` key, so the switch is read back from
|
|
63048
63286
|
* the existing file and carried into the generated one instead of being lost.
|
|
@@ -63101,6 +63339,11 @@ function parseLettacodeSettings$1(fileContent, filePath) {
|
|
|
63101
63339
|
* and UI preferences, ...), so generation merges the `hooks` key into the
|
|
63102
63340
|
* existing file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
|
|
63103
63341
|
*
|
|
63342
|
+
* Letta Code marks this surface deprecated in favor of Mods (see
|
|
63343
|
+
* `LETTACODE_HOOKS_DEPRECATION_WARNING`); generation warns once per run when it
|
|
63344
|
+
* writes any hook.
|
|
63345
|
+
*
|
|
63346
|
+
* @see https://docs.letta.com/reference/deprecated/hooks
|
|
63104
63347
|
* @see https://github.com/letta-ai/letta-code/blob/main/src/hooks/types.ts
|
|
63105
63348
|
* @see https://github.com/letta-ai/letta-code/blob/main/src/hooks/loader.ts
|
|
63106
63349
|
*/
|
|
@@ -63145,6 +63388,7 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
|
|
|
63145
63388
|
converterConfig: LETTACODE_CONVERTER_CONFIG,
|
|
63146
63389
|
logger
|
|
63147
63390
|
});
|
|
63391
|
+
if (Object.keys(hooks).length > 0) warnOnceWithFallback(logger, LETTACODE_HOOKS_DEPRECATION_WARNING);
|
|
63148
63392
|
const fileContent = applySharedConfigPatch({
|
|
63149
63393
|
fileKey: sharedConfigFileKey(paths),
|
|
63150
63394
|
feature: "hooks",
|
|
@@ -65142,11 +65386,38 @@ const VIBE_DIR = ".vibe";
|
|
|
65142
65386
|
const VIBE_HOOKS_FILE_NAME = "hooks.toml";
|
|
65143
65387
|
/**
|
|
65144
65388
|
* Vibe scopes the tool-name `match` glob and the `strict` flag to tool hooks
|
|
65145
|
-
* (`pre_tool` / `post_tool`)
|
|
65146
|
-
*
|
|
65147
|
-
* @
|
|
65389
|
+
* (`pre_tool` / `post_tool`). `post_agent` takes a `match` too since v3.0.3,
|
|
65390
|
+
* but on the name of the agent that finished its turn, not a tool name (see
|
|
65391
|
+
* {@link VIBE_MAIN_AGENT_MATCH}).
|
|
65392
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
65148
65393
|
*/
|
|
65149
65394
|
const VIBE_TOOL_EVENTS = /* @__PURE__ */ new Set(["pre_tool", "post_tool"]);
|
|
65395
|
+
/** The Vibe event both canonical `stop` and `subagentStop` become. */
|
|
65396
|
+
const VIBE_POST_AGENT_EVENT = "post_agent";
|
|
65397
|
+
/**
|
|
65398
|
+
* The reserved agent alias a `post_agent` hook's `match` uses to run only
|
|
65399
|
+
* after the main agent. Without a `match`, `post_agent` runs after every
|
|
65400
|
+
* agent, subagents included (v3.0.2+), whereas canonical `stop` is
|
|
65401
|
+
* main-agent-only — so `stop` is written with this match.
|
|
65402
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
65403
|
+
*/
|
|
65404
|
+
const VIBE_MAIN_AGENT_MATCH = "main";
|
|
65405
|
+
/**
|
|
65406
|
+
* The `post_agent` `match` for a canonical `subagentStop` hook, or null to
|
|
65407
|
+
* skip it. Vibe has no selector for "every subagent but not the main agent",
|
|
65408
|
+
* so a matcher naming the subagent is required.
|
|
65409
|
+
*/
|
|
65410
|
+
function toVibeSubagentMatch({ matcher, logger }) {
|
|
65411
|
+
if (typeof matcher !== "string" || matcher === "" || matcher === "*") {
|
|
65412
|
+
logger?.warn("Vibe hooks: skipping a \"subagentStop\" hook without an agent-name matcher. Vibe's post_agent cannot select every subagent without also running after the main agent; set \"matcher\" to the subagent name.");
|
|
65413
|
+
return null;
|
|
65414
|
+
}
|
|
65415
|
+
if (matcher.toLowerCase() === VIBE_MAIN_AGENT_MATCH) {
|
|
65416
|
+
logger?.warn("Vibe hooks: skipping a \"subagentStop\" hook whose matcher is \"main\". Vibe reserves \"main\" for the main agent; use a \"stop\" hook instead.");
|
|
65417
|
+
return null;
|
|
65418
|
+
}
|
|
65419
|
+
return matcher;
|
|
65420
|
+
}
|
|
65150
65421
|
const SUPPORTED_VIBE_EVENTS = new Set(VIBE_HOOK_EVENTS);
|
|
65151
65422
|
/**
|
|
65152
65423
|
* The `match` a canonical `Bash` matcher becomes. Vibe tests `match` against a
|
|
@@ -65170,7 +65441,8 @@ function fromVibeHookMatch(match) {
|
|
|
65170
65441
|
/**
|
|
65171
65442
|
* Build the flat `[[hooks]]` array for `.vibe/hooks.toml` from a canonical
|
|
65172
65443
|
* hooks config. Vibe uses a flat array where each entry carries its own event
|
|
65173
|
-
* `type`,
|
|
65444
|
+
* `type`, `match` glob/regex (a tool name for tool hooks, an agent name
|
|
65445
|
+
* for `post_agent`), and `command`. Only `type: "command"`
|
|
65174
65446
|
* canonical hooks are emitted: a Vibe hook is a shell command line. (v2.25.1
|
|
65175
65447
|
* to v2.25.4 briefly exec'd it without a shell under the unified harness and
|
|
65176
65448
|
* skipped any command containing a backslash; v2.25.5 restored shell
|
|
@@ -65178,7 +65450,7 @@ function fromVibeHookMatch(match) {
|
|
|
65178
65450
|
* now run through the shell" — and dropped the backslash skip.)
|
|
65179
65451
|
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.2/README.md
|
|
65180
65452
|
*/
|
|
65181
|
-
function canonicalToVibeHooks({ config, toolOverride }) {
|
|
65453
|
+
function canonicalToVibeHooks({ config, toolOverride, logger }) {
|
|
65182
65454
|
const shared = {};
|
|
65183
65455
|
for (const [event, defs] of Object.entries(config.hooks)) if (SUPPORTED_VIBE_EVENTS.has(event)) shared[event] = defs;
|
|
65184
65456
|
const effective = {
|
|
@@ -65186,30 +65458,40 @@ function canonicalToVibeHooks({ config, toolOverride }) {
|
|
|
65186
65458
|
...toolOverride
|
|
65187
65459
|
};
|
|
65188
65460
|
const hooks = [];
|
|
65461
|
+
const nextIndexByVibeEvent = /* @__PURE__ */ new Map();
|
|
65189
65462
|
for (const [event, defs] of Object.entries(effective)) {
|
|
65190
65463
|
if (!SUPPORTED_VIBE_EVENTS.has(event)) continue;
|
|
65191
65464
|
const vibeEvent = lookupOwn({
|
|
65192
65465
|
record: CANONICAL_TO_VIBE_EVENT_NAMES,
|
|
65193
65466
|
key: event
|
|
65194
65467
|
}) ?? event;
|
|
65195
|
-
let index = 0;
|
|
65196
65468
|
for (const def of defs) {
|
|
65197
65469
|
if ((def.type ?? "command") !== "command") continue;
|
|
65198
65470
|
if (typeof def.command !== "string") continue;
|
|
65199
|
-
const name = typeof def.name === "string" ? def.name : `${vibeEvent}-${index}`;
|
|
65200
65471
|
const isToolEvent = VIBE_TOOL_EVENTS.has(vibeEvent);
|
|
65472
|
+
let match;
|
|
65473
|
+
if (isToolEvent) match = toVibeHookMatch(def.matcher);
|
|
65474
|
+
else if (event === "subagentStop") {
|
|
65475
|
+
const subagentMatch = toVibeSubagentMatch({
|
|
65476
|
+
matcher: def.matcher,
|
|
65477
|
+
logger
|
|
65478
|
+
});
|
|
65479
|
+
if (subagentMatch === null) continue;
|
|
65480
|
+
match = subagentMatch;
|
|
65481
|
+
} else match = VIBE_MAIN_AGENT_MATCH;
|
|
65482
|
+
const index = nextIndexByVibeEvent.get(vibeEvent) ?? 0;
|
|
65201
65483
|
const entry = {
|
|
65202
|
-
name
|
|
65484
|
+
name: typeof def.name === "string" ? def.name : `${vibeEvent}-${index}`,
|
|
65203
65485
|
type: vibeEvent,
|
|
65486
|
+
match,
|
|
65204
65487
|
command: def.command
|
|
65205
65488
|
};
|
|
65206
|
-
if (isToolEvent) entry.match = toVibeHookMatch(def.matcher);
|
|
65207
65489
|
if (typeof def.timeout === "number") entry.timeout = def.timeout;
|
|
65208
65490
|
const strict = def.strict;
|
|
65209
65491
|
if (isToolEvent && typeof strict === "boolean") entry.strict = strict;
|
|
65210
65492
|
if (typeof def.description === "string") entry.description = def.description;
|
|
65211
65493
|
hooks.push(entry);
|
|
65212
|
-
index
|
|
65494
|
+
nextIndexByVibeEvent.set(vibeEvent, index + 1);
|
|
65213
65495
|
}
|
|
65214
65496
|
}
|
|
65215
65497
|
return { hooks };
|
|
@@ -65221,14 +65503,22 @@ function vibeEntryToCanonicalDef(raw) {
|
|
|
65221
65503
|
const vibeEvent = typeof entry.type === "string" ? entry.type : void 0;
|
|
65222
65504
|
if (vibeEvent === void 0) return null;
|
|
65223
65505
|
if (isPrototypePollutionKey(vibeEvent)) return null;
|
|
65224
|
-
|
|
65506
|
+
let canonicalEvent = lookupOwn({
|
|
65225
65507
|
record: VIBE_TO_CANONICAL_EVENT_NAMES,
|
|
65226
65508
|
key: vibeEvent
|
|
65227
65509
|
}) ?? vibeEvent;
|
|
65228
65510
|
const def = { type: "command" };
|
|
65229
65511
|
if (typeof entry.command === "string") def.command = entry.command;
|
|
65230
|
-
|
|
65231
|
-
|
|
65512
|
+
if (vibeEvent === VIBE_POST_AGENT_EVENT) {
|
|
65513
|
+
const agentMatch = typeof entry.match === "string" ? entry.match : "";
|
|
65514
|
+
if (agentMatch !== "" && agentMatch !== "*" && agentMatch.toLowerCase() !== VIBE_MAIN_AGENT_MATCH) {
|
|
65515
|
+
canonicalEvent = "subagentStop";
|
|
65516
|
+
def.matcher = agentMatch;
|
|
65517
|
+
}
|
|
65518
|
+
} else {
|
|
65519
|
+
const matcher = fromVibeHookMatch(entry.match);
|
|
65520
|
+
if (matcher !== void 0) def.matcher = matcher;
|
|
65521
|
+
}
|
|
65232
65522
|
if (typeof entry.timeout === "number") def.timeout = entry.timeout;
|
|
65233
65523
|
if (typeof entry.name === "string") def.name = entry.name;
|
|
65234
65524
|
if (typeof entry.description === "string") def.description = entry.description;
|
|
@@ -65259,6 +65549,26 @@ function vibeHooksToCanonical(parsed) {
|
|
|
65259
65549
|
}
|
|
65260
65550
|
return canonical;
|
|
65261
65551
|
}
|
|
65552
|
+
/** Vibe reads a `match` with a `re:` prefix or glob metacharacters as a pattern. */
|
|
65553
|
+
const VIBE_PATTERN_MATCH = /^re:|[*?[]/;
|
|
65554
|
+
/**
|
|
65555
|
+
* Move the imported `subagentStop` hooks into the `vibe` override when any of
|
|
65556
|
+
* them matches agents by a regex or glob rather than by a literal name. Such a
|
|
65557
|
+
* pattern can also select the main agent (a main session answers to its mode
|
|
65558
|
+
* name, e.g. `accept-edits`), and other tools would read it as their own
|
|
65559
|
+
* matcher syntax, so it is kept Vibe-only. The override replaces the shared
|
|
65560
|
+
* list for its event, so the whole list moves to keep every entry on Vibe.
|
|
65561
|
+
*/
|
|
65562
|
+
function takeVibeOnlySubagentStop({ hooks, logger }) {
|
|
65563
|
+
const defs = lookupOwn({
|
|
65564
|
+
record: hooks,
|
|
65565
|
+
key: "subagentStop"
|
|
65566
|
+
});
|
|
65567
|
+
if (defs === void 0 || !defs.some((def) => typeof def.matcher === "string" && VIBE_PATTERN_MATCH.test(def.matcher))) return;
|
|
65568
|
+
delete hooks.subagentStop;
|
|
65569
|
+
logger?.warn("Vibe hooks: a post_agent hook matches agents by a pattern, so its subagentStop hooks are imported into the vibe override instead of the shared hooks.");
|
|
65570
|
+
return defs;
|
|
65571
|
+
}
|
|
65262
65572
|
function parseVibeToml(fileContent) {
|
|
65263
65573
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
65264
65574
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -65298,12 +65608,13 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65298
65608
|
validate
|
|
65299
65609
|
});
|
|
65300
65610
|
}
|
|
65301
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
65611
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
65302
65612
|
const paths = this.getSettablePaths({ global });
|
|
65303
65613
|
const config = rulesyncHooks.getJson();
|
|
65304
65614
|
const vibeHooks = canonicalToVibeHooks({
|
|
65305
65615
|
config,
|
|
65306
|
-
toolOverride: config.vibe?.hooks
|
|
65616
|
+
toolOverride: config.vibe?.hooks,
|
|
65617
|
+
logger
|
|
65307
65618
|
});
|
|
65308
65619
|
const fileContent = smol_toml.stringify(vibeHooks);
|
|
65309
65620
|
return new this({
|
|
@@ -65314,15 +65625,30 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65314
65625
|
validate
|
|
65315
65626
|
});
|
|
65316
65627
|
}
|
|
65317
|
-
toRulesyncHooks() {
|
|
65628
|
+
toRulesyncHooks(options = {}) {
|
|
65318
65629
|
const hooks = vibeHooksToCanonical(this.parseFileContentOrThrow({
|
|
65319
65630
|
displayName: "Vibe",
|
|
65320
65631
|
parse: (fileContent) => parseVibeToml(fileContent)
|
|
65321
65632
|
}));
|
|
65322
|
-
|
|
65633
|
+
const vibeOnlySubagentStop = takeVibeOnlySubagentStop({
|
|
65634
|
+
hooks,
|
|
65635
|
+
logger: options.logger
|
|
65636
|
+
});
|
|
65637
|
+
const config = buildImportedHooksConfig({
|
|
65323
65638
|
hooks,
|
|
65324
65639
|
overrideKey: "vibe"
|
|
65325
|
-
})
|
|
65640
|
+
});
|
|
65641
|
+
if (vibeOnlySubagentStop !== void 0) {
|
|
65642
|
+
const override = config.vibe ?? {};
|
|
65643
|
+
config.vibe = {
|
|
65644
|
+
...override,
|
|
65645
|
+
hooks: {
|
|
65646
|
+
...override.hooks,
|
|
65647
|
+
subagentStop: vibeOnlySubagentStop
|
|
65648
|
+
}
|
|
65649
|
+
};
|
|
65650
|
+
}
|
|
65651
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
65326
65652
|
}
|
|
65327
65653
|
validate() {
|
|
65328
65654
|
try {
|
|
@@ -76939,6 +77265,12 @@ const REASONIX_PLUGIN_FIELDS = [
|
|
|
76939
77265
|
"load",
|
|
76940
77266
|
"oauth_allow_missing_pkce_metadata"
|
|
76941
77267
|
];
|
|
77268
|
+
/**
|
|
77269
|
+
* The `load` mode that puts a server's tools in the provider schema. Reasonix
|
|
77270
|
+
* compares it case-insensitively after trimming (`normalizeMCPLoad`).
|
|
77271
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.53.0/internal/contract/config/mcp_load.go
|
|
77272
|
+
*/
|
|
77273
|
+
const REASONIX_MCP_LOAD_ALWAYS = "always";
|
|
76942
77274
|
/** The `[[plugins]]` key that canonical `disabledTools` is written to and read from. */
|
|
76943
77275
|
const REASONIX_DISABLED_TOOLS_KEY = "disabled_tools";
|
|
76944
77276
|
var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
@@ -77103,7 +77435,9 @@ function hasExpectedType(value, expected) {
|
|
|
77103
77435
|
* `concurrency` string decodes fine but is ignored in favor of the server-name
|
|
77104
77436
|
* default, so it is dropped too rather than written as a setting that does
|
|
77105
77437
|
* nothing. An unknown `load` string is read as `deferred`, the default, so it
|
|
77106
|
-
* passes through as authored.
|
|
77438
|
+
* passes through as authored. A project `load = "always"` also passes through,
|
|
77439
|
+
* but is warned about in `writePassthroughFields`: see the v2.53.0 note on
|
|
77440
|
+
* `REASONIX_PLUGIN_FIELDS`.
|
|
77107
77441
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.31.0/internal/contract/config/plugin_entry.go
|
|
77108
77442
|
*/
|
|
77109
77443
|
function invalidPassthroughFieldReason(field, value) {
|
|
@@ -77143,6 +77477,7 @@ function writePassthroughFields({ name, server, plugin, global, logger }) {
|
|
|
77143
77477
|
continue;
|
|
77144
77478
|
}
|
|
77145
77479
|
if (field === "oauth_allow_missing_pkce_metadata" && value === true) logger?.warn(`Reasonix MCP: "${name}" sets "oauth_allow_missing_pkce_metadata", which lets OAuth proceed with an authorization server that does not advertise PKCE support.` + (global ? "" : ` Reasonix honours it only from the user config, not a project reasonix.toml.`));
|
|
77480
|
+
if (field === "load" && !global && server.disabled !== true && typeof value === "string" && value.trim().toLowerCase() === REASONIX_MCP_LOAD_ALWAYS) logger?.warn(`Reasonix MCP: "${name}" sets "load" to "always" in the project reasonix.toml. Reasonix 2.53.0 and later ignore a project-declared load mode and defer the server; set it in the user config (the server's "load" or [tools.mcp_load]) instead.`);
|
|
77146
77481
|
plugin[field] = value;
|
|
77147
77482
|
}
|
|
77148
77483
|
}
|
|
@@ -86081,6 +86416,41 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
86081
86416
|
// src/features/permissions/codebuddy-permissions.ts
|
|
86082
86417
|
/** Top-level key of `.codebuddy/settings.json` that rulesync owns. */
|
|
86083
86418
|
const CODEBUDDY_PERMISSIONS_KEY = "permissions";
|
|
86419
|
+
/**
|
|
86420
|
+
* The top-level settings groups the `codebuddy` override may author, each with
|
|
86421
|
+
* the only keys of that group rulesync writes and imports. A group's other
|
|
86422
|
+
* keys are kept as the file has them; an override key not listed here is
|
|
86423
|
+
* skipped with a warning.
|
|
86424
|
+
*
|
|
86425
|
+
* - `autoReview`: the `auto` permission mode's review policy (`policy`
|
|
86426
|
+
* replaces the built-in security policy, `extraPolicy` is appended to it).
|
|
86427
|
+
* - `autoMode`: the legacy natural-language `auto` rules, still read as
|
|
86428
|
+
* additional policy.
|
|
86429
|
+
* - `memory.parentDiscovery`: whether `CODEBUDDY.md` / `AGENTS.md` are also
|
|
86430
|
+
* discovered in parent directories of the launch directory.
|
|
86431
|
+
*
|
|
86432
|
+
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
86433
|
+
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
86434
|
+
*/
|
|
86435
|
+
const CODEBUDDY_OVERRIDE_GROUP_KEYS = {
|
|
86436
|
+
autoReview: ["policy", "extraPolicy"],
|
|
86437
|
+
autoMode: [
|
|
86438
|
+
"environment",
|
|
86439
|
+
"allow",
|
|
86440
|
+
"soft_deny",
|
|
86441
|
+
"hard_deny"
|
|
86442
|
+
],
|
|
86443
|
+
memory: ["parentDiscovery"]
|
|
86444
|
+
};
|
|
86445
|
+
const CODEBUDDY_OVERRIDE_GROUPS = Object.keys(CODEBUDDY_OVERRIDE_GROUP_KEYS);
|
|
86446
|
+
/**
|
|
86447
|
+
* Groups CodeBuddy reads from the user and project-local settings only: the
|
|
86448
|
+
* shared project `.codebuddy/settings.json` is not read for them, so a checked-in
|
|
86449
|
+
* repository cannot relax the review of its own actions. They are written and
|
|
86450
|
+
* imported in global mode alone.
|
|
86451
|
+
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
86452
|
+
*/
|
|
86453
|
+
const CODEBUDDY_USER_ONLY_GROUPS = /* @__PURE__ */ new Set(["autoReview", "autoMode"]);
|
|
86084
86454
|
/** The rule lists under `permissions`, one per canonical action. */
|
|
86085
86455
|
const CODEBUDDY_RULE_LISTS = [
|
|
86086
86456
|
"allow",
|
|
@@ -86234,6 +86604,87 @@ function buildCodebuddyRuleLists({ config, existingPermissions, logger }) {
|
|
|
86234
86604
|
return lists;
|
|
86235
86605
|
}
|
|
86236
86606
|
/**
|
|
86607
|
+
* Whether a settings value has the shape the `codebuddy` override schema
|
|
86608
|
+
* accepts for that key, so an off-spec value is left in the file instead of
|
|
86609
|
+
* being written or making the imported permissions file fail validation.
|
|
86610
|
+
*/
|
|
86611
|
+
function isLiftableCodebuddyValue(key, value) {
|
|
86612
|
+
switch (key) {
|
|
86613
|
+
case "policy":
|
|
86614
|
+
case "extraPolicy": return typeof value === "string";
|
|
86615
|
+
case "parentDiscovery": return typeof value === "boolean";
|
|
86616
|
+
default: return isStringArray(value);
|
|
86617
|
+
}
|
|
86618
|
+
}
|
|
86619
|
+
/** Name every written override setting that can make the `auto` review more lenient. */
|
|
86620
|
+
function announceCodebuddyReviewSettings({ picked, group, where, logger }) {
|
|
86621
|
+
if (group === "autoReview" && typeof picked.policy === "string") logger?.warn(`CodeBuddy permissions: codebuddy.autoReview.policy was written to ${where}, so it replaces CodeBuddy's built-in security policy for the 'auto' permission mode.`);
|
|
86622
|
+
if (group === "autoReview" && typeof picked.extraPolicy === "string") logger?.warn(`CodeBuddy permissions: codebuddy.autoReview.extraPolicy was written to ${where}, so it is appended to the security policy CodeBuddy's 'auto' permission mode reviews actions against.`);
|
|
86623
|
+
if (group === "autoMode" && toStringArray$1(picked.allow).length > 0) logger?.warn(`CodeBuddy permissions: codebuddy.autoMode.allow was written to ${where}, so CodeBuddy's 'auto' permission mode leans toward approving the actions it describes.`);
|
|
86624
|
+
}
|
|
86625
|
+
/**
|
|
86626
|
+
* The top-level keys the `codebuddy` override contributes to the settings
|
|
86627
|
+
* patch, besides `permissions`. Each group is the existing group with the
|
|
86628
|
+
* authored keys laid over it; a group is only in the patch when authored, so
|
|
86629
|
+
* a hand-set value survives an unrelated regeneration.
|
|
86630
|
+
*/
|
|
86631
|
+
function buildCodebuddyOverridePatch({ override, existing, global, where, logger }) {
|
|
86632
|
+
const patch = {};
|
|
86633
|
+
if (override === void 0) return patch;
|
|
86634
|
+
for (const group of CODEBUDDY_OVERRIDE_GROUPS) {
|
|
86635
|
+
const authored = override[group];
|
|
86636
|
+
if (!isRecord(authored)) continue;
|
|
86637
|
+
if (CODEBUDDY_USER_ONLY_GROUPS.has(group) && !global) {
|
|
86638
|
+
logger?.warn(`CodeBuddy permissions: skipping codebuddy.${group} in project mode — CodeBuddy does not read ${group} from the shared project .codebuddy/settings.json. Run 'rulesync generate --global' to write it to ~/.codebuddy/settings.json.`);
|
|
86639
|
+
continue;
|
|
86640
|
+
}
|
|
86641
|
+
const allowedKeys = CODEBUDDY_OVERRIDE_GROUP_KEYS[group];
|
|
86642
|
+
const picked = {};
|
|
86643
|
+
for (const [key, value] of Object.entries(authored)) {
|
|
86644
|
+
if (isPrototypePollutionKey(key) || value === void 0) continue;
|
|
86645
|
+
if (!allowedKeys.includes(key)) {
|
|
86646
|
+
logger?.warn(`CodeBuddy permissions: skipping codebuddy.${group}.${key} — Rulesync writes only ${allowedKeys.map((k) => `${group}.${k}`).join(", ")} from the codebuddy override; set other keys in ${where} by hand.`);
|
|
86647
|
+
continue;
|
|
86648
|
+
}
|
|
86649
|
+
picked[key] = value;
|
|
86650
|
+
}
|
|
86651
|
+
if (Object.keys(picked).length === 0) continue;
|
|
86652
|
+
const existingValue = existing[group];
|
|
86653
|
+
if (existingValue !== void 0 && !isRecord(existingValue)) {
|
|
86654
|
+
logger?.warn(`CodeBuddy permissions: skipping codebuddy.${group} — ${group} in ${where} is not an object, so Rulesync leaves it as it is. Fix it by hand to let Rulesync write ${group}.`);
|
|
86655
|
+
continue;
|
|
86656
|
+
}
|
|
86657
|
+
patch[group] = {
|
|
86658
|
+
...existingValue,
|
|
86659
|
+
...picked
|
|
86660
|
+
};
|
|
86661
|
+
announceCodebuddyReviewSettings({
|
|
86662
|
+
picked,
|
|
86663
|
+
group,
|
|
86664
|
+
where,
|
|
86665
|
+
logger
|
|
86666
|
+
});
|
|
86667
|
+
}
|
|
86668
|
+
return patch;
|
|
86669
|
+
}
|
|
86670
|
+
/**
|
|
86671
|
+
* Lift the settings the `codebuddy` override authors back out of a settings
|
|
86672
|
+
* file. `autoReview` and `autoMode` are lifted only from the user file, the
|
|
86673
|
+
* one of the two rulesync manages that CodeBuddy reads them from.
|
|
86674
|
+
*/
|
|
86675
|
+
function liftCodebuddyOverride({ settings, global }) {
|
|
86676
|
+
const lifted = {};
|
|
86677
|
+
for (const group of CODEBUDDY_OVERRIDE_GROUPS) {
|
|
86678
|
+
if (CODEBUDDY_USER_ONLY_GROUPS.has(group) && !global) continue;
|
|
86679
|
+
const existingGroup = settings[group];
|
|
86680
|
+
if (!isRecord(existingGroup)) continue;
|
|
86681
|
+
const picked = {};
|
|
86682
|
+
for (const key of CODEBUDDY_OVERRIDE_GROUP_KEYS[group]) if (Object.hasOwn(existingGroup, key) && isLiftableCodebuddyValue(key, existingGroup[key])) picked[key] = existingGroup[key];
|
|
86683
|
+
if (Object.keys(picked).length > 0) lifted[group] = picked;
|
|
86684
|
+
}
|
|
86685
|
+
return lifted;
|
|
86686
|
+
}
|
|
86687
|
+
/**
|
|
86237
86688
|
* CodeBuddy Code permissions.
|
|
86238
86689
|
*
|
|
86239
86690
|
* Rules live under the `permissions` key of `.codebuddy/settings.json`
|
|
@@ -86244,7 +86695,10 @@ function buildCodebuddyRuleLists({ config, existingPermissions, logger }) {
|
|
|
86244
86695
|
* writes go through the shared-config gateway, the sibling keys of the rule
|
|
86245
86696
|
* lists (`defaultMode`, `additionalDirectories`, `trustedDirectories`, ...)
|
|
86246
86697
|
* are kept, and the file is never deleted. The gitignored
|
|
86247
|
-
* `.codebuddy/settings.local.json` layer is left to CodeBuddy.
|
|
86698
|
+
* `.codebuddy/settings.local.json` layer is left to CodeBuddy. The
|
|
86699
|
+
* `codebuddy` override's other settings (`autoReview`, `autoMode`,
|
|
86700
|
+
* `memory.parentDiscovery`) go through `buildCodebuddyOverridePatch` /
|
|
86701
|
+
* `liftCodebuddyOverride`.
|
|
86248
86702
|
*
|
|
86249
86703
|
* @see https://www.codebuddy.ai/docs/cli/permissions
|
|
86250
86704
|
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
@@ -86283,8 +86737,9 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86283
86737
|
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
86284
86738
|
});
|
|
86285
86739
|
const existingPermissions = isRecord(existing[CODEBUDDY_PERMISSIONS_KEY]) ? existing[CODEBUDDY_PERMISSIONS_KEY] : {};
|
|
86740
|
+
const config = rulesyncPermissions.getJson();
|
|
86286
86741
|
const lists = buildCodebuddyRuleLists({
|
|
86287
|
-
config
|
|
86742
|
+
config,
|
|
86288
86743
|
existingPermissions,
|
|
86289
86744
|
logger
|
|
86290
86745
|
});
|
|
@@ -86299,7 +86754,16 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86299
86754
|
fileKey: sharedConfigFileKey(paths),
|
|
86300
86755
|
feature: "permissions",
|
|
86301
86756
|
existingContent,
|
|
86302
|
-
patch: {
|
|
86757
|
+
patch: {
|
|
86758
|
+
[CODEBUDDY_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
|
|
86759
|
+
...buildCodebuddyOverridePatch({
|
|
86760
|
+
override: config.codebuddy,
|
|
86761
|
+
existing,
|
|
86762
|
+
global,
|
|
86763
|
+
where: `${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)}${global ? " in the home directory" : ""}`,
|
|
86764
|
+
logger
|
|
86765
|
+
})
|
|
86766
|
+
},
|
|
86303
86767
|
filePath
|
|
86304
86768
|
}),
|
|
86305
86769
|
validate: true,
|
|
@@ -86320,7 +86784,14 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86320
86784
|
const existing = rules[parsed.pattern];
|
|
86321
86785
|
if (existing === void 0 || PERMISSION_ACTION_PRIORITY[action] > PERMISSION_ACTION_PRIORITY[existing]) rules[parsed.pattern] = action;
|
|
86322
86786
|
}
|
|
86323
|
-
|
|
86787
|
+
const codebuddy = liftCodebuddyOverride({
|
|
86788
|
+
settings,
|
|
86789
|
+
global: this.global
|
|
86790
|
+
});
|
|
86791
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
86792
|
+
permission,
|
|
86793
|
+
...Object.keys(codebuddy).length > 0 && { codebuddy }
|
|
86794
|
+
}, null, 2) });
|
|
86324
86795
|
}
|
|
86325
86796
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
86326
86797
|
return new CodebuddyPermissions({
|
|
@@ -89286,7 +89757,7 @@ const AUTO_APPROVE_KEYS = {
|
|
|
89286
89757
|
*
|
|
89287
89758
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
89288
89759
|
*/
|
|
89289
|
-
const DEFAULT_PERMISSION_MODE_KEY = "chat.permissions.default";
|
|
89760
|
+
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
89290
89761
|
/**
|
|
89291
89762
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
89292
89763
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
@@ -89574,7 +90045,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
89574
90045
|
});
|
|
89575
90046
|
}
|
|
89576
90047
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
89577
|
-
if (defaultPermissionMode !== void 0) patch[DEFAULT_PERMISSION_MODE_KEY] = defaultPermissionMode;
|
|
90048
|
+
if (defaultPermissionMode !== void 0) patch[DEFAULT_PERMISSION_MODE_KEY$1] = defaultPermissionMode;
|
|
89578
90049
|
const sandbox = config.copilot?.sandbox;
|
|
89579
90050
|
if (sandbox !== void 0) {
|
|
89580
90051
|
Object.assign(patch, buildSandboxPatch({
|
|
@@ -89630,7 +90101,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
89630
90101
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
89631
90102
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
89632
90103
|
}
|
|
89633
|
-
const defaultPermissionMode = settings[DEFAULT_PERMISSION_MODE_KEY];
|
|
90104
|
+
const defaultPermissionMode = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
89634
90105
|
const sandbox = readSandboxFromSettings(settings);
|
|
89635
90106
|
const copilot = {
|
|
89636
90107
|
...isCopilotDefaultPermissionMode(defaultPermissionMode) && { defaultPermissionMode },
|
|
@@ -89663,6 +90134,16 @@ const WEBFETCH_CATEGORY$1 = "webfetch";
|
|
|
89663
90134
|
/** `~/.copilot/settings.json` and `.github/copilot/settings.json` keys. */
|
|
89664
90135
|
const ALLOWED_URLS_KEY = "allowedUrls";
|
|
89665
90136
|
const DENIED_URLS_KEY = "deniedUrls";
|
|
90137
|
+
/**
|
|
90138
|
+
* `~/.copilot/settings.json` only: the startup permission mode for new
|
|
90139
|
+
* interactive sessions, authored through the `copilotcli.defaultPermissionMode`
|
|
90140
|
+
* override. It is a user setting — the repository settings file does not list
|
|
90141
|
+
* it among its repo-overridable keys.
|
|
90142
|
+
*/
|
|
90143
|
+
const DEFAULT_PERMISSION_MODE_KEY = "defaultPermissionMode";
|
|
90144
|
+
function isCopilotcliDefaultPermissionMode(value) {
|
|
90145
|
+
return COPILOTCLI_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90146
|
+
}
|
|
89666
90147
|
function toUrlList(value) {
|
|
89667
90148
|
if (!Array.isArray(value)) return [];
|
|
89668
90149
|
return value.filter((entry) => typeof entry === "string");
|
|
@@ -89704,6 +90185,13 @@ function splitUrlRules(rules) {
|
|
|
89704
90185
|
* top-level keys in the user `settings.json`, so only documented keys are ever
|
|
89705
90186
|
* emitted there either.)
|
|
89706
90187
|
*
|
|
90188
|
+
* The `copilotcli.defaultPermissionMode` override (`manual` | `assisted` |
|
|
90189
|
+
* `allow-all`) writes the user-scope `defaultPermissionMode` key in global mode
|
|
90190
|
+
* only; in project mode it is skipped with a warning, since the repository
|
|
90191
|
+
* settings file does not accept it. It is patched only when authored, so a mode
|
|
90192
|
+
* the user picked through `/settings` otherwise survives. Writing `allow-all`
|
|
90193
|
+
* warns, because it makes new sessions skip every approval prompt.
|
|
90194
|
+
*
|
|
89707
90195
|
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
89708
90196
|
* @see https://github.com/github/copilot-cli/blob/main/changelog.md
|
|
89709
90197
|
*/
|
|
@@ -89746,7 +90234,8 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89746
90234
|
const paths = CopilotcliPermissions.getSettablePaths({ global });
|
|
89747
90235
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
89748
90236
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
89749
|
-
const
|
|
90237
|
+
const config = rulesyncPermissions.getJson();
|
|
90238
|
+
const rules = config.permission[WEBFETCH_CATEGORY$1];
|
|
89750
90239
|
const patch = {};
|
|
89751
90240
|
if (rules !== void 0) {
|
|
89752
90241
|
const { allowedUrls, deniedUrls } = splitUrlRules(rules);
|
|
@@ -89754,6 +90243,13 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89754
90243
|
if (global) patch[ALLOWED_URLS_KEY] = allowedUrls.length > 0 ? allowedUrls : void 0;
|
|
89755
90244
|
else if (allowedUrls.length > 0) logger?.warn(`Copilot CLI permissions: dropping ${allowedUrls.length} "webfetch" allow rule(s) at project scope — ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)} does not accept "${ALLOWED_URLS_KEY}" (repository settings may only add denials). Author allow rules in global scope (\`--global\`) instead.`);
|
|
89756
90245
|
}
|
|
90246
|
+
const defaultPermissionMode = config.copilotcli?.defaultPermissionMode;
|
|
90247
|
+
if (defaultPermissionMode !== void 0) {
|
|
90248
|
+
if (global) {
|
|
90249
|
+
patch[DEFAULT_PERMISSION_MODE_KEY] = defaultPermissionMode;
|
|
90250
|
+
if (defaultPermissionMode === "allow-all") logger?.warn(`Copilot CLI permissions: writing "${DEFAULT_PERMISSION_MODE_KEY}": "allow-all" to ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)} — new interactive sessions will start with every tool, path and URL approved without prompting (equivalent to \`--allow-all\`). Deny rules and enterprise policy still apply.`);
|
|
90251
|
+
} else logger?.warn(`Copilot CLI permissions: skipping "copilotcli.${DEFAULT_PERMISSION_MODE_KEY}" at project scope — ${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)} does not accept "${DEFAULT_PERMISSION_MODE_KEY}" (it is a user setting). Author it in global scope (\`--global\`) instead.`);
|
|
90252
|
+
}
|
|
89757
90253
|
return new CopilotcliPermissions({
|
|
89758
90254
|
outputRoot,
|
|
89759
90255
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -89775,7 +90271,11 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89775
90271
|
for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
|
|
89776
90272
|
if (this.global) for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) rules[pattern] ??= "allow";
|
|
89777
90273
|
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY$1]: rules } : {};
|
|
89778
|
-
|
|
90274
|
+
const defaultPermissionMode = this.global ? settings[DEFAULT_PERMISSION_MODE_KEY] : void 0;
|
|
90275
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
90276
|
+
permission,
|
|
90277
|
+
...isCopilotcliDefaultPermissionMode(defaultPermissionMode) && { copilotcli: { defaultPermissionMode } }
|
|
90278
|
+
}, null, 2) });
|
|
89779
90279
|
}
|
|
89780
90280
|
parseSettings() {
|
|
89781
90281
|
const relativePath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
@@ -95280,6 +95780,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
95280
95780
|
// src/features/permissions/omp-permissions.ts
|
|
95281
95781
|
const CATCH_ALL_PATTERN$5 = "*";
|
|
95282
95782
|
const APPROVAL_MODE_KEY = "approvalMode";
|
|
95783
|
+
const RESTRICTED_FOLDERS_KEY = "restrictedFolders";
|
|
95283
95784
|
const ACTION_TO_OMP_APPROVAL = {
|
|
95284
95785
|
allow: "allow",
|
|
95285
95786
|
ask: "prompt",
|
|
@@ -95318,9 +95819,9 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
95318
95819
|
*
|
|
95319
95820
|
* - `tools.approval.<tool>: allow | prompt | deny` is a per-tool policy honored
|
|
95320
95821
|
* in every approval mode. A category's catch-all `*` rule maps here
|
|
95321
|
-
* (`ask` → `prompt`).
|
|
95322
|
-
* with pattern-specific rules collapses to one action (deny > ask >
|
|
95323
|
-
* an implicit `ask` when there is no catch-all) and a warning.
|
|
95822
|
+
* (`ask` → `prompt`). `tools.approval` has no per-path matcher, so a non-bash
|
|
95823
|
+
* category with pattern-specific rules collapses to one action (deny > ask >
|
|
95824
|
+
* allow, with an implicit `ask` when there is no catch-all) and a warning.
|
|
95324
95825
|
* - `bash.patterns: [{ match, approval }]` is an ordered, first-match-wins list
|
|
95325
95826
|
* of command globs (only `*` is a wildcard). Every `bash` rule except a
|
|
95326
95827
|
* catch-all `allow` is written there, ordered deny → prompt → allow so that
|
|
@@ -95337,12 +95838,15 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
95337
95838
|
*
|
|
95338
95839
|
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
95339
95840
|
* `always-ask-cached`, `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
95340
|
-
* import a recognized value is read back into the override.
|
|
95841
|
+
* import a recognized value is read back into the override. Likewise the
|
|
95842
|
+
* `omp.restrictedFolders` override writes `tools.restrictedFolders` (folders
|
|
95843
|
+
* where every action prompts in every approval mode, `yolo` included), is left
|
|
95844
|
+
* alone when absent, and is read back on import.
|
|
95341
95845
|
*
|
|
95342
95846
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
95343
95847
|
* (the keys rulesync manages), `bash.patterns` and an authored
|
|
95344
|
-
* `tools.approvalMode` are rewritten; sibling
|
|
95345
|
-
* kept, and the file is never deleted.
|
|
95848
|
+
* `tools.approvalMode` or `tools.restrictedFolders` are rewritten; sibling
|
|
95849
|
+
* keys such as `bash.enabled` are kept, and the file is never deleted.
|
|
95346
95850
|
*
|
|
95347
95851
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
95348
95852
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
@@ -95391,6 +95895,7 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
95391
95895
|
logger
|
|
95392
95896
|
});
|
|
95393
95897
|
const approvalMode = config.omp?.approvalMode;
|
|
95898
|
+
const restrictedFolders = config.omp?.restrictedFolders;
|
|
95394
95899
|
const existingTools = isRecord(existing.tools) ? existing.tools : {};
|
|
95395
95900
|
const nextApproval = {
|
|
95396
95901
|
...isRecord(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)])) : {},
|
|
@@ -95398,6 +95903,7 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
95398
95903
|
};
|
|
95399
95904
|
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
95400
95905
|
if (approvalMode !== void 0) toolsSiblings[APPROVAL_MODE_KEY] = approvalMode;
|
|
95906
|
+
if (restrictedFolders !== void 0) toolsSiblings[RESTRICTED_FOLDERS_KEY] = restrictedFolders;
|
|
95401
95907
|
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
95402
95908
|
...toolsSiblings,
|
|
95403
95909
|
approval: nextApproval
|
|
@@ -95648,11 +96154,26 @@ function convertOmpToRulesync(config) {
|
|
|
95648
96154
|
}
|
|
95649
96155
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
95650
96156
|
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
96157
|
+
const restrictedFolders = importRestrictedFolders(tools[RESTRICTED_FOLDERS_KEY]);
|
|
96158
|
+
const omp = {
|
|
96159
|
+
...isOmpApprovalMode(approvalMode) && { approvalMode },
|
|
96160
|
+
...restrictedFolders !== void 0 && { restrictedFolders }
|
|
96161
|
+
};
|
|
95651
96162
|
return {
|
|
95652
96163
|
permission,
|
|
95653
|
-
...
|
|
96164
|
+
...Object.keys(omp).length > 0 && { omp }
|
|
95654
96165
|
};
|
|
95655
96166
|
}
|
|
96167
|
+
/**
|
|
96168
|
+
* `tools.restrictedFolders` as the override list: its string entries, or
|
|
96169
|
+
* `undefined` when it is unset or no entry survives (so a list of non-strings
|
|
96170
|
+
* is not imported as `[]` and then rewritten as an empty list).
|
|
96171
|
+
*/
|
|
96172
|
+
function importRestrictedFolders(value) {
|
|
96173
|
+
if (!Array.isArray(value)) return;
|
|
96174
|
+
const folders = value.filter((entry) => typeof entry === "string");
|
|
96175
|
+
return folders.length === 0 && value.length > 0 ? void 0 : folders;
|
|
96176
|
+
}
|
|
95656
96177
|
function isOmpApprovalMode(value) {
|
|
95657
96178
|
return OMP_APPROVAL_MODES.some((mode) => mode === value);
|
|
95658
96179
|
}
|
|
@@ -100749,19 +101270,31 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
100749
101270
|
};
|
|
100750
101271
|
/**
|
|
100751
101272
|
* The tables earlier rulesync versions wrote for canonical categories that
|
|
100752
|
-
* Vibe 3.x
|
|
100753
|
-
* effect")
|
|
100754
|
-
* `agents
|
|
101273
|
+
* Vibe 3.x does not read: `[tools.grep]` (Vibe warns that it "has no
|
|
101274
|
+
* effect"), `[tools.task]` for the subagent tool v3.0.0 renamed to
|
|
101275
|
+
* `agents.*`, and `[tools.external_directory]` — Vibe has no such tool, and
|
|
101276
|
+
* its unused-settings check skips unknown tool names as custom/MCP tools, so
|
|
101277
|
+
* that deny was silently inert. A regenerate that configures the category removes them — and
|
|
100755
101278
|
* the matching `disabled_tools` entry — instead of leaving an inert table.
|
|
100756
101279
|
* Import still reads them (see {@link VIBE_TO_CANONICAL_TOOL_NAMES}), so a
|
|
100757
101280
|
* Vibe 2.x config is lifted into the canonical categories rather than lost.
|
|
100758
101281
|
*/
|
|
100759
101282
|
const VIBE_RETIRED_TOOL_TABLES = {
|
|
100760
101283
|
grep: "grep",
|
|
100761
|
-
agent: "task"
|
|
101284
|
+
agent: "task",
|
|
101285
|
+
external_directory: "external_directory"
|
|
100762
101286
|
};
|
|
100763
101287
|
/** The canonical category Vibe's `enable_subagents` switch expresses. */
|
|
100764
101288
|
const VIBE_SUBAGENT_CATEGORY = "agent";
|
|
101289
|
+
/** The canonical category Vibe's top-level `outside_workdir` setting expresses. */
|
|
101290
|
+
const VIBE_OUTSIDE_WORKDIR_CATEGORY = "external_directory";
|
|
101291
|
+
/**
|
|
101292
|
+
* The values of Vibe's `outside_workdir` (v3.0.3+). There is no allow value:
|
|
101293
|
+
* access outside the working directory is granted per path through a tool's
|
|
101294
|
+
* `allowlist` (`vibe-path:...`) or `--add-dir`.
|
|
101295
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
101296
|
+
*/
|
|
101297
|
+
const VIBE_OUTSIDE_WORKDIR_VALUES = /* @__PURE__ */ new Set(["ask", "deny"]);
|
|
100765
101298
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
100766
101299
|
bash: "bash",
|
|
100767
101300
|
read_file: "read",
|
|
@@ -100837,7 +101370,8 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
100837
101370
|
"grep",
|
|
100838
101371
|
"glob",
|
|
100839
101372
|
"notebookedit",
|
|
100840
|
-
"agent"
|
|
101373
|
+
"agent",
|
|
101374
|
+
"external_directory"
|
|
100841
101375
|
]);
|
|
100842
101376
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
100843
101377
|
toml;
|
|
@@ -100918,7 +101452,11 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
100918
101452
|
rules: permission[VIBE_SUBAGENT_CATEGORY],
|
|
100919
101453
|
logger
|
|
100920
101454
|
});
|
|
100921
|
-
const
|
|
101455
|
+
const outsideWorkdirPatch = resolveOutsideWorkdirPatch({
|
|
101456
|
+
rules: permission[VIBE_OUTSIDE_WORKDIR_CATEGORY],
|
|
101457
|
+
logger
|
|
101458
|
+
});
|
|
101459
|
+
const tableCategories = Object.entries(permission).filter(([category]) => category !== VIBE_SUBAGENT_CATEGORY && category !== VIBE_OUTSIDE_WORKDIR_CATEGORY);
|
|
100922
101460
|
for (const [category, rules] of tableCategories) {
|
|
100923
101461
|
const vibeToolNames = resolveNames(category);
|
|
100924
101462
|
if (vibeToolNames === void 0) {
|
|
@@ -100992,7 +101530,8 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
100992
101530
|
enabledTools
|
|
100993
101531
|
}),
|
|
100994
101532
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0,
|
|
100995
|
-
...enableSubagentsPatch
|
|
101533
|
+
...enableSubagentsPatch,
|
|
101534
|
+
...outsideWorkdirPatch
|
|
100996
101535
|
},
|
|
100997
101536
|
filePath
|
|
100998
101537
|
}),
|
|
@@ -101036,6 +101575,8 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
101036
101575
|
}
|
|
101037
101576
|
for (const category of silencedCategories) ensurePermission(permission, category)["*"] = "deny";
|
|
101038
101577
|
if (this.toml.enable_subagents === false) ensurePermission(permission, VIBE_SUBAGENT_CATEGORY)["*"] = "deny";
|
|
101578
|
+
const outsideWorkdir = this.toml.outside_workdir;
|
|
101579
|
+
if (typeof outsideWorkdir === "string" && VIBE_OUTSIDE_WORKDIR_VALUES.has(outsideWorkdir)) ensurePermission(permission, VIBE_OUTSIDE_WORKDIR_CATEGORY)["*"] = outsideWorkdir === "deny" ? "deny" : "ask";
|
|
101039
101580
|
warnOnUnresolvableDisabledTools({
|
|
101040
101581
|
entries: disabledToolEntries,
|
|
101041
101582
|
tools: this.toml.tools
|
|
@@ -101112,7 +101653,7 @@ function retireLegacyToolTables({ permission, tools, disabledTools, logger }) {
|
|
|
101112
101653
|
}
|
|
101113
101654
|
if (disabledTools.delete(legacyToolName)) removed.push(`disabled_tools entry '${legacyToolName}'`);
|
|
101114
101655
|
}
|
|
101115
|
-
if (removed.length > 0) logger?.warn(`Removed ${removed.join(", ")} from the Vibe config: Vibe 3.x
|
|
101656
|
+
if (removed.length > 0) logger?.warn(`Removed ${removed.join(", ")} from the Vibe config: Vibe 3.x does not read [tools.grep], the former 'task' subagent tool or an 'external_directory' tool (the 'external_directory' category is written to outside_workdir instead), so those entries had no effect.`);
|
|
101116
101657
|
}
|
|
101117
101658
|
/**
|
|
101118
101659
|
* Report the rules of a category that has no Vibe tool table. `grep` gets its
|
|
@@ -101148,6 +101689,30 @@ function resolveEnableSubagentsPatch({ rules, logger }) {
|
|
|
101148
101689
|
return { enable_subagents: action === "deny" ? false : void 0 };
|
|
101149
101690
|
}
|
|
101150
101691
|
/**
|
|
101692
|
+
* Translate the canonical `external_directory` category into Vibe's top-level
|
|
101693
|
+
* `outside_workdir` setting (v3.0.3+), which decides whether a file or shell
|
|
101694
|
+
* tool call reaching outside the working directory prompts (`"ask"`, the
|
|
101695
|
+
* default) or is refused with a tool error (`"deny"`). A `*` deny or ask writes
|
|
101696
|
+
* that value; a `*` allow has no Vibe value, so it is warned about and the key
|
|
101697
|
+
* is removed, leaving Vibe's default prompt. Without a `*` rule the key is left
|
|
101698
|
+
* as the file has it. Pattern rules are skipped with a warning: path grants go
|
|
101699
|
+
* in a tool's `allowlist` (`vibe-path:...`) instead.
|
|
101700
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
101701
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/app_server/_unified_permissions.py
|
|
101702
|
+
*/
|
|
101703
|
+
function resolveOutsideWorkdirPatch({ rules, logger }) {
|
|
101704
|
+
if (rules === void 0) return {};
|
|
101705
|
+
const patternCount = Object.keys(rules).filter((pattern) => pattern !== "*").length;
|
|
101706
|
+
if (patternCount > 0) logger?.warn(`Vibe's outside_workdir applies to every path outside the working directory; skipping ${patternCount} pattern rule(s) of the '${VIBE_OUTSIDE_WORKDIR_CATEGORY}' category. Grant a path through a tool's allowlist ("vibe-path:...") in the Vibe config instead.`);
|
|
101707
|
+
const action = rules["*"];
|
|
101708
|
+
if (action === void 0) return {};
|
|
101709
|
+
if (action === "allow") {
|
|
101710
|
+
logger?.warn(`Vibe's outside_workdir only takes "ask" or "deny"; the '${VIBE_OUTSIDE_WORKDIR_CATEGORY}' category's "*": "allow" leaves Vibe's default, which prompts before reaching outside the working directory.`);
|
|
101711
|
+
return { outside_workdir: void 0 };
|
|
101712
|
+
}
|
|
101713
|
+
return { outside_workdir: action };
|
|
101714
|
+
}
|
|
101715
|
+
/**
|
|
101151
101716
|
* Apply the Vibe-scoped override's per-tool `sensitive_patterns` (patterns that
|
|
101152
101717
|
* escalate to ASK even when the base permission is ALWAYS). rulesync owns this
|
|
101153
101718
|
* list for any category the override names: a present list is set, an empty
|