rulesync 43.1.1 → 43.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/index.cjs +4 -4
- package/dist/cli/index.js +4 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BiQjbRR6.cjs → import-DAT3ge4z.cjs} +680 -113
- package/dist/{import-COu_2AUF.js → import-DPXjeK8q.js} +681 -114
- package/dist/{import-COu_2AUF.js.map → import-DPXjeK8q.js.map} +1 -1
- package/dist/index.cjs +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()),
|
|
@@ -13730,7 +13811,11 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13730
13811
|
},
|
|
13731
13812
|
permissions: {
|
|
13732
13813
|
kind: "replace-owned-keys",
|
|
13733
|
-
ownedKeys: [
|
|
13814
|
+
ownedKeys: [
|
|
13815
|
+
"allowedUrls",
|
|
13816
|
+
"deniedUrls",
|
|
13817
|
+
"defaultPermissionMode"
|
|
13818
|
+
]
|
|
13734
13819
|
}
|
|
13735
13820
|
}
|
|
13736
13821
|
},
|
|
@@ -13909,7 +13994,12 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
13909
13994
|
},
|
|
13910
13995
|
permissions: {
|
|
13911
13996
|
kind: "replace-owned-keys",
|
|
13912
|
-
ownedKeys: [
|
|
13997
|
+
ownedKeys: [
|
|
13998
|
+
"permissions",
|
|
13999
|
+
"autoReview",
|
|
14000
|
+
"autoMode",
|
|
14001
|
+
"memory"
|
|
14002
|
+
]
|
|
13913
14003
|
}
|
|
13914
14004
|
}
|
|
13915
14005
|
},
|
|
@@ -14219,7 +14309,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
14219
14309
|
"tools",
|
|
14220
14310
|
"enabled_tools",
|
|
14221
14311
|
"disabled_tools",
|
|
14222
|
-
"enable_subagents"
|
|
14312
|
+
"enable_subagents",
|
|
14313
|
+
"outside_workdir"
|
|
14223
14314
|
]
|
|
14224
14315
|
}
|
|
14225
14316
|
}
|
|
@@ -15889,17 +15980,24 @@ function toolSkillSearchRoots(paths) {
|
|
|
15889
15980
|
return [paths.relativeDirPath, ...paths.alternativeSkillRoots ?? []];
|
|
15890
15981
|
}
|
|
15891
15982
|
/**
|
|
15892
|
-
*
|
|
15893
|
-
*
|
|
15894
|
-
*
|
|
15895
|
-
*
|
|
15896
|
-
*
|
|
15897
|
-
*
|
|
15898
|
-
*
|
|
15899
|
-
*
|
|
15900
|
-
*
|
|
15983
|
+
* Skill roots that every tool scanning them imports leniently (skip-and-warn
|
|
15984
|
+
* per skill) instead of failing fast, regardless of the tool's own
|
|
15985
|
+
* `lenientImport` flag.
|
|
15986
|
+
*
|
|
15987
|
+
* - The Agent Skills interoperability roots: project `.agents/skills/`, global
|
|
15988
|
+
* `~/.agents/skills/` (same relative path, home output root), and Amp's
|
|
15989
|
+
* global equivalent `~/.config/agents/skills/`. Foreign-authored —
|
|
15990
|
+
* potentially non-conformant — skills are the most likely to live here, so
|
|
15991
|
+
* they are imported leniently per the Agent Skills client-implementation
|
|
15992
|
+
* guide, while each tool keeps fail-fast behavior for its own native root.
|
|
15993
|
+
* https://agentskills.io/client-implementation/adding-skills-support
|
|
15994
|
+
* - Amp's read-only global discovery root `~/.config/amp/skills/`, which is not
|
|
15995
|
+
* an interop root but follows the same skip-and-warn rule.
|
|
15996
|
+
*
|
|
15997
|
+
* Other import-only roots (e.g. GitLab Duo's legacy skills root, Kimi Code's
|
|
15998
|
+
* shared root) are not listed here and stay fail-fast.
|
|
15901
15999
|
*/
|
|
15902
|
-
const
|
|
16000
|
+
const LENIENT_IMPORT_SKILL_ROOTS = /* @__PURE__ */ new Set([
|
|
15903
16001
|
toPosixPath(AGENTSMD_SKILLS_DIR_PATH),
|
|
15904
16002
|
toPosixPath(AMP_SKILLS_GLOBAL_DIR),
|
|
15905
16003
|
toPosixPath(AMP_SKILLS_NATIVE_GLOBAL_DIR)
|
|
@@ -15924,8 +16022,8 @@ function warnOnEmptyLoadedDescription({ skillFilePath, description }) {
|
|
|
15924
16022
|
if (typeof description !== "string" || description.length > 0) return;
|
|
15925
16023
|
warnOnceWithFallback(void 0, `${stripControlCharacters(toPosixPath(skillFilePath))}: ${EMPTY_SKILL_DESCRIPTION_VIOLATION}. Rulesync imports it anyway so the content is not lost; fill it in before relying on this skill.`);
|
|
15926
16024
|
}
|
|
15927
|
-
function
|
|
15928
|
-
return
|
|
16025
|
+
function isLenientImportSkillRoot(relativeDirPath) {
|
|
16026
|
+
return LENIENT_IMPORT_SKILL_ROOTS.has(toPosixPath(relativeDirPath));
|
|
15929
16027
|
}
|
|
15930
16028
|
/** Ordered import roots: managed roots first, followed by read-only discovery roots. */
|
|
15931
16029
|
function toolSkillImportRoots(paths) {
|
|
@@ -30565,7 +30663,10 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
|
|
|
30565
30663
|
}
|
|
30566
30664
|
}
|
|
30567
30665
|
static getSettablePaths({ global: _global = false } = {}) {
|
|
30568
|
-
return {
|
|
30666
|
+
return {
|
|
30667
|
+
relativeDirPath: QWENCODE_SKILLS_DIR_PATH,
|
|
30668
|
+
importOnlySkillRoots: [AGENTSMD_SKILLS_DIR_PATH]
|
|
30669
|
+
};
|
|
30569
30670
|
}
|
|
30570
30671
|
getFrontmatter() {
|
|
30571
30672
|
return QwencodeSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
|
|
@@ -33155,7 +33256,7 @@ var SkillsProcessor = class extends DirFeatureProcessor {
|
|
|
33155
33256
|
for (const root of roots) {
|
|
33156
33257
|
const rootOutputRoot = typeof root === "string" ? this.outputRoot : root.outputRoot;
|
|
33157
33258
|
const relativeDirPath = typeof root === "string" ? root : root.relativeDirPath;
|
|
33158
|
-
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true ||
|
|
33259
|
+
const isLenientRoot = configuredRootPaths.has(relativeDirPath) || factory.meta.lenientImport === true || isLenientImportSkillRoot(relativeDirPath);
|
|
33159
33260
|
const skillsDirPath = (0, node_path.join)(rootOutputRoot, relativeDirPath);
|
|
33160
33261
|
if (!await directoryExists(skillsDirPath)) continue;
|
|
33161
33262
|
const ownedDirNames = [];
|
|
@@ -39586,6 +39687,16 @@ var VibePluginSubagent = class VibePluginSubagent extends ToolSubagent {
|
|
|
39586
39687
|
*/
|
|
39587
39688
|
const VIBE_PROMPTS_DIR_PATH = (0, node_path.join)(".vibe", "prompts");
|
|
39588
39689
|
/**
|
|
39690
|
+
* The agent name Vibe reserves for `post_agent` hook matching (v3.0.3+).
|
|
39691
|
+
* `AgentProfile.from_toml` refuses a file whose stem matches it case-insensitively, so an
|
|
39692
|
+
* agent written as `main.toml` would never load.
|
|
39693
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/core/agents/models.py
|
|
39694
|
+
*/
|
|
39695
|
+
const VIBE_RESERVED_AGENT_NAME = "main";
|
|
39696
|
+
function isReservedVibeAgentFileName(relativeFilePath) {
|
|
39697
|
+
return (0, node_path.basename)(relativeFilePath).replace(/\.(md|toml)$/, "").toLowerCase() === VIBE_RESERVED_AGENT_NAME;
|
|
39698
|
+
}
|
|
39699
|
+
/**
|
|
39589
39700
|
* Vibe resolves a `system_prompt_id` by file name, and
|
|
39590
39701
|
* `VibeConfigSchema._check_system_prompt` evaluates it during validation — an
|
|
39591
39702
|
* unresolvable id raises and the agent is dropped at discovery. The id must
|
|
@@ -39700,9 +39811,17 @@ var VibeSubagent = class VibeSubagent extends ToolSubagent {
|
|
|
39700
39811
|
* evaluates the property during validation, so an unresolvable
|
|
39701
39812
|
* `system_prompt_id` raises and `AgentRegistry._try_load` drops the agent
|
|
39702
39813
|
* with a warning.
|
|
39814
|
+
*
|
|
39815
|
+
* A subagent named `main` is skipped with a warning: Vibe reserves that name
|
|
39816
|
+
* for hook matching and does not load an agent file called `main.toml`.
|
|
39703
39817
|
*/
|
|
39704
|
-
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false }) {
|
|
39818
|
+
static fromRulesyncSubagents({ rulesyncSubagents, outputRoot = process.cwd(), global = false, logger }) {
|
|
39705
39819
|
return rulesyncSubagents.flatMap((rulesyncSubagent) => {
|
|
39820
|
+
const sourcePath = rulesyncSubagent.getRelativeFilePath();
|
|
39821
|
+
if (isReservedVibeAgentFileName(sourcePath)) {
|
|
39822
|
+
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.`);
|
|
39823
|
+
return [];
|
|
39824
|
+
}
|
|
39706
39825
|
const agentFile = VibeSubagent.fromRulesyncSubagent({
|
|
39707
39826
|
outputRoot,
|
|
39708
39827
|
relativeDirPath: VibeSubagent.getSettablePaths({ global }).relativeDirPath,
|
|
@@ -44989,17 +45108,49 @@ var ClaudecodeRule = class ClaudecodeRule extends ToolRule {
|
|
|
44989
45108
|
* - `alwaysApply: true`: force the rule to always load (equivalent to a file
|
|
44990
45109
|
* with no frontmatter at all).
|
|
44991
45110
|
* - `description`: label surfaced in the rule toggle UI.
|
|
45111
|
+
* - `disabled: true` (or `enabled: false`): the rule is switched off. Since
|
|
45112
|
+
* Cline v4.1.26 (cline/cline#14605) the rule toggle writes this flag into the
|
|
45113
|
+
* rule file itself instead of keeping it only in extension state.
|
|
44992
45114
|
*
|
|
44993
45115
|
* Rules without frontmatter are always active. Uses `z.looseObject()` so
|
|
44994
|
-
* forward-compatible fields added upstream are preserved on round-trip
|
|
45116
|
+
* forward-compatible fields added upstream are preserved on round-trip: every
|
|
45117
|
+
* key other than `paths` / `alwaysApply` / `description` is carried in the
|
|
45118
|
+
* rulesync `cline:` section.
|
|
44995
45119
|
*
|
|
44996
45120
|
* @see https://docs.cline.bot/customization/cline-rules
|
|
45121
|
+
* @see https://github.com/cline/cline/blob/main/sdk/packages/core/src/extensions/config/user-instruction-config-loader.ts
|
|
44997
45122
|
*/
|
|
44998
45123
|
const ClineRuleFrontmatterSchema = zod_mini.z.looseObject({
|
|
44999
45124
|
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.array(zod_mini.z.string()), zod_mini.z.string()])),
|
|
45000
45125
|
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
45001
45126
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
45002
45127
|
});
|
|
45128
|
+
const CLINE_RULE_FIELD_TYPES = {
|
|
45129
|
+
disabled: "boolean",
|
|
45130
|
+
enabled: "boolean"
|
|
45131
|
+
};
|
|
45132
|
+
const CANONICAL_CLINE_RULE_KEYS = [
|
|
45133
|
+
"paths",
|
|
45134
|
+
"alwaysApply",
|
|
45135
|
+
"description"
|
|
45136
|
+
];
|
|
45137
|
+
/** Returns a copy of `record` without the canonical `paths` / `alwaysApply` / `description` keys. */
|
|
45138
|
+
function omitCanonicalClineRuleKeys(record) {
|
|
45139
|
+
return Object.fromEntries(Object.entries(record ?? {}).filter(([key]) => !CANONICAL_CLINE_RULE_KEYS.includes(key)));
|
|
45140
|
+
}
|
|
45141
|
+
/**
|
|
45142
|
+
* Splits a Cline rule frontmatter into the keys that have canonical rulesync
|
|
45143
|
+
* homes (`paths` / `alwaysApply` / `description`) and the rest, which are
|
|
45144
|
+
* lifted into the rulesync `cline:` section.
|
|
45145
|
+
*/
|
|
45146
|
+
function extractClineSection({ frontmatter, relativeFilePath }) {
|
|
45147
|
+
return Object.fromEntries(Object.entries(omitCanonicalClineRuleKeys(frontmatter)).filter(([key, value]) => {
|
|
45148
|
+
const expected = Object.hasOwn(CLINE_RULE_FIELD_TYPES, key) ? CLINE_RULE_FIELD_TYPES[key] : void 0;
|
|
45149
|
+
if (expected === void 0 || value === void 0 || typeof value === expected) return true;
|
|
45150
|
+
warnWithFallback(void 0, `Cline rules: dropping ${key} from ${relativeFilePath}: Cline rejects the whole rule unless it is a ${expected}.`);
|
|
45151
|
+
return false;
|
|
45152
|
+
}));
|
|
45153
|
+
}
|
|
45003
45154
|
function isNonRootParams$1(params) {
|
|
45004
45155
|
return "frontmatter" in params && "body" in params;
|
|
45005
45156
|
}
|
|
@@ -45074,6 +45225,10 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45074
45225
|
toRulesyncRule() {
|
|
45075
45226
|
if (this.isRoot()) return this.toRulesyncRuleDefault();
|
|
45076
45227
|
const globs = this.frontmatter?.alwaysApply === true ? ["**/*"] : normalizePaths$1(this.frontmatter?.paths);
|
|
45228
|
+
const cline = extractClineSection({
|
|
45229
|
+
frontmatter: this.frontmatter,
|
|
45230
|
+
relativeFilePath: this.getRelativeFilePath()
|
|
45231
|
+
});
|
|
45077
45232
|
return new RulesyncRule({
|
|
45078
45233
|
outputRoot: process.cwd(),
|
|
45079
45234
|
relativeDirPath: RULESYNC_RULES_RELATIVE_DIR_PATH,
|
|
@@ -45082,7 +45237,8 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45082
45237
|
root: false,
|
|
45083
45238
|
targets: ["*"],
|
|
45084
45239
|
description: this.frontmatter?.description,
|
|
45085
|
-
globs
|
|
45240
|
+
globs,
|
|
45241
|
+
...Object.keys(cline).length > 0 && { cline }
|
|
45086
45242
|
},
|
|
45087
45243
|
body: this.body ?? this.getFileContent()
|
|
45088
45244
|
});
|
|
@@ -45127,16 +45283,22 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
45127
45283
|
* universal match-everything globs mean "always", so they map to
|
|
45128
45284
|
* `alwaysApply: true` instead of a `paths` list; specific globs become
|
|
45129
45285
|
* conditional `paths`; an empty globs list leaves the rule always-active with
|
|
45130
|
-
* no `paths`/`alwaysApply`.
|
|
45286
|
+
* no `paths`/`alwaysApply`. Every key of the rulesync `cline:` section
|
|
45287
|
+
* (e.g. `disabled: true`) is written into the frontmatter too; the canonical
|
|
45288
|
+
* `paths` / `alwaysApply` / `description` win over same-named keys there.
|
|
45131
45289
|
*/
|
|
45132
45290
|
static buildNonRootRule({ outputRoot, rulesyncRule, validate, relativeDirPath }) {
|
|
45133
45291
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
45134
45292
|
const globs = normalizePaths$1(rulesyncFrontmatter.globs);
|
|
45135
45293
|
const isUniversal = globs.length > 0 && globs.every((glob) => glob === "**/*" || glob === "*");
|
|
45294
|
+
const rawClineSection = rulesyncFrontmatter.cline ?? {};
|
|
45295
|
+
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.`);
|
|
45296
|
+
const clineSection = omitCanonicalClineRuleKeys(rawClineSection);
|
|
45136
45297
|
const frontmatter = {
|
|
45137
45298
|
paths: globs.length > 0 && !isUniversal ? globs : void 0,
|
|
45138
45299
|
alwaysApply: isUniversal ? true : void 0,
|
|
45139
|
-
description: rulesyncFrontmatter.description || void 0
|
|
45300
|
+
description: rulesyncFrontmatter.description || void 0,
|
|
45301
|
+
...clineSection
|
|
45140
45302
|
};
|
|
45141
45303
|
return new ClineRule({
|
|
45142
45304
|
outputRoot,
|
|
@@ -55823,6 +55985,14 @@ function applyCommandPrefix({ def, converterConfig }) {
|
|
|
55823
55985
|
if (isExecForm) return `${bracePlaceholder(converterConfig.projectDirVar)}/${stripSurroundingQuotes(relativeCommand)}`;
|
|
55824
55986
|
return anchorInline(`"${converterConfig.projectDirVar}"/${relativeCommand}`);
|
|
55825
55987
|
}
|
|
55988
|
+
/** Whether a field restricted to some events applies on this one. */
|
|
55989
|
+
function isEventApplicable({ events, eventName }) {
|
|
55990
|
+
return events === void 0 || events.has(eventName);
|
|
55991
|
+
}
|
|
55992
|
+
/** Quote and comma-join a set of names (events, hook types) for a warning. */
|
|
55993
|
+
function describeEvents(names) {
|
|
55994
|
+
return [...names].map((name) => `"${name}"`).join(", ");
|
|
55995
|
+
}
|
|
55826
55996
|
/**
|
|
55827
55997
|
* Whether a field registered for `command` hooks only applies to this hook.
|
|
55828
55998
|
* Applied on both export and import: a value imported into a canonical field
|
|
@@ -55844,7 +56014,7 @@ function isFieldApplicable({ commandOnly, hookType }) {
|
|
|
55844
56014
|
* silence — the mirror of the import side.
|
|
55845
56015
|
*/
|
|
55846
56016
|
function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn }) {
|
|
55847
|
-
for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength } of fields) {
|
|
56017
|
+
for (const { canonical, tool, commandOnly, maxLength, collapseWhitespaceForLength, onlyWithFlag, events } of fields) {
|
|
55848
56018
|
const value = def[canonical];
|
|
55849
56019
|
if (value === void 0) continue;
|
|
55850
56020
|
if (!isFieldApplicable({
|
|
@@ -55854,6 +56024,13 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55854
56024
|
warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on "command" hooks only, so it is not generated.`);
|
|
55855
56025
|
continue;
|
|
55856
56026
|
}
|
|
56027
|
+
if (events !== void 0 && !isEventApplicable({
|
|
56028
|
+
events,
|
|
56029
|
+
eventName
|
|
56030
|
+
})) {
|
|
56031
|
+
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.`);
|
|
56032
|
+
continue;
|
|
56033
|
+
}
|
|
55857
56034
|
if (!isValid({
|
|
55858
56035
|
value,
|
|
55859
56036
|
canonical
|
|
@@ -55861,14 +56038,18 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55861
56038
|
warn?.(`Dropping "${canonical}" from a "${hookType}" hook on "${eventName}": ${quoteValueForWarning(value)} is not a value this tool can express as "${tool}".`);
|
|
55862
56039
|
continue;
|
|
55863
56040
|
}
|
|
56041
|
+
if (onlyWithFlag !== void 0 && def[onlyWithFlag] !== true) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" has no effect without "${onlyWithFlag}": true; this tool reads "${tool}" only on "${onlyWithFlag}" hooks.`);
|
|
55864
56042
|
if (maxLength !== void 0 && typeof value === "string") {
|
|
55865
56043
|
const length = collapseWhitespaceForLength ? value.replace(/\s+/g, " ").trim().length : value.length;
|
|
55866
|
-
if (length > maxLength) warn?.(`"${canonical}" on a "${hookType}" hook on "${eventName}" is ${length} characters long; this tool caps "${tool}" at ${maxLength} characters and will shorten it.`);
|
|
56044
|
+
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.`);
|
|
55867
56045
|
}
|
|
55868
56046
|
}
|
|
55869
|
-
return Object.fromEntries(fields.filter(({ canonical, commandOnly }) => isFieldApplicable({
|
|
56047
|
+
return Object.fromEntries(fields.filter(({ canonical, commandOnly, events }) => isFieldApplicable({
|
|
55870
56048
|
commandOnly,
|
|
55871
56049
|
hookType
|
|
56050
|
+
}) && isEventApplicable({
|
|
56051
|
+
events,
|
|
56052
|
+
eventName
|
|
55872
56053
|
}) && isValid({
|
|
55873
56054
|
value: def[canonical],
|
|
55874
56055
|
canonical
|
|
@@ -55881,16 +56062,24 @@ function emitPassthroughFields({ def, hookType, eventName, fields, isValid, warn
|
|
|
55881
56062
|
* kind has a rule an authored file can plausibly violate — turns a rejected
|
|
55882
56063
|
* value into a warning instead of a silent drop.
|
|
55883
56064
|
*/
|
|
55884
|
-
function importPassthroughFields({ h, hookType, fields, isValid, describeInvalid, warn }) {
|
|
55885
|
-
const
|
|
56065
|
+
function importPassthroughFields({ h, hookType, eventName, fields, isValid, describeInvalid, warn }) {
|
|
56066
|
+
const typeApplicable = fields.filter(({ commandOnly }) => isFieldApplicable({
|
|
55886
56067
|
commandOnly,
|
|
55887
56068
|
hookType
|
|
55888
56069
|
}));
|
|
56070
|
+
const applicable = typeApplicable.filter(({ events }) => isEventApplicable({
|
|
56071
|
+
events,
|
|
56072
|
+
eventName
|
|
56073
|
+
}));
|
|
55889
56074
|
const skipped = fields.filter(({ commandOnly }) => !isFieldApplicable({
|
|
55890
56075
|
commandOnly,
|
|
55891
56076
|
hookType
|
|
55892
56077
|
}));
|
|
55893
56078
|
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.`);
|
|
56079
|
+
for (const { tool, events } of typeApplicable) if (events !== void 0 && !isEventApplicable({
|
|
56080
|
+
events,
|
|
56081
|
+
eventName
|
|
56082
|
+
}) && 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.`);
|
|
55894
56083
|
for (const { tool, canonical } of applicable) if (describeInvalid !== void 0 && h[tool] !== void 0 && !isValid({
|
|
55895
56084
|
value: h[tool],
|
|
55896
56085
|
canonical
|
|
@@ -56058,11 +56247,11 @@ function isKeywordFieldApplicable({ spec, hookType, eventName, flags, direction,
|
|
|
56058
56247
|
return false;
|
|
56059
56248
|
}
|
|
56060
56249
|
if (hookTypes !== void 0 && !hookTypes.has(hookType)) {
|
|
56061
|
-
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${
|
|
56250
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool documents "${tool}" on ${describeEvents(hookTypes)} hooks only, so it is not ${direction}.`);
|
|
56062
56251
|
return false;
|
|
56063
56252
|
}
|
|
56064
56253
|
if (events !== void 0 && !events.has(eventName)) {
|
|
56065
|
-
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${
|
|
56254
|
+
warn?.(`Dropping "${field}" from a "${hookType}" hook on "${eventName}": this tool honours "${tool}" on ${describeEvents(events)} only, so it is not ${direction}.`);
|
|
56066
56255
|
return false;
|
|
56067
56256
|
}
|
|
56068
56257
|
if (ignoredEvents?.has(eventName)) {
|
|
@@ -56508,6 +56697,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56508
56697
|
...importPassthroughFields({
|
|
56509
56698
|
h,
|
|
56510
56699
|
hookType,
|
|
56700
|
+
eventName,
|
|
56511
56701
|
fields: converterConfig.booleanPassthroughFields ?? [],
|
|
56512
56702
|
isValid: isBooleanValue,
|
|
56513
56703
|
warn
|
|
@@ -56522,6 +56712,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56522
56712
|
...importPassthroughFields({
|
|
56523
56713
|
h,
|
|
56524
56714
|
hookType,
|
|
56715
|
+
eventName,
|
|
56525
56716
|
fields: converterConfig.numberPassthroughFields ?? [],
|
|
56526
56717
|
isValid: isImportableNumber,
|
|
56527
56718
|
describeInvalid: describeInvalidScalar,
|
|
@@ -56530,6 +56721,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56530
56721
|
...importPassthroughFields({
|
|
56531
56722
|
h,
|
|
56532
56723
|
hookType,
|
|
56724
|
+
eventName,
|
|
56533
56725
|
fields: converterConfig.stringPassthroughFields ?? [],
|
|
56534
56726
|
isValid: isImportableString,
|
|
56535
56727
|
describeInvalid: describeInvalidScalar,
|
|
@@ -56538,6 +56730,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56538
56730
|
...importPassthroughFields({
|
|
56539
56731
|
h,
|
|
56540
56732
|
hookType,
|
|
56733
|
+
eventName,
|
|
56541
56734
|
fields: converterConfig.arrayPassthroughFields ?? [],
|
|
56542
56735
|
isValid: isImportableArray,
|
|
56543
56736
|
describeInvalid: describeInvalidArray,
|
|
@@ -56546,6 +56739,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56546
56739
|
...importPassthroughFields({
|
|
56547
56740
|
h,
|
|
56548
56741
|
hookType,
|
|
56742
|
+
eventName,
|
|
56549
56743
|
fields: converterConfig.recordPassthroughFields ?? [],
|
|
56550
56744
|
isValid: isEmittableRecord,
|
|
56551
56745
|
describeInvalid: describeInvalidRecord,
|
|
@@ -56554,6 +56748,7 @@ function importAllPassthroughFields({ h, hookType, eventName, converterConfig, w
|
|
|
56554
56748
|
...importPassthroughFields({
|
|
56555
56749
|
h,
|
|
56556
56750
|
hookType,
|
|
56751
|
+
eventName,
|
|
56557
56752
|
fields: converterConfig.objectPassthroughFields ?? [],
|
|
56558
56753
|
isValid: isImportableObject,
|
|
56559
56754
|
describeInvalid: describeInvalidObject,
|
|
@@ -61386,6 +61581,11 @@ const GROKCLI_CONVERTER_CONFIG = {
|
|
|
61386
61581
|
tool: "env",
|
|
61387
61582
|
commandOnly: true
|
|
61388
61583
|
}],
|
|
61584
|
+
booleanPassthroughFields: [{
|
|
61585
|
+
canonical: "failClosed",
|
|
61586
|
+
tool: "failClosed",
|
|
61587
|
+
events: /* @__PURE__ */ new Set(["preToolUse", "beforeSubmitPrompt"])
|
|
61588
|
+
}],
|
|
61389
61589
|
arrayPassthroughFields: [{
|
|
61390
61590
|
canonical: "args",
|
|
61391
61591
|
tool: "args",
|
|
@@ -63035,6 +63235,15 @@ const LETTACODE_CONVERTER_CONFIG = {
|
|
|
63035
63235
|
timeoutUnit: "milliseconds"
|
|
63036
63236
|
};
|
|
63037
63237
|
/**
|
|
63238
|
+
* Letta Code documents settings-file hooks as deprecated in favor of Mods
|
|
63239
|
+
* (https://docs.letta.com/reference/deprecated/hooks) but still ships the
|
|
63240
|
+
* loader, so rulesync keeps emitting them and tells the user once per run.
|
|
63241
|
+
* Mods are not modeled: they are JS/TS code modules loaded only from
|
|
63242
|
+
* `~/.letta/mods/` or an agent's memory directory, with no declarative or
|
|
63243
|
+
* project-local form a canonical hook could be translated into.
|
|
63244
|
+
*/
|
|
63245
|
+
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";
|
|
63246
|
+
/**
|
|
63038
63247
|
* Letta Code keeps a boolean `disabled` switch beside the event arrays inside
|
|
63039
63248
|
* `hooks`. rulesync owns the whole `hooks` key, so the switch is read back from
|
|
63040
63249
|
* the existing file and carried into the generated one instead of being lost.
|
|
@@ -63093,6 +63302,11 @@ function parseLettacodeSettings$1(fileContent, filePath) {
|
|
|
63093
63302
|
* and UI preferences, ...), so generation merges the `hooks` key into the
|
|
63094
63303
|
* existing file (see `SHARED_CONFIG_OWNERSHIP`) instead of overwriting it.
|
|
63095
63304
|
*
|
|
63305
|
+
* Letta Code marks this surface deprecated in favor of Mods (see
|
|
63306
|
+
* `LETTACODE_HOOKS_DEPRECATION_WARNING`); generation warns once per run when it
|
|
63307
|
+
* writes any hook.
|
|
63308
|
+
*
|
|
63309
|
+
* @see https://docs.letta.com/reference/deprecated/hooks
|
|
63096
63310
|
* @see https://github.com/letta-ai/letta-code/blob/main/src/hooks/types.ts
|
|
63097
63311
|
* @see https://github.com/letta-ai/letta-code/blob/main/src/hooks/loader.ts
|
|
63098
63312
|
*/
|
|
@@ -63137,6 +63351,7 @@ var LettacodeHooks = class LettacodeHooks extends ToolHooks {
|
|
|
63137
63351
|
converterConfig: LETTACODE_CONVERTER_CONFIG,
|
|
63138
63352
|
logger
|
|
63139
63353
|
});
|
|
63354
|
+
if (Object.keys(hooks).length > 0) warnOnceWithFallback(logger, LETTACODE_HOOKS_DEPRECATION_WARNING);
|
|
63140
63355
|
const fileContent = applySharedConfigPatch({
|
|
63141
63356
|
fileKey: sharedConfigFileKey(paths),
|
|
63142
63357
|
feature: "hooks",
|
|
@@ -64264,14 +64479,16 @@ const QODER_SPEC = {
|
|
|
64264
64479
|
{
|
|
64265
64480
|
canonical: "rewakeMessage",
|
|
64266
64481
|
tool: "rewakeMessage",
|
|
64267
|
-
commandOnly: true
|
|
64482
|
+
commandOnly: true,
|
|
64483
|
+
onlyWithFlag: "asyncRewake"
|
|
64268
64484
|
},
|
|
64269
64485
|
{
|
|
64270
64486
|
canonical: "rewakeSummary",
|
|
64271
64487
|
tool: "rewakeSummary",
|
|
64272
64488
|
commandOnly: true,
|
|
64273
64489
|
maxLength: 300,
|
|
64274
|
-
collapseWhitespaceForLength: true
|
|
64490
|
+
collapseWhitespaceForLength: true,
|
|
64491
|
+
onlyWithFlag: "asyncRewake"
|
|
64275
64492
|
}
|
|
64276
64493
|
],
|
|
64277
64494
|
booleanPassthroughFields: [
|
|
@@ -65132,11 +65349,38 @@ const VIBE_DIR = ".vibe";
|
|
|
65132
65349
|
const VIBE_HOOKS_FILE_NAME = "hooks.toml";
|
|
65133
65350
|
/**
|
|
65134
65351
|
* Vibe scopes the tool-name `match` glob and the `strict` flag to tool hooks
|
|
65135
|
-
* (`pre_tool` / `post_tool`)
|
|
65136
|
-
*
|
|
65137
|
-
* @
|
|
65352
|
+
* (`pre_tool` / `post_tool`). `post_agent` takes a `match` too since v3.0.3,
|
|
65353
|
+
* but on the name of the agent that finished its turn, not a tool name (see
|
|
65354
|
+
* {@link VIBE_MAIN_AGENT_MATCH}).
|
|
65355
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
65138
65356
|
*/
|
|
65139
65357
|
const VIBE_TOOL_EVENTS = /* @__PURE__ */ new Set(["pre_tool", "post_tool"]);
|
|
65358
|
+
/** The Vibe event both canonical `stop` and `subagentStop` become. */
|
|
65359
|
+
const VIBE_POST_AGENT_EVENT = "post_agent";
|
|
65360
|
+
/**
|
|
65361
|
+
* The reserved agent alias a `post_agent` hook's `match` uses to run only
|
|
65362
|
+
* after the main agent. Without a `match`, `post_agent` runs after every
|
|
65363
|
+
* agent, subagents included (v3.0.2+), whereas canonical `stop` is
|
|
65364
|
+
* main-agent-only — so `stop` is written with this match.
|
|
65365
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
65366
|
+
*/
|
|
65367
|
+
const VIBE_MAIN_AGENT_MATCH = "main";
|
|
65368
|
+
/**
|
|
65369
|
+
* The `post_agent` `match` for a canonical `subagentStop` hook, or null to
|
|
65370
|
+
* skip it. Vibe has no selector for "every subagent but not the main agent",
|
|
65371
|
+
* so a matcher naming the subagent is required.
|
|
65372
|
+
*/
|
|
65373
|
+
function toVibeSubagentMatch({ matcher, logger }) {
|
|
65374
|
+
if (typeof matcher !== "string" || matcher === "" || matcher === "*") {
|
|
65375
|
+
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.");
|
|
65376
|
+
return null;
|
|
65377
|
+
}
|
|
65378
|
+
if (matcher.toLowerCase() === VIBE_MAIN_AGENT_MATCH) {
|
|
65379
|
+
logger?.warn("Vibe hooks: skipping a \"subagentStop\" hook whose matcher is \"main\". Vibe reserves \"main\" for the main agent; use a \"stop\" hook instead.");
|
|
65380
|
+
return null;
|
|
65381
|
+
}
|
|
65382
|
+
return matcher;
|
|
65383
|
+
}
|
|
65140
65384
|
const SUPPORTED_VIBE_EVENTS = new Set(VIBE_HOOK_EVENTS);
|
|
65141
65385
|
/**
|
|
65142
65386
|
* The `match` a canonical `Bash` matcher becomes. Vibe tests `match` against a
|
|
@@ -65160,7 +65404,8 @@ function fromVibeHookMatch(match) {
|
|
|
65160
65404
|
/**
|
|
65161
65405
|
* Build the flat `[[hooks]]` array for `.vibe/hooks.toml` from a canonical
|
|
65162
65406
|
* hooks config. Vibe uses a flat array where each entry carries its own event
|
|
65163
|
-
* `type`,
|
|
65407
|
+
* `type`, `match` glob/regex (a tool name for tool hooks, an agent name
|
|
65408
|
+
* for `post_agent`), and `command`. Only `type: "command"`
|
|
65164
65409
|
* canonical hooks are emitted: a Vibe hook is a shell command line. (v2.25.1
|
|
65165
65410
|
* to v2.25.4 briefly exec'd it without a shell under the unified harness and
|
|
65166
65411
|
* skipped any command containing a backslash; v2.25.5 restored shell
|
|
@@ -65168,7 +65413,7 @@ function fromVibeHookMatch(match) {
|
|
|
65168
65413
|
* now run through the shell" — and dropped the backslash skip.)
|
|
65169
65414
|
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.2/README.md
|
|
65170
65415
|
*/
|
|
65171
|
-
function canonicalToVibeHooks({ config, toolOverride }) {
|
|
65416
|
+
function canonicalToVibeHooks({ config, toolOverride, logger }) {
|
|
65172
65417
|
const shared = {};
|
|
65173
65418
|
for (const [event, defs] of Object.entries(config.hooks)) if (SUPPORTED_VIBE_EVENTS.has(event)) shared[event] = defs;
|
|
65174
65419
|
const effective = {
|
|
@@ -65176,30 +65421,40 @@ function canonicalToVibeHooks({ config, toolOverride }) {
|
|
|
65176
65421
|
...toolOverride
|
|
65177
65422
|
};
|
|
65178
65423
|
const hooks = [];
|
|
65424
|
+
const nextIndexByVibeEvent = /* @__PURE__ */ new Map();
|
|
65179
65425
|
for (const [event, defs] of Object.entries(effective)) {
|
|
65180
65426
|
if (!SUPPORTED_VIBE_EVENTS.has(event)) continue;
|
|
65181
65427
|
const vibeEvent = lookupOwn({
|
|
65182
65428
|
record: CANONICAL_TO_VIBE_EVENT_NAMES,
|
|
65183
65429
|
key: event
|
|
65184
65430
|
}) ?? event;
|
|
65185
|
-
let index = 0;
|
|
65186
65431
|
for (const def of defs) {
|
|
65187
65432
|
if ((def.type ?? "command") !== "command") continue;
|
|
65188
65433
|
if (typeof def.command !== "string") continue;
|
|
65189
|
-
const name = typeof def.name === "string" ? def.name : `${vibeEvent}-${index}`;
|
|
65190
65434
|
const isToolEvent = VIBE_TOOL_EVENTS.has(vibeEvent);
|
|
65435
|
+
let match;
|
|
65436
|
+
if (isToolEvent) match = toVibeHookMatch(def.matcher);
|
|
65437
|
+
else if (event === "subagentStop") {
|
|
65438
|
+
const subagentMatch = toVibeSubagentMatch({
|
|
65439
|
+
matcher: def.matcher,
|
|
65440
|
+
logger
|
|
65441
|
+
});
|
|
65442
|
+
if (subagentMatch === null) continue;
|
|
65443
|
+
match = subagentMatch;
|
|
65444
|
+
} else match = VIBE_MAIN_AGENT_MATCH;
|
|
65445
|
+
const index = nextIndexByVibeEvent.get(vibeEvent) ?? 0;
|
|
65191
65446
|
const entry = {
|
|
65192
|
-
name
|
|
65447
|
+
name: typeof def.name === "string" ? def.name : `${vibeEvent}-${index}`,
|
|
65193
65448
|
type: vibeEvent,
|
|
65449
|
+
match,
|
|
65194
65450
|
command: def.command
|
|
65195
65451
|
};
|
|
65196
|
-
if (isToolEvent) entry.match = toVibeHookMatch(def.matcher);
|
|
65197
65452
|
if (typeof def.timeout === "number") entry.timeout = def.timeout;
|
|
65198
65453
|
const strict = def.strict;
|
|
65199
65454
|
if (isToolEvent && typeof strict === "boolean") entry.strict = strict;
|
|
65200
65455
|
if (typeof def.description === "string") entry.description = def.description;
|
|
65201
65456
|
hooks.push(entry);
|
|
65202
|
-
index
|
|
65457
|
+
nextIndexByVibeEvent.set(vibeEvent, index + 1);
|
|
65203
65458
|
}
|
|
65204
65459
|
}
|
|
65205
65460
|
return { hooks };
|
|
@@ -65211,14 +65466,22 @@ function vibeEntryToCanonicalDef(raw) {
|
|
|
65211
65466
|
const vibeEvent = typeof entry.type === "string" ? entry.type : void 0;
|
|
65212
65467
|
if (vibeEvent === void 0) return null;
|
|
65213
65468
|
if (isPrototypePollutionKey(vibeEvent)) return null;
|
|
65214
|
-
|
|
65469
|
+
let canonicalEvent = lookupOwn({
|
|
65215
65470
|
record: VIBE_TO_CANONICAL_EVENT_NAMES,
|
|
65216
65471
|
key: vibeEvent
|
|
65217
65472
|
}) ?? vibeEvent;
|
|
65218
65473
|
const def = { type: "command" };
|
|
65219
65474
|
if (typeof entry.command === "string") def.command = entry.command;
|
|
65220
|
-
|
|
65221
|
-
|
|
65475
|
+
if (vibeEvent === VIBE_POST_AGENT_EVENT) {
|
|
65476
|
+
const agentMatch = typeof entry.match === "string" ? entry.match : "";
|
|
65477
|
+
if (agentMatch !== "" && agentMatch !== "*" && agentMatch.toLowerCase() !== VIBE_MAIN_AGENT_MATCH) {
|
|
65478
|
+
canonicalEvent = "subagentStop";
|
|
65479
|
+
def.matcher = agentMatch;
|
|
65480
|
+
}
|
|
65481
|
+
} else {
|
|
65482
|
+
const matcher = fromVibeHookMatch(entry.match);
|
|
65483
|
+
if (matcher !== void 0) def.matcher = matcher;
|
|
65484
|
+
}
|
|
65222
65485
|
if (typeof entry.timeout === "number") def.timeout = entry.timeout;
|
|
65223
65486
|
if (typeof entry.name === "string") def.name = entry.name;
|
|
65224
65487
|
if (typeof entry.description === "string") def.description = entry.description;
|
|
@@ -65249,6 +65512,26 @@ function vibeHooksToCanonical(parsed) {
|
|
|
65249
65512
|
}
|
|
65250
65513
|
return canonical;
|
|
65251
65514
|
}
|
|
65515
|
+
/** Vibe reads a `match` with a `re:` prefix or glob metacharacters as a pattern. */
|
|
65516
|
+
const VIBE_PATTERN_MATCH = /^re:|[*?[]/;
|
|
65517
|
+
/**
|
|
65518
|
+
* Move the imported `subagentStop` hooks into the `vibe` override when any of
|
|
65519
|
+
* them matches agents by a regex or glob rather than by a literal name. Such a
|
|
65520
|
+
* pattern can also select the main agent (a main session answers to its mode
|
|
65521
|
+
* name, e.g. `accept-edits`), and other tools would read it as their own
|
|
65522
|
+
* matcher syntax, so it is kept Vibe-only. The override replaces the shared
|
|
65523
|
+
* list for its event, so the whole list moves to keep every entry on Vibe.
|
|
65524
|
+
*/
|
|
65525
|
+
function takeVibeOnlySubagentStop({ hooks, logger }) {
|
|
65526
|
+
const defs = lookupOwn({
|
|
65527
|
+
record: hooks,
|
|
65528
|
+
key: "subagentStop"
|
|
65529
|
+
});
|
|
65530
|
+
if (defs === void 0 || !defs.some((def) => typeof def.matcher === "string" && VIBE_PATTERN_MATCH.test(def.matcher))) return;
|
|
65531
|
+
delete hooks.subagentStop;
|
|
65532
|
+
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.");
|
|
65533
|
+
return defs;
|
|
65534
|
+
}
|
|
65252
65535
|
function parseVibeToml(fileContent) {
|
|
65253
65536
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
65254
65537
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -65288,12 +65571,13 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65288
65571
|
validate
|
|
65289
65572
|
});
|
|
65290
65573
|
}
|
|
65291
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
65574
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
65292
65575
|
const paths = this.getSettablePaths({ global });
|
|
65293
65576
|
const config = rulesyncHooks.getJson();
|
|
65294
65577
|
const vibeHooks = canonicalToVibeHooks({
|
|
65295
65578
|
config,
|
|
65296
|
-
toolOverride: config.vibe?.hooks
|
|
65579
|
+
toolOverride: config.vibe?.hooks,
|
|
65580
|
+
logger
|
|
65297
65581
|
});
|
|
65298
65582
|
const fileContent = smol_toml.stringify(vibeHooks);
|
|
65299
65583
|
return new this({
|
|
@@ -65304,15 +65588,30 @@ var VibeHooks = class extends ToolHooks {
|
|
|
65304
65588
|
validate
|
|
65305
65589
|
});
|
|
65306
65590
|
}
|
|
65307
|
-
toRulesyncHooks() {
|
|
65591
|
+
toRulesyncHooks(options = {}) {
|
|
65308
65592
|
const hooks = vibeHooksToCanonical(this.parseFileContentOrThrow({
|
|
65309
65593
|
displayName: "Vibe",
|
|
65310
65594
|
parse: (fileContent) => parseVibeToml(fileContent)
|
|
65311
65595
|
}));
|
|
65312
|
-
|
|
65596
|
+
const vibeOnlySubagentStop = takeVibeOnlySubagentStop({
|
|
65597
|
+
hooks,
|
|
65598
|
+
logger: options.logger
|
|
65599
|
+
});
|
|
65600
|
+
const config = buildImportedHooksConfig({
|
|
65313
65601
|
hooks,
|
|
65314
65602
|
overrideKey: "vibe"
|
|
65315
|
-
})
|
|
65603
|
+
});
|
|
65604
|
+
if (vibeOnlySubagentStop !== void 0) {
|
|
65605
|
+
const override = config.vibe ?? {};
|
|
65606
|
+
config.vibe = {
|
|
65607
|
+
...override,
|
|
65608
|
+
hooks: {
|
|
65609
|
+
...override.hooks,
|
|
65610
|
+
subagentStop: vibeOnlySubagentStop
|
|
65611
|
+
}
|
|
65612
|
+
};
|
|
65613
|
+
}
|
|
65614
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
65316
65615
|
}
|
|
65317
65616
|
validate() {
|
|
65318
65617
|
try {
|
|
@@ -69651,7 +69950,7 @@ var CodebuddyMcp = class CodebuddyMcp extends ToolMcp {
|
|
|
69651
69950
|
}
|
|
69652
69951
|
};
|
|
69653
69952
|
// src/features/mcp/codebuff-mcp.ts
|
|
69654
|
-
const TOOL_NAME$
|
|
69953
|
+
const TOOL_NAME$2 = "codebuff";
|
|
69655
69954
|
const MAPPED_KEYS = /* @__PURE__ */ new Set([
|
|
69656
69955
|
"type",
|
|
69657
69956
|
"transport",
|
|
@@ -69686,14 +69985,14 @@ function toCodebuffEnv({ name, env, logger }) {
|
|
|
69686
69985
|
}
|
|
69687
69986
|
if (value.startsWith("$")) {
|
|
69688
69987
|
warnAndSkipMcpServer({
|
|
69689
|
-
toolName: TOOL_NAME$
|
|
69988
|
+
toolName: TOOL_NAME$2,
|
|
69690
69989
|
serverName: name,
|
|
69691
69990
|
reason: `env ${quoteValueForWarning(key)} starting with "$" that is not a whole \${VAR} reference, which Codebuff would resolve as a variable name and fail to load the whole mcp.json`,
|
|
69692
69991
|
logger
|
|
69693
69992
|
});
|
|
69694
69993
|
return null;
|
|
69695
69994
|
}
|
|
69696
|
-
if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$
|
|
69995
|
+
if (CANONICAL_ENV_REF.test(value)) logger?.warn(`${TOOL_NAME$2} MCP: env ${quoteValueForWarning(key)} of server ${quoteValueForWarning(name)} embeds a \${VAR} reference; Codebuff only resolves a whole-value \$VAR, so it is written as literal text.`);
|
|
69697
69996
|
converted.push([key, value]);
|
|
69698
69997
|
}
|
|
69699
69998
|
return Object.fromEntries(converted);
|
|
@@ -69710,31 +70009,31 @@ function toCodebuffEnv({ name, env, logger }) {
|
|
|
69710
70009
|
*/
|
|
69711
70010
|
function toCodebuffServer({ name, server, logger }) {
|
|
69712
70011
|
if (server.disabled === true) return warnAndSkipMcpServer({
|
|
69713
|
-
toolName: TOOL_NAME$
|
|
70012
|
+
toolName: TOOL_NAME$2,
|
|
69714
70013
|
serverName: name,
|
|
69715
70014
|
reason: "disabled: true, which Codebuff has no per-server switch for",
|
|
69716
70015
|
logger
|
|
69717
70016
|
});
|
|
69718
70017
|
const transport = server.type ?? server.transport;
|
|
69719
70018
|
if (transport === "ws") return warnAndSkipMcpServer({
|
|
69720
|
-
toolName: TOOL_NAME$
|
|
70019
|
+
toolName: TOOL_NAME$2,
|
|
69721
70020
|
serverName: name,
|
|
69722
70021
|
reason: "the WebSocket transport, which Codebuff does not support",
|
|
69723
70022
|
logger
|
|
69724
70023
|
});
|
|
69725
70024
|
const dropped = Object.keys(server).filter((key) => !MAPPED_KEYS.has(key));
|
|
69726
|
-
if (dropped.length > 0) logger?.warn(`${TOOL_NAME$
|
|
70025
|
+
if (dropped.length > 0) logger?.warn(`${TOOL_NAME$2} MCP: dropping ${dropped.map((key) => quoteValueForWarning(key)).join(", ")} from server ${quoteValueForWarning(name)}; Codebuff rejects keys outside its server schema.`);
|
|
69727
70026
|
if (isRemoteMcpServer(server)) {
|
|
69728
70027
|
const url = resolveRemoteMcpUrl(server);
|
|
69729
70028
|
if (url === void 0) return warnAndSkipMcpServer({
|
|
69730
|
-
toolName: TOOL_NAME$
|
|
70029
|
+
toolName: TOOL_NAME$2,
|
|
69731
70030
|
serverName: name,
|
|
69732
70031
|
reason: "a remote transport without a url",
|
|
69733
70032
|
logger
|
|
69734
70033
|
});
|
|
69735
70034
|
const params = server.params;
|
|
69736
70035
|
const validParams = isRecord(params) && Object.values(params).every((value) => typeof value === "string");
|
|
69737
|
-
if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$
|
|
70036
|
+
if (params !== void 0 && !validParams) logger?.warn(`${TOOL_NAME$2} MCP: dropping params from server ${quoteValueForWarning(name)}; Codebuff accepts only a map of string values.`);
|
|
69738
70037
|
return {
|
|
69739
70038
|
type: transport === "sse" ? "sse" : "http",
|
|
69740
70039
|
url,
|
|
@@ -69744,7 +70043,7 @@ function toCodebuffServer({ name, server, logger }) {
|
|
|
69744
70043
|
}
|
|
69745
70044
|
const [command, ...args] = resolveLocalMcpCommand(server);
|
|
69746
70045
|
if (command === void 0) return warnAndSkipMcpServer({
|
|
69747
|
-
toolName: TOOL_NAME$
|
|
70046
|
+
toolName: TOOL_NAME$2,
|
|
69748
70047
|
serverName: name,
|
|
69749
70048
|
reason: "no command to spawn",
|
|
69750
70049
|
logger
|
|
@@ -72414,7 +72713,7 @@ var CursorMcp = class CursorMcp extends ToolMcp {
|
|
|
72414
72713
|
}
|
|
72415
72714
|
};
|
|
72416
72715
|
// src/features/mcp/deepagents-mcp.ts
|
|
72417
|
-
const TOOL_NAME = "deepagents";
|
|
72716
|
+
const TOOL_NAME$1 = "deepagents";
|
|
72418
72717
|
/**
|
|
72419
72718
|
* Map a canonical transport onto the three dcode accepts.
|
|
72420
72719
|
*
|
|
@@ -72455,7 +72754,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
|
|
|
72455
72754
|
return true;
|
|
72456
72755
|
}
|
|
72457
72756
|
warnAndSkipMcpServer({
|
|
72458
|
-
toolName: TOOL_NAME,
|
|
72757
|
+
toolName: TOOL_NAME$1,
|
|
72459
72758
|
serverName: name,
|
|
72460
72759
|
reason: "an empty command list, which leaves deepagents nothing to spawn",
|
|
72461
72760
|
logger
|
|
@@ -72483,7 +72782,7 @@ function applyDeepagentsCommand({ name, server, converted, logger }) {
|
|
|
72483
72782
|
*/
|
|
72484
72783
|
function toDeepagentsOauth({ name, oauth, converted, logger }) {
|
|
72485
72784
|
if (!isSlackOauthServer(converted)) {
|
|
72486
|
-
logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
|
|
72785
|
+
logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents reads it only on a Slack (slack.com) server with auth: "oauth" and skips any other server that sets it.`);
|
|
72487
72786
|
return;
|
|
72488
72787
|
}
|
|
72489
72788
|
const source = isRecord(oauth) ? oauth : {};
|
|
@@ -72492,18 +72791,18 @@ function toDeepagentsOauth({ name, oauth, converted, logger }) {
|
|
|
72492
72791
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
72493
72792
|
const nativeKey = key === "clientId" ? "client_id" : key;
|
|
72494
72793
|
if (nativeKey !== "client_id" && nativeKey !== "client_secret") {
|
|
72495
|
-
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
|
|
72794
|
+
logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents accepts only client_id and client_secret.`);
|
|
72496
72795
|
continue;
|
|
72497
72796
|
}
|
|
72498
72797
|
if (nativeKey !== key && isDeepagentsOauthValue(source[nativeKey])) continue;
|
|
72499
72798
|
if (!isDeepagentsOauthValue(value)) {
|
|
72500
|
-
logger?.warn(`${TOOL_NAME} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
|
|
72799
|
+
logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth.${key} on "${name}"; deepagents requires a non-empty string without control characters.`);
|
|
72501
72800
|
continue;
|
|
72502
72801
|
}
|
|
72503
72802
|
result[nativeKey] = value;
|
|
72504
72803
|
}
|
|
72505
72804
|
if (result.client_id === void 0) {
|
|
72506
|
-
logger?.warn(`${TOOL_NAME} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
|
|
72805
|
+
logger?.warn(`${TOOL_NAME$1} MCP: dropping oauth on "${name}"; deepagents requires oauth.client_id (canonical clientId) and skips a server without it.`);
|
|
72507
72806
|
return;
|
|
72508
72807
|
}
|
|
72509
72808
|
return result;
|
|
@@ -72532,14 +72831,14 @@ function isSlackOauthServer(server) {
|
|
|
72532
72831
|
*/
|
|
72533
72832
|
function toDeepagentsServer({ name, server, logger }) {
|
|
72534
72833
|
if (server.disabled === true) return warnAndSkipMcpServer({
|
|
72535
|
-
toolName: TOOL_NAME,
|
|
72834
|
+
toolName: TOOL_NAME$1,
|
|
72536
72835
|
serverName: name,
|
|
72537
72836
|
reason: "disabled: true, which deepagents does not read from .mcp.json (the server would still run)",
|
|
72538
72837
|
logger
|
|
72539
72838
|
});
|
|
72540
72839
|
const rawTransport = server.transport ?? server.type;
|
|
72541
72840
|
if (rawTransport === "ws") return warnAndSkipMcpServer({
|
|
72542
|
-
toolName: TOOL_NAME,
|
|
72841
|
+
toolName: TOOL_NAME$1,
|
|
72543
72842
|
serverName: name,
|
|
72544
72843
|
reason: "the WebSocket transport, which deepagents does not support",
|
|
72545
72844
|
logger
|
|
@@ -72569,21 +72868,21 @@ function toDeepagentsServer({ name, server, logger }) {
|
|
|
72569
72868
|
if (deepagentsOauth !== void 0) converted.oauth = deepagentsOauth;
|
|
72570
72869
|
}
|
|
72571
72870
|
if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
|
|
72572
|
-
toolName: TOOL_NAME,
|
|
72871
|
+
toolName: TOOL_NAME$1,
|
|
72573
72872
|
serverName: name,
|
|
72574
72873
|
reason: "both enabledTools and disabledTools, which deepagents rejects — pick one",
|
|
72575
72874
|
logger
|
|
72576
72875
|
});
|
|
72577
72876
|
if (enabledTools !== void 0) {
|
|
72578
72877
|
if (enabledTools.length === 0) return warnAndSkipMcpServer({
|
|
72579
|
-
toolName: TOOL_NAME,
|
|
72878
|
+
toolName: TOOL_NAME$1,
|
|
72580
72879
|
serverName: name,
|
|
72581
72880
|
reason: "an empty enabledTools list, which allows no tools at all and which deepagents rejects",
|
|
72582
72881
|
logger
|
|
72583
72882
|
});
|
|
72584
72883
|
converted.allowedTools = enabledTools;
|
|
72585
72884
|
} else if (disabledTools !== void 0) {
|
|
72586
|
-
if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
|
|
72885
|
+
if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME$1} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
|
|
72587
72886
|
else converted.disabledTools = disabledTools;
|
|
72588
72887
|
}
|
|
72589
72888
|
return converted;
|
|
@@ -73235,6 +73534,32 @@ var DshMcp = class DshMcp extends ToolMcp {
|
|
|
73235
73534
|
}
|
|
73236
73535
|
};
|
|
73237
73536
|
// src/features/mcp/factorydroid-mcp.ts
|
|
73537
|
+
const TOOL_NAME = "factorydroid";
|
|
73538
|
+
/**
|
|
73539
|
+
* Drop servers whose `enabledTools` allowlist is empty.
|
|
73540
|
+
*
|
|
73541
|
+
* Canonically an empty allowlist allows no tools, but Droid applies
|
|
73542
|
+
* `enabledTools` only "when the list is non-empty"
|
|
73543
|
+
* (https://docs.factory.com/harness/mcp), so writing `[]` would load every
|
|
73544
|
+
* tool the server reports. Skipping the server is the only form that does not
|
|
73545
|
+
* hand the model more tools than the canonical config allows.
|
|
73546
|
+
*/
|
|
73547
|
+
function skipEmptyAllowlistServers({ mcpServers, logger }) {
|
|
73548
|
+
const kept = {};
|
|
73549
|
+
for (const [name, server] of Object.entries(mcpServers)) {
|
|
73550
|
+
if (server.enabledTools?.length === 0) {
|
|
73551
|
+
warnAndSkipMcpServer({
|
|
73552
|
+
toolName: TOOL_NAME,
|
|
73553
|
+
serverName: name,
|
|
73554
|
+
reason: "an empty enabledTools list, which allows no tools at all but which Factory Droid treats as no filter",
|
|
73555
|
+
logger
|
|
73556
|
+
});
|
|
73557
|
+
continue;
|
|
73558
|
+
}
|
|
73559
|
+
kept[name] = server;
|
|
73560
|
+
}
|
|
73561
|
+
return kept;
|
|
73562
|
+
}
|
|
73238
73563
|
var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
|
|
73239
73564
|
json;
|
|
73240
73565
|
constructor(params) {
|
|
@@ -73260,8 +73585,11 @@ var FactorydroidMcp = class FactorydroidMcp extends ToolMcp {
|
|
|
73260
73585
|
validate
|
|
73261
73586
|
});
|
|
73262
73587
|
}
|
|
73263
|
-
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true }) {
|
|
73264
|
-
const factorydroidConfig = { mcpServers:
|
|
73588
|
+
static fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, logger }) {
|
|
73589
|
+
const factorydroidConfig = { mcpServers: skipEmptyAllowlistServers({
|
|
73590
|
+
mcpServers: rulesyncMcp.getMcpServers(),
|
|
73591
|
+
logger
|
|
73592
|
+
}) };
|
|
73265
73593
|
const fileContent = JSON.stringify(factorydroidConfig, null, 2);
|
|
73266
73594
|
return new FactorydroidMcp({
|
|
73267
73595
|
outputRoot,
|
|
@@ -76900,6 +77228,12 @@ const REASONIX_PLUGIN_FIELDS = [
|
|
|
76900
77228
|
"load",
|
|
76901
77229
|
"oauth_allow_missing_pkce_metadata"
|
|
76902
77230
|
];
|
|
77231
|
+
/**
|
|
77232
|
+
* The `load` mode that puts a server's tools in the provider schema. Reasonix
|
|
77233
|
+
* compares it case-insensitively after trimming (`normalizeMCPLoad`).
|
|
77234
|
+
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/studio-v2.53.0/internal/contract/config/mcp_load.go
|
|
77235
|
+
*/
|
|
77236
|
+
const REASONIX_MCP_LOAD_ALWAYS = "always";
|
|
76903
77237
|
/** The `[[plugins]]` key that canonical `disabledTools` is written to and read from. */
|
|
76904
77238
|
const REASONIX_DISABLED_TOOLS_KEY = "disabled_tools";
|
|
76905
77239
|
var ReasonixMcp = class ReasonixMcp extends ToolMcp {
|
|
@@ -77064,7 +77398,9 @@ function hasExpectedType(value, expected) {
|
|
|
77064
77398
|
* `concurrency` string decodes fine but is ignored in favor of the server-name
|
|
77065
77399
|
* default, so it is dropped too rather than written as a setting that does
|
|
77066
77400
|
* nothing. An unknown `load` string is read as `deferred`, the default, so it
|
|
77067
|
-
* passes through as authored.
|
|
77401
|
+
* passes through as authored. A project `load = "always"` also passes through,
|
|
77402
|
+
* but is warned about in `writePassthroughFields`: see the v2.53.0 note on
|
|
77403
|
+
* `REASONIX_PLUGIN_FIELDS`.
|
|
77068
77404
|
* @see https://github.com/esengine/DeepSeek-Reasonix/blob/v2.31.0/internal/contract/config/plugin_entry.go
|
|
77069
77405
|
*/
|
|
77070
77406
|
function invalidPassthroughFieldReason(field, value) {
|
|
@@ -77104,6 +77440,7 @@ function writePassthroughFields({ name, server, plugin, global, logger }) {
|
|
|
77104
77440
|
continue;
|
|
77105
77441
|
}
|
|
77106
77442
|
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.`));
|
|
77443
|
+
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.`);
|
|
77107
77444
|
plugin[field] = value;
|
|
77108
77445
|
}
|
|
77109
77446
|
}
|
|
@@ -86042,6 +86379,41 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
86042
86379
|
// src/features/permissions/codebuddy-permissions.ts
|
|
86043
86380
|
/** Top-level key of `.codebuddy/settings.json` that rulesync owns. */
|
|
86044
86381
|
const CODEBUDDY_PERMISSIONS_KEY = "permissions";
|
|
86382
|
+
/**
|
|
86383
|
+
* The top-level settings groups the `codebuddy` override may author, each with
|
|
86384
|
+
* the only keys of that group rulesync writes and imports. A group's other
|
|
86385
|
+
* keys are kept as the file has them; an override key not listed here is
|
|
86386
|
+
* skipped with a warning.
|
|
86387
|
+
*
|
|
86388
|
+
* - `autoReview`: the `auto` permission mode's review policy (`policy`
|
|
86389
|
+
* replaces the built-in security policy, `extraPolicy` is appended to it).
|
|
86390
|
+
* - `autoMode`: the legacy natural-language `auto` rules, still read as
|
|
86391
|
+
* additional policy.
|
|
86392
|
+
* - `memory.parentDiscovery`: whether `CODEBUDDY.md` / `AGENTS.md` are also
|
|
86393
|
+
* discovered in parent directories of the launch directory.
|
|
86394
|
+
*
|
|
86395
|
+
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
86396
|
+
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
86397
|
+
*/
|
|
86398
|
+
const CODEBUDDY_OVERRIDE_GROUP_KEYS = {
|
|
86399
|
+
autoReview: ["policy", "extraPolicy"],
|
|
86400
|
+
autoMode: [
|
|
86401
|
+
"environment",
|
|
86402
|
+
"allow",
|
|
86403
|
+
"soft_deny",
|
|
86404
|
+
"hard_deny"
|
|
86405
|
+
],
|
|
86406
|
+
memory: ["parentDiscovery"]
|
|
86407
|
+
};
|
|
86408
|
+
const CODEBUDDY_OVERRIDE_GROUPS = Object.keys(CODEBUDDY_OVERRIDE_GROUP_KEYS);
|
|
86409
|
+
/**
|
|
86410
|
+
* Groups CodeBuddy reads from the user and project-local settings only: the
|
|
86411
|
+
* shared project `.codebuddy/settings.json` is not read for them, so a checked-in
|
|
86412
|
+
* repository cannot relax the review of its own actions. They are written and
|
|
86413
|
+
* imported in global mode alone.
|
|
86414
|
+
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
86415
|
+
*/
|
|
86416
|
+
const CODEBUDDY_USER_ONLY_GROUPS = /* @__PURE__ */ new Set(["autoReview", "autoMode"]);
|
|
86045
86417
|
/** The rule lists under `permissions`, one per canonical action. */
|
|
86046
86418
|
const CODEBUDDY_RULE_LISTS = [
|
|
86047
86419
|
"allow",
|
|
@@ -86195,6 +86567,87 @@ function buildCodebuddyRuleLists({ config, existingPermissions, logger }) {
|
|
|
86195
86567
|
return lists;
|
|
86196
86568
|
}
|
|
86197
86569
|
/**
|
|
86570
|
+
* Whether a settings value has the shape the `codebuddy` override schema
|
|
86571
|
+
* accepts for that key, so an off-spec value is left in the file instead of
|
|
86572
|
+
* being written or making the imported permissions file fail validation.
|
|
86573
|
+
*/
|
|
86574
|
+
function isLiftableCodebuddyValue(key, value) {
|
|
86575
|
+
switch (key) {
|
|
86576
|
+
case "policy":
|
|
86577
|
+
case "extraPolicy": return typeof value === "string";
|
|
86578
|
+
case "parentDiscovery": return typeof value === "boolean";
|
|
86579
|
+
default: return isStringArray(value);
|
|
86580
|
+
}
|
|
86581
|
+
}
|
|
86582
|
+
/** Name every written override setting that can make the `auto` review more lenient. */
|
|
86583
|
+
function announceCodebuddyReviewSettings({ picked, group, where, logger }) {
|
|
86584
|
+
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.`);
|
|
86585
|
+
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.`);
|
|
86586
|
+
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.`);
|
|
86587
|
+
}
|
|
86588
|
+
/**
|
|
86589
|
+
* The top-level keys the `codebuddy` override contributes to the settings
|
|
86590
|
+
* patch, besides `permissions`. Each group is the existing group with the
|
|
86591
|
+
* authored keys laid over it; a group is only in the patch when authored, so
|
|
86592
|
+
* a hand-set value survives an unrelated regeneration.
|
|
86593
|
+
*/
|
|
86594
|
+
function buildCodebuddyOverridePatch({ override, existing, global, where, logger }) {
|
|
86595
|
+
const patch = {};
|
|
86596
|
+
if (override === void 0) return patch;
|
|
86597
|
+
for (const group of CODEBUDDY_OVERRIDE_GROUPS) {
|
|
86598
|
+
const authored = override[group];
|
|
86599
|
+
if (!isRecord(authored)) continue;
|
|
86600
|
+
if (CODEBUDDY_USER_ONLY_GROUPS.has(group) && !global) {
|
|
86601
|
+
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.`);
|
|
86602
|
+
continue;
|
|
86603
|
+
}
|
|
86604
|
+
const allowedKeys = CODEBUDDY_OVERRIDE_GROUP_KEYS[group];
|
|
86605
|
+
const picked = {};
|
|
86606
|
+
for (const [key, value] of Object.entries(authored)) {
|
|
86607
|
+
if (isPrototypePollutionKey(key) || value === void 0) continue;
|
|
86608
|
+
if (!allowedKeys.includes(key)) {
|
|
86609
|
+
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.`);
|
|
86610
|
+
continue;
|
|
86611
|
+
}
|
|
86612
|
+
picked[key] = value;
|
|
86613
|
+
}
|
|
86614
|
+
if (Object.keys(picked).length === 0) continue;
|
|
86615
|
+
const existingValue = existing[group];
|
|
86616
|
+
if (existingValue !== void 0 && !isRecord(existingValue)) {
|
|
86617
|
+
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}.`);
|
|
86618
|
+
continue;
|
|
86619
|
+
}
|
|
86620
|
+
patch[group] = {
|
|
86621
|
+
...existingValue,
|
|
86622
|
+
...picked
|
|
86623
|
+
};
|
|
86624
|
+
announceCodebuddyReviewSettings({
|
|
86625
|
+
picked,
|
|
86626
|
+
group,
|
|
86627
|
+
where,
|
|
86628
|
+
logger
|
|
86629
|
+
});
|
|
86630
|
+
}
|
|
86631
|
+
return patch;
|
|
86632
|
+
}
|
|
86633
|
+
/**
|
|
86634
|
+
* Lift the settings the `codebuddy` override authors back out of a settings
|
|
86635
|
+
* file. `autoReview` and `autoMode` are lifted only from the user file, the
|
|
86636
|
+
* one of the two rulesync manages that CodeBuddy reads them from.
|
|
86637
|
+
*/
|
|
86638
|
+
function liftCodebuddyOverride({ settings, global }) {
|
|
86639
|
+
const lifted = {};
|
|
86640
|
+
for (const group of CODEBUDDY_OVERRIDE_GROUPS) {
|
|
86641
|
+
if (CODEBUDDY_USER_ONLY_GROUPS.has(group) && !global) continue;
|
|
86642
|
+
const existingGroup = settings[group];
|
|
86643
|
+
if (!isRecord(existingGroup)) continue;
|
|
86644
|
+
const picked = {};
|
|
86645
|
+
for (const key of CODEBUDDY_OVERRIDE_GROUP_KEYS[group]) if (Object.hasOwn(existingGroup, key) && isLiftableCodebuddyValue(key, existingGroup[key])) picked[key] = existingGroup[key];
|
|
86646
|
+
if (Object.keys(picked).length > 0) lifted[group] = picked;
|
|
86647
|
+
}
|
|
86648
|
+
return lifted;
|
|
86649
|
+
}
|
|
86650
|
+
/**
|
|
86198
86651
|
* CodeBuddy Code permissions.
|
|
86199
86652
|
*
|
|
86200
86653
|
* Rules live under the `permissions` key of `.codebuddy/settings.json`
|
|
@@ -86205,7 +86658,10 @@ function buildCodebuddyRuleLists({ config, existingPermissions, logger }) {
|
|
|
86205
86658
|
* writes go through the shared-config gateway, the sibling keys of the rule
|
|
86206
86659
|
* lists (`defaultMode`, `additionalDirectories`, `trustedDirectories`, ...)
|
|
86207
86660
|
* are kept, and the file is never deleted. The gitignored
|
|
86208
|
-
* `.codebuddy/settings.local.json` layer is left to CodeBuddy.
|
|
86661
|
+
* `.codebuddy/settings.local.json` layer is left to CodeBuddy. The
|
|
86662
|
+
* `codebuddy` override's other settings (`autoReview`, `autoMode`,
|
|
86663
|
+
* `memory.parentDiscovery`) go through `buildCodebuddyOverridePatch` /
|
|
86664
|
+
* `liftCodebuddyOverride`.
|
|
86209
86665
|
*
|
|
86210
86666
|
* @see https://www.codebuddy.ai/docs/cli/permissions
|
|
86211
86667
|
* @see https://www.codebuddy.ai/docs/cli/settings
|
|
@@ -86244,8 +86700,9 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86244
86700
|
relativePath: (0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)
|
|
86245
86701
|
});
|
|
86246
86702
|
const existingPermissions = isRecord(existing[CODEBUDDY_PERMISSIONS_KEY]) ? existing[CODEBUDDY_PERMISSIONS_KEY] : {};
|
|
86703
|
+
const config = rulesyncPermissions.getJson();
|
|
86247
86704
|
const lists = buildCodebuddyRuleLists({
|
|
86248
|
-
config
|
|
86705
|
+
config,
|
|
86249
86706
|
existingPermissions,
|
|
86250
86707
|
logger
|
|
86251
86708
|
});
|
|
@@ -86260,7 +86717,16 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86260
86717
|
fileKey: sharedConfigFileKey(paths),
|
|
86261
86718
|
feature: "permissions",
|
|
86262
86719
|
existingContent,
|
|
86263
|
-
patch: {
|
|
86720
|
+
patch: {
|
|
86721
|
+
[CODEBUDDY_PERMISSIONS_KEY]: Object.keys(permissions).length > 0 ? permissions : void 0,
|
|
86722
|
+
...buildCodebuddyOverridePatch({
|
|
86723
|
+
override: config.codebuddy,
|
|
86724
|
+
existing,
|
|
86725
|
+
global,
|
|
86726
|
+
where: `${(0, node_path.join)(paths.relativeDirPath, paths.relativeFilePath)}${global ? " in the home directory" : ""}`,
|
|
86727
|
+
logger
|
|
86728
|
+
})
|
|
86729
|
+
},
|
|
86264
86730
|
filePath
|
|
86265
86731
|
}),
|
|
86266
86732
|
validate: true,
|
|
@@ -86281,7 +86747,14 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
|
|
|
86281
86747
|
const existing = rules[parsed.pattern];
|
|
86282
86748
|
if (existing === void 0 || PERMISSION_ACTION_PRIORITY[action] > PERMISSION_ACTION_PRIORITY[existing]) rules[parsed.pattern] = action;
|
|
86283
86749
|
}
|
|
86284
|
-
|
|
86750
|
+
const codebuddy = liftCodebuddyOverride({
|
|
86751
|
+
settings,
|
|
86752
|
+
global: this.global
|
|
86753
|
+
});
|
|
86754
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
86755
|
+
permission,
|
|
86756
|
+
...Object.keys(codebuddy).length > 0 && { codebuddy }
|
|
86757
|
+
}, null, 2) });
|
|
86285
86758
|
}
|
|
86286
86759
|
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
86287
86760
|
return new CodebuddyPermissions({
|
|
@@ -89247,7 +89720,7 @@ const AUTO_APPROVE_KEYS = {
|
|
|
89247
89720
|
*
|
|
89248
89721
|
* @see https://code.visualstudio.com/docs/agents/reference/ai-settings
|
|
89249
89722
|
*/
|
|
89250
|
-
const DEFAULT_PERMISSION_MODE_KEY = "chat.permissions.default";
|
|
89723
|
+
const DEFAULT_PERMISSION_MODE_KEY$1 = "chat.permissions.default";
|
|
89251
89724
|
/**
|
|
89252
89725
|
* The prefix every Agent Host sandbox setting shares. A `copilot.sandbox` leaf
|
|
89253
89726
|
* at path `a.b` is written to `chat.agent.sandbox.a.b`.
|
|
@@ -89535,7 +90008,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
89535
90008
|
});
|
|
89536
90009
|
}
|
|
89537
90010
|
const defaultPermissionMode = config.copilot?.defaultPermissionMode;
|
|
89538
|
-
if (defaultPermissionMode !== void 0) patch[DEFAULT_PERMISSION_MODE_KEY] = defaultPermissionMode;
|
|
90011
|
+
if (defaultPermissionMode !== void 0) patch[DEFAULT_PERMISSION_MODE_KEY$1] = defaultPermissionMode;
|
|
89539
90012
|
const sandbox = config.copilot?.sandbox;
|
|
89540
90013
|
if (sandbox !== void 0) {
|
|
89541
90014
|
Object.assign(patch, buildSandboxPatch({
|
|
@@ -89591,7 +90064,7 @@ var CopilotPermissions = class CopilotPermissions extends ToolPermissions {
|
|
|
89591
90064
|
for (const [pattern, flag] of Object.entries(autoApprove)) rules[pattern] = flag ? "allow" : "deny";
|
|
89592
90065
|
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
89593
90066
|
}
|
|
89594
|
-
const defaultPermissionMode = settings[DEFAULT_PERMISSION_MODE_KEY];
|
|
90067
|
+
const defaultPermissionMode = settings[DEFAULT_PERMISSION_MODE_KEY$1];
|
|
89595
90068
|
const sandbox = readSandboxFromSettings(settings);
|
|
89596
90069
|
const copilot = {
|
|
89597
90070
|
...isCopilotDefaultPermissionMode(defaultPermissionMode) && { defaultPermissionMode },
|
|
@@ -89624,6 +90097,16 @@ const WEBFETCH_CATEGORY$1 = "webfetch";
|
|
|
89624
90097
|
/** `~/.copilot/settings.json` and `.github/copilot/settings.json` keys. */
|
|
89625
90098
|
const ALLOWED_URLS_KEY = "allowedUrls";
|
|
89626
90099
|
const DENIED_URLS_KEY = "deniedUrls";
|
|
90100
|
+
/**
|
|
90101
|
+
* `~/.copilot/settings.json` only: the startup permission mode for new
|
|
90102
|
+
* interactive sessions, authored through the `copilotcli.defaultPermissionMode`
|
|
90103
|
+
* override. It is a user setting — the repository settings file does not list
|
|
90104
|
+
* it among its repo-overridable keys.
|
|
90105
|
+
*/
|
|
90106
|
+
const DEFAULT_PERMISSION_MODE_KEY = "defaultPermissionMode";
|
|
90107
|
+
function isCopilotcliDefaultPermissionMode(value) {
|
|
90108
|
+
return COPILOTCLI_DEFAULT_PERMISSION_MODES.some((mode) => mode === value);
|
|
90109
|
+
}
|
|
89627
90110
|
function toUrlList(value) {
|
|
89628
90111
|
if (!Array.isArray(value)) return [];
|
|
89629
90112
|
return value.filter((entry) => typeof entry === "string");
|
|
@@ -89665,6 +90148,13 @@ function splitUrlRules(rules) {
|
|
|
89665
90148
|
* top-level keys in the user `settings.json`, so only documented keys are ever
|
|
89666
90149
|
* emitted there either.)
|
|
89667
90150
|
*
|
|
90151
|
+
* The `copilotcli.defaultPermissionMode` override (`manual` | `assisted` |
|
|
90152
|
+
* `allow-all`) writes the user-scope `defaultPermissionMode` key in global mode
|
|
90153
|
+
* only; in project mode it is skipped with a warning, since the repository
|
|
90154
|
+
* settings file does not accept it. It is patched only when authored, so a mode
|
|
90155
|
+
* the user picked through `/settings` otherwise survives. Writing `allow-all`
|
|
90156
|
+
* warns, because it makes new sessions skip every approval prompt.
|
|
90157
|
+
*
|
|
89668
90158
|
* @see https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference
|
|
89669
90159
|
* @see https://github.com/github/copilot-cli/blob/main/changelog.md
|
|
89670
90160
|
*/
|
|
@@ -89707,7 +90197,8 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89707
90197
|
const paths = CopilotcliPermissions.getSettablePaths({ global });
|
|
89708
90198
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
89709
90199
|
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
89710
|
-
const
|
|
90200
|
+
const config = rulesyncPermissions.getJson();
|
|
90201
|
+
const rules = config.permission[WEBFETCH_CATEGORY$1];
|
|
89711
90202
|
const patch = {};
|
|
89712
90203
|
if (rules !== void 0) {
|
|
89713
90204
|
const { allowedUrls, deniedUrls } = splitUrlRules(rules);
|
|
@@ -89715,6 +90206,13 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89715
90206
|
if (global) patch[ALLOWED_URLS_KEY] = allowedUrls.length > 0 ? allowedUrls : void 0;
|
|
89716
90207
|
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.`);
|
|
89717
90208
|
}
|
|
90209
|
+
const defaultPermissionMode = config.copilotcli?.defaultPermissionMode;
|
|
90210
|
+
if (defaultPermissionMode !== void 0) {
|
|
90211
|
+
if (global) {
|
|
90212
|
+
patch[DEFAULT_PERMISSION_MODE_KEY] = defaultPermissionMode;
|
|
90213
|
+
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.`);
|
|
90214
|
+
} 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.`);
|
|
90215
|
+
}
|
|
89718
90216
|
return new CopilotcliPermissions({
|
|
89719
90217
|
outputRoot,
|
|
89720
90218
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -89736,7 +90234,11 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
|
|
|
89736
90234
|
for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
|
|
89737
90235
|
if (this.global) for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) rules[pattern] ??= "allow";
|
|
89738
90236
|
const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY$1]: rules } : {};
|
|
89739
|
-
|
|
90237
|
+
const defaultPermissionMode = this.global ? settings[DEFAULT_PERMISSION_MODE_KEY] : void 0;
|
|
90238
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
|
|
90239
|
+
permission,
|
|
90240
|
+
...isCopilotcliDefaultPermissionMode(defaultPermissionMode) && { copilotcli: { defaultPermissionMode } }
|
|
90241
|
+
}, null, 2) });
|
|
89740
90242
|
}
|
|
89741
90243
|
parseSettings() {
|
|
89742
90244
|
const relativePath = (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath());
|
|
@@ -95241,6 +95743,7 @@ var MimocodePermissions = class extends OpencodePermissions {
|
|
|
95241
95743
|
// src/features/permissions/omp-permissions.ts
|
|
95242
95744
|
const CATCH_ALL_PATTERN$5 = "*";
|
|
95243
95745
|
const APPROVAL_MODE_KEY = "approvalMode";
|
|
95746
|
+
const RESTRICTED_FOLDERS_KEY = "restrictedFolders";
|
|
95244
95747
|
const ACTION_TO_OMP_APPROVAL = {
|
|
95245
95748
|
allow: "allow",
|
|
95246
95749
|
ask: "prompt",
|
|
@@ -95279,9 +95782,9 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
95279
95782
|
*
|
|
95280
95783
|
* - `tools.approval.<tool>: allow | prompt | deny` is a per-tool policy honored
|
|
95281
95784
|
* in every approval mode. A category's catch-all `*` rule maps here
|
|
95282
|
-
* (`ask` → `prompt`).
|
|
95283
|
-
* with pattern-specific rules collapses to one action (deny > ask >
|
|
95284
|
-
* an implicit `ask` when there is no catch-all) and a warning.
|
|
95785
|
+
* (`ask` → `prompt`). `tools.approval` has no per-path matcher, so a non-bash
|
|
95786
|
+
* category with pattern-specific rules collapses to one action (deny > ask >
|
|
95787
|
+
* allow, with an implicit `ask` when there is no catch-all) and a warning.
|
|
95285
95788
|
* - `bash.patterns: [{ match, approval }]` is an ordered, first-match-wins list
|
|
95286
95789
|
* of command globs (only `*` is a wildcard). Every `bash` rule except a
|
|
95287
95790
|
* catch-all `allow` is written there, ordered deny → prompt → allow so that
|
|
@@ -95298,12 +95801,15 @@ const MANAGED_OMP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
95298
95801
|
*
|
|
95299
95802
|
* The `omp.approvalMode` override writes `tools.approvalMode` (`always-ask`,
|
|
95300
95803
|
* `always-ask-cached`, `write` or `yolo`); without it the key is left as it is in the file, and on
|
|
95301
|
-
* import a recognized value is read back into the override.
|
|
95804
|
+
* import a recognized value is read back into the override. Likewise the
|
|
95805
|
+
* `omp.restrictedFolders` override writes `tools.restrictedFolders` (folders
|
|
95806
|
+
* where every action prompts in every approval mode, `yolo` included), is left
|
|
95807
|
+
* alone when absent, and is read back on import.
|
|
95302
95808
|
*
|
|
95303
95809
|
* `config.yml` carries every other oh-my-pi setting, so only `tools.approval`
|
|
95304
95810
|
* (the keys rulesync manages), `bash.patterns` and an authored
|
|
95305
|
-
* `tools.approvalMode` are rewritten; sibling
|
|
95306
|
-
* kept, and the file is never deleted.
|
|
95811
|
+
* `tools.approvalMode` or `tools.restrictedFolders` are rewritten; sibling
|
|
95812
|
+
* keys such as `bash.enabled` are kept, and the file is never deleted.
|
|
95307
95813
|
*
|
|
95308
95814
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md
|
|
95309
95815
|
* @see https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md
|
|
@@ -95352,6 +95858,7 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
95352
95858
|
logger
|
|
95353
95859
|
});
|
|
95354
95860
|
const approvalMode = config.omp?.approvalMode;
|
|
95861
|
+
const restrictedFolders = config.omp?.restrictedFolders;
|
|
95355
95862
|
const existingTools = isRecord(existing.tools) ? existing.tools : {};
|
|
95356
95863
|
const nextApproval = {
|
|
95357
95864
|
...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)])) : {},
|
|
@@ -95359,6 +95866,7 @@ var OmpPermissions = class OmpPermissions extends ToolPermissions {
|
|
|
95359
95866
|
};
|
|
95360
95867
|
const toolsSiblings = withoutKey(existingTools, "approval");
|
|
95361
95868
|
if (approvalMode !== void 0) toolsSiblings[APPROVAL_MODE_KEY] = approvalMode;
|
|
95869
|
+
if (restrictedFolders !== void 0) toolsSiblings[RESTRICTED_FOLDERS_KEY] = restrictedFolders;
|
|
95362
95870
|
const nextTools = Object.keys(nextApproval).length > 0 ? {
|
|
95363
95871
|
...toolsSiblings,
|
|
95364
95872
|
approval: nextApproval
|
|
@@ -95609,11 +96117,26 @@ function convertOmpToRulesync(config) {
|
|
|
95609
96117
|
}
|
|
95610
96118
|
if (Object.keys(bashRules).length > 0) permission.bash = bashRules;
|
|
95611
96119
|
const approvalMode = tools[APPROVAL_MODE_KEY];
|
|
96120
|
+
const restrictedFolders = importRestrictedFolders(tools[RESTRICTED_FOLDERS_KEY]);
|
|
96121
|
+
const omp = {
|
|
96122
|
+
...isOmpApprovalMode(approvalMode) && { approvalMode },
|
|
96123
|
+
...restrictedFolders !== void 0 && { restrictedFolders }
|
|
96124
|
+
};
|
|
95612
96125
|
return {
|
|
95613
96126
|
permission,
|
|
95614
|
-
...
|
|
96127
|
+
...Object.keys(omp).length > 0 && { omp }
|
|
95615
96128
|
};
|
|
95616
96129
|
}
|
|
96130
|
+
/**
|
|
96131
|
+
* `tools.restrictedFolders` as the override list: its string entries, or
|
|
96132
|
+
* `undefined` when it is unset or no entry survives (so a list of non-strings
|
|
96133
|
+
* is not imported as `[]` and then rewritten as an empty list).
|
|
96134
|
+
*/
|
|
96135
|
+
function importRestrictedFolders(value) {
|
|
96136
|
+
if (!Array.isArray(value)) return;
|
|
96137
|
+
const folders = value.filter((entry) => typeof entry === "string");
|
|
96138
|
+
return folders.length === 0 && value.length > 0 ? void 0 : folders;
|
|
96139
|
+
}
|
|
95617
96140
|
function isOmpApprovalMode(value) {
|
|
95618
96141
|
return OMP_APPROVAL_MODES.some((mode) => mode === value);
|
|
95619
96142
|
}
|
|
@@ -100710,19 +101233,31 @@ const CANONICAL_TO_VIBE_TOOL_NAMES = {
|
|
|
100710
101233
|
};
|
|
100711
101234
|
/**
|
|
100712
101235
|
* The tables earlier rulesync versions wrote for canonical categories that
|
|
100713
|
-
* Vibe 3.x
|
|
100714
|
-
* effect")
|
|
100715
|
-
* `agents
|
|
101236
|
+
* Vibe 3.x does not read: `[tools.grep]` (Vibe warns that it "has no
|
|
101237
|
+
* effect"), `[tools.task]` for the subagent tool v3.0.0 renamed to
|
|
101238
|
+
* `agents.*`, and `[tools.external_directory]` — Vibe has no such tool, and
|
|
101239
|
+
* its unused-settings check skips unknown tool names as custom/MCP tools, so
|
|
101240
|
+
* that deny was silently inert. A regenerate that configures the category removes them — and
|
|
100716
101241
|
* the matching `disabled_tools` entry — instead of leaving an inert table.
|
|
100717
101242
|
* Import still reads them (see {@link VIBE_TO_CANONICAL_TOOL_NAMES}), so a
|
|
100718
101243
|
* Vibe 2.x config is lifted into the canonical categories rather than lost.
|
|
100719
101244
|
*/
|
|
100720
101245
|
const VIBE_RETIRED_TOOL_TABLES = {
|
|
100721
101246
|
grep: "grep",
|
|
100722
|
-
agent: "task"
|
|
101247
|
+
agent: "task",
|
|
101248
|
+
external_directory: "external_directory"
|
|
100723
101249
|
};
|
|
100724
101250
|
/** The canonical category Vibe's `enable_subagents` switch expresses. */
|
|
100725
101251
|
const VIBE_SUBAGENT_CATEGORY = "agent";
|
|
101252
|
+
/** The canonical category Vibe's top-level `outside_workdir` setting expresses. */
|
|
101253
|
+
const VIBE_OUTSIDE_WORKDIR_CATEGORY = "external_directory";
|
|
101254
|
+
/**
|
|
101255
|
+
* The values of Vibe's `outside_workdir` (v3.0.3+). There is no allow value:
|
|
101256
|
+
* access outside the working directory is granted per path through a tool's
|
|
101257
|
+
* `allowlist` (`vibe-path:...`) or `--add-dir`.
|
|
101258
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
101259
|
+
*/
|
|
101260
|
+
const VIBE_OUTSIDE_WORKDIR_VALUES = /* @__PURE__ */ new Set(["ask", "deny"]);
|
|
100726
101261
|
const VIBE_TO_CANONICAL_TOOL_NAMES = {
|
|
100727
101262
|
bash: "bash",
|
|
100728
101263
|
read_file: "read",
|
|
@@ -100798,7 +101333,8 @@ const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
|
100798
101333
|
"grep",
|
|
100799
101334
|
"glob",
|
|
100800
101335
|
"notebookedit",
|
|
100801
|
-
"agent"
|
|
101336
|
+
"agent",
|
|
101337
|
+
"external_directory"
|
|
100802
101338
|
]);
|
|
100803
101339
|
var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
100804
101340
|
toml;
|
|
@@ -100879,7 +101415,11 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
100879
101415
|
rules: permission[VIBE_SUBAGENT_CATEGORY],
|
|
100880
101416
|
logger
|
|
100881
101417
|
});
|
|
100882
|
-
const
|
|
101418
|
+
const outsideWorkdirPatch = resolveOutsideWorkdirPatch({
|
|
101419
|
+
rules: permission[VIBE_OUTSIDE_WORKDIR_CATEGORY],
|
|
101420
|
+
logger
|
|
101421
|
+
});
|
|
101422
|
+
const tableCategories = Object.entries(permission).filter(([category]) => category !== VIBE_SUBAGENT_CATEGORY && category !== VIBE_OUTSIDE_WORKDIR_CATEGORY);
|
|
100883
101423
|
for (const [category, rules] of tableCategories) {
|
|
100884
101424
|
const vibeToolNames = resolveNames(category);
|
|
100885
101425
|
if (vibeToolNames === void 0) {
|
|
@@ -100953,7 +101493,8 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
100953
101493
|
enabledTools
|
|
100954
101494
|
}),
|
|
100955
101495
|
disabled_tools: disabledTools.size > 0 ? [...disabledTools].toSorted() : void 0,
|
|
100956
|
-
...enableSubagentsPatch
|
|
101496
|
+
...enableSubagentsPatch,
|
|
101497
|
+
...outsideWorkdirPatch
|
|
100957
101498
|
},
|
|
100958
101499
|
filePath
|
|
100959
101500
|
}),
|
|
@@ -100997,6 +101538,8 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
100997
101538
|
}
|
|
100998
101539
|
for (const category of silencedCategories) ensurePermission(permission, category)["*"] = "deny";
|
|
100999
101540
|
if (this.toml.enable_subagents === false) ensurePermission(permission, VIBE_SUBAGENT_CATEGORY)["*"] = "deny";
|
|
101541
|
+
const outsideWorkdir = this.toml.outside_workdir;
|
|
101542
|
+
if (typeof outsideWorkdir === "string" && VIBE_OUTSIDE_WORKDIR_VALUES.has(outsideWorkdir)) ensurePermission(permission, VIBE_OUTSIDE_WORKDIR_CATEGORY)["*"] = outsideWorkdir === "deny" ? "deny" : "ask";
|
|
101000
101543
|
warnOnUnresolvableDisabledTools({
|
|
101001
101544
|
entries: disabledToolEntries,
|
|
101002
101545
|
tools: this.toml.tools
|
|
@@ -101073,7 +101616,7 @@ function retireLegacyToolTables({ permission, tools, disabledTools, logger }) {
|
|
|
101073
101616
|
}
|
|
101074
101617
|
if (disabledTools.delete(legacyToolName)) removed.push(`disabled_tools entry '${legacyToolName}'`);
|
|
101075
101618
|
}
|
|
101076
|
-
if (removed.length > 0) logger?.warn(`Removed ${removed.join(", ")} from the Vibe config: Vibe 3.x
|
|
101619
|
+
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.`);
|
|
101077
101620
|
}
|
|
101078
101621
|
/**
|
|
101079
101622
|
* Report the rules of a category that has no Vibe tool table. `grep` gets its
|
|
@@ -101109,6 +101652,30 @@ function resolveEnableSubagentsPatch({ rules, logger }) {
|
|
|
101109
101652
|
return { enable_subagents: action === "deny" ? false : void 0 };
|
|
101110
101653
|
}
|
|
101111
101654
|
/**
|
|
101655
|
+
* Translate the canonical `external_directory` category into Vibe's top-level
|
|
101656
|
+
* `outside_workdir` setting (v3.0.3+), which decides whether a file or shell
|
|
101657
|
+
* tool call reaching outside the working directory prompts (`"ask"`, the
|
|
101658
|
+
* default) or is refused with a tool error (`"deny"`). A `*` deny or ask writes
|
|
101659
|
+
* that value; a `*` allow has no Vibe value, so it is warned about and the key
|
|
101660
|
+
* is removed, leaving Vibe's default prompt. Without a `*` rule the key is left
|
|
101661
|
+
* as the file has it. Pattern rules are skipped with a warning: path grants go
|
|
101662
|
+
* in a tool's `allowlist` (`vibe-path:...`) instead.
|
|
101663
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/README.md
|
|
101664
|
+
* @see https://github.com/mistralai/mistral-vibe/blob/v3.0.3/vibe/app_server/_unified_permissions.py
|
|
101665
|
+
*/
|
|
101666
|
+
function resolveOutsideWorkdirPatch({ rules, logger }) {
|
|
101667
|
+
if (rules === void 0) return {};
|
|
101668
|
+
const patternCount = Object.keys(rules).filter((pattern) => pattern !== "*").length;
|
|
101669
|
+
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.`);
|
|
101670
|
+
const action = rules["*"];
|
|
101671
|
+
if (action === void 0) return {};
|
|
101672
|
+
if (action === "allow") {
|
|
101673
|
+
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.`);
|
|
101674
|
+
return { outside_workdir: void 0 };
|
|
101675
|
+
}
|
|
101676
|
+
return { outside_workdir: action };
|
|
101677
|
+
}
|
|
101678
|
+
/**
|
|
101112
101679
|
* Apply the Vibe-scoped override's per-tool `sensitive_patterns` (patterns that
|
|
101113
101680
|
* escalate to ASK even when the base permission is ALWAYS). rulesync owns this
|
|
101114
101681
|
* list for any category the override names: a present list is set, an empty
|