rulesync 9.2.0 → 9.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +5 -62
- package/dist/cli/index.js +3 -60
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-BkxwFCDM.js → import-BxqZVTKb.js} +1799 -509
- package/dist/import-BxqZVTKb.js.map +1 -0
- package/dist/{import-H0MKtu9x.cjs → import-DVGMvuhx.cjs} +1813 -619
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-BkxwFCDM.js.map +0 -1
|
@@ -114,22 +114,22 @@ function formatError(error) {
|
|
|
114
114
|
}
|
|
115
115
|
//#endregion
|
|
116
116
|
//#region src/constants/rulesync-paths.ts
|
|
117
|
-
const { join: join$
|
|
117
|
+
const { join: join$247 } = node_path.posix;
|
|
118
118
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
119
119
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
120
120
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
121
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
122
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
123
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
124
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
125
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
126
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
121
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
122
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
123
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
124
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
125
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
126
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
127
127
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
128
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
128
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
129
129
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
130
130
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
131
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
132
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
131
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
132
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$247(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
133
133
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
134
134
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
135
135
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
@@ -3029,8 +3029,8 @@ const DEVIN_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "skills");
|
|
|
3029
3029
|
const DEVIN_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_DIR, "agents");
|
|
3030
3030
|
const DEVIN_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(".config", "devin");
|
|
3031
3031
|
const DEVIN_GLOBAL_AGENTS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "agents");
|
|
3032
|
+
const DEVIN_GLOBAL_SKILLS_DIR_PATH = (0, node_path.join)(DEVIN_GLOBAL_CONFIG_DIR_PATH, "skills");
|
|
3032
3033
|
const CODEIUM_WINDSURF_GLOBAL_WORKFLOWS_DIR_PATH = (0, node_path.join)(CODEIUM_WINDSURF_DIR, "global_workflows");
|
|
3033
|
-
const CODEIUM_WINDSURF_SKILLS_DIR_PATH = (0, node_path.join)(CODEIUM_WINDSURF_DIR, "skills");
|
|
3034
3034
|
const DEVIN_CONFIG_FILE_NAME = "config.json";
|
|
3035
3035
|
const DEVIN_HOOKS_V1_FILE_NAME = "hooks.v1.json";
|
|
3036
3036
|
const DEVIN_GLOBAL_AGENTS_FILE_NAME = "AGENTS.md";
|
|
@@ -3919,6 +3919,297 @@ const OPENCODE_JSON_FILE_NAME = "opencode.json";
|
|
|
3919
3919
|
const OPENCODE_RULE_FILE_NAME = "AGENTS.md";
|
|
3920
3920
|
const OPENCODE_HOOKS_FILE_NAME = "rulesync-hooks.js";
|
|
3921
3921
|
//#endregion
|
|
3922
|
+
//#region src/constants/takt-paths.ts
|
|
3923
|
+
const TAKT_DIR = ".takt";
|
|
3924
|
+
const TAKT_FACETS_SUBDIR = "facets";
|
|
3925
|
+
const TAKT_FACETS_DIR_PATH = (0, node_path.join)(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
3926
|
+
const TAKT_RULES_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "policies");
|
|
3927
|
+
const TAKT_COMMANDS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "instructions");
|
|
3928
|
+
const TAKT_SKILLS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
3929
|
+
const TAKT_SUBAGENTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "personas");
|
|
3930
|
+
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
3931
|
+
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
3932
|
+
/**
|
|
3933
|
+
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
3934
|
+
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
3935
|
+
* profiles (including permission modes), and other Takt settings.
|
|
3936
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3937
|
+
*/
|
|
3938
|
+
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
3939
|
+
/**
|
|
3940
|
+
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
3941
|
+
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
3942
|
+
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
3943
|
+
* server using it is permitted to run.
|
|
3944
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3945
|
+
*/
|
|
3946
|
+
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
3947
|
+
//#endregion
|
|
3948
|
+
//#region src/utils/prototype-pollution.ts
|
|
3949
|
+
/**
|
|
3950
|
+
* Keys that, if walked into when constructing or merging objects from
|
|
3951
|
+
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
3952
|
+
* chain) and propagate state to every other object in the runtime. Any code
|
|
3953
|
+
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
3954
|
+
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
3955
|
+
*/
|
|
3956
|
+
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
3957
|
+
"__proto__",
|
|
3958
|
+
"constructor",
|
|
3959
|
+
"prototype"
|
|
3960
|
+
]);
|
|
3961
|
+
function isPrototypePollutionKey(key) {
|
|
3962
|
+
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
3963
|
+
}
|
|
3964
|
+
/**
|
|
3965
|
+
* Returns a shallow copy of a record's own entries with every
|
|
3966
|
+
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
3967
|
+
*
|
|
3968
|
+
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
3969
|
+
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
3970
|
+
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
3971
|
+
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
3972
|
+
* prototype). Walking the entries through this helper severs that path while
|
|
3973
|
+
* preserving every legitimate key.
|
|
3974
|
+
*/
|
|
3975
|
+
function omitPrototypePollutionKeys(record) {
|
|
3976
|
+
const sanitized = {};
|
|
3977
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3978
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
3979
|
+
sanitized[key] = value;
|
|
3980
|
+
}
|
|
3981
|
+
return sanitized;
|
|
3982
|
+
}
|
|
3983
|
+
//#endregion
|
|
3984
|
+
//#region src/features/shared/shared-config-gateway.ts
|
|
3985
|
+
function sanitizeSharedConfigValue(value) {
|
|
3986
|
+
if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
|
|
3987
|
+
if (!isPlainObject(value)) return value;
|
|
3988
|
+
const result = {};
|
|
3989
|
+
for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
|
|
3990
|
+
return result;
|
|
3991
|
+
}
|
|
3992
|
+
/**
|
|
3993
|
+
* Parse a shared config file into a plain document: an empty/whitespace file
|
|
3994
|
+
* is `{}`, prototype-pollution keys are dropped recursively, and a non-mapping
|
|
3995
|
+
* root follows `invalidRootPolicy`. Syntax errors are wrapped with the file
|
|
3996
|
+
* path when one is given.
|
|
3997
|
+
*/
|
|
3998
|
+
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty" }) {
|
|
3999
|
+
if (fileContent.trim() === "") return {};
|
|
4000
|
+
const at = filePath === void 0 ? "" : ` at ${filePath}`;
|
|
4001
|
+
let parsed;
|
|
4002
|
+
try {
|
|
4003
|
+
if (format === "yaml") parsed = (0, js_yaml.load)(fileContent);
|
|
4004
|
+
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
4005
|
+
else parsed = (0, jsonc_parser.parse)(fileContent);
|
|
4006
|
+
} catch (error) {
|
|
4007
|
+
throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
|
|
4008
|
+
}
|
|
4009
|
+
if (parsed === void 0 || parsed === null) return {};
|
|
4010
|
+
if (!isPlainObject(parsed)) {
|
|
4011
|
+
if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
|
|
4012
|
+
return {};
|
|
4013
|
+
}
|
|
4014
|
+
return sanitizeSharedConfigValue(parsed);
|
|
4015
|
+
}
|
|
4016
|
+
/**
|
|
4017
|
+
* Serialize a shared config document. YAML output always ends with exactly one
|
|
4018
|
+
* newline; JSON output matches the 2-space `JSON.stringify` shape the JSON
|
|
4019
|
+
* writers have always emitted (no trailing newline).
|
|
4020
|
+
*/
|
|
4021
|
+
function stringifySharedConfig({ format, document }) {
|
|
4022
|
+
if (format === "yaml") return (0, js_yaml.dump)(document, {
|
|
4023
|
+
noRefs: true,
|
|
4024
|
+
sortKeys: false
|
|
4025
|
+
}).trimEnd() + "\n";
|
|
4026
|
+
return JSON.stringify(document, null, 2);
|
|
4027
|
+
}
|
|
4028
|
+
/**
|
|
4029
|
+
* Shallow merge: every top-level key in `patch` replaces the base key
|
|
4030
|
+
* wholesale; all other base keys are preserved. The policy for a feature that
|
|
4031
|
+
* owns a fixed set of top-level keys.
|
|
4032
|
+
*/
|
|
4033
|
+
function mergeSharedConfigShallow({ base, patch }) {
|
|
4034
|
+
return {
|
|
4035
|
+
...base,
|
|
4036
|
+
...sanitizeSharedConfigValue(patch)
|
|
4037
|
+
};
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* Deep merge (`patch` wins): nested plain objects are merged key-by-key; every
|
|
4041
|
+
* other value (arrays, scalars) is replaced wholesale. The policy for a
|
|
4042
|
+
* feature whose contribution interleaves with user-authored siblings at any
|
|
4043
|
+
* depth (e.g. permissions overlays onto `approvals`/`security` structures, or
|
|
4044
|
+
* per-provider option tables) — nested sibling keys are preserved by
|
|
4045
|
+
* construction instead of by per-tool re-implementation. Prototype-pollution
|
|
4046
|
+
* keys are dropped.
|
|
4047
|
+
*/
|
|
4048
|
+
function mergeSharedConfigDeep({ base, patch }) {
|
|
4049
|
+
const result = { ...base };
|
|
4050
|
+
for (const [key, patchValue] of Object.entries(patch)) {
|
|
4051
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4052
|
+
const baseValue = result[key];
|
|
4053
|
+
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
|
|
4054
|
+
base: baseValue,
|
|
4055
|
+
patch: patchValue
|
|
4056
|
+
});
|
|
4057
|
+
else result[key] = sanitizeSharedConfigValue(patchValue);
|
|
4058
|
+
}
|
|
4059
|
+
return result;
|
|
4060
|
+
}
|
|
4061
|
+
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
4062
|
+
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
4063
|
+
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
4064
|
+
/**
|
|
4065
|
+
* Who owns what in each gateway-managed shared config file, and which policy
|
|
4066
|
+
* resolves conflicts. Keys are `dir/file` tokens matching
|
|
4067
|
+
* `deriveSharedFileWriters()`; a test keeps each entry's feature set in
|
|
4068
|
+
* lock-step with the writers derived from the processor registry, so an
|
|
4069
|
+
* undeclared writer fails CI instead of merging by accident.
|
|
4070
|
+
*/
|
|
4071
|
+
const SHARED_CONFIG_OWNERSHIP = {
|
|
4072
|
+
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
4073
|
+
format: "json",
|
|
4074
|
+
features: {
|
|
4075
|
+
ignore: {
|
|
4076
|
+
kind: "custom",
|
|
4077
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
4078
|
+
},
|
|
4079
|
+
hooks: {
|
|
4080
|
+
kind: "replace-owned-keys",
|
|
4081
|
+
ownedKeys: ["hooks"]
|
|
4082
|
+
},
|
|
4083
|
+
permissions: {
|
|
4084
|
+
kind: "custom",
|
|
4085
|
+
policyFunction: "applyPermissions"
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
},
|
|
4089
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: {
|
|
4090
|
+
format: "yaml",
|
|
4091
|
+
features: {
|
|
4092
|
+
subagents: {
|
|
4093
|
+
kind: "replace-owned-keys",
|
|
4094
|
+
ownedKeys: ["plugins"]
|
|
4095
|
+
},
|
|
4096
|
+
mcp: {
|
|
4097
|
+
kind: "replace-owned-keys",
|
|
4098
|
+
ownedKeys: ["mcp_servers"]
|
|
4099
|
+
},
|
|
4100
|
+
hooks: {
|
|
4101
|
+
kind: "replace-owned-keys",
|
|
4102
|
+
ownedKeys: ["hooks"]
|
|
4103
|
+
},
|
|
4104
|
+
permissions: {
|
|
4105
|
+
kind: "deep-merge",
|
|
4106
|
+
replaceKeys: ["permissions"]
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
},
|
|
4110
|
+
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
4111
|
+
format: "yaml",
|
|
4112
|
+
invalidRootPolicy: "error",
|
|
4113
|
+
features: {
|
|
4114
|
+
mcp: {
|
|
4115
|
+
kind: "replace-owned-keys",
|
|
4116
|
+
ownedKeys: [TAKT_WORKFLOW_MCP_SERVERS_KEY]
|
|
4117
|
+
},
|
|
4118
|
+
permissions: { kind: "deep-merge" }
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
/**
|
|
4123
|
+
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
4124
|
+
* the existing content, merge the patch under the feature's declared policy,
|
|
4125
|
+
* and serialize. Throws when the file or feature is undeclared, when a
|
|
4126
|
+
* `replace-owned-keys` patch strays outside its owned keys, or when the
|
|
4127
|
+
* feature's policy is `custom` (those calls go to the named policy function
|
|
4128
|
+
* instead).
|
|
4129
|
+
*/
|
|
4130
|
+
function applySharedConfigPatch({ fileKey, feature, existingContent, patch, filePath }) {
|
|
4131
|
+
const declaration = SHARED_CONFIG_OWNERSHIP[fileKey];
|
|
4132
|
+
if (!declaration) throw new Error(`Shared config file '${fileKey}' has no SHARED_CONFIG_OWNERSHIP declaration; declare its writers and policies before writing it through the gateway.`);
|
|
4133
|
+
const policy = declaration.features[feature];
|
|
4134
|
+
if (!policy) throw new Error(`Feature '${feature}' declares no ownership of '${fileKey}'; add it to SHARED_CONFIG_OWNERSHIP before writing.`);
|
|
4135
|
+
if (policy.kind === "custom") throw new Error(`Feature '${feature}' writes '${fileKey}' through its dedicated policy function '${policy.policyFunction}' in shared-config-gateway.ts, not applySharedConfigPatch.`);
|
|
4136
|
+
const base = parseSharedConfig({
|
|
4137
|
+
format: declaration.format,
|
|
4138
|
+
fileContent: existingContent,
|
|
4139
|
+
filePath,
|
|
4140
|
+
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy }
|
|
4141
|
+
});
|
|
4142
|
+
if (policy.kind === "replace-owned-keys") {
|
|
4143
|
+
const unowned = Object.keys(patch).filter((key) => !policy.ownedKeys.includes(key));
|
|
4144
|
+
if (unowned.length > 0) throw new Error(`Feature '${feature}' tried to write undeclared keys [${unowned.join(", ")}] to '${fileKey}'; extend its ownedKeys declaration if that ownership is intended.`);
|
|
4145
|
+
return stringifySharedConfig({
|
|
4146
|
+
format: declaration.format,
|
|
4147
|
+
document: mergeSharedConfigShallow({
|
|
4148
|
+
base,
|
|
4149
|
+
patch
|
|
4150
|
+
})
|
|
4151
|
+
});
|
|
4152
|
+
}
|
|
4153
|
+
const merged = mergeSharedConfigDeep({
|
|
4154
|
+
base,
|
|
4155
|
+
patch
|
|
4156
|
+
});
|
|
4157
|
+
for (const key of policy.replaceKeys ?? []) if (patch[key] !== void 0) merged[key] = sanitizeSharedConfigValue(patch[key]);
|
|
4158
|
+
return stringifySharedConfig({
|
|
4159
|
+
format: declaration.format,
|
|
4160
|
+
document: merged
|
|
4161
|
+
});
|
|
4162
|
+
}
|
|
4163
|
+
const READ_TOOL_NAME = "Read";
|
|
4164
|
+
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
4165
|
+
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
4166
|
+
const parsePermissionsBlock = (settings) => {
|
|
4167
|
+
const permissions = settings.permissions ?? {};
|
|
4168
|
+
return {
|
|
4169
|
+
allow: permissions.allow ?? [],
|
|
4170
|
+
ask: permissions.ask ?? [],
|
|
4171
|
+
deny: permissions.deny ?? []
|
|
4172
|
+
};
|
|
4173
|
+
};
|
|
4174
|
+
const withPermissions = (settings, next) => {
|
|
4175
|
+
const permissions = { ...settings.permissions };
|
|
4176
|
+
const assign = (key, values) => {
|
|
4177
|
+
if (values.length > 0) permissions[key] = values;
|
|
4178
|
+
else delete permissions[key];
|
|
4179
|
+
};
|
|
4180
|
+
assign("allow", next.allow);
|
|
4181
|
+
assign("ask", next.ask);
|
|
4182
|
+
assign("deny", next.deny);
|
|
4183
|
+
return {
|
|
4184
|
+
...settings,
|
|
4185
|
+
permissions
|
|
4186
|
+
};
|
|
4187
|
+
};
|
|
4188
|
+
const applyIgnoreReadDenies = (params) => {
|
|
4189
|
+
const { settings, readDenies } = params;
|
|
4190
|
+
const current = parsePermissionsBlock(settings);
|
|
4191
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
4192
|
+
return withPermissions(settings, {
|
|
4193
|
+
allow: current.allow,
|
|
4194
|
+
ask: current.ask,
|
|
4195
|
+
deny: (0, es_toolkit.uniq)([...preservedDeny, ...readDenies].toSorted())
|
|
4196
|
+
});
|
|
4197
|
+
};
|
|
4198
|
+
const applyPermissions = (params) => {
|
|
4199
|
+
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
4200
|
+
const current = parsePermissionsBlock(settings);
|
|
4201
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
4202
|
+
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
4203
|
+
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
4204
|
+
if (overwrittenReadDenies.length > 0) logger.warn(`Permissions feature manages '${READ_TOOL_NAME}' tool and will overwrite ${overwrittenReadDenies.length} existing ${READ_TOOL_NAME} deny entries. Permissions take precedence.`);
|
|
4205
|
+
}
|
|
4206
|
+
return withPermissions(settings, {
|
|
4207
|
+
allow: (0, es_toolkit.uniq)([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
4208
|
+
ask: (0, es_toolkit.uniq)([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
4209
|
+
deny: (0, es_toolkit.uniq)([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
4210
|
+
});
|
|
4211
|
+
};
|
|
4212
|
+
//#endregion
|
|
3922
4213
|
//#region src/features/opencode-config.ts
|
|
3923
4214
|
/**
|
|
3924
4215
|
* Reads and parses the OpenCode config (`opencode.jsonc` preferred, then
|
|
@@ -3943,9 +4234,10 @@ async function readOpencodeConfig({ outputRoot, global = false }) {
|
|
|
3943
4234
|
});
|
|
3944
4235
|
const fileContent = await readFileContentOrNull((0, node_path.join)(configDir, "opencode.jsonc")) ?? await readFileContentOrNull((0, node_path.join)(configDir, "opencode.json"));
|
|
3945
4236
|
if (!fileContent) return {};
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4237
|
+
return parseSharedConfig({
|
|
4238
|
+
format: "jsonc",
|
|
4239
|
+
fileContent
|
|
4240
|
+
});
|
|
3949
4241
|
}
|
|
3950
4242
|
/**
|
|
3951
4243
|
* Narrows an unknown value to a plain record of entries keyed by name, as used
|
|
@@ -4854,32 +5146,6 @@ var RovodevPromptsManifest = class extends ToolFile {
|
|
|
4854
5146
|
}
|
|
4855
5147
|
};
|
|
4856
5148
|
//#endregion
|
|
4857
|
-
//#region src/constants/takt-paths.ts
|
|
4858
|
-
const TAKT_DIR = ".takt";
|
|
4859
|
-
const TAKT_FACETS_SUBDIR = "facets";
|
|
4860
|
-
const TAKT_FACETS_DIR_PATH = (0, node_path.join)(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
4861
|
-
const TAKT_RULES_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "policies");
|
|
4862
|
-
const TAKT_COMMANDS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "instructions");
|
|
4863
|
-
const TAKT_SKILLS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
4864
|
-
const TAKT_SUBAGENTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "personas");
|
|
4865
|
-
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
4866
|
-
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
4867
|
-
/**
|
|
4868
|
-
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
4869
|
-
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
4870
|
-
* profiles (including permission modes), and other Takt settings.
|
|
4871
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4872
|
-
*/
|
|
4873
|
-
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
4874
|
-
/**
|
|
4875
|
-
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
4876
|
-
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
4877
|
-
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
4878
|
-
* server using it is permitted to run.
|
|
4879
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4880
|
-
*/
|
|
4881
|
-
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
4882
|
-
//#endregion
|
|
4883
5149
|
//#region src/features/takt-shared.ts
|
|
4884
5150
|
/**
|
|
4885
5151
|
* Shared utilities for all TAKT-* tool file classes.
|
|
@@ -5715,6 +5981,8 @@ const DEEPAGENTS_HOOK_EVENTS = [
|
|
|
5715
5981
|
"sessionEnd",
|
|
5716
5982
|
"beforeSubmitPrompt",
|
|
5717
5983
|
"permissionRequest",
|
|
5984
|
+
"preToolUse",
|
|
5985
|
+
"postToolUse",
|
|
5718
5986
|
"postToolUseFailure",
|
|
5719
5987
|
"stop",
|
|
5720
5988
|
"preCompact",
|
|
@@ -5740,7 +6008,13 @@ const CODEXCLI_HOOK_EVENTS = [
|
|
|
5740
6008
|
* Goose adopts the Open Plugins hooks spec: each plugin's `hooks/hooks.json`
|
|
5741
6009
|
* maps PascalCase event names to matcher/handler arrays. Every Goose event has a
|
|
5742
6010
|
* 1:1 canonical equivalent, so no new canonical events are required.
|
|
5743
|
-
*
|
|
6011
|
+
*
|
|
6012
|
+
* Goose's `HookEvent` enum defines exactly these 11 events (v1.41.0). Notably it
|
|
6013
|
+
* has NO `SubagentStart`/`SubagentStop` arms — emitting them would write keys
|
|
6014
|
+
* Goose silently ignores, so `subagentStart`/`subagentStop` are intentionally
|
|
6015
|
+
* excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
|
|
6016
|
+
* @see https://github.com/block/goose/blob/v1.41.0/crates/goose/src/hooks/mod.rs
|
|
6017
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
5744
6018
|
*/
|
|
5745
6019
|
const GOOSE_HOOK_EVENTS = [
|
|
5746
6020
|
"sessionStart",
|
|
@@ -5753,9 +6027,7 @@ const GOOSE_HOOK_EVENTS = [
|
|
|
5753
6027
|
"beforeReadFile",
|
|
5754
6028
|
"afterFileEdit",
|
|
5755
6029
|
"beforeShellExecution",
|
|
5756
|
-
"afterShellExecution"
|
|
5757
|
-
"subagentStart",
|
|
5758
|
-
"subagentStop"
|
|
6030
|
+
"afterShellExecution"
|
|
5759
6031
|
];
|
|
5760
6032
|
/** Hook events supported by Kiro CLI. */
|
|
5761
6033
|
const KIRO_HOOK_EVENTS = [
|
|
@@ -6208,9 +6480,7 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
|
|
|
6208
6480
|
beforeReadFile: "BeforeReadFile",
|
|
6209
6481
|
afterFileEdit: "AfterFileEdit",
|
|
6210
6482
|
beforeShellExecution: "BeforeShellExecution",
|
|
6211
|
-
afterShellExecution: "AfterShellExecution"
|
|
6212
|
-
subagentStart: "SubagentStart",
|
|
6213
|
-
subagentStop: "SubagentStop"
|
|
6483
|
+
afterShellExecution: "AfterShellExecution"
|
|
6214
6484
|
};
|
|
6215
6485
|
/**
|
|
6216
6486
|
* Map Goose PascalCase event names to canonical camelCase.
|
|
@@ -6224,6 +6494,8 @@ const CANONICAL_TO_DEEPAGENTS_EVENT_NAMES = {
|
|
|
6224
6494
|
sessionEnd: "session.end",
|
|
6225
6495
|
beforeSubmitPrompt: "user.prompt",
|
|
6226
6496
|
permissionRequest: "permission.request",
|
|
6497
|
+
preToolUse: "tool.use",
|
|
6498
|
+
postToolUse: "tool.result",
|
|
6227
6499
|
postToolUseFailure: "tool.error",
|
|
6228
6500
|
stop: "task.complete",
|
|
6229
6501
|
preCompact: "context.compact",
|
|
@@ -6351,42 +6623,6 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
6351
6623
|
*/
|
|
6352
6624
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6353
6625
|
//#endregion
|
|
6354
|
-
//#region src/utils/prototype-pollution.ts
|
|
6355
|
-
/**
|
|
6356
|
-
* Keys that, if walked into when constructing or merging objects from
|
|
6357
|
-
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
6358
|
-
* chain) and propagate state to every other object in the runtime. Any code
|
|
6359
|
-
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
6360
|
-
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
6361
|
-
*/
|
|
6362
|
-
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
6363
|
-
"__proto__",
|
|
6364
|
-
"constructor",
|
|
6365
|
-
"prototype"
|
|
6366
|
-
]);
|
|
6367
|
-
function isPrototypePollutionKey(key) {
|
|
6368
|
-
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
6369
|
-
}
|
|
6370
|
-
/**
|
|
6371
|
-
* Returns a shallow copy of a record's own entries with every
|
|
6372
|
-
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
6373
|
-
*
|
|
6374
|
-
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
6375
|
-
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
6376
|
-
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
6377
|
-
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
6378
|
-
* prototype). Walking the entries through this helper severs that path while
|
|
6379
|
-
* preserving every legitimate key.
|
|
6380
|
-
*/
|
|
6381
|
-
function omitPrototypePollutionKeys(record) {
|
|
6382
|
-
const sanitized = {};
|
|
6383
|
-
for (const [key, value] of Object.entries(record)) {
|
|
6384
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
6385
|
-
sanitized[key] = value;
|
|
6386
|
-
}
|
|
6387
|
-
return sanitized;
|
|
6388
|
-
}
|
|
6389
|
-
//#endregion
|
|
6390
6626
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
6391
6627
|
function isToolMatcherEntry(x) {
|
|
6392
6628
|
if (x === null || typeof x !== "object") return false;
|
|
@@ -7045,24 +7281,19 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
|
7045
7281
|
const paths = ClaudecodeHooks.getSettablePaths({ global });
|
|
7046
7282
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7047
7283
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
7048
|
-
let settings;
|
|
7049
|
-
try {
|
|
7050
|
-
settings = JSON.parse(existingContent);
|
|
7051
|
-
} catch (error) {
|
|
7052
|
-
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7053
|
-
}
|
|
7054
7284
|
const config = rulesyncHooks.getJson();
|
|
7055
|
-
const
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
7059
|
-
|
|
7285
|
+
const fileContent = applySharedConfigPatch({
|
|
7286
|
+
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
7287
|
+
feature: "hooks",
|
|
7288
|
+
existingContent,
|
|
7289
|
+
patch: { hooks: canonicalToToolHooks({
|
|
7290
|
+
config,
|
|
7291
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
7292
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
7293
|
+
logger
|
|
7294
|
+
}) },
|
|
7295
|
+
filePath
|
|
7060
7296
|
});
|
|
7061
|
-
const merged = {
|
|
7062
|
-
...settings,
|
|
7063
|
-
hooks: claudeHooks
|
|
7064
|
-
};
|
|
7065
|
-
const fileContent = JSON.stringify(merged, null, 2);
|
|
7066
7297
|
return new ClaudecodeHooks({
|
|
7067
7298
|
outputRoot,
|
|
7068
7299
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -8218,7 +8449,7 @@ const GOOSE_CONVERTER_CONFIG = {
|
|
|
8218
8449
|
*
|
|
8219
8450
|
* The JSON shape matches Claude Code's: each PascalCase event maps to an array of
|
|
8220
8451
|
* `{ matcher, hooks: [{ type: "command", command }] }` entries.
|
|
8221
|
-
* @see https://
|
|
8452
|
+
* @see https://block.github.io/goose/docs/guides/context-engineering/hooks/
|
|
8222
8453
|
*/
|
|
8223
8454
|
var GooseHooks = class GooseHooks extends ToolHooks {
|
|
8224
8455
|
constructor(params) {
|
|
@@ -8269,7 +8500,7 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8269
8500
|
throw new Error(`Failed to parse Goose hooks content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
8270
8501
|
}
|
|
8271
8502
|
const hooks = toolHooksToCanonical({
|
|
8272
|
-
hooks: parsed.hooks,
|
|
8503
|
+
hooks: parsed.hooks && typeof parsed.hooks === "object" && !Array.isArray(parsed.hooks) ? Object.fromEntries(Object.entries(parsed.hooks).filter(([eventName]) => Object.hasOwn(GOOSE_TO_CANONICAL_EVENT_NAMES, eventName))) : parsed.hooks,
|
|
8273
8504
|
converterConfig: GOOSE_CONVERTER_CONFIG
|
|
8274
8505
|
});
|
|
8275
8506
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
@@ -8294,37 +8525,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8294
8525
|
}
|
|
8295
8526
|
};
|
|
8296
8527
|
//#endregion
|
|
8297
|
-
//#region src/features/hermes-config.ts
|
|
8298
|
-
function sanitizeHermesConfigValue(value) {
|
|
8299
|
-
if (Array.isArray(value)) return value.map(sanitizeHermesConfigValue);
|
|
8300
|
-
if (!isPlainObject(value)) return value;
|
|
8301
|
-
const sanitized = omitPrototypePollutionKeys(value);
|
|
8302
|
-
const result = {};
|
|
8303
|
-
for (const [key, nestedValue] of Object.entries(sanitized)) {
|
|
8304
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8305
|
-
result[key] = sanitizeHermesConfigValue(nestedValue);
|
|
8306
|
-
}
|
|
8307
|
-
return result;
|
|
8308
|
-
}
|
|
8309
|
-
function parseHermesConfig(fileContent) {
|
|
8310
|
-
if (!fileContent.trim()) return {};
|
|
8311
|
-
const parsed = (0, js_yaml.load)(fileContent);
|
|
8312
|
-
if (isPlainObject(parsed)) return sanitizeHermesConfigValue(parsed);
|
|
8313
|
-
return {};
|
|
8314
|
-
}
|
|
8315
|
-
function stringifyHermesConfig(config) {
|
|
8316
|
-
return (0, js_yaml.dump)(config, {
|
|
8317
|
-
noRefs: true,
|
|
8318
|
-
sortKeys: false
|
|
8319
|
-
}).trimEnd() + "\n";
|
|
8320
|
-
}
|
|
8321
|
-
function mergeHermesConfig(fileContent, patch) {
|
|
8322
|
-
return stringifyHermesConfig({
|
|
8323
|
-
...parseHermesConfig(fileContent),
|
|
8324
|
-
...patch
|
|
8325
|
-
});
|
|
8326
|
-
}
|
|
8327
|
-
//#endregion
|
|
8328
8528
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8329
8529
|
/**
|
|
8330
8530
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -8452,10 +8652,21 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8452
8652
|
return true;
|
|
8453
8653
|
}
|
|
8454
8654
|
setFileContent(fileContent) {
|
|
8455
|
-
this.fileContent =
|
|
8655
|
+
this.fileContent = applySharedConfigPatch({
|
|
8656
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
8657
|
+
feature: "hooks",
|
|
8658
|
+
existingContent: fileContent,
|
|
8659
|
+
patch: parseSharedConfig({
|
|
8660
|
+
format: "yaml",
|
|
8661
|
+
fileContent: this.fileContent
|
|
8662
|
+
})
|
|
8663
|
+
});
|
|
8456
8664
|
}
|
|
8457
8665
|
toRulesyncHooks() {
|
|
8458
|
-
const hooks = hermesHooksToCanonical(
|
|
8666
|
+
const hooks = hermesHooksToCanonical(parseSharedConfig({
|
|
8667
|
+
format: "yaml",
|
|
8668
|
+
fileContent: this.getFileContent()
|
|
8669
|
+
}).hooks);
|
|
8459
8670
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
8460
8671
|
version: 1,
|
|
8461
8672
|
hooks
|
|
@@ -8465,11 +8676,14 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8465
8676
|
const config = rulesyncHooks.getJson();
|
|
8466
8677
|
return new HermesagentHooks({
|
|
8467
8678
|
outputRoot,
|
|
8468
|
-
fileContent:
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8679
|
+
fileContent: stringifySharedConfig({
|
|
8680
|
+
format: "yaml",
|
|
8681
|
+
document: { hooks: canonicalToHermesHooks({
|
|
8682
|
+
config,
|
|
8683
|
+
toolOverrideHooks: config.hermesagent?.hooks,
|
|
8684
|
+
logger
|
|
8685
|
+
}) }
|
|
8686
|
+
})
|
|
8473
8687
|
});
|
|
8474
8688
|
}
|
|
8475
8689
|
};
|
|
@@ -10489,66 +10703,6 @@ var AugmentcodeIgnore = class AugmentcodeIgnore extends ToolIgnore {
|
|
|
10489
10703
|
}
|
|
10490
10704
|
};
|
|
10491
10705
|
//#endregion
|
|
10492
|
-
//#region src/features/claudecode-settings-gateway.ts
|
|
10493
|
-
/**
|
|
10494
|
-
* Single owner of the `.claude/settings.json` `permissions` block, which both
|
|
10495
|
-
* `ignore` (writes `Read(...)` into `permissions.deny`) and `permissions`
|
|
10496
|
-
* (writes the whole `allow`/`ask`/`deny`) read-modify-write. The entry format,
|
|
10497
|
-
* the merge, and the cross-feature ownership rule (permissions' explicit `Read`
|
|
10498
|
-
* rules win over ignore-derived `Read` denies) used to be duplicated across both
|
|
10499
|
-
* feature files; they live here once so each feature just states its intent and
|
|
10500
|
-
* never reasons about the other's existence.
|
|
10501
|
-
*/
|
|
10502
|
-
const READ_TOOL_NAME = "Read";
|
|
10503
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
10504
|
-
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
10505
|
-
const parsePermissionsBlock = (settings) => {
|
|
10506
|
-
const permissions = settings.permissions ?? {};
|
|
10507
|
-
return {
|
|
10508
|
-
allow: permissions.allow ?? [],
|
|
10509
|
-
ask: permissions.ask ?? [],
|
|
10510
|
-
deny: permissions.deny ?? []
|
|
10511
|
-
};
|
|
10512
|
-
};
|
|
10513
|
-
const withPermissions = (settings, next) => {
|
|
10514
|
-
const permissions = { ...settings.permissions };
|
|
10515
|
-
const assign = (key, values) => {
|
|
10516
|
-
if (values.length > 0) permissions[key] = values;
|
|
10517
|
-
else delete permissions[key];
|
|
10518
|
-
};
|
|
10519
|
-
assign("allow", next.allow);
|
|
10520
|
-
assign("ask", next.ask);
|
|
10521
|
-
assign("deny", next.deny);
|
|
10522
|
-
return {
|
|
10523
|
-
...settings,
|
|
10524
|
-
permissions
|
|
10525
|
-
};
|
|
10526
|
-
};
|
|
10527
|
-
const applyIgnoreReadDenies = (params) => {
|
|
10528
|
-
const { settings, readDenies } = params;
|
|
10529
|
-
const current = parsePermissionsBlock(settings);
|
|
10530
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
10531
|
-
return withPermissions(settings, {
|
|
10532
|
-
allow: current.allow,
|
|
10533
|
-
ask: current.ask,
|
|
10534
|
-
deny: (0, es_toolkit.uniq)([...preservedDeny, ...readDenies].toSorted())
|
|
10535
|
-
});
|
|
10536
|
-
};
|
|
10537
|
-
const applyPermissions = (params) => {
|
|
10538
|
-
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
10539
|
-
const current = parsePermissionsBlock(settings);
|
|
10540
|
-
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
10541
|
-
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
10542
|
-
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
10543
|
-
if (overwrittenReadDenies.length > 0) logger.warn(`Permissions feature manages '${READ_TOOL_NAME}' tool and will overwrite ${overwrittenReadDenies.length} existing ${READ_TOOL_NAME} deny entries. Permissions take precedence.`);
|
|
10544
|
-
}
|
|
10545
|
-
return withPermissions(settings, {
|
|
10546
|
-
allow: (0, es_toolkit.uniq)([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
10547
|
-
ask: (0, es_toolkit.uniq)([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
10548
|
-
deny: (0, es_toolkit.uniq)([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
10549
|
-
});
|
|
10550
|
-
};
|
|
10551
|
-
//#endregion
|
|
10552
10706
|
//#region src/features/ignore/claudecode-ignore.ts
|
|
10553
10707
|
const DEFAULT_FILE_MODE = "shared";
|
|
10554
10708
|
/**
|
|
@@ -11971,7 +12125,6 @@ var AntigravityIdeMcp = class extends AntigravityMcp {
|
|
|
11971
12125
|
};
|
|
11972
12126
|
//#endregion
|
|
11973
12127
|
//#region src/features/mcp/augmentcode-mcp.ts
|
|
11974
|
-
const AUGMENTCODE_GLOBAL_ONLY_MESSAGE = "AugmentCode MCP is global-only; use --global to sync ~/.augment/settings.json";
|
|
11975
12128
|
function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
11976
12129
|
const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
11977
12130
|
let parsed;
|
|
@@ -11986,13 +12139,15 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
|
|
|
11986
12139
|
/**
|
|
11987
12140
|
* AugmentCode (Auggie CLI) MCP servers.
|
|
11988
12141
|
*
|
|
11989
|
-
* MCP servers are persisted in the shared
|
|
11990
|
-
*
|
|
11991
|
-
*
|
|
12142
|
+
* MCP servers are persisted in the shared settings file `.augment/settings.json`
|
|
12143
|
+
* at either scope: the committed workspace file for team-shared servers (project)
|
|
12144
|
+
* or `~/.augment/settings.json` (global). That same file also holds `hooks` and
|
|
11992
12145
|
* `toolPermissions`, so generation merges the `mcpServers` block into the
|
|
11993
|
-
* existing settings instead of overwriting it, and the file is never deleted.
|
|
12146
|
+
* existing settings instead of overwriting it, and the file is never deleted. On
|
|
12147
|
+
* project-scope import the gitignored `.augment/settings.local.json` overrides
|
|
12148
|
+
* file is overlaid on top (the same layering the hooks/permissions adapters use).
|
|
11994
12149
|
*
|
|
11995
|
-
* @see https://docs.augmentcode.com/cli/
|
|
12150
|
+
* @see https://docs.augmentcode.com/cli/config
|
|
11996
12151
|
*/
|
|
11997
12152
|
var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
11998
12153
|
json;
|
|
@@ -12014,9 +12169,14 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12014
12169
|
};
|
|
12015
12170
|
}
|
|
12016
12171
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
12017
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12018
12172
|
const paths = this.getSettablePaths({ global });
|
|
12019
|
-
const json = parseAugmentcodeSettings(await
|
|
12173
|
+
const json = parseAugmentcodeSettings(await readAugmentcodeSettingsWithLocalOverlay({
|
|
12174
|
+
outputRoot,
|
|
12175
|
+
relativeDirPath: paths.relativeDirPath,
|
|
12176
|
+
baseFileName: paths.relativeFilePath,
|
|
12177
|
+
baseFallbackContent: "{}",
|
|
12178
|
+
includeLocalOverlay: !global
|
|
12179
|
+
}), paths.relativeDirPath, paths.relativeFilePath);
|
|
12020
12180
|
const newJson = {
|
|
12021
12181
|
...json,
|
|
12022
12182
|
mcpServers: json.mcpServers ?? {}
|
|
@@ -12031,7 +12191,6 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12031
12191
|
});
|
|
12032
12192
|
}
|
|
12033
12193
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12034
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12035
12194
|
const paths = this.getSettablePaths({ global });
|
|
12036
12195
|
const merged = {
|
|
12037
12196
|
...parseAugmentcodeSettings(await readOrInitializeFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath), JSON.stringify({}, null, 2)), paths.relativeDirPath, paths.relativeFilePath),
|
|
@@ -12306,6 +12465,38 @@ const RULESYNC_TO_CODEX_FIELD_MAP = {
|
|
|
12306
12465
|
envVars: "env_vars"
|
|
12307
12466
|
};
|
|
12308
12467
|
const MAX_REMOVE_EMPTY_ENTRIES_DEPTH$1 = 32;
|
|
12468
|
+
/**
|
|
12469
|
+
* Translate a server's `oauth` table from the canonical rulesync shape (Claude
|
|
12470
|
+
* Code style camelCase) into the shape Codex CLI understands. Codex expects the
|
|
12471
|
+
* OAuth client id under snake_case `client_id`; without it `codex mcp login`
|
|
12472
|
+
* falls back to dynamic client registration and fails for providers that do not
|
|
12473
|
+
* support it (e.g. Slack, see #2158). The canonical `clientId` is kept alongside
|
|
12474
|
+
* the added `client_id` so tools that read the camelCase shape keep working and
|
|
12475
|
+
* the round-trip stays stable.
|
|
12476
|
+
*/
|
|
12477
|
+
function mapOauthToCodex(oauth) {
|
|
12478
|
+
const result = omitPrototypePollutionKeys(oauth);
|
|
12479
|
+
if (typeof oauth["clientId"] === "string" && !("client_id" in result)) result["client_id"] = oauth["clientId"];
|
|
12480
|
+
return result;
|
|
12481
|
+
}
|
|
12482
|
+
/**
|
|
12483
|
+
* Reverse of {@link mapOauthToCodex}: collapse Codex's `oauth.client_id` back to
|
|
12484
|
+
* the canonical `clientId` on import. When both keys are present (the shape
|
|
12485
|
+
* rulesync itself emits) the canonical `clientId` wins and `client_id` is
|
|
12486
|
+
* dropped so a subsequent generate does not accumulate duplicates.
|
|
12487
|
+
*/
|
|
12488
|
+
function mapOauthFromCodex(oauth) {
|
|
12489
|
+
const result = {};
|
|
12490
|
+
for (const [key, value] of Object.entries(oauth)) {
|
|
12491
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12492
|
+
if (key === "client_id") {
|
|
12493
|
+
if (!("clientId" in oauth)) result["clientId"] = value;
|
|
12494
|
+
continue;
|
|
12495
|
+
}
|
|
12496
|
+
result[key] = value;
|
|
12497
|
+
}
|
|
12498
|
+
return result;
|
|
12499
|
+
}
|
|
12309
12500
|
function convertFromCodexFormat(codexMcp) {
|
|
12310
12501
|
const result = {};
|
|
12311
12502
|
for (const [name, config] of Object.entries(codexMcp)) {
|
|
@@ -12315,7 +12506,8 @@ function convertFromCodexFormat(codexMcp) {
|
|
|
12315
12506
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12316
12507
|
if (key === "enabled") {
|
|
12317
12508
|
if (value === false) converted["disabled"] = true;
|
|
12318
|
-
} else if (
|
|
12509
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthFromCodex(value);
|
|
12510
|
+
else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
|
|
12319
12511
|
const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
|
|
12320
12512
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12321
12513
|
else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
|
|
@@ -12335,7 +12527,8 @@ function convertToCodexFormat(mcpServers) {
|
|
|
12335
12527
|
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
12336
12528
|
if (key === "disabled") {
|
|
12337
12529
|
if (value === true) converted["enabled"] = false;
|
|
12338
|
-
} else if (
|
|
12530
|
+
} else if (key === "oauth" && isRecord(value)) converted[key] = mapOauthToCodex(value);
|
|
12531
|
+
else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
|
|
12339
12532
|
const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
|
|
12340
12533
|
if (mappedKey) if (isStringArray(value)) converted[mappedKey] = value;
|
|
12341
12534
|
else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
|
|
@@ -13551,10 +13744,12 @@ function resolveHermesTimeout(config) {
|
|
|
13551
13744
|
*
|
|
13552
13745
|
* Hermes is close to the MCP spec but not identical: `command` must be a single
|
|
13553
13746
|
* executable string (an array's tail folds into `args`), a server is disabled
|
|
13554
|
-
* via `enabled: false` (not the canonical `disabled: true`),
|
|
13555
|
-
*
|
|
13556
|
-
* `
|
|
13557
|
-
*
|
|
13747
|
+
* via `enabled: false` (not the canonical `disabled: true`), remote servers use
|
|
13748
|
+
* `url`/`headers`, and per-server tool scoping lives under a `tools: { include,
|
|
13749
|
+
* exclude }` block (from the canonical `enabledTools`/`disabledTools`). Only
|
|
13750
|
+
* fields Hermes understands are emitted, so the shared `config.yaml` is not
|
|
13751
|
+
* polluted with canonical-only aliases (`type`, `transport`, `httpUrl`,
|
|
13752
|
+
* `networkTimeout`, ...).
|
|
13558
13753
|
*/
|
|
13559
13754
|
function convertServerToHermes(config) {
|
|
13560
13755
|
const out = {};
|
|
@@ -13578,6 +13773,10 @@ function convertServerToHermes(config) {
|
|
|
13578
13773
|
if (config.disabled === true) out.enabled = false;
|
|
13579
13774
|
const timeout = resolveHermesTimeout(config);
|
|
13580
13775
|
if (timeout !== void 0) out.timeout = timeout;
|
|
13776
|
+
const tools = {};
|
|
13777
|
+
if (isStringArray(config.enabledTools)) tools.include = config.enabledTools;
|
|
13778
|
+
if (isStringArray(config.disabledTools)) tools.exclude = config.disabledTools;
|
|
13779
|
+
if (Object.keys(tools).length > 0) out.tools = tools;
|
|
13581
13780
|
return out;
|
|
13582
13781
|
}
|
|
13583
13782
|
/**
|
|
@@ -13619,6 +13818,10 @@ function convertFromHermesFormat(mcpServers) {
|
|
|
13619
13818
|
if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
|
|
13620
13819
|
if (config.enabled === false) server.disabled = true;
|
|
13621
13820
|
if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
|
|
13821
|
+
if (isRecord(config.tools)) {
|
|
13822
|
+
if (isStringArray(config.tools.include)) server.enabledTools = config.tools.include;
|
|
13823
|
+
if (isStringArray(config.tools.exclude)) server.disabledTools = config.tools.exclude;
|
|
13824
|
+
}
|
|
13622
13825
|
result[name] = server;
|
|
13623
13826
|
}
|
|
13624
13827
|
return result;
|
|
@@ -13637,7 +13840,10 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13637
13840
|
config;
|
|
13638
13841
|
constructor(params) {
|
|
13639
13842
|
super(params);
|
|
13640
|
-
this.config = this.fileContent !== void 0 ?
|
|
13843
|
+
this.config = this.fileContent !== void 0 ? parseSharedConfig({
|
|
13844
|
+
format: "yaml",
|
|
13845
|
+
fileContent: this.fileContent
|
|
13846
|
+
}) : {};
|
|
13641
13847
|
}
|
|
13642
13848
|
getConfig() {
|
|
13643
13849
|
return this.config;
|
|
@@ -13646,9 +13852,17 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13646
13852
|
return true;
|
|
13647
13853
|
}
|
|
13648
13854
|
setFileContent(fileContent) {
|
|
13649
|
-
const merged = mergeHermesMcpServers(
|
|
13855
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13856
|
+
format: "yaml",
|
|
13857
|
+
fileContent
|
|
13858
|
+
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
13650
13859
|
this.config = merged;
|
|
13651
|
-
super.setFileContent(
|
|
13860
|
+
super.setFileContent(applySharedConfigPatch({
|
|
13861
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13862
|
+
feature: "mcp",
|
|
13863
|
+
existingContent: fileContent,
|
|
13864
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13865
|
+
}));
|
|
13652
13866
|
}
|
|
13653
13867
|
isDeletable() {
|
|
13654
13868
|
return false;
|
|
@@ -13675,12 +13889,21 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13675
13889
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13676
13890
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
13677
13891
|
const paths = this.getSettablePaths({ global });
|
|
13678
|
-
const
|
|
13892
|
+
const fileContent = await readOrInitializeFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath), "");
|
|
13893
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13894
|
+
format: "yaml",
|
|
13895
|
+
fileContent
|
|
13896
|
+
}), convertToHermesFormat(rulesyncMcp.getMcpServers()));
|
|
13679
13897
|
return new HermesagentMcp({
|
|
13680
13898
|
outputRoot,
|
|
13681
13899
|
relativeDirPath: paths.relativeDirPath,
|
|
13682
13900
|
relativeFilePath: paths.relativeFilePath,
|
|
13683
|
-
fileContent:
|
|
13901
|
+
fileContent: applySharedConfigPatch({
|
|
13902
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13903
|
+
feature: "mcp",
|
|
13904
|
+
existingContent: fileContent,
|
|
13905
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13906
|
+
}),
|
|
13684
13907
|
validate,
|
|
13685
13908
|
global
|
|
13686
13909
|
});
|
|
@@ -14845,28 +15068,6 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
14845
15068
|
}
|
|
14846
15069
|
};
|
|
14847
15070
|
//#endregion
|
|
14848
|
-
//#region src/features/shared/takt-config.ts
|
|
14849
|
-
/**
|
|
14850
|
-
* Parse a Takt `config.yaml` into a plain object, treating an empty file as `{}`.
|
|
14851
|
-
*
|
|
14852
|
-
* Shared by the Takt adapters that read-modify-write the same `config.yaml`
|
|
14853
|
-
* (mcp, permissions, ...). Uses `isPlainObject` (not `isRecord`) so class
|
|
14854
|
-
* instances are rejected for prototype-pollution hardening; a YAML mapping
|
|
14855
|
-
* always parses to a plain object.
|
|
14856
|
-
*/
|
|
14857
|
-
function parseTaktConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
14858
|
-
const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
14859
|
-
let parsed;
|
|
14860
|
-
try {
|
|
14861
|
-
parsed = fileContent.trim() === "" ? {} : (0, js_yaml.load)(fileContent);
|
|
14862
|
-
} catch (error) {
|
|
14863
|
-
throw new Error(`Failed to parse Takt config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
14864
|
-
}
|
|
14865
|
-
if (parsed === void 0 || parsed === null) return {};
|
|
14866
|
-
if (!isPlainObject(parsed)) throw new Error(`Failed to parse Takt config at ${configPath}: expected a YAML mapping`);
|
|
14867
|
-
return parsed;
|
|
14868
|
-
}
|
|
14869
|
-
//#endregion
|
|
14870
15071
|
//#region src/features/mcp/takt-mcp.ts
|
|
14871
15072
|
/**
|
|
14872
15073
|
* MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -14932,17 +15133,20 @@ var TaktMcp = class TaktMcp extends ToolMcp {
|
|
|
14932
15133
|
}
|
|
14933
15134
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14934
15135
|
const paths = TaktMcp.getSettablePaths({ global });
|
|
14935
|
-
const
|
|
15136
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15137
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
14936
15138
|
const allowlist = deriveTransportAllowlist(rulesyncMcp.getMcpServers());
|
|
14937
|
-
const merged = {
|
|
14938
|
-
...config,
|
|
14939
|
-
[TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist
|
|
14940
|
-
};
|
|
14941
15139
|
return new TaktMcp({
|
|
14942
15140
|
outputRoot,
|
|
14943
15141
|
relativeDirPath: paths.relativeDirPath,
|
|
14944
15142
|
relativeFilePath: paths.relativeFilePath,
|
|
14945
|
-
fileContent: (
|
|
15143
|
+
fileContent: applySharedConfigPatch({
|
|
15144
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
15145
|
+
feature: "mcp",
|
|
15146
|
+
existingContent,
|
|
15147
|
+
patch: { [TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist },
|
|
15148
|
+
filePath
|
|
15149
|
+
}),
|
|
14946
15150
|
validate,
|
|
14947
15151
|
global
|
|
14948
15152
|
});
|
|
@@ -15387,7 +15591,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
15387
15591
|
["augmentcode", {
|
|
15388
15592
|
class: AugmentcodeMcp,
|
|
15389
15593
|
meta: {
|
|
15390
|
-
supportsProject:
|
|
15594
|
+
supportsProject: true,
|
|
15391
15595
|
supportsGlobal: true,
|
|
15392
15596
|
supportsEnabledTools: false,
|
|
15393
15597
|
supportsDisabledTools: false
|
|
@@ -15774,17 +15978,317 @@ const PermissionActionSchema = zod_mini.z.enum([
|
|
|
15774
15978
|
*/
|
|
15775
15979
|
const PermissionRulesSchema = zod_mini.z.record(zod_mini.z.string(), PermissionActionSchema);
|
|
15776
15980
|
/**
|
|
15981
|
+
* OpenCode-specific permission value. Unlike the shared canonical block, which
|
|
15982
|
+
* only accepts a pattern-to-action map, OpenCode also allows a bare action
|
|
15983
|
+
* string that applies to the whole category (e.g. `"external_directory": "deny"`).
|
|
15984
|
+
* This mirrors OpenCode's own permission schema in `opencode-permissions.ts`.
|
|
15985
|
+
*/
|
|
15986
|
+
const OpencodeOverridePermissionValueSchema = zod_mini.z.union([PermissionActionSchema, PermissionRulesSchema]);
|
|
15987
|
+
/**
|
|
15988
|
+
* Tool-scoped override block for OpenCode. Permission categories placed here
|
|
15989
|
+
* (e.g. OpenCode-only categories such as `external_directory`) are emitted only
|
|
15990
|
+
* into OpenCode's config and never leak into other tools' permission files. It
|
|
15991
|
+
* also lets a shared category be overridden with an OpenCode-specific value.
|
|
15992
|
+
* Kept `looseObject` so future OpenCode categories are accepted.
|
|
15993
|
+
*
|
|
15994
|
+
* @example
|
|
15995
|
+
* { "permission": { "external_directory": "deny", "webfetch": "allow" } }
|
|
15996
|
+
*/
|
|
15997
|
+
const OpencodePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
15998
|
+
/**
|
|
15999
|
+
* Tool-scoped override block for Hermes Agent. Keys placed here are deep-merged
|
|
16000
|
+
* into Hermes's `~/.hermes/config.yaml` and never leak into other tools' configs.
|
|
16001
|
+
* It carries Hermes-specific approval/security controls that have no canonical
|
|
16002
|
+
* permission category — e.g. `approvals` (`mode`, `cron_mode`, ...),
|
|
16003
|
+
* `security` (`allow_private_urls`, ...), `skills.write_approval`,
|
|
16004
|
+
* `memory.write_approval`. Kept `looseObject` (a verbatim passthrough) so any
|
|
16005
|
+
* current or future Hermes config key can be authored without modeling each one.
|
|
16006
|
+
*
|
|
16007
|
+
* @example
|
|
16008
|
+
* { "approvals": { "mode": "smart" }, "security": { "allow_private_urls": false } }
|
|
16009
|
+
*/
|
|
16010
|
+
const HermesPermissionsOverrideSchema = zod_mini.z.looseObject({});
|
|
16011
|
+
/**
|
|
16012
|
+
* Tool-scoped override block for Cline. Cline's `command-permissions.json`
|
|
16013
|
+
* carries a single global `allowRedirects` boolean (gates shell redirection
|
|
16014
|
+
* operators `>`/`>>`/`<`) that has no per-command dimension and therefore no
|
|
16015
|
+
* canonical permission category. Placing it here lets users author it
|
|
16016
|
+
* declaratively; it is emitted only into Cline's config. Kept `looseObject` so
|
|
16017
|
+
* future Cline-only knobs can be added.
|
|
16018
|
+
*
|
|
16019
|
+
* @example
|
|
16020
|
+
* { "allowRedirects": true }
|
|
16021
|
+
*/
|
|
16022
|
+
const ClinePermissionsOverrideSchema = zod_mini.z.looseObject({ allowRedirects: zod_mini.z.optional(zod_mini.z.boolean()) });
|
|
16023
|
+
/**
|
|
16024
|
+
* Tool-scoped override block for Kilo Code (an OpenCode fork). Kilo's permission
|
|
16025
|
+
* object is a free-form record with tool-specific keys that have no canonical
|
|
16026
|
+
* category — OpenCode-inherited ones (`external_directory`, `doom_loop`, `lsp`,
|
|
16027
|
+
* `question`, `todowrite`, `skill`, `task`, `list`) and Kilo-unique ones
|
|
16028
|
+
* (`agent_manager`, `notebook_read`, `notebook_edit`, `notebook_execute`,
|
|
16029
|
+
* `repo_clone`, `repo_overview`). Placing them here makes them authorable and
|
|
16030
|
+
* portable and keeps them out of other tools' configs. Mirrors the OpenCode
|
|
16031
|
+
* override; each value may be a bare action string or a pattern map.
|
|
16032
|
+
*
|
|
16033
|
+
* @example
|
|
16034
|
+
* { "permission": { "external_directory": "deny", "doom_loop": "ask" } }
|
|
16035
|
+
*/
|
|
16036
|
+
const KiloPermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), OpencodeOverridePermissionValueSchema)) });
|
|
16037
|
+
/**
|
|
16038
|
+
* Tool-scoped override block for Claude Code. Claude Code's `permissions` object
|
|
16039
|
+
* (in `.claude/settings.json`) carries non-list fields that have no canonical
|
|
16040
|
+
* permission category — `defaultMode` (the session-start permission mode) and
|
|
16041
|
+
* `additionalDirectories` (extra working directories) being the primary ones.
|
|
16042
|
+
* Fields placed under `claudecode.permissions` are merged into the settings
|
|
16043
|
+
* `permissions` object and emitted only for Claude Code, while the shared
|
|
16044
|
+
* `permission` block continues to drive the `allow`/`ask`/`deny` arrays. Kept a
|
|
16045
|
+
* `looseObject` passthrough so any current or future `permissions` field can be
|
|
16046
|
+
* authored without modeling each one; the managed `allow`/`ask`/`deny` arrays are
|
|
16047
|
+
* ignored here (rulesync owns them).
|
|
16048
|
+
*
|
|
16049
|
+
* @example
|
|
16050
|
+
* { "permissions": { "defaultMode": "acceptEdits", "additionalDirectories": ["../shared"] } }
|
|
16051
|
+
*/
|
|
16052
|
+
const ClaudecodePermissionsOverrideSchema = zod_mini.z.looseObject({ permissions: zod_mini.z.optional(zod_mini.z.looseObject({})) });
|
|
16053
|
+
/**
|
|
16054
|
+
* Tool-scoped override block for Mistral Vibe. Vibe's per-tool `BaseToolConfig`
|
|
16055
|
+
* carries a `sensitive_patterns` list — patterns that escalate to ASK even when
|
|
16056
|
+
* the tool's base permission is ALWAYS (allow). The canonical model can only set
|
|
16057
|
+
* a pattern to a single `allow`/`ask`/`deny`, so an "allow by default but ask on
|
|
16058
|
+
* these patterns" escalation cannot be expressed. Entries under
|
|
16059
|
+
* `vibe.permission.<category>.sensitive_patterns` carry that list per canonical
|
|
16060
|
+
* category; the shared `permission` block still drives the base permission and
|
|
16061
|
+
* allow/deny lists. Keyed by canonical category (e.g. `bash`, `edit`).
|
|
16062
|
+
*
|
|
16063
|
+
* @example
|
|
16064
|
+
* { "permission": { "bash": { "sensitive_patterns": ["rm *", "sudo *"] } } }
|
|
16065
|
+
*/
|
|
16066
|
+
const VibePermissionsOverrideSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.looseObject({ sensitive_patterns: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) }))) });
|
|
16067
|
+
/**
|
|
16068
|
+
* Tool-scoped override block for Cursor CLI. Cursor's `cli.json` carries scalar
|
|
16069
|
+
* autonomy settings with no canonical permission category — `approvalMode`
|
|
16070
|
+
* (`allowlist` | `auto-review` | `unrestricted`) and a `sandbox` object
|
|
16071
|
+
* (`mode`/`networkAccess`). Fields placed here are merged into the top-level of
|
|
16072
|
+
* `.cursor/cli.json` (project) / `~/.cursor/cli-config.json` (global) and emitted
|
|
16073
|
+
* only for Cursor, while the shared `permission` block continues to drive the
|
|
16074
|
+
* `permissions.allow`/`permissions.deny` arrays. Kept a `looseObject` so extra
|
|
16075
|
+
* `cli.json` keys can be authored (they are merged verbatim on generate);
|
|
16076
|
+
* `sandbox`'s accepted values are not documented so it passes through verbatim.
|
|
16077
|
+
* Note: only `approvalMode` and `sandbox` round-trip back on import — other keys
|
|
16078
|
+
* authored here reach `cli.json` on generate but are not re-extracted.
|
|
16079
|
+
*
|
|
16080
|
+
* @example
|
|
16081
|
+
* { "approvalMode": "auto-review" }
|
|
16082
|
+
*/
|
|
16083
|
+
const CursorPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16084
|
+
approvalMode: zod_mini.z.optional(zod_mini.z.string()),
|
|
16085
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16086
|
+
});
|
|
16087
|
+
/**
|
|
16088
|
+
* Tool-scoped override block for Qwen Code. Qwen's `settings.json` exposes
|
|
16089
|
+
* autonomy/sandbox controls with no canonical permission category — under
|
|
16090
|
+
* `tools` (`approvalMode` = plan/default/auto-edit/auto/yolo, `autoAccept`,
|
|
16091
|
+
* `sandbox`, `sandboxImage`, `disabled`) and `security` (`folderTrust`). Fields
|
|
16092
|
+
* placed here are merged into the matching `settings.json` group and emitted
|
|
16093
|
+
* only for Qwen, while the shared `permission` block continues to drive the
|
|
16094
|
+
* `permissions.allow`/`ask`/`deny` arrays. Kept `looseObject` (verbatim
|
|
16095
|
+
* passthrough) so any current or future `tools`/`security` key can be authored.
|
|
16096
|
+
*
|
|
16097
|
+
* @example
|
|
16098
|
+
* { "tools": { "approvalMode": "auto-edit" }, "security": { "folderTrust": { "enabled": true } } }
|
|
16099
|
+
*/
|
|
16100
|
+
const QwencodePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16101
|
+
tools: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16102
|
+
security: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16103
|
+
});
|
|
16104
|
+
/**
|
|
16105
|
+
* Tool-scoped override block for Reasonix. Reasonix has security axes orthogonal
|
|
16106
|
+
* to per-tool allow/ask/deny with no canonical category — the `[sandbox]`
|
|
16107
|
+
* enforcement table (`workspace_root`, `allow_write`, `forbid_read`, `bash`,
|
|
16108
|
+
* `network`) and plan-mode read-only trust lists under `[agent]`
|
|
16109
|
+
* (`plan_mode_allowed_tools`, `plan_mode_read_only_commands`). Fields placed here
|
|
16110
|
+
* are merged into the matching `reasonix.toml` table and emitted only for
|
|
16111
|
+
* Reasonix, while the shared `permission` block continues to drive
|
|
16112
|
+
* `[permissions].allow`/`ask`/`deny`. Kept `looseObject` (verbatim passthrough).
|
|
16113
|
+
* Note: the whole `[sandbox]` table round-trips, but only the plan-mode keys are
|
|
16114
|
+
* re-extracted from `[agent]` on import — other `agent` keys authored here reach
|
|
16115
|
+
* `reasonix.toml` on generate but are not re-extracted back into the override.
|
|
16116
|
+
*
|
|
16117
|
+
* @example
|
|
16118
|
+
* { "sandbox": { "bash": "enforce", "network": false }, "agent": { "plan_mode_read_only_commands": ["gh pr diff"] } }
|
|
16119
|
+
*/
|
|
16120
|
+
const ReasonixPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16121
|
+
sandbox: zod_mini.z.optional(zod_mini.z.looseObject({})),
|
|
16122
|
+
agent: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16123
|
+
});
|
|
16124
|
+
/**
|
|
16125
|
+
* Tool-scoped override block for Factory Droid. Factory Droid's `settings.json`
|
|
16126
|
+
* exposes security controls with no canonical per-command allow/ask/deny slot —
|
|
16127
|
+
* `commandBlocklist` (a hard-block tier that can never be approved, distinct from
|
|
16128
|
+
* an approvable `deny`), `networkPolicy` (`allowedIps`), `sandbox`
|
|
16129
|
+
* (`enabled`/`mode`/`filesystem`/`network`), `mcpPolicy`, `enableDroidShield`,
|
|
16130
|
+
* and autonomy settings (`sessionDefaultSettings`, `maxAutonomyLevel`,
|
|
16131
|
+
* `interactionMode`). Fields placed here are merged into `settings.json` and
|
|
16132
|
+
* emitted only for Factory Droid, while the shared `permission` block continues
|
|
16133
|
+
* to drive `commandAllowlist`/`commandDenylist`. Kept `looseObject` passthrough.
|
|
16134
|
+
*
|
|
16135
|
+
* @example
|
|
16136
|
+
* { "commandBlocklist": ["rm -rf /*"], "sandbox": { "enabled": true } }
|
|
16137
|
+
*/
|
|
16138
|
+
const FactorydroidPermissionsOverrideSchema = zod_mini.z.looseObject({ commandBlocklist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) });
|
|
16139
|
+
/**
|
|
16140
|
+
* Tool-scoped override block for Warp. Warp's `[agents.profiles]` table exposes
|
|
16141
|
+
* file-read/read-only autonomy keys with no canonical per-command allow/ask/deny
|
|
16142
|
+
* slot — `agent_mode_coding_permissions`
|
|
16143
|
+
* (`always_ask_before_reading` | `always_allow_reading` | `allow_reading_specific_files`),
|
|
16144
|
+
* `agent_mode_coding_file_read_allowlist` (a path array), and
|
|
16145
|
+
* `agent_mode_execute_readonly_commands` (a read-only auto-execution boolean).
|
|
16146
|
+
* Fields placed here are merged into `[agents.profiles]` of Warp's global
|
|
16147
|
+
* `settings.toml`, while the shared `permission` block continues to drive the
|
|
16148
|
+
* `agent_mode_command_execution_allowlist`/`_denylist` command regex arrays.
|
|
16149
|
+
* Warp permissions are global-only.
|
|
16150
|
+
*
|
|
16151
|
+
* @example
|
|
16152
|
+
* { "agent_mode_coding_permissions": "always_allow_reading", "agent_mode_execute_readonly_commands": true }
|
|
16153
|
+
*/
|
|
16154
|
+
const WarpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16155
|
+
agent_mode_coding_permissions: zod_mini.z.optional(zod_mini.z.string()),
|
|
16156
|
+
agent_mode_coding_file_read_allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
16157
|
+
agent_mode_execute_readonly_commands: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16158
|
+
});
|
|
16159
|
+
/**
|
|
16160
|
+
* Tool-scoped override block for JetBrains Junie. Junie's `allowlist.json` has
|
|
16161
|
+
* two top-level autonomy knobs with no canonical per-glob slot:
|
|
16162
|
+
* `allowReadonlyCommands` (a boolean auto-allowing read-only commands) and
|
|
16163
|
+
* `defaultBehavior` (the fallback action applied when no rule matches — Junie
|
|
16164
|
+
* documents only `allow`/`ask`). Fields placed here are merged onto the
|
|
16165
|
+
* top level of `allowlist.json`, while the shared `permission` block continues
|
|
16166
|
+
* to drive the per-category `rules` groups.
|
|
16167
|
+
*
|
|
16168
|
+
* @example
|
|
16169
|
+
* { "allowReadonlyCommands": true, "defaultBehavior": "ask" }
|
|
16170
|
+
*/
|
|
16171
|
+
const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16172
|
+
allowReadonlyCommands: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
16173
|
+
defaultBehavior: zod_mini.z.optional(zod_mini.z.string())
|
|
16174
|
+
});
|
|
16175
|
+
/**
|
|
16176
|
+
* Tool-scoped override block for Takt. Takt's `config.yaml` carries two
|
|
16177
|
+
* permission surfaces the canonical coarse-mode mapping can't express:
|
|
16178
|
+
* `step_permission_overrides` — a per-workflow-step map (`<step>` →
|
|
16179
|
+
* `readonly`/`edit`/`full`) that lives inside the active provider profile
|
|
16180
|
+
* alongside `default_permission_mode` and layers on top of it at that step; and
|
|
16181
|
+
* `provider_options` — a top-level, per-provider table of sandbox/network knobs
|
|
16182
|
+
* orthogonal to the permission mode (e.g. `codex.network_access`,
|
|
16183
|
+
* `claude.sandbox.allow_unsandboxed_commands`, `opencode.allowed_tools`). Fields
|
|
16184
|
+
* placed here are merged into `config.yaml` and emitted only for Takt, while the
|
|
16185
|
+
* shared `permission` block continues to drive `default_permission_mode`. Kept
|
|
16186
|
+
* `looseObject` (verbatim passthrough); Takt validates its own value sets (e.g.
|
|
16187
|
+
* `provider_options.<p>.base_url` must be loopback). Both project and global
|
|
16188
|
+
* scope are supported.
|
|
16189
|
+
*
|
|
16190
|
+
* Note: Takt's config loader hard-rejects unknown top-level keys, so only keys
|
|
16191
|
+
* Takt actually recognizes belong here. `required_permission_mode` is NOT one —
|
|
16192
|
+
* it is a per-step field of the workflow YAML (not `config.yaml`), so it is out
|
|
16193
|
+
* of scope for this override.
|
|
16194
|
+
*
|
|
16195
|
+
* @example
|
|
16196
|
+
* { "step_permission_overrides": { "ai_review": "readonly" }, "provider_options": { "codex": { "network_access": true } } }
|
|
16197
|
+
*/
|
|
16198
|
+
const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16199
|
+
step_permission_overrides: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
16200
|
+
provider_options: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16201
|
+
});
|
|
16202
|
+
/**
|
|
16203
|
+
* Tool-scoped override block for Amp. Amp's `amp.permissions` array and sibling
|
|
16204
|
+
* settings carry shapes the canonical per-command allow/ask/deny model can't
|
|
16205
|
+
* express, so they are authored here and merged into the shared Amp settings
|
|
16206
|
+
* file, while the shared `permission` block continues to drive the canonical
|
|
16207
|
+
* `amp.permissions` (allow/ask/reject) + `amp.tools.disable` entries:
|
|
16208
|
+
* - `permissions` — extra `amp.permissions` entries with non-`cmd` matchers
|
|
16209
|
+
* (`path`/`url`/`query`/…), regex/array match values, `context`
|
|
16210
|
+
* (`thread`/`subagent`), `delegate` (+`to`), or `reject` (+`message`). These
|
|
16211
|
+
* are appended AFTER the canonical-generated entries (Amp is first-match-wins,
|
|
16212
|
+
* so generated allow/ask/reject rules take precedence; authored entries act as
|
|
16213
|
+
* later fallbacks), preserving author order.
|
|
16214
|
+
* - `mcpPermissions` — Amp's `amp.mcpPermissions` array (`{ matches, action }`).
|
|
16215
|
+
* - `guardedFiles` — `amp.guardedFiles.allowlist` (globs allowed without
|
|
16216
|
+
* confirmation).
|
|
16217
|
+
* - `dangerouslyAllowAll` — `amp.dangerouslyAllowAll` (disable all confirmation).
|
|
16218
|
+
* Kept `looseObject` (verbatim passthrough). Both project and global scope are
|
|
16219
|
+
* supported.
|
|
16220
|
+
*
|
|
16221
|
+
* @example
|
|
16222
|
+
* { "dangerouslyAllowAll": false, "guardedFiles": { "allowlist": ["docs/**"] },
|
|
16223
|
+
* "permissions": [{ "tool": "Bash", "action": "delegate", "to": "approve.sh" }] }
|
|
16224
|
+
*/
|
|
16225
|
+
const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16226
|
+
permissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
16227
|
+
tool: zod_mini.z.string(),
|
|
16228
|
+
action: zod_mini.z.string()
|
|
16229
|
+
}))),
|
|
16230
|
+
mcpPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({}))),
|
|
16231
|
+
guardedFiles: zod_mini.z.optional(zod_mini.z.looseObject({ allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
|
|
16232
|
+
dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16233
|
+
});
|
|
16234
|
+
/**
|
|
16235
|
+
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
16236
|
+
* `settings.json` carries two global autonomy/sandbox knobs outside the
|
|
16237
|
+
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
16238
|
+
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
16239
|
+
* `always-proceed` / `strict`) and `enableTerminalSandbox` (a boolean confining
|
|
16240
|
+
* agent-run commands to OS containment). Antigravity applies the allow/deny
|
|
16241
|
+
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
16242
|
+
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
16243
|
+
* Fields placed here are merged onto the top level of
|
|
16244
|
+
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
16245
|
+
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
16246
|
+
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
16247
|
+
* Verified against https://antigravity.google/docs/cli/reference and
|
|
16248
|
+
* https://antigravity.google/docs/cli/sandbox.
|
|
16249
|
+
*
|
|
16250
|
+
* @example
|
|
16251
|
+
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
16252
|
+
*/
|
|
16253
|
+
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16254
|
+
toolPermission: zod_mini.z.optional(zod_mini.z.string()),
|
|
16255
|
+
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16256
|
+
});
|
|
16257
|
+
/**
|
|
15777
16258
|
* Permissions configuration.
|
|
15778
16259
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
15779
16260
|
* Values are pattern-to-action mappings for that tool category.
|
|
15780
16261
|
*
|
|
16262
|
+
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
16263
|
+
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
|
|
16264
|
+
* `antigravity-cli` keys are tool-scoped overrides consumed only by their
|
|
16265
|
+
* respective translator (see the matching `*PermissionsOverrideSchema`); every
|
|
16266
|
+
* other tool reads the shared `permission` block and ignores them.
|
|
16267
|
+
*
|
|
15781
16268
|
* @example
|
|
15782
16269
|
* {
|
|
15783
16270
|
* "bash": { "*": "ask", "git *": "allow", "rm *": "deny" },
|
|
15784
16271
|
* "edit": { "*": "deny", "src/**": "allow" }
|
|
15785
16272
|
* }
|
|
15786
16273
|
*/
|
|
15787
|
-
const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
16274
|
+
const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
16275
|
+
permission: zod_mini.z.record(zod_mini.z.string(), PermissionRulesSchema),
|
|
16276
|
+
opencode: zod_mini.z.optional(OpencodePermissionsOverrideSchema),
|
|
16277
|
+
hermes: zod_mini.z.optional(HermesPermissionsOverrideSchema),
|
|
16278
|
+
cline: zod_mini.z.optional(ClinePermissionsOverrideSchema),
|
|
16279
|
+
kilo: zod_mini.z.optional(KiloPermissionsOverrideSchema),
|
|
16280
|
+
claudecode: zod_mini.z.optional(ClaudecodePermissionsOverrideSchema),
|
|
16281
|
+
vibe: zod_mini.z.optional(VibePermissionsOverrideSchema),
|
|
16282
|
+
cursor: zod_mini.z.optional(CursorPermissionsOverrideSchema),
|
|
16283
|
+
qwencode: zod_mini.z.optional(QwencodePermissionsOverrideSchema),
|
|
16284
|
+
reasonix: zod_mini.z.optional(ReasonixPermissionsOverrideSchema),
|
|
16285
|
+
factorydroid: zod_mini.z.optional(FactorydroidPermissionsOverrideSchema),
|
|
16286
|
+
warp: zod_mini.z.optional(WarpPermissionsOverrideSchema),
|
|
16287
|
+
junie: zod_mini.z.optional(JuniePermissionsOverrideSchema),
|
|
16288
|
+
takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
|
|
16289
|
+
amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
|
|
16290
|
+
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema)
|
|
16291
|
+
});
|
|
15788
16292
|
/**
|
|
15789
16293
|
* Full permissions file schema including optional $schema field.
|
|
15790
16294
|
*/
|
|
@@ -15895,6 +16399,39 @@ const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
|
15895
16399
|
* Reference: https://ampcode.com/manual ("amp.permissions").
|
|
15896
16400
|
*/
|
|
15897
16401
|
const AMP_PERMISSIONS_KEY = "amp.permissions";
|
|
16402
|
+
/**
|
|
16403
|
+
* The `amp.guardedFiles.allowlist` array (file globs allowed without
|
|
16404
|
+
* confirmation), `amp.dangerouslyAllowAll` boolean (disable all confirmation),
|
|
16405
|
+
* and `amp.mcpPermissions` array — sibling settings authored through the `amp`
|
|
16406
|
+
* permissions override. Reference: https://ampcode.com/manual.
|
|
16407
|
+
*/
|
|
16408
|
+
const AMP_GUARDED_FILES_ALLOWLIST_KEY = "amp.guardedFiles.allowlist";
|
|
16409
|
+
const AMP_DANGEROUSLY_ALLOW_ALL_KEY = "amp.dangerouslyAllowAll";
|
|
16410
|
+
const AMP_MCP_PERMISSIONS_KEY = "amp.mcpPermissions";
|
|
16411
|
+
/**
|
|
16412
|
+
* The read-only `cmd` string of an entry's `matches`, or `undefined` when the
|
|
16413
|
+
* matcher is absent or uses a non-`cmd` key / non-string value.
|
|
16414
|
+
*/
|
|
16415
|
+
function ampMatchesCmd(entry) {
|
|
16416
|
+
const cmd = entry.matches?.cmd;
|
|
16417
|
+
return typeof cmd === "string" ? cmd : void 0;
|
|
16418
|
+
}
|
|
16419
|
+
/**
|
|
16420
|
+
* Whether an `amp.permissions` entry is fully expressible in the canonical
|
|
16421
|
+
* per-command allow/ask/deny model: a plain `allow`/`ask`/`reject` action with
|
|
16422
|
+
* no `context`/`message`/`to` and a matcher that is either absent or exactly a
|
|
16423
|
+
* single string `cmd`. Everything else (non-`cmd` matchers, regex/array match
|
|
16424
|
+
* values, `delegate`, `reject`+`message`, `context`) is routed to the `amp`
|
|
16425
|
+
* override verbatim instead of being flattened with loss or dropped.
|
|
16426
|
+
*/
|
|
16427
|
+
function isCanonicalAmpEntry(entry) {
|
|
16428
|
+
if (entry.action === "delegate") return false;
|
|
16429
|
+
if (entry.context !== void 0 || entry.message !== void 0 || entry.to !== void 0) return false;
|
|
16430
|
+
const matches = entry.matches;
|
|
16431
|
+
if (matches === void 0) return true;
|
|
16432
|
+
const keys = Object.keys(matches);
|
|
16433
|
+
return keys.length === 0 || keys.length === 1 && typeof matches.cmd === "string";
|
|
16434
|
+
}
|
|
15898
16435
|
function parseAmpSettings(fileContent) {
|
|
15899
16436
|
const errors = [];
|
|
15900
16437
|
const parsed = (0, jsonc_parser.parse)(fileContent || "{}", errors, { allowTrailingComma: true });
|
|
@@ -15910,10 +16447,20 @@ function toDisableList(value) {
|
|
|
15910
16447
|
return value.filter((entry) => typeof entry === "string");
|
|
15911
16448
|
}
|
|
15912
16449
|
/**
|
|
16450
|
+
* Read `amp.guardedFiles.allowlist` (an array of file glob strings) from parsed
|
|
16451
|
+
* settings, returning `undefined` when the key is absent or not an array so the
|
|
16452
|
+
* override omits it.
|
|
16453
|
+
*/
|
|
16454
|
+
function extractAmpGuardedAllowlist(value) {
|
|
16455
|
+
if (!Array.isArray(value)) return void 0;
|
|
16456
|
+
return value.filter((entry) => typeof entry === "string");
|
|
16457
|
+
}
|
|
16458
|
+
/**
|
|
15913
16459
|
* Read an `amp.permissions` array from untrusted parsed settings. Only entries
|
|
15914
|
-
* whose
|
|
15915
|
-
*
|
|
15916
|
-
*
|
|
16460
|
+
* whose `tool`/`action` rulesync understands are retained; every other key —
|
|
16461
|
+
* including the full `matches` object (so non-`cmd` matchers, regex/array
|
|
16462
|
+
* values, `context`, `to`, `message`) — is kept verbatim so it survives a
|
|
16463
|
+
* round-trip.
|
|
15917
16464
|
*/
|
|
15918
16465
|
function toPermissionsList(value) {
|
|
15919
16466
|
if (!Array.isArray(value)) return [];
|
|
@@ -15923,14 +16470,11 @@ function toPermissionsList(value) {
|
|
|
15923
16470
|
const { tool, action } = raw;
|
|
15924
16471
|
if (typeof tool !== "string" || typeof action !== "string") continue;
|
|
15925
16472
|
if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
|
|
15926
|
-
const matches = raw.matches;
|
|
15927
|
-
let normalizedMatches;
|
|
15928
|
-
if (isPlainObject(matches) && typeof matches.cmd === "string") normalizedMatches = { cmd: matches.cmd };
|
|
15929
16473
|
entries.push({
|
|
15930
16474
|
...raw,
|
|
15931
16475
|
tool,
|
|
15932
16476
|
action,
|
|
15933
|
-
...
|
|
16477
|
+
...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
|
|
15934
16478
|
});
|
|
15935
16479
|
}
|
|
15936
16480
|
return entries;
|
|
@@ -15955,6 +16499,13 @@ function toPermissionsList(value) {
|
|
|
15955
16499
|
* The settings file is shared with the MCP feature (`amp.mcpServers`), so reads
|
|
15956
16500
|
* and writes merge into the existing JSON rather than overwriting it, and the
|
|
15957
16501
|
* file is never deleted.
|
|
16502
|
+
*
|
|
16503
|
+
* Amp shapes with no canonical category are authored and round-tripped through
|
|
16504
|
+
* the `amp` permissions override (see `AmpPermissionsOverrideSchema`): extra
|
|
16505
|
+
* `amp.permissions` entries with non-`cmd` matchers / `context` / `delegate` /
|
|
16506
|
+
* `reject`+`message` (appended after the generated canonical entries), plus the
|
|
16507
|
+
* sibling `amp.mcpPermissions`, `amp.guardedFiles.allowlist`, and
|
|
16508
|
+
* `amp.dangerouslyAllowAll` settings.
|
|
15958
16509
|
*/
|
|
15959
16510
|
var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
15960
16511
|
constructor(params) {
|
|
@@ -16024,15 +16575,25 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16024
16575
|
const jsonDir = (0, node_path.join)(outputRoot, basePaths.relativeDirPath);
|
|
16025
16576
|
const { fileContent, relativeFilePath } = await this.resolveSettingsFile(jsonDir);
|
|
16026
16577
|
const json = fileContent ? parseAmpSettings(fileContent) : {};
|
|
16027
|
-
const
|
|
16028
|
-
const
|
|
16578
|
+
const config = rulesyncPermissions.getJson();
|
|
16579
|
+
const { disable, permissions } = convertRulesyncToAmp(config);
|
|
16580
|
+
const override = config.amp;
|
|
16581
|
+
const authoredExtras = override?.permissions ? toPermissionsList(override.permissions) : [];
|
|
16582
|
+
const preservedDelegates = override?.permissions ? [] : toPermissionsList(json[AMP_PERMISSIONS_KEY]).filter((entry) => entry.action === "delegate");
|
|
16029
16583
|
const newJson = {
|
|
16030
16584
|
...json,
|
|
16031
16585
|
[AMP_TOOLS_DISABLE_KEY]: disable
|
|
16032
16586
|
};
|
|
16033
|
-
const mergedPermissions = [
|
|
16587
|
+
const mergedPermissions = mergeAmpPermissions([
|
|
16588
|
+
...permissions,
|
|
16589
|
+
...authoredExtras,
|
|
16590
|
+
...preservedDelegates
|
|
16591
|
+
]);
|
|
16034
16592
|
if (mergedPermissions.length > 0) newJson[AMP_PERMISSIONS_KEY] = mergedPermissions;
|
|
16035
16593
|
else delete newJson[AMP_PERMISSIONS_KEY];
|
|
16594
|
+
if (override?.guardedFiles?.allowlist !== void 0) newJson[AMP_GUARDED_FILES_ALLOWLIST_KEY] = override.guardedFiles.allowlist;
|
|
16595
|
+
if (override?.dangerouslyAllowAll !== void 0) newJson[AMP_DANGEROUSLY_ALLOW_ALL_KEY] = override.dangerouslyAllowAll;
|
|
16596
|
+
if (override?.mcpPermissions !== void 0) newJson[AMP_MCP_PERMISSIONS_KEY] = override.mcpPermissions;
|
|
16036
16597
|
return new AmpPermissions({
|
|
16037
16598
|
outputRoot,
|
|
16038
16599
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -16043,11 +16604,22 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16043
16604
|
}
|
|
16044
16605
|
toRulesyncPermissions() {
|
|
16045
16606
|
const json = parseAmpSettings(this.getFileContent());
|
|
16607
|
+
const allPermissions = toPermissionsList(json[AMP_PERMISSIONS_KEY]);
|
|
16608
|
+
const canonicalEntries = allPermissions.filter(isCanonicalAmpEntry);
|
|
16609
|
+
const overrideEntries = allPermissions.filter((entry) => !isCanonicalAmpEntry(entry));
|
|
16046
16610
|
const config = convertAmpToRulesync({
|
|
16047
16611
|
disable: toDisableList(json[AMP_TOOLS_DISABLE_KEY]),
|
|
16048
|
-
permissions:
|
|
16049
|
-
});
|
|
16050
|
-
|
|
16612
|
+
permissions: canonicalEntries
|
|
16613
|
+
});
|
|
16614
|
+
const ampOverride = {};
|
|
16615
|
+
if (overrideEntries.length > 0) ampOverride.permissions = overrideEntries;
|
|
16616
|
+
if (Array.isArray(json[AMP_MCP_PERMISSIONS_KEY])) ampOverride.mcpPermissions = json[AMP_MCP_PERMISSIONS_KEY];
|
|
16617
|
+
const allowlist = extractAmpGuardedAllowlist(json[AMP_GUARDED_FILES_ALLOWLIST_KEY]);
|
|
16618
|
+
if (allowlist !== void 0) ampOverride.guardedFiles = { allowlist };
|
|
16619
|
+
if (typeof json[AMP_DANGEROUSLY_ALLOW_ALL_KEY] === "boolean") ampOverride.dangerouslyAllowAll = json[AMP_DANGEROUSLY_ALLOW_ALL_KEY];
|
|
16620
|
+
const result = { ...config };
|
|
16621
|
+
if (Object.keys(ampOverride).length > 0) result.amp = ampOverride;
|
|
16622
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16051
16623
|
}
|
|
16052
16624
|
validate() {
|
|
16053
16625
|
try {
|
|
@@ -16094,6 +16666,37 @@ const ACTION_PRIORITY = {
|
|
|
16094
16666
|
allow: 2
|
|
16095
16667
|
};
|
|
16096
16668
|
/**
|
|
16669
|
+
* Fail-closed action priority for merging the full `amp.permissions` list
|
|
16670
|
+
* (generated + authored/preserved). `reject` leads so no `allow` can shadow it
|
|
16671
|
+
* under first-match-wins; `delegate` (which defers to an external approver, so
|
|
16672
|
+
* it never auto-allows) trails as the final fallback.
|
|
16673
|
+
*/
|
|
16674
|
+
const MERGE_ACTION_PRIORITY = {
|
|
16675
|
+
reject: 0,
|
|
16676
|
+
ask: 1,
|
|
16677
|
+
allow: 2,
|
|
16678
|
+
delegate: 3
|
|
16679
|
+
};
|
|
16680
|
+
/**
|
|
16681
|
+
* Stable fail-closed merge of generated and authored `amp.permissions` entries:
|
|
16682
|
+
* order by {@link MERGE_ACTION_PRIORITY} only, preserving each source's relative
|
|
16683
|
+
* order within an action class. Guarantees an authored `reject` cannot be
|
|
16684
|
+
* shadowed by a generated catch-all `allow`.
|
|
16685
|
+
*/
|
|
16686
|
+
function mergeAmpPermissions(entries) {
|
|
16687
|
+
const decorated = entries.map((entry, index) => ({
|
|
16688
|
+
entry,
|
|
16689
|
+
index
|
|
16690
|
+
}));
|
|
16691
|
+
decorated.sort((a, b) => {
|
|
16692
|
+
const ap = MERGE_ACTION_PRIORITY[a.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16693
|
+
const bp = MERGE_ACTION_PRIORITY[b.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16694
|
+
if (ap !== bp) return ap - bp;
|
|
16695
|
+
return a.index - b.index;
|
|
16696
|
+
});
|
|
16697
|
+
return decorated.map((d) => d.entry);
|
|
16698
|
+
}
|
|
16699
|
+
/**
|
|
16097
16700
|
* Convert a rulesync permissions config into Amp's two permission surfaces.
|
|
16098
16701
|
*
|
|
16099
16702
|
* For each `(category, pattern, action)` — where the category name IS the Amp
|
|
@@ -16158,14 +16761,14 @@ function sortAmpPermissions(entries) {
|
|
|
16158
16761
|
const ap = ACTION_PRIORITY[a.entry.action] ?? 0;
|
|
16159
16762
|
const bp = ACTION_PRIORITY[b.entry.action] ?? 0;
|
|
16160
16763
|
if (ap !== bp) return ap - bp;
|
|
16161
|
-
const aHasCmd = a.entry
|
|
16162
|
-
const bHasCmd = b.entry
|
|
16764
|
+
const aHasCmd = ampMatchesCmd(a.entry) !== void 0 ? 0 : 1;
|
|
16765
|
+
const bHasCmd = ampMatchesCmd(b.entry) !== void 0 ? 0 : 1;
|
|
16163
16766
|
if (aHasCmd !== bHasCmd) return aHasCmd - bHasCmd;
|
|
16164
16767
|
const at = a.entry.tool;
|
|
16165
16768
|
const bt = b.entry.tool;
|
|
16166
16769
|
if (at !== bt) return at < bt ? -1 : 1;
|
|
16167
|
-
const ac = a.entry
|
|
16168
|
-
const bc = b.entry
|
|
16770
|
+
const ac = ampMatchesCmd(a.entry) ?? "";
|
|
16771
|
+
const bc = ampMatchesCmd(b.entry) ?? "";
|
|
16169
16772
|
if (ac !== bc) return ac < bc ? -1 : 1;
|
|
16170
16773
|
return a.index - b.index;
|
|
16171
16774
|
});
|
|
@@ -16204,7 +16807,7 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
16204
16807
|
for (const tool of disable) assign(tool, "*", "deny");
|
|
16205
16808
|
for (const entry of permissions) {
|
|
16206
16809
|
if (entry.action === "delegate") continue;
|
|
16207
|
-
const pattern = entry
|
|
16810
|
+
const pattern = ampMatchesCmd(entry) ?? "*";
|
|
16208
16811
|
const action = entry.action === "reject" ? "deny" : entry.action === "ask" ? "ask" : "allow";
|
|
16209
16812
|
assign(entry.tool, pattern, action);
|
|
16210
16813
|
}
|
|
@@ -16286,6 +16889,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
16286
16889
|
* Permissions are written to the global `~/.gemini/antigravity-cli/settings.json`
|
|
16287
16890
|
* file (global scope only). The file holds other CLI settings besides
|
|
16288
16891
|
* permissions, so it is never deleted.
|
|
16892
|
+
*
|
|
16893
|
+
* Two CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
16894
|
+
* `toolPermission` (the global autonomy preset) and `enableTerminalSandbox` — are
|
|
16895
|
+
* authored and round-tripped through the `antigravity-cli` permissions override
|
|
16896
|
+
* (see `AntigravityCliPermissionsOverrideSchema`).
|
|
16289
16897
|
*/
|
|
16290
16898
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
16291
16899
|
constructor(params) {
|
|
@@ -16346,6 +16954,9 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16346
16954
|
...settings,
|
|
16347
16955
|
permissions: mergedPermissions
|
|
16348
16956
|
};
|
|
16957
|
+
const override = config["antigravity-cli"];
|
|
16958
|
+
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
16959
|
+
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
16349
16960
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
16350
16961
|
return new AntigravityCliPermissions({
|
|
16351
16962
|
outputRoot,
|
|
@@ -16369,7 +16980,12 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16369
16980
|
ask: permissions.ask ?? [],
|
|
16370
16981
|
deny: permissions.deny ?? []
|
|
16371
16982
|
});
|
|
16372
|
-
|
|
16983
|
+
const override = {};
|
|
16984
|
+
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
16985
|
+
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
16986
|
+
const result = { ...config };
|
|
16987
|
+
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
16988
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16373
16989
|
}
|
|
16374
16990
|
validate() {
|
|
16375
16991
|
return {
|
|
@@ -17155,6 +17771,14 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17155
17771
|
}
|
|
17156
17772
|
const config = rulesyncPermissions.getJson();
|
|
17157
17773
|
const { allow, ask, deny } = convertRulesyncToClaudePermissions(config);
|
|
17774
|
+
const overridePermissions = config.claudecode?.permissions;
|
|
17775
|
+
if (overridePermissions && typeof overridePermissions === "object") {
|
|
17776
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = overridePermissions;
|
|
17777
|
+
settings.permissions = {
|
|
17778
|
+
...settings.permissions,
|
|
17779
|
+
...nonListFields
|
|
17780
|
+
};
|
|
17781
|
+
}
|
|
17158
17782
|
const managedToolNames = new Set(Object.keys(config.permission).map((category) => toClaudeToolName(category)));
|
|
17159
17783
|
const merged = applyPermissions({
|
|
17160
17784
|
settings,
|
|
@@ -17187,6 +17811,8 @@ var ClaudecodePermissions = class ClaudecodePermissions extends ToolPermissions
|
|
|
17187
17811
|
ask: permissions.ask ?? [],
|
|
17188
17812
|
deny: permissions.deny ?? []
|
|
17189
17813
|
});
|
|
17814
|
+
const { allow: _a, ask: _k, deny: _d, ...nonListFields } = permissions;
|
|
17815
|
+
if (Object.keys(nonListFields).length > 0) config.claudecode = { permissions: nonListFields };
|
|
17190
17816
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17191
17817
|
}
|
|
17192
17818
|
validate() {
|
|
@@ -17360,7 +17986,8 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17360
17986
|
} catch (error) {
|
|
17361
17987
|
throw new Error(`Failed to parse existing Cline command-permissions at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
17362
17988
|
}
|
|
17363
|
-
const
|
|
17989
|
+
const config = rulesyncPermissions.getJson();
|
|
17990
|
+
const { allow, deny, droppedCategories, translatedAskPatterns } = translateClinePermissions(config.permission);
|
|
17364
17991
|
warnClineTranslationNotices({
|
|
17365
17992
|
droppedCategories,
|
|
17366
17993
|
translatedAskPatterns,
|
|
@@ -17376,7 +18003,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17376
18003
|
...existing,
|
|
17377
18004
|
allow: dedupedAllow,
|
|
17378
18005
|
deny: mergedDeny,
|
|
17379
|
-
allowRedirects: existing.allowRedirects ?? false
|
|
18006
|
+
allowRedirects: config.cline?.allowRedirects ?? existing.allowRedirects ?? false
|
|
17380
18007
|
};
|
|
17381
18008
|
return new ClinePermissions({
|
|
17382
18009
|
outputRoot,
|
|
@@ -17400,6 +18027,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
|
|
|
17400
18027
|
for (const pattern of parsed.allow ?? []) bashRules[pattern] = "allow";
|
|
17401
18028
|
for (const pattern of parsed.deny ?? []) bashRules[pattern] = "deny";
|
|
17402
18029
|
const config = Object.keys(bashRules).length > 0 ? { permission: { bash: bashRules } } : { permission: {} };
|
|
18030
|
+
if (parsed.allowRedirects === true) config.cline = { allowRedirects: true };
|
|
17403
18031
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
|
|
17404
18032
|
}
|
|
17405
18033
|
validate() {
|
|
@@ -17836,13 +18464,13 @@ const CURSOR_TYPE_TO_CANONICAL = {
|
|
|
17836
18464
|
WebFetch: "webfetch",
|
|
17837
18465
|
Mcp: "mcp"
|
|
17838
18466
|
};
|
|
17839
|
-
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
18467
|
+
const MCP_CANONICAL_PREFIX$1 = "mcp__";
|
|
17840
18468
|
/**
|
|
17841
18469
|
* Returns true if the canonical category is the per-tool MCP form
|
|
17842
18470
|
* `mcp__<server>__<tool>`.
|
|
17843
18471
|
*/
|
|
17844
18472
|
function isMcpScopedCategory(canonical) {
|
|
17845
|
-
return canonical.startsWith(MCP_CANONICAL_PREFIX) && canonical.length > 5;
|
|
18473
|
+
return canonical.startsWith(MCP_CANONICAL_PREFIX$1) && canonical.length > 5;
|
|
17846
18474
|
}
|
|
17847
18475
|
function toCursorType(canonical) {
|
|
17848
18476
|
if (isMcpScopedCategory(canonical)) return "Mcp";
|
|
@@ -17873,7 +18501,7 @@ function toCursorPattern(canonical, pattern) {
|
|
|
17873
18501
|
function toCanonicalCategory$1(cursorType, pattern) {
|
|
17874
18502
|
if (cursorType === "Mcp") {
|
|
17875
18503
|
const match = pattern.match(/^([^:()]+):([^:()]+)$/);
|
|
17876
|
-
if (match) return `${MCP_CANONICAL_PREFIX}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
18504
|
+
if (match) return `${MCP_CANONICAL_PREFIX$1}${match[1] ?? "*"}__${match[2] ?? "*"}`;
|
|
17877
18505
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
17878
18506
|
}
|
|
17879
18507
|
return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
|
|
@@ -18007,8 +18635,10 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
18007
18635
|
mergedPermissions.allow = mergedAllow;
|
|
18008
18636
|
if (mergedDeny.length > 0) mergedPermissions.deny = mergedDeny;
|
|
18009
18637
|
else delete mergedPermissions.deny;
|
|
18638
|
+
const cursorOverride = config.cursor;
|
|
18010
18639
|
const merged = {
|
|
18011
18640
|
...settings,
|
|
18641
|
+
...cursorOverride,
|
|
18012
18642
|
version: settings.version ?? 1,
|
|
18013
18643
|
editor: {
|
|
18014
18644
|
...existingEditor,
|
|
@@ -18034,11 +18664,15 @@ var CursorPermissions = class CursorPermissions extends ToolPermissions {
|
|
|
18034
18664
|
}
|
|
18035
18665
|
const permissionsRaw = settings.permissions;
|
|
18036
18666
|
const permissions = permissionsRaw !== null && typeof permissionsRaw === "object" && !Array.isArray(permissionsRaw) ? permissionsRaw : {};
|
|
18037
|
-
const
|
|
18667
|
+
const result = { ...convertCursorToRulesyncPermissions({
|
|
18038
18668
|
allow: asCursorPermissionEntryArray(permissions.allow),
|
|
18039
18669
|
deny: asCursorPermissionEntryArray(permissions.deny)
|
|
18040
|
-
});
|
|
18041
|
-
|
|
18670
|
+
}) };
|
|
18671
|
+
const cursorOverride = {};
|
|
18672
|
+
if (settings.approvalMode !== void 0) cursorOverride.approvalMode = settings.approvalMode;
|
|
18673
|
+
if (settings.sandbox !== null && typeof settings.sandbox === "object" && !Array.isArray(settings.sandbox)) cursorOverride.sandbox = settings.sandbox;
|
|
18674
|
+
if (Object.keys(cursorOverride).length > 0) result.cursor = cursorOverride;
|
|
18675
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18042
18676
|
}
|
|
18043
18677
|
validate() {
|
|
18044
18678
|
return {
|
|
@@ -18097,7 +18731,7 @@ function convertCursorToRulesyncPermissions(params) {
|
|
|
18097
18731
|
const { type, pattern } = parseCursorPermissionEntry(entry);
|
|
18098
18732
|
const canonical = toCanonicalCategory$1(type, pattern);
|
|
18099
18733
|
if (!permission[canonical]) permission[canonical] = {};
|
|
18100
|
-
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX) ? "*" : pattern;
|
|
18734
|
+
const canonicalPattern = type === "Mcp" && canonical.startsWith(MCP_CANONICAL_PREFIX$1) ? "*" : pattern;
|
|
18101
18735
|
permission[canonical][canonicalPattern] = action;
|
|
18102
18736
|
}
|
|
18103
18737
|
};
|
|
@@ -18346,6 +18980,16 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18346
18980
|
}
|
|
18347
18981
|
//#endregion
|
|
18348
18982
|
//#region src/features/permissions/factorydroid-permissions.ts
|
|
18983
|
+
const FACTORYDROID_OVERRIDE_KEYS = [
|
|
18984
|
+
"commandBlocklist",
|
|
18985
|
+
"networkPolicy",
|
|
18986
|
+
"sandbox",
|
|
18987
|
+
"mcpPolicy",
|
|
18988
|
+
"enableDroidShield",
|
|
18989
|
+
"sessionDefaultSettings",
|
|
18990
|
+
"maxAutonomyLevel",
|
|
18991
|
+
"interactionMode"
|
|
18992
|
+
];
|
|
18349
18993
|
/**
|
|
18350
18994
|
* Permissions adapter for Factory Droid.
|
|
18351
18995
|
*
|
|
@@ -18363,18 +19007,14 @@ function convertDevinToRulesyncPermissions(params) {
|
|
|
18363
19007
|
* skipped (with a warning when they carry `deny` rules, to surface the gap).
|
|
18364
19008
|
*
|
|
18365
19009
|
* Factory Droid also has a stronger `commandBlocklist` tier — commands that can
|
|
18366
|
-
* never run, not even under full autonomy
|
|
18367
|
-
*
|
|
18368
|
-
*
|
|
18369
|
-
*
|
|
18370
|
-
*
|
|
18371
|
-
*
|
|
18372
|
-
*
|
|
18373
|
-
*
|
|
18374
|
-
* `commandBlocklist` and re-exporting it to a *fresh* config writes it back as
|
|
18375
|
-
* `commandDenylist`, not `commandBlocklist` — the hard-block tier is not
|
|
18376
|
-
* reconstructed. Re-running over the original file keeps it intact via the
|
|
18377
|
-
* verbatim preservation above.)
|
|
19010
|
+
* never run, not even under full autonomy — plus other security controls
|
|
19011
|
+
* (`networkPolicy`, `sandbox`, `mcpPolicy`, `enableDroidShield`, autonomy
|
|
19012
|
+
* settings) that do not fit the canonical `allow | ask | deny` per-command
|
|
19013
|
+
* model. These are authored and round-tripped through the `factorydroid`
|
|
19014
|
+
* override namespace (see `FactorydroidPermissionsOverrideSchema`): on **import**
|
|
19015
|
+
* they are lifted from `settings.json` into the override, and on **export** they
|
|
19016
|
+
* are merged back in — so `commandBlocklist`'s never-runs guarantee is preserved
|
|
19017
|
+
* faithfully rather than being collapsed onto an approvable `deny`.
|
|
18378
19018
|
*/
|
|
18379
19019
|
var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissions {
|
|
18380
19020
|
constructor(params) {
|
|
@@ -18417,11 +19057,16 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18417
19057
|
} catch (error) {
|
|
18418
19058
|
throw new Error(`Failed to parse existing Factory Droid settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18419
19059
|
}
|
|
19060
|
+
const config = rulesyncPermissions.getJson();
|
|
18420
19061
|
const { allow, deny } = convertRulesyncToFactorydroidPermissions({
|
|
18421
|
-
config
|
|
19062
|
+
config,
|
|
18422
19063
|
logger
|
|
18423
19064
|
});
|
|
18424
|
-
const
|
|
19065
|
+
const override = config.factorydroid;
|
|
19066
|
+
const merged = {
|
|
19067
|
+
...settings,
|
|
19068
|
+
...override !== void 0 && typeof override === "object" ? override : {}
|
|
19069
|
+
};
|
|
18425
19070
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
18426
19071
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
18427
19072
|
if (mergedAllow.length > 0) merged.commandAllowlist = mergedAllow;
|
|
@@ -18445,10 +19090,13 @@ var FactorydroidPermissions = class FactorydroidPermissions extends ToolPermissi
|
|
|
18445
19090
|
}
|
|
18446
19091
|
const config = convertFactorydroidToRulesyncPermissions({
|
|
18447
19092
|
allow: Array.isArray(settings.commandAllowlist) ? settings.commandAllowlist : [],
|
|
18448
|
-
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18449
|
-
block: Array.isArray(settings.commandBlocklist) ? settings.commandBlocklist : []
|
|
19093
|
+
deny: Array.isArray(settings.commandDenylist) ? settings.commandDenylist : []
|
|
18450
19094
|
});
|
|
18451
|
-
|
|
19095
|
+
const factorydroidOverride = {};
|
|
19096
|
+
for (const key of FACTORYDROID_OVERRIDE_KEYS) if (settings[key] !== void 0) factorydroidOverride[key] = settings[key];
|
|
19097
|
+
const result = { ...config };
|
|
19098
|
+
if (Object.keys(factorydroidOverride).length > 0) result.factorydroid = factorydroidOverride;
|
|
19099
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
18452
19100
|
}
|
|
18453
19101
|
validate() {
|
|
18454
19102
|
return {
|
|
@@ -18495,18 +19143,18 @@ function convertRulesyncToFactorydroidPermissions({ config, logger }) {
|
|
|
18495
19143
|
};
|
|
18496
19144
|
}
|
|
18497
19145
|
/**
|
|
18498
|
-
* Convert Factory Droid allow/deny
|
|
18499
|
-
*
|
|
19146
|
+
* Convert Factory Droid allow/deny command lists back to rulesync config under
|
|
19147
|
+
* the `bash` category.
|
|
18500
19148
|
*
|
|
18501
|
-
* `commandBlocklist` (hard
|
|
18502
|
-
*
|
|
18503
|
-
* the
|
|
19149
|
+
* `commandBlocklist` (the hard-block tier) is no longer collapsed here — it has
|
|
19150
|
+
* no canonical equivalent and now round-trips through the `factorydroid`
|
|
19151
|
+
* override so the never-runs guarantee is preserved instead of being weakened to
|
|
19152
|
+
* an approvable `deny`.
|
|
18504
19153
|
*/
|
|
18505
|
-
function convertFactorydroidToRulesyncPermissions({ allow, deny
|
|
19154
|
+
function convertFactorydroidToRulesyncPermissions({ allow, deny }) {
|
|
18506
19155
|
const bash = {};
|
|
18507
19156
|
for (const pattern of allow) bash[pattern] = "allow";
|
|
18508
19157
|
for (const pattern of deny) bash[pattern] = "deny";
|
|
18509
|
-
for (const pattern of block) bash[pattern] = "deny";
|
|
18510
19158
|
return { permission: Object.keys(bash).length > 0 ? { bash } : {} };
|
|
18511
19159
|
}
|
|
18512
19160
|
//#endregion
|
|
@@ -18700,30 +19348,114 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
|
|
|
18700
19348
|
const GROKCLI_GLOBAL_ONLY_MESSAGE = "Grok CLI permissions are global-only; use --global to sync ~/.grok/config.toml";
|
|
18701
19349
|
const GROKCLI_UI_KEY = "ui";
|
|
18702
19350
|
const GROKCLI_PERMISSION_MODE_KEY = "permission_mode";
|
|
19351
|
+
const GROKCLI_PERMISSION_KEY = "permission";
|
|
18703
19352
|
const CATCH_ALL_PATTERN$2 = "*";
|
|
19353
|
+
const MCP_CANONICAL_PREFIX = "mcp__";
|
|
19354
|
+
const CATEGORY_TO_GROK_TOOL = {
|
|
19355
|
+
bash: "Bash",
|
|
19356
|
+
read: "Read",
|
|
19357
|
+
edit: "Edit",
|
|
19358
|
+
write: "Edit",
|
|
19359
|
+
grep: "Grep",
|
|
19360
|
+
webfetch: "WebFetch"
|
|
19361
|
+
};
|
|
19362
|
+
const GROK_TOOL_TO_CATEGORY = {
|
|
19363
|
+
Bash: "bash",
|
|
19364
|
+
Read: "read",
|
|
19365
|
+
Edit: "edit",
|
|
19366
|
+
Grep: "grep",
|
|
19367
|
+
WebFetch: "webfetch"
|
|
19368
|
+
};
|
|
19369
|
+
const GROK_MCP_TOOL = "MCPTool";
|
|
19370
|
+
/**
|
|
19371
|
+
* Build a Grok Claude-style permission entry (e.g. `Bash(git *)`, `Read`,
|
|
19372
|
+
* `MCPTool(server__tool)`) from a canonical category + pattern. Returns `null`
|
|
19373
|
+
* for categories Grok cannot express so callers can skip them.
|
|
19374
|
+
*
|
|
19375
|
+
* Scoped MCP categories (`mcp__<remainder>`) fold their address into the
|
|
19376
|
+
* parentheses (`MCPTool(<remainder>)`); the bare `mcp` category becomes
|
|
19377
|
+
* `MCPTool`. For every other tool, a `*` pattern emits the bare tool name and a
|
|
19378
|
+
* concrete pattern emits `Tool(pattern)`.
|
|
19379
|
+
*/
|
|
19380
|
+
function buildGrokEntry(category, pattern) {
|
|
19381
|
+
if (category.startsWith(MCP_CANONICAL_PREFIX)) {
|
|
19382
|
+
const remainder = category.slice(5);
|
|
19383
|
+
return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
|
|
19384
|
+
}
|
|
19385
|
+
if (category === "mcp") return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
|
|
19386
|
+
const tool = CATEGORY_TO_GROK_TOOL[category];
|
|
19387
|
+
if (tool === void 0) return null;
|
|
19388
|
+
return pattern === CATCH_ALL_PATTERN$2 || pattern === "" ? tool : `${tool}(${pattern})`;
|
|
19389
|
+
}
|
|
19390
|
+
/**
|
|
19391
|
+
* Parse a Grok Claude-style permission entry back into a canonical category +
|
|
19392
|
+
* pattern. Entries without parentheses are treated as catch-all (`*`). Returns
|
|
19393
|
+
* `null` for tool prefixes with no canonical equivalent (e.g. `any`).
|
|
19394
|
+
*/
|
|
19395
|
+
function parseGrokEntry(entry) {
|
|
19396
|
+
const trimmed = entry.trim();
|
|
19397
|
+
const parenIndex = trimmed.indexOf("(");
|
|
19398
|
+
let tool;
|
|
19399
|
+
let inner;
|
|
19400
|
+
if (parenIndex === -1 || !trimmed.endsWith(")")) {
|
|
19401
|
+
tool = trimmed;
|
|
19402
|
+
inner = "";
|
|
19403
|
+
} else {
|
|
19404
|
+
tool = trimmed.slice(0, parenIndex);
|
|
19405
|
+
inner = trimmed.slice(parenIndex + 1, -1).trim();
|
|
19406
|
+
}
|
|
19407
|
+
if (tool === GROK_MCP_TOOL) return inner.length > 0 ? {
|
|
19408
|
+
category: `${MCP_CANONICAL_PREFIX}${inner}`,
|
|
19409
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19410
|
+
} : {
|
|
19411
|
+
category: "mcp",
|
|
19412
|
+
pattern: CATCH_ALL_PATTERN$2
|
|
19413
|
+
};
|
|
19414
|
+
const category = GROK_TOOL_TO_CATEGORY[tool];
|
|
19415
|
+
if (category === void 0) return null;
|
|
19416
|
+
return {
|
|
19417
|
+
category,
|
|
19418
|
+
pattern: inner.length > 0 ? inner : CATCH_ALL_PATTERN$2
|
|
19419
|
+
};
|
|
19420
|
+
}
|
|
18704
19421
|
/**
|
|
18705
19422
|
* Permissions adapter for the xAI Grok Build CLI (`grokcli`).
|
|
18706
19423
|
*
|
|
18707
|
-
* Grok
|
|
18708
|
-
*
|
|
18709
|
-
*
|
|
18710
|
-
*
|
|
18711
|
-
*
|
|
18712
|
-
*
|
|
18713
|
-
*
|
|
18714
|
-
*
|
|
18715
|
-
*
|
|
18716
|
-
*
|
|
18717
|
-
* `
|
|
18718
|
-
*
|
|
18719
|
-
*
|
|
18720
|
-
*
|
|
18721
|
-
* the
|
|
18722
|
-
*
|
|
18723
|
-
*
|
|
18724
|
-
*
|
|
18725
|
-
*
|
|
18726
|
-
*
|
|
19424
|
+
* Grok Build CLI ships a Claude-style rule system under `[permission]` in
|
|
19425
|
+
* `~/.grok/config.toml`: `allow` / `deny` / `ask` arrays of entries such as
|
|
19426
|
+
* `Bash(git *)`, `Read(src/**)`, `Edit`, `Grep`, `MCPTool(server__tool)`, and
|
|
19427
|
+
* `WebFetch`, evaluated with precedence `deny > ask > allow`
|
|
19428
|
+
* (https://docs.x.ai/build/settings/reference). rulesync's canonical
|
|
19429
|
+
* per-category, per-pattern model maps almost 1:1:
|
|
19430
|
+
* - Generate: each `permission.<category>.<pattern> = allow|ask|deny` becomes
|
|
19431
|
+
* the matching Grok entry and is bucketed into the `[permission]` array for
|
|
19432
|
+
* that action. `bash|read|edit|grep|webfetch` map to their Grok tool;
|
|
19433
|
+
* `write` collapses onto `Edit` (Grok has no `Write` tool); `mcp__*` maps to
|
|
19434
|
+
* `MCPTool(...)` (a scoped MCP category folds its address into the
|
|
19435
|
+
* parentheses, so a non-`*` argument pattern on it is not represented).
|
|
19436
|
+
* Categories with no Grok tool (`websearch`, `glob`, `notebookedit`,
|
|
19437
|
+
* `agent`) are skipped (with a warning when they carry a `deny` rule, to
|
|
19438
|
+
* surface the gap). When two canonical rules collapse onto the same Grok
|
|
19439
|
+
* entry with different actions (e.g. `edit` allow + `write` deny → `Edit`),
|
|
19440
|
+
* the strictest wins (`deny > ask > allow`) and a warning is logged, so the
|
|
19441
|
+
* entry never lands contradictorily in two arrays.
|
|
19442
|
+
* - Import: the `[permission]` arrays are parsed back into canonical
|
|
19443
|
+
* categories. When no `[permission]` section is present (older configs), the
|
|
19444
|
+
* coarse `[ui] permission_mode` is used as a fallback.
|
|
19445
|
+
*
|
|
19446
|
+
* The coarse `[ui] permission_mode` toggle is still written for backward
|
|
19447
|
+
* compatibility with older Grok versions: `always-approve` when the config is
|
|
19448
|
+
* pure-`allow`, otherwise `ask` (conservative — it is never `always-approve`
|
|
19449
|
+
* while any `deny`/`ask` rule exists, so it never contradicts the fine-grained
|
|
19450
|
+
* arrays).
|
|
19451
|
+
*
|
|
19452
|
+
* This surface is **global only** — the adapter syncs the user-level
|
|
19453
|
+
* `~/.grok/config.toml`. (Grok also supports a project-scoped `[permission]`
|
|
19454
|
+
* file; project scope is not modeled here.) The shared config is merged in
|
|
19455
|
+
* place: rulesync owns the `[permission]` `allow`/`deny`/`ask` entries for the
|
|
19456
|
+
* tools it models and the `[ui] permission_mode` value, while every other key
|
|
19457
|
+
* (e.g. `[mcp_servers]`, `[permission] rules`, `[sandbox]`) and any user-authored
|
|
19458
|
+
* entries for tools rulesync cannot model (e.g. `WebSearch`) are preserved. The
|
|
18727
19459
|
* file is never deleted.
|
|
18728
19460
|
*/
|
|
18729
19461
|
var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
@@ -18755,7 +19487,7 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18755
19487
|
global: true
|
|
18756
19488
|
});
|
|
18757
19489
|
}
|
|
18758
|
-
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
19490
|
+
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, logger, global = false }) {
|
|
18759
19491
|
if (!global) throw new Error(GROKCLI_GLOBAL_ONLY_MESSAGE);
|
|
18760
19492
|
const paths = GrokcliPermissions.getSettablePaths({ global });
|
|
18761
19493
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
@@ -18766,7 +19498,16 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18766
19498
|
} catch (error) {
|
|
18767
19499
|
throw new Error(`Failed to parse existing Grok config.toml at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18768
19500
|
}
|
|
18769
|
-
const
|
|
19501
|
+
const config = rulesyncPermissions.getJson();
|
|
19502
|
+
const existingPermission = isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {};
|
|
19503
|
+
const buckets = buildGrokPermissionArrays(config, existingPermission, logger);
|
|
19504
|
+
parsed[GROKCLI_PERMISSION_KEY] = {
|
|
19505
|
+
...existingPermission,
|
|
19506
|
+
allow: buckets.allow,
|
|
19507
|
+
deny: buckets.deny,
|
|
19508
|
+
ask: buckets.ask
|
|
19509
|
+
};
|
|
19510
|
+
const mode = deriveGrokPermissionMode(config);
|
|
18770
19511
|
const existingUi = isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {};
|
|
18771
19512
|
parsed[GROKCLI_UI_KEY] = {
|
|
18772
19513
|
...existingUi,
|
|
@@ -18789,8 +19530,8 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18789
19530
|
} catch (error) {
|
|
18790
19531
|
throw new Error(`Failed to parse Grok config.toml content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
18791
19532
|
}
|
|
18792
|
-
const
|
|
18793
|
-
const rulesyncConfig = { permission: { bash: { [CATCH_ALL_PATTERN$2]:
|
|
19533
|
+
const fineGrained = parseGrokPermissionArrays(isRecord(parsed[GROKCLI_PERMISSION_KEY]) ? parsed[GROKCLI_PERMISSION_KEY] : {});
|
|
19534
|
+
const rulesyncConfig = fineGrained ? { permission: fineGrained } : { permission: { bash: { [CATCH_ALL_PATTERN$2]: legacyModeAction(parsed) } } };
|
|
18794
19535
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(rulesyncConfig, null, 2) });
|
|
18795
19536
|
}
|
|
18796
19537
|
validate() {
|
|
@@ -18810,6 +19551,84 @@ var GrokcliPermissions = class GrokcliPermissions extends ToolPermissions {
|
|
|
18810
19551
|
});
|
|
18811
19552
|
}
|
|
18812
19553
|
};
|
|
19554
|
+
const ACTION_RANK = {
|
|
19555
|
+
allow: 0,
|
|
19556
|
+
ask: 1,
|
|
19557
|
+
deny: 2
|
|
19558
|
+
};
|
|
19559
|
+
/**
|
|
19560
|
+
* Collect user-authored entries from an existing `[permission]` array whose
|
|
19561
|
+
* tool prefix rulesync cannot model (e.g. `WebSearch`, `any`). Such entries are
|
|
19562
|
+
* preserved verbatim so replacing the arrays does not silently drop them
|
|
19563
|
+
* (mirrors the Cursor adapter's preservation of unmanaged types).
|
|
19564
|
+
*/
|
|
19565
|
+
function unmanagedEntries(existingPermission, key) {
|
|
19566
|
+
return (isStringArray(existingPermission[key]) ? existingPermission[key] : []).filter((entry) => parseGrokEntry(entry) === null);
|
|
19567
|
+
}
|
|
19568
|
+
/**
|
|
19569
|
+
* Bucket canonical rules into Grok's `[permission]` allow/deny/ask arrays of
|
|
19570
|
+
* Claude-style entries, resolving collapse collisions via `deny > ask > allow`.
|
|
19571
|
+
* Categories Grok cannot express are skipped (with a warning when they carry a
|
|
19572
|
+
* `deny` rule); entries the user authored for tools rulesync cannot model are
|
|
19573
|
+
* preserved from `existingPermission`.
|
|
19574
|
+
*/
|
|
19575
|
+
function buildGrokPermissionArrays(config, existingPermission, logger) {
|
|
19576
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
19577
|
+
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19578
|
+
const entry = buildGrokEntry(category, pattern);
|
|
19579
|
+
if (entry === null) {
|
|
19580
|
+
if (action === "deny" && logger) logger.warn(`Grok CLI has no permission tool for the '${category}' category; its 'deny' rule could not be represented and was skipped.`);
|
|
19581
|
+
continue;
|
|
19582
|
+
}
|
|
19583
|
+
const existing = ranked.get(entry);
|
|
19584
|
+
if (existing !== void 0 && existing !== action) logger?.warn(`Grok permission entry '${entry}' received conflicting actions ('${existing}' and '${action}'); keeping the stricter one (deny > ask > allow).`);
|
|
19585
|
+
if (existing === void 0 || ACTION_RANK[action] > ACTION_RANK[existing]) ranked.set(entry, action);
|
|
19586
|
+
}
|
|
19587
|
+
const allow = unmanagedEntries(existingPermission, "allow");
|
|
19588
|
+
const deny = unmanagedEntries(existingPermission, "deny");
|
|
19589
|
+
const ask = unmanagedEntries(existingPermission, "ask");
|
|
19590
|
+
for (const [entry, action] of ranked) if (action === "allow") allow.push(entry);
|
|
19591
|
+
else if (action === "deny") deny.push(entry);
|
|
19592
|
+
else ask.push(entry);
|
|
19593
|
+
return {
|
|
19594
|
+
allow: (0, es_toolkit.uniq)(allow.toSorted()),
|
|
19595
|
+
deny: (0, es_toolkit.uniq)(deny.toSorted()),
|
|
19596
|
+
ask: (0, es_toolkit.uniq)(ask.toSorted())
|
|
19597
|
+
};
|
|
19598
|
+
}
|
|
19599
|
+
/**
|
|
19600
|
+
* Parse Grok's `[permission]` allow/deny/ask arrays back into a canonical
|
|
19601
|
+
* permission map. Returns `null` when the section defines none of the three
|
|
19602
|
+
* arrays, so the caller can fall back to the coarse `permission_mode`.
|
|
19603
|
+
* Precedence `deny > ask > allow` is applied so a tool listed in multiple
|
|
19604
|
+
* arrays resolves to the strictest action.
|
|
19605
|
+
*/
|
|
19606
|
+
function parseGrokPermissionArrays(permission) {
|
|
19607
|
+
const allow = isStringArray(permission.allow) ? permission.allow : void 0;
|
|
19608
|
+
const deny = isStringArray(permission.deny) ? permission.deny : void 0;
|
|
19609
|
+
const ask = isStringArray(permission.ask) ? permission.ask : void 0;
|
|
19610
|
+
if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) === 0) return null;
|
|
19611
|
+
const result = {};
|
|
19612
|
+
const apply = (entries, action) => {
|
|
19613
|
+
for (const entry of entries ?? []) {
|
|
19614
|
+
const parsed = parseGrokEntry(entry);
|
|
19615
|
+
if (parsed === null) continue;
|
|
19616
|
+
const bucket = result[parsed.category] ??= {};
|
|
19617
|
+
bucket[parsed.pattern] = action;
|
|
19618
|
+
}
|
|
19619
|
+
};
|
|
19620
|
+
apply(allow, "allow");
|
|
19621
|
+
apply(ask, "ask");
|
|
19622
|
+
apply(deny, "deny");
|
|
19623
|
+
return result;
|
|
19624
|
+
}
|
|
19625
|
+
/**
|
|
19626
|
+
* Legacy coarse fallback: map `[ui] permission_mode` to a canonical action.
|
|
19627
|
+
* `always-approve` ⇒ `allow`; anything else (including a missing mode) ⇒ `ask`.
|
|
19628
|
+
*/
|
|
19629
|
+
function legacyModeAction(parsed) {
|
|
19630
|
+
return (isRecord(parsed[GROKCLI_UI_KEY]) ? parsed[GROKCLI_UI_KEY] : {})[GROKCLI_PERMISSION_MODE_KEY] === "always-approve" ? "allow" : "ask";
|
|
19631
|
+
}
|
|
18813
19632
|
/**
|
|
18814
19633
|
* Collapse a rulesync permissions config into Grok's single coarse mode.
|
|
18815
19634
|
* Any `deny`/`ask` rule anywhere keeps prompting (`ask`); otherwise an existing
|
|
@@ -18825,6 +19644,10 @@ function deriveGrokPermissionMode(config) {
|
|
|
18825
19644
|
}
|
|
18826
19645
|
//#endregion
|
|
18827
19646
|
//#region src/features/permissions/hermesagent-permissions.ts
|
|
19647
|
+
/** Collect the glob patterns in a canonical category that carry a given action. */
|
|
19648
|
+
function patternsByAction(category, action) {
|
|
19649
|
+
return Object.entries(category ?? {}).filter(([, value]) => value === action).map(([pattern]) => pattern);
|
|
19650
|
+
}
|
|
18828
19651
|
var HermesagentPermissions = class HermesagentPermissions extends ToolPermissions {
|
|
18829
19652
|
static getSettablePaths() {
|
|
18830
19653
|
return {
|
|
@@ -18848,10 +19671,21 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18848
19671
|
return true;
|
|
18849
19672
|
}
|
|
18850
19673
|
setFileContent(fileContent) {
|
|
18851
|
-
this.fileContent =
|
|
19674
|
+
this.fileContent = applySharedConfigPatch({
|
|
19675
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
19676
|
+
feature: "permissions",
|
|
19677
|
+
existingContent: fileContent,
|
|
19678
|
+
patch: parseSharedConfig({
|
|
19679
|
+
format: "yaml",
|
|
19680
|
+
fileContent: this.fileContent
|
|
19681
|
+
})
|
|
19682
|
+
});
|
|
18852
19683
|
}
|
|
18853
19684
|
toRulesyncPermissions() {
|
|
18854
|
-
const config =
|
|
19685
|
+
const config = parseSharedConfig({
|
|
19686
|
+
format: "yaml",
|
|
19687
|
+
fileContent: this.getFileContent()
|
|
19688
|
+
});
|
|
18855
19689
|
const permissions = config.permissions && typeof config.permissions === "object" ? config.permissions.rulesync : {};
|
|
18856
19690
|
return new RulesyncPermissions({
|
|
18857
19691
|
relativeDirPath: "",
|
|
@@ -18861,11 +19695,27 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18861
19695
|
}
|
|
18862
19696
|
static fromRulesyncPermissions({ outputRoot, rulesyncPermissions }) {
|
|
18863
19697
|
const permissions = rulesyncPermissions.getJson();
|
|
19698
|
+
const permissionBlock = permissions.permission ?? {};
|
|
19699
|
+
const commandAllowlist = Object.entries(permissionBlock).flatMap(([, patterns]) => patternsByAction(patterns, "allow"));
|
|
19700
|
+
const bashDeny = patternsByAction(permissionBlock.bash, "deny");
|
|
19701
|
+
const webfetchDeny = patternsByAction(permissionBlock.webfetch, "deny");
|
|
19702
|
+
let config = {};
|
|
19703
|
+
if (commandAllowlist.length > 0) config.command_allowlist = commandAllowlist;
|
|
19704
|
+
if (bashDeny.length > 0) config.approvals = { deny: bashDeny };
|
|
19705
|
+
if (webfetchDeny.length > 0) config.security = { website_blocklist: {
|
|
19706
|
+
enabled: true,
|
|
19707
|
+
domains: webfetchDeny
|
|
19708
|
+
} };
|
|
19709
|
+
if (permissions.hermes && typeof permissions.hermes === "object") config = mergeSharedConfigDeep({
|
|
19710
|
+
base: config,
|
|
19711
|
+
patch: permissions.hermes
|
|
19712
|
+
});
|
|
19713
|
+
config.permissions = { rulesync: permissions };
|
|
18864
19714
|
return new HermesagentPermissions({
|
|
18865
19715
|
outputRoot,
|
|
18866
|
-
fileContent:
|
|
18867
|
-
|
|
18868
|
-
|
|
19716
|
+
fileContent: stringifySharedConfig({
|
|
19717
|
+
format: "yaml",
|
|
19718
|
+
document: config
|
|
18869
19719
|
})
|
|
18870
19720
|
});
|
|
18871
19721
|
}
|
|
@@ -18887,14 +19737,18 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18887
19737
|
* "executables": [ { "prefix": "git ", "action": "allow" } ],
|
|
18888
19738
|
* "fileEditing": [ { "pattern": "src/**", "action": "allow" } ],
|
|
18889
19739
|
* "mcpTools": [ { "prefix": "search", "action": "allow" } ],
|
|
18890
|
-
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "
|
|
19740
|
+
* "readOutsideProject":[ { "pattern": "/etc/**", "action": "ask" } ]
|
|
18891
19741
|
* }
|
|
18892
19742
|
* }
|
|
18893
19743
|
* ```
|
|
18894
19744
|
*
|
|
18895
19745
|
* Each rule carries a literal `prefix` (matches commands that start with it) or
|
|
18896
|
-
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action
|
|
18897
|
-
*
|
|
19746
|
+
* a glob `pattern` (`*`, `**`, `?`, `[abc]`, `[!abc]`) plus an `action`. Junie
|
|
19747
|
+
* documents only `allow` and `ask` as valid actions — there is **no `deny`**
|
|
19748
|
+
* (https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html). rulesync's
|
|
19749
|
+
* canonical `allow`/`ask` map 1:1; a canonical `deny` has no Junie equivalent
|
|
19750
|
+
* and is mapped to the nearest valid action, `ask` (still withholds
|
|
19751
|
+
* auto-approval), with a warning so the downgrade is surfaced.
|
|
18898
19752
|
*
|
|
18899
19753
|
* Category mapping (rulesync canonical <-> Junie rule group):
|
|
18900
19754
|
* - `bash` <-> `executables`
|
|
@@ -18904,8 +19758,11 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
18904
19758
|
*
|
|
18905
19759
|
* Categories Junie cannot represent (e.g. `webfetch`) are skipped on export
|
|
18906
19760
|
* (with a warning when they carry rules). The top-level `defaultBehavior` and
|
|
18907
|
-
* `allowReadonlyCommands` settings have no canonical
|
|
18908
|
-
*
|
|
19761
|
+
* `allowReadonlyCommands` settings have no canonical per-glob slot: they are
|
|
19762
|
+
* authored and round-tripped through the `junie` override namespace (see
|
|
19763
|
+
* `JuniePermissionsOverrideSchema`) — lifted into the override on import and
|
|
19764
|
+
* merged back onto the top level on export — and any other unmodeled top-level
|
|
19765
|
+
* key is preserved verbatim.
|
|
18909
19766
|
*
|
|
18910
19767
|
* @see https://junie.jetbrains.com/docs/action-allowlist-junie-cli.html
|
|
18911
19768
|
*/
|
|
@@ -18928,7 +19785,6 @@ const JUNIE_GROUP_TO_CANONICAL = {
|
|
|
18928
19785
|
mcpTools: "mcp",
|
|
18929
19786
|
readOutsideProject: "read"
|
|
18930
19787
|
};
|
|
18931
|
-
const JUNIE_DEFAULT_BEHAVIOR = "ask";
|
|
18932
19788
|
function isPermissionAction$1(value) {
|
|
18933
19789
|
return PermissionActionSchema.safeParse(value).success;
|
|
18934
19790
|
}
|
|
@@ -18978,13 +19834,16 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
18978
19834
|
} catch (error) {
|
|
18979
19835
|
throw new Error(`Failed to parse existing Junie allowlist at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
18980
19836
|
}
|
|
19837
|
+
const config = rulesyncPermissions.getJson();
|
|
18981
19838
|
const rules = convertRulesyncToJunieRules({
|
|
18982
|
-
config
|
|
19839
|
+
config,
|
|
18983
19840
|
logger
|
|
18984
19841
|
});
|
|
19842
|
+
const override = config.junie;
|
|
19843
|
+
const overrideObj = override !== void 0 && typeof override === "object" ? override : {};
|
|
18985
19844
|
const merged = {
|
|
18986
19845
|
...existing,
|
|
18987
|
-
|
|
19846
|
+
...overrideObj,
|
|
18988
19847
|
rules
|
|
18989
19848
|
};
|
|
18990
19849
|
return new JuniePermissions({
|
|
@@ -19004,7 +19863,12 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
|
|
|
19004
19863
|
throw new Error(`Failed to parse Junie permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
|
|
19005
19864
|
}
|
|
19006
19865
|
const config = convertJunieToRulesyncPermissions({ allowlist });
|
|
19007
|
-
|
|
19866
|
+
const junieOverride = {};
|
|
19867
|
+
if (typeof allowlist.allowReadonlyCommands === "boolean") junieOverride.allowReadonlyCommands = allowlist.allowReadonlyCommands;
|
|
19868
|
+
if (typeof allowlist.defaultBehavior === "string") junieOverride.defaultBehavior = allowlist.defaultBehavior;
|
|
19869
|
+
const result = { ...config };
|
|
19870
|
+
if (Object.keys(junieOverride).length > 0) result.junie = junieOverride;
|
|
19871
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19008
19872
|
}
|
|
19009
19873
|
validate() {
|
|
19010
19874
|
return {
|
|
@@ -19036,12 +19900,13 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19036
19900
|
continue;
|
|
19037
19901
|
}
|
|
19038
19902
|
for (const [pattern, action] of Object.entries(patterns)) {
|
|
19903
|
+
const junieAction = toJunieAction(action, category, pattern, logger);
|
|
19039
19904
|
const rule = isGlobPattern(pattern) ? {
|
|
19040
19905
|
pattern,
|
|
19041
|
-
action
|
|
19906
|
+
action: junieAction
|
|
19042
19907
|
} : {
|
|
19043
19908
|
prefix: pattern,
|
|
19044
|
-
action
|
|
19909
|
+
action: junieAction
|
|
19045
19910
|
};
|
|
19046
19911
|
(rules[group] ??= []).push(rule);
|
|
19047
19912
|
}
|
|
@@ -19049,6 +19914,19 @@ function convertRulesyncToJunieRules({ config, logger }) {
|
|
|
19049
19914
|
return rules;
|
|
19050
19915
|
}
|
|
19051
19916
|
/**
|
|
19917
|
+
* Map a canonical action onto a valid Junie allowlist action. Junie supports
|
|
19918
|
+
* only `allow` and `ask`; a canonical `deny` is downgraded to `ask` (the
|
|
19919
|
+
* nearest valid action — both withhold auto-approval) with a warning, so
|
|
19920
|
+
* rulesync never emits a `deny` that Junie would silently ignore.
|
|
19921
|
+
*/
|
|
19922
|
+
function toJunieAction(action, category, pattern, logger) {
|
|
19923
|
+
if (action === "deny") {
|
|
19924
|
+
logger?.warn(`Junie's allowlist supports only 'allow'/'ask' actions; the '${category}' deny rule for '${pattern}' was downgraded to 'ask' (Junie has no 'deny').`);
|
|
19925
|
+
return "ask";
|
|
19926
|
+
}
|
|
19927
|
+
return action;
|
|
19928
|
+
}
|
|
19929
|
+
/**
|
|
19052
19930
|
* Convert a Junie allowlist back into rulesync permissions config. The
|
|
19053
19931
|
* top-level `defaultBehavior` / `allowReadonlyCommands` settings have no
|
|
19054
19932
|
* canonical equivalent and are not imported.
|
|
@@ -19082,6 +19960,31 @@ const KiloPermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
19082
19960
|
]))]);
|
|
19083
19961
|
const KiloPermissionsConfigSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), KiloPermissionSchema)) });
|
|
19084
19962
|
/**
|
|
19963
|
+
* Kilo permission keys that share a name with a canonical rulesync category and
|
|
19964
|
+
* therefore stay in the shared `permission` block. Everything else (Kilo-only
|
|
19965
|
+
* keys such as `external_directory`, `doom_loop`, `notebook_edit`, ...) is
|
|
19966
|
+
* routed into the `kilo` override on import so it does not leak into other
|
|
19967
|
+
* tools' configs. Kilo folds `write` into `edit`, uses `notebook_edit`/`task`
|
|
19968
|
+
* rather than the canonical `notebookedit`/`agent`, and has no `mcp` key (MCP is
|
|
19969
|
+
* addressed via `mcp__*` tool-name keys), so those canonical names are not Kilo
|
|
19970
|
+
* keys in practice — they simply never appear on the shared side.
|
|
19971
|
+
*/
|
|
19972
|
+
const KILO_SHARED_CATEGORIES = /* @__PURE__ */ new Set([
|
|
19973
|
+
"bash",
|
|
19974
|
+
"read",
|
|
19975
|
+
"edit",
|
|
19976
|
+
"write",
|
|
19977
|
+
"webfetch",
|
|
19978
|
+
"websearch",
|
|
19979
|
+
"grep",
|
|
19980
|
+
"glob",
|
|
19981
|
+
"notebookedit",
|
|
19982
|
+
"agent"
|
|
19983
|
+
]);
|
|
19984
|
+
function isSharedKiloCategory(key) {
|
|
19985
|
+
return key === "*" || KILO_SHARED_CATEGORIES.has(key) || key.startsWith("mcp__");
|
|
19986
|
+
}
|
|
19987
|
+
/**
|
|
19085
19988
|
* Parse a JSONC string and throw on syntax errors. The `jsonc-parser` `parse()` function is
|
|
19086
19989
|
* non-throwing best-effort: invalid input silently yields a partial value (often `undefined`,
|
|
19087
19990
|
* coerced to `{}` by callers). That behavior would silently drop a user's existing `deny` rules
|
|
@@ -19193,12 +20096,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19193
20096
|
const parsed = parseKiloJsoncStrict(await readFileContentOrNull(filePath) ?? "{}", filePath);
|
|
19194
20097
|
const parsedPermission = parsed.permission;
|
|
19195
20098
|
const existingPermission = parsedPermission && typeof parsedPermission === "object" && !Array.isArray(parsedPermission) ? { ...parsedPermission } : {};
|
|
19196
|
-
const
|
|
20099
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20100
|
+
const kiloOverride = rulesyncJson.kilo;
|
|
20101
|
+
const incomingPermission = {
|
|
20102
|
+
...rulesyncJson.permission,
|
|
20103
|
+
...kiloOverride?.permission
|
|
20104
|
+
};
|
|
19197
20105
|
const droppedDenyByKey = {};
|
|
19198
|
-
for (const key of Object.
|
|
19199
|
-
const
|
|
19200
|
-
const
|
|
19201
|
-
const nextDenyPatterns = new Set(collectKiloDenyPatterns(rulesyncPermission[key]));
|
|
20106
|
+
for (const [key, value] of Object.entries(incomingPermission)) {
|
|
20107
|
+
const previousDenyPatterns = collectKiloDenyPatterns(existingPermission[key]);
|
|
20108
|
+
const nextDenyPatterns = new Set(collectKiloDenyPatterns(value));
|
|
19202
20109
|
const dropped = previousDenyPatterns.filter((p) => !nextDenyPatterns.has(p));
|
|
19203
20110
|
if (dropped.length > 0) droppedDenyByKey[key] = dropped;
|
|
19204
20111
|
}
|
|
@@ -19206,8 +20113,10 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19206
20113
|
const summary = Object.entries(droppedDenyByKey).map(([key, patterns]) => `${key}: [${patterns.join(", ")}]`).join("; ");
|
|
19207
20114
|
logger?.warn(`WARNING: Kilo permissions regeneration drops existing 'deny' rule(s) because rulesync output owns these tool keys. Dropped — ${summary}. To preserve these denies, add them to '.rulesync/permissions.json'.`);
|
|
19208
20115
|
}
|
|
19209
|
-
const mergedPermission = {
|
|
19210
|
-
|
|
20116
|
+
const mergedPermission = {
|
|
20117
|
+
...existingPermission,
|
|
20118
|
+
...incomingPermission
|
|
20119
|
+
};
|
|
19211
20120
|
const nextJson = {
|
|
19212
20121
|
...parsed,
|
|
19213
20122
|
permission: mergedPermission
|
|
@@ -19221,8 +20130,16 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19221
20130
|
});
|
|
19222
20131
|
}
|
|
19223
20132
|
toRulesyncPermissions() {
|
|
19224
|
-
const
|
|
19225
|
-
|
|
20133
|
+
const rawPermission = this.json.permission ?? {};
|
|
20134
|
+
const shared = {};
|
|
20135
|
+
const overrideOnly = {};
|
|
20136
|
+
for (const [key, value] of Object.entries(rawPermission)) if (isSharedKiloCategory(key)) shared[key] = typeof value === "string" ? { "*": value } : value;
|
|
20137
|
+
else overrideOnly[key] = value;
|
|
20138
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20139
|
+
permission: shared,
|
|
20140
|
+
kilo: { permission: overrideOnly }
|
|
20141
|
+
} : { permission: shared };
|
|
20142
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19226
20143
|
}
|
|
19227
20144
|
validate() {
|
|
19228
20145
|
try {
|
|
@@ -19252,10 +20169,6 @@ var KiloPermissions = class KiloPermissions extends ToolPermissions {
|
|
|
19252
20169
|
validate: false
|
|
19253
20170
|
});
|
|
19254
20171
|
}
|
|
19255
|
-
normalizePermission(permission) {
|
|
19256
|
-
if (!permission) return {};
|
|
19257
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
19258
|
-
}
|
|
19259
20172
|
};
|
|
19260
20173
|
//#endregion
|
|
19261
20174
|
//#region src/features/permissions/kiro-permissions.ts
|
|
@@ -19313,29 +20226,16 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19313
20226
|
}
|
|
19314
20227
|
const permission = {};
|
|
19315
20228
|
const toolsSettings = parsed.toolsSettings ?? {};
|
|
19316
|
-
const
|
|
19317
|
-
|
|
19318
|
-
const
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
const readDeny = asStringArray(readSettings.deniedPaths);
|
|
19327
|
-
if (readAllow.length > 0 || readDeny.length > 0) {
|
|
19328
|
-
permission.read = {};
|
|
19329
|
-
for (const pattern of readAllow) permission.read[pattern] = "allow";
|
|
19330
|
-
for (const pattern of readDeny) permission.read[pattern] = "deny";
|
|
19331
|
-
}
|
|
19332
|
-
const writeSettings = asRecord$1(toolsSettings.write);
|
|
19333
|
-
const writeAllow = asStringArray(writeSettings.allowedPaths);
|
|
19334
|
-
const writeDeny = asStringArray(writeSettings.deniedPaths);
|
|
19335
|
-
if (writeAllow.length > 0 || writeDeny.length > 0) {
|
|
19336
|
-
permission.write = {};
|
|
19337
|
-
for (const pattern of writeAllow) permission.write[pattern] = "allow";
|
|
19338
|
-
for (const pattern of writeDeny) permission.write[pattern] = "deny";
|
|
20229
|
+
const shellRules = rulesFromArrays(asRecord$1(toolsSettings.shell), "allowedCommands", "deniedCommands");
|
|
20230
|
+
if (Object.keys(shellRules).length > 0) permission.bash = shellRules;
|
|
20231
|
+
for (const category of [
|
|
20232
|
+
"read",
|
|
20233
|
+
"write",
|
|
20234
|
+
"grep",
|
|
20235
|
+
"glob"
|
|
20236
|
+
]) {
|
|
20237
|
+
const rules = rulesFromArrays(asRecord$1(toolsSettings[category]), "allowedPaths", "deniedPaths");
|
|
20238
|
+
if (Object.keys(rules).length > 0) permission[category] = rules;
|
|
19339
20239
|
}
|
|
19340
20240
|
const allowedTools = new Set(parsed.allowedTools ?? []);
|
|
19341
20241
|
if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
|
|
@@ -19361,45 +20261,63 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
|
|
|
19361
20261
|
function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
|
|
19362
20262
|
const nextAllowedTools = new Set(existing.allowedTools ?? []);
|
|
19363
20263
|
const nextToolsSettings = { ...asRecord$1(existing.toolsSettings) };
|
|
20264
|
+
const pathBuckets = {};
|
|
20265
|
+
const pushPath = (key, action, pattern) => {
|
|
20266
|
+
const bucket = pathBuckets[key] ??= {
|
|
20267
|
+
allow: [],
|
|
20268
|
+
deny: []
|
|
20269
|
+
};
|
|
20270
|
+
(action === "allow" ? bucket.allow : bucket.deny).push(pattern);
|
|
20271
|
+
};
|
|
19364
20272
|
const shell = {
|
|
19365
20273
|
allowedCommands: [],
|
|
19366
20274
|
deniedCommands: []
|
|
19367
20275
|
};
|
|
19368
|
-
const read = {
|
|
19369
|
-
allowedPaths: [],
|
|
19370
|
-
deniedPaths: []
|
|
19371
|
-
};
|
|
19372
|
-
const write = {
|
|
19373
|
-
allowedPaths: [],
|
|
19374
|
-
deniedPaths: []
|
|
19375
|
-
};
|
|
19376
20276
|
for (const [category, rules] of Object.entries(config.permission)) for (const [pattern, action] of Object.entries(rules)) {
|
|
19377
20277
|
if (action === "ask") {
|
|
19378
20278
|
logger?.warn(`Kiro permissions do not support "ask". Skipping ${category}:${pattern}`);
|
|
19379
20279
|
continue;
|
|
19380
20280
|
}
|
|
19381
20281
|
if (category === "bash") (action === "allow" ? shell.allowedCommands : shell.deniedCommands).push(pattern);
|
|
19382
|
-
else if (category === "read"
|
|
19383
|
-
else if (category === "edit" || category === "write") (
|
|
19384
|
-
else if (category === "webfetch" || category === "websearch") {
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
} else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
20282
|
+
else if (category === "read" || category === "grep" || category === "glob") pushPath(category, action, pattern);
|
|
20283
|
+
else if (category === "edit" || category === "write") pushPath("write", action, pattern);
|
|
20284
|
+
else if (category === "webfetch" || category === "websearch") applyKiroWebPermission({
|
|
20285
|
+
category,
|
|
20286
|
+
pattern,
|
|
20287
|
+
action,
|
|
20288
|
+
nextAllowedTools,
|
|
20289
|
+
logger
|
|
20290
|
+
});
|
|
20291
|
+
else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
|
|
19393
20292
|
}
|
|
19394
20293
|
nextToolsSettings.shell = shell;
|
|
19395
|
-
nextToolsSettings.read = read;
|
|
19396
|
-
nextToolsSettings.write = write;
|
|
20294
|
+
nextToolsSettings.read = pathTable(pathBuckets.read);
|
|
20295
|
+
nextToolsSettings.write = pathTable(pathBuckets.write);
|
|
20296
|
+
for (const key of ["grep", "glob"]) {
|
|
20297
|
+
const bucket = pathBuckets[key];
|
|
20298
|
+
if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
|
|
20299
|
+
}
|
|
19397
20300
|
return {
|
|
19398
20301
|
...existing,
|
|
19399
20302
|
allowedTools: [...nextAllowedTools].toSorted(),
|
|
19400
20303
|
toolsSettings: nextToolsSettings
|
|
19401
20304
|
};
|
|
19402
20305
|
}
|
|
20306
|
+
function pathTable(bucket) {
|
|
20307
|
+
return {
|
|
20308
|
+
allowedPaths: bucket?.allow ?? [],
|
|
20309
|
+
deniedPaths: bucket?.deny ?? []
|
|
20310
|
+
};
|
|
20311
|
+
}
|
|
20312
|
+
function applyKiroWebPermission({ category, pattern, action, nextAllowedTools, logger }) {
|
|
20313
|
+
if (pattern !== "*") {
|
|
20314
|
+
logger?.warn(`Kiro ${category} supports only wildcard (*) via allowedTools. Skipping rule: ${pattern}`);
|
|
20315
|
+
return;
|
|
20316
|
+
}
|
|
20317
|
+
const toolName = category === "webfetch" ? "web_fetch" : "web_search";
|
|
20318
|
+
if (action === "allow") nextAllowedTools.add(toolName);
|
|
20319
|
+
else nextAllowedTools.delete(toolName);
|
|
20320
|
+
}
|
|
19403
20321
|
function asRecord$1(value) {
|
|
19404
20322
|
const result = UnknownRecordSchema.safeParse(value);
|
|
19405
20323
|
return result.success ? result.data : {};
|
|
@@ -19407,6 +20325,17 @@ function asRecord$1(value) {
|
|
|
19407
20325
|
function asStringArray(value) {
|
|
19408
20326
|
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
19409
20327
|
}
|
|
20328
|
+
/**
|
|
20329
|
+
* Build a canonical `{ pattern: action }` map from a Kiro tool settings record's
|
|
20330
|
+
* allow/deny string arrays (e.g. `allowedPaths`/`deniedPaths` or
|
|
20331
|
+
* `allowedCommands`/`deniedCommands`).
|
|
20332
|
+
*/
|
|
20333
|
+
function rulesFromArrays(settings, allowKey, denyKey) {
|
|
20334
|
+
const rules = {};
|
|
20335
|
+
for (const pattern of asStringArray(settings[allowKey])) rules[pattern] = "allow";
|
|
20336
|
+
for (const pattern of asStringArray(settings[denyKey])) rules[pattern] = "deny";
|
|
20337
|
+
return rules;
|
|
20338
|
+
}
|
|
19410
20339
|
//#endregion
|
|
19411
20340
|
//#region src/features/permissions/opencode-permissions.ts
|
|
19412
20341
|
const OpencodePermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
@@ -19418,6 +20347,28 @@ const OpencodePermissionSchema = zod_mini.z.union([zod_mini.z.enum([
|
|
|
19418
20347
|
"ask",
|
|
19419
20348
|
"deny"
|
|
19420
20349
|
]))]);
|
|
20350
|
+
/**
|
|
20351
|
+
* Canonical rulesync permission categories that carry a cross-tool meaning (see
|
|
20352
|
+
* the "Supported tool categories" list in `docs/reference/file-formats.md`).
|
|
20353
|
+
* On import, any OpenCode category outside this set — plus MCP tool names — is
|
|
20354
|
+
* treated as OpenCode-only and routed into the `opencode` override block so a
|
|
20355
|
+
* subsequent `rulesync generate` does not leak it into other tools' configs.
|
|
20356
|
+
*/
|
|
20357
|
+
const CANONICAL_PERMISSION_CATEGORIES = /* @__PURE__ */ new Set([
|
|
20358
|
+
"bash",
|
|
20359
|
+
"read",
|
|
20360
|
+
"edit",
|
|
20361
|
+
"write",
|
|
20362
|
+
"webfetch",
|
|
20363
|
+
"websearch",
|
|
20364
|
+
"grep",
|
|
20365
|
+
"glob",
|
|
20366
|
+
"notebookedit",
|
|
20367
|
+
"agent"
|
|
20368
|
+
]);
|
|
20369
|
+
function isSharedPermissionCategory(category) {
|
|
20370
|
+
return category === "*" || CANONICAL_PERMISSION_CATEGORIES.has(category) || category.startsWith("mcp__");
|
|
20371
|
+
}
|
|
19421
20372
|
const OpencodePermissionsConfigSchema = zod_mini.z.looseObject({ permission: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.enum([
|
|
19422
20373
|
"allow",
|
|
19423
20374
|
"ask",
|
|
@@ -19479,9 +20430,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19479
20430
|
fileContent = await readFileContentOrNull(jsonPath);
|
|
19480
20431
|
if (fileContent) relativeFilePath = OPENCODE_JSON_FILE_NAME;
|
|
19481
20432
|
}
|
|
20433
|
+
const parsed = (0, jsonc_parser.parse)(fileContent ?? "{}");
|
|
20434
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20435
|
+
const overridePermission = rulesyncJson.opencode?.permission ?? {};
|
|
19482
20436
|
const nextJson = {
|
|
19483
|
-
...
|
|
19484
|
-
permission:
|
|
20437
|
+
...parsed,
|
|
20438
|
+
permission: {
|
|
20439
|
+
...rulesyncJson.permission,
|
|
20440
|
+
...overridePermission
|
|
20441
|
+
}
|
|
19485
20442
|
};
|
|
19486
20443
|
return new OpencodePermissions({
|
|
19487
20444
|
outputRoot,
|
|
@@ -19492,8 +20449,20 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19492
20449
|
});
|
|
19493
20450
|
}
|
|
19494
20451
|
toRulesyncPermissions() {
|
|
19495
|
-
const
|
|
19496
|
-
|
|
20452
|
+
const rawPermission = this.json.permission;
|
|
20453
|
+
if (rawPermission === void 0 || typeof rawPermission === "string") {
|
|
20454
|
+
const permission = this.normalizePermission(rawPermission);
|
|
20455
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
|
|
20456
|
+
}
|
|
20457
|
+
const shared = {};
|
|
20458
|
+
const overrideOnly = {};
|
|
20459
|
+
for (const [category, value] of Object.entries(rawPermission)) if (isSharedPermissionCategory(category)) shared[category] = typeof value === "string" ? { "*": value } : value;
|
|
20460
|
+
else overrideOnly[category] = value;
|
|
20461
|
+
const json = Object.keys(overrideOnly).length > 0 ? {
|
|
20462
|
+
permission: shared,
|
|
20463
|
+
opencode: { permission: overrideOnly }
|
|
20464
|
+
} : { permission: shared };
|
|
20465
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
19497
20466
|
}
|
|
19498
20467
|
validate() {
|
|
19499
20468
|
try {
|
|
@@ -19523,10 +20492,15 @@ var OpencodePermissions = class OpencodePermissions extends ToolPermissions {
|
|
|
19523
20492
|
validate: false
|
|
19524
20493
|
});
|
|
19525
20494
|
}
|
|
20495
|
+
/**
|
|
20496
|
+
* Normalize the uniform/undefined forms of OpenCode's `permission` field into
|
|
20497
|
+
* the canonical rulesync shape. The object form is handled directly in
|
|
20498
|
+
* `toRulesyncPermissions` (it needs to split shared vs OpenCode-only
|
|
20499
|
+
* categories), so this only covers the two remaining cases.
|
|
20500
|
+
*/
|
|
19526
20501
|
normalizePermission(permission) {
|
|
19527
20502
|
if (!permission) return {};
|
|
19528
|
-
|
|
19529
|
-
return Object.fromEntries(Object.entries(permission).map(([tool, value]) => [tool, typeof value === "string" ? { "*": value } : value]));
|
|
20503
|
+
return { "*": { "*": permission } };
|
|
19530
20504
|
}
|
|
19531
20505
|
};
|
|
19532
20506
|
//#endregion
|
|
@@ -19600,6 +20574,24 @@ function buildQwenPermissionEntry(toolName, pattern) {
|
|
|
19600
20574
|
if (pattern === "*") return toolName;
|
|
19601
20575
|
return `${toolName}(${pattern})`;
|
|
19602
20576
|
}
|
|
20577
|
+
const QWEN_OVERRIDE_TOOLS_KEYS = [
|
|
20578
|
+
"approvalMode",
|
|
20579
|
+
"autoAccept",
|
|
20580
|
+
"sandbox",
|
|
20581
|
+
"sandboxImage",
|
|
20582
|
+
"disabled"
|
|
20583
|
+
];
|
|
20584
|
+
const QWEN_OVERRIDE_SECURITY_KEYS = ["folderTrust"];
|
|
20585
|
+
function asPlainRecord(value) {
|
|
20586
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20587
|
+
}
|
|
20588
|
+
/** Pick the override-managed keys out of a settings group into a fresh record. */
|
|
20589
|
+
function pickQwenOverrideKeys(group, keys) {
|
|
20590
|
+
const source = asPlainRecord(group);
|
|
20591
|
+
const picked = {};
|
|
20592
|
+
for (const key of keys) if (source[key] !== void 0) picked[key] = source[key];
|
|
20593
|
+
return picked;
|
|
20594
|
+
}
|
|
19603
20595
|
var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
19604
20596
|
constructor(params) {
|
|
19605
20597
|
super({
|
|
@@ -19665,6 +20657,15 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19665
20657
|
...settings,
|
|
19666
20658
|
permissions: mergedPermissions
|
|
19667
20659
|
};
|
|
20660
|
+
const override = config.qwencode;
|
|
20661
|
+
if (override?.tools !== void 0) merged.tools = {
|
|
20662
|
+
...asPlainRecord(settings.tools),
|
|
20663
|
+
...asPlainRecord(override.tools)
|
|
20664
|
+
};
|
|
20665
|
+
if (override?.security !== void 0) merged.security = {
|
|
20666
|
+
...asPlainRecord(settings.security),
|
|
20667
|
+
...asPlainRecord(override.security)
|
|
20668
|
+
};
|
|
19668
20669
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
19669
20670
|
return new QwencodePermissions({
|
|
19670
20671
|
outputRoot,
|
|
@@ -19690,7 +20691,14 @@ var QwencodePermissions = class QwencodePermissions extends ToolPermissions {
|
|
|
19690
20691
|
ask: permissions.ask ?? [],
|
|
19691
20692
|
deny: permissions.deny ?? []
|
|
19692
20693
|
});
|
|
19693
|
-
|
|
20694
|
+
const overrideTools = pickQwenOverrideKeys(settings.tools, QWEN_OVERRIDE_TOOLS_KEYS);
|
|
20695
|
+
const overrideSecurity = pickQwenOverrideKeys(settings.security, QWEN_OVERRIDE_SECURITY_KEYS);
|
|
20696
|
+
const qwencodeOverride = {};
|
|
20697
|
+
if (Object.keys(overrideTools).length > 0) qwencodeOverride.tools = overrideTools;
|
|
20698
|
+
if (Object.keys(overrideSecurity).length > 0) qwencodeOverride.security = overrideSecurity;
|
|
20699
|
+
const result = { ...config };
|
|
20700
|
+
if (Object.keys(qwencodeOverride).length > 0) result.qwencode = qwencodeOverride;
|
|
20701
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19694
20702
|
}
|
|
19695
20703
|
validate() {
|
|
19696
20704
|
try {
|
|
@@ -19851,6 +20859,16 @@ function toPermissionsTable(value) {
|
|
|
19851
20859
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
19852
20860
|
return { ...value };
|
|
19853
20861
|
}
|
|
20862
|
+
const REASONIX_OVERRIDE_AGENT_KEYS = ["plan_mode_allowed_tools", "plan_mode_read_only_commands"];
|
|
20863
|
+
function asReasonixRecord(value) {
|
|
20864
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
20865
|
+
}
|
|
20866
|
+
function pickReasonixKeys(source, keys) {
|
|
20867
|
+
const record = asReasonixRecord(source);
|
|
20868
|
+
const picked = {};
|
|
20869
|
+
for (const key of keys) if (record[key] !== void 0) picked[key] = record[key];
|
|
20870
|
+
return picked;
|
|
20871
|
+
}
|
|
19854
20872
|
var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
19855
20873
|
toml;
|
|
19856
20874
|
constructor(params) {
|
|
@@ -19912,6 +20930,15 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19912
20930
|
...parsed,
|
|
19913
20931
|
permissions: mergedPermissions
|
|
19914
20932
|
};
|
|
20933
|
+
const override = config.reasonix;
|
|
20934
|
+
if (override?.sandbox !== void 0) merged.sandbox = {
|
|
20935
|
+
...asReasonixRecord(parsed.sandbox),
|
|
20936
|
+
...asReasonixRecord(override.sandbox)
|
|
20937
|
+
};
|
|
20938
|
+
if (override?.agent !== void 0) merged.agent = {
|
|
20939
|
+
...asReasonixRecord(parsed.agent),
|
|
20940
|
+
...asReasonixRecord(override.agent)
|
|
20941
|
+
};
|
|
19915
20942
|
const fileContent = smol_toml.stringify(merged);
|
|
19916
20943
|
return new ReasonixPermissions({
|
|
19917
20944
|
outputRoot,
|
|
@@ -19928,7 +20955,14 @@ var ReasonixPermissions = class ReasonixPermissions extends ToolPermissions {
|
|
|
19928
20955
|
ask: toStringArray$1(permissions.ask),
|
|
19929
20956
|
deny: toStringArray$1(permissions.deny)
|
|
19930
20957
|
});
|
|
19931
|
-
|
|
20958
|
+
const sandbox = asReasonixRecord(this.toml.sandbox);
|
|
20959
|
+
const agentPlanMode = pickReasonixKeys(this.toml.agent, REASONIX_OVERRIDE_AGENT_KEYS);
|
|
20960
|
+
const reasonixOverride = {};
|
|
20961
|
+
if (Object.keys(sandbox).length > 0) reasonixOverride.sandbox = sandbox;
|
|
20962
|
+
if (Object.keys(agentPlanMode).length > 0) reasonixOverride.agent = agentPlanMode;
|
|
20963
|
+
const result = { ...config };
|
|
20964
|
+
if (Object.keys(reasonixOverride).length > 0) result.reasonix = reasonixOverride;
|
|
20965
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
19932
20966
|
}
|
|
19933
20967
|
validate() {
|
|
19934
20968
|
try {
|
|
@@ -20238,6 +21272,8 @@ function isPermissionAction(value) {
|
|
|
20238
21272
|
const TAKT_PROVIDER_KEY = "provider";
|
|
20239
21273
|
const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
|
|
20240
21274
|
const TAKT_DEFAULT_PERMISSION_MODE_KEY = "default_permission_mode";
|
|
21275
|
+
const TAKT_STEP_PERMISSION_OVERRIDES_KEY = "step_permission_overrides";
|
|
21276
|
+
const TAKT_PROVIDER_OPTIONS_KEY = "provider_options";
|
|
20241
21277
|
const TAKT_DEFAULT_PROVIDER = "claude";
|
|
20242
21278
|
const CATCH_ALL_PATTERN = "*";
|
|
20243
21279
|
/**
|
|
@@ -20267,10 +21303,16 @@ const CATCH_ALL_PATTERN = "*";
|
|
|
20267
21303
|
* `bash: { "*": "deny" }`. These round-trip the generate mapping.
|
|
20268
21304
|
*
|
|
20269
21305
|
* Both project and global scope are supported. The shared config is merged in
|
|
20270
|
-
* place:
|
|
20271
|
-
*
|
|
20272
|
-
*
|
|
20273
|
-
*
|
|
21306
|
+
* place: `provider_profiles.<provider>.default_permission_mode` is set from the
|
|
21307
|
+
* derived mode; every other provider profile and all other top-level keys are
|
|
21308
|
+
* preserved. The file is never deleted.
|
|
21309
|
+
*
|
|
21310
|
+
* Two Takt-specific surfaces with no canonical category round-trip through the
|
|
21311
|
+
* `takt` override (see `TaktPermissionsOverrideSchema`):
|
|
21312
|
+
* `step_permission_overrides` (a per-step mode map inside the active provider
|
|
21313
|
+
* profile, layered on top of `default_permission_mode`) and `provider_options`
|
|
21314
|
+
* (a top-level per-provider sandbox/network table). Both are authored on
|
|
21315
|
+
* generate and re-extracted on import.
|
|
20274
21316
|
*/
|
|
20275
21317
|
var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
20276
21318
|
constructor(params) {
|
|
@@ -20302,37 +21344,62 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
20302
21344
|
}
|
|
20303
21345
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
20304
21346
|
const paths = TaktPermissions.getSettablePaths({ global });
|
|
20305
|
-
const
|
|
21347
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
21348
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
21349
|
+
const config = parseSharedConfig({
|
|
21350
|
+
format: "yaml",
|
|
21351
|
+
fileContent: existingContent,
|
|
21352
|
+
filePath,
|
|
21353
|
+
invalidRootPolicy: "error"
|
|
21354
|
+
});
|
|
21355
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20306
21356
|
const provider = resolveActiveProvider(config);
|
|
20307
|
-
const mode = deriveTaktPermissionMode(
|
|
20308
|
-
const
|
|
20309
|
-
const
|
|
20310
|
-
const
|
|
20311
|
-
|
|
20312
|
-
[TAKT_PROVIDER_PROFILES_KEY]: {
|
|
20313
|
-
|
|
20314
|
-
[
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
}
|
|
20318
|
-
}
|
|
21357
|
+
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
21358
|
+
const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
21359
|
+
const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21360
|
+
const overrideProviderOptions = isPlainObject(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21361
|
+
const patch = {
|
|
21362
|
+
[TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
|
|
21363
|
+
[TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
|
|
21364
|
+
...stepOverrides !== void 0 && { [TAKT_STEP_PERMISSION_OVERRIDES_KEY]: stepOverrides }
|
|
21365
|
+
} },
|
|
21366
|
+
...overrideProviderOptions !== void 0 && { [TAKT_PROVIDER_OPTIONS_KEY]: overrideProviderOptions }
|
|
20319
21367
|
};
|
|
20320
21368
|
return new TaktPermissions({
|
|
20321
21369
|
outputRoot,
|
|
20322
21370
|
relativeDirPath: paths.relativeDirPath,
|
|
20323
21371
|
relativeFilePath: paths.relativeFilePath,
|
|
20324
|
-
fileContent: (
|
|
21372
|
+
fileContent: applySharedConfigPatch({
|
|
21373
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
21374
|
+
feature: "permissions",
|
|
21375
|
+
existingContent,
|
|
21376
|
+
patch,
|
|
21377
|
+
filePath
|
|
21378
|
+
}),
|
|
20325
21379
|
validate: true,
|
|
20326
21380
|
global
|
|
20327
21381
|
});
|
|
20328
21382
|
}
|
|
20329
21383
|
toRulesyncPermissions() {
|
|
20330
|
-
const config =
|
|
21384
|
+
const config = parseSharedConfig({
|
|
21385
|
+
format: "yaml",
|
|
21386
|
+
fileContent: this.getFileContent(),
|
|
21387
|
+
filePath: (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath()),
|
|
21388
|
+
invalidRootPolicy: "error"
|
|
21389
|
+
});
|
|
20331
21390
|
const provider = resolveActiveProvider(config);
|
|
20332
21391
|
const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
|
|
20333
|
-
const
|
|
21392
|
+
const profile = isPlainObject(profiles[provider]) ? profiles[provider] : {};
|
|
21393
|
+
const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
|
|
20334
21394
|
const rulesyncConfig = taktModeToRulesyncConfig(mode);
|
|
20335
|
-
|
|
21395
|
+
const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21396
|
+
const providerOptions = isPlainObject(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21397
|
+
const taktOverride = {};
|
|
21398
|
+
if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
|
|
21399
|
+
if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
|
|
21400
|
+
const result = { ...rulesyncConfig };
|
|
21401
|
+
if (Object.keys(taktOverride).length > 0) result.takt = taktOverride;
|
|
21402
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
20336
21403
|
}
|
|
20337
21404
|
validate() {
|
|
20338
21405
|
return {
|
|
@@ -20492,6 +21559,7 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20492
21559
|
if (deny.size > 0) nextTool.denylist = [...deny].toSorted();
|
|
20493
21560
|
tools[vibeToolName] = nextTool;
|
|
20494
21561
|
}
|
|
21562
|
+
applyVibeSensitivePatterns(tools, rulesyncPermissions.getJson().vibe);
|
|
20495
21563
|
config.tools = tools;
|
|
20496
21564
|
if (enabledTools.size > 0) config.enabled_tools = [...enabledTools].toSorted();
|
|
20497
21565
|
else delete config.enabled_tools;
|
|
@@ -20508,16 +21576,25 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20508
21576
|
}
|
|
20509
21577
|
toRulesyncPermissions() {
|
|
20510
21578
|
const permission = {};
|
|
21579
|
+
const vibeOverridePermission = {};
|
|
20511
21580
|
for (const tool of toStringArray(this.toml.enabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "allow";
|
|
20512
21581
|
for (const tool of toStringArray(this.toml.disabled_tools)) ensurePermission(permission, toCanonicalToolName$1(tool))["*"] = "deny";
|
|
20513
21582
|
for (const [vibeToolName, toolConfig] of Object.entries(toVibeToolsRecord(this.toml.tools))) {
|
|
20514
|
-
const
|
|
21583
|
+
const category = toCanonicalToolName$1(vibeToolName);
|
|
21584
|
+
const rules = ensurePermission(permission, category);
|
|
20515
21585
|
const action = fromVibePermission(toolConfig.permission);
|
|
20516
21586
|
if (action !== void 0) rules["*"] = action;
|
|
20517
21587
|
for (const pattern of toStringArray(toolConfig.allow ?? toolConfig.allowlist)) rules[pattern] = "allow";
|
|
20518
21588
|
for (const pattern of toStringArray(toolConfig.deny ?? toolConfig.denylist)) rules[pattern] = "deny";
|
|
21589
|
+
const sensitivePatterns = toStringArray(toolConfig.sensitive_patterns);
|
|
21590
|
+
if (sensitivePatterns.length > 0) vibeOverridePermission[category] = { sensitive_patterns: sensitivePatterns };
|
|
20519
21591
|
}
|
|
20520
|
-
|
|
21592
|
+
for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
|
|
21593
|
+
const json = Object.keys(vibeOverridePermission).length > 0 ? {
|
|
21594
|
+
permission,
|
|
21595
|
+
vibe: { permission: vibeOverridePermission }
|
|
21596
|
+
} : { permission };
|
|
21597
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(json, null, 2) });
|
|
20521
21598
|
}
|
|
20522
21599
|
validate() {
|
|
20523
21600
|
try {
|
|
@@ -20544,6 +21621,22 @@ var VibePermissions = class VibePermissions extends ToolPermissions {
|
|
|
20544
21621
|
});
|
|
20545
21622
|
}
|
|
20546
21623
|
};
|
|
21624
|
+
/**
|
|
21625
|
+
* Apply the Vibe-scoped override's per-tool `sensitive_patterns` (patterns that
|
|
21626
|
+
* escalate to ASK even when the base permission is ALWAYS). rulesync owns this
|
|
21627
|
+
* list for any category the override names: a present list is set, an empty
|
|
21628
|
+
* one clears it. Categories not named keep whatever the existing file had.
|
|
21629
|
+
*/
|
|
21630
|
+
function applyVibeSensitivePatterns(tools, vibeOverride) {
|
|
21631
|
+
for (const [category, toolOverride] of Object.entries(vibeOverride?.permission ?? {})) {
|
|
21632
|
+
const vibeToolName = toVibeToolName(category);
|
|
21633
|
+
const nextTool = toVibeToolConfig(tools[vibeToolName]);
|
|
21634
|
+
const patterns = toStringArray(toolOverride.sensitive_patterns);
|
|
21635
|
+
if (patterns.length > 0) nextTool.sensitive_patterns = [...patterns].toSorted();
|
|
21636
|
+
else delete nextTool.sensitive_patterns;
|
|
21637
|
+
tools[vibeToolName] = nextTool;
|
|
21638
|
+
}
|
|
21639
|
+
}
|
|
20547
21640
|
function parseVibeConfig(fileContent) {
|
|
20548
21641
|
const parsed = smol_toml.parse(fileContent || smol_toml.stringify({}));
|
|
20549
21642
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
@@ -20588,6 +21681,11 @@ function ensurePermission(permission, category) {
|
|
|
20588
21681
|
const WARP_GLOBAL_ONLY_MESSAGE = "Warp permissions are global-only; use --global to sync Warp's settings.toml";
|
|
20589
21682
|
const ALLOWLIST_KEY = "agent_mode_command_execution_allowlist";
|
|
20590
21683
|
const DENYLIST_KEY = "agent_mode_command_execution_denylist";
|
|
21684
|
+
const WARP_OVERRIDE_KEYS = [
|
|
21685
|
+
"agent_mode_coding_permissions",
|
|
21686
|
+
"agent_mode_coding_file_read_allowlist",
|
|
21687
|
+
"agent_mode_execute_readonly_commands"
|
|
21688
|
+
];
|
|
20591
21689
|
/**
|
|
20592
21690
|
* Warp's `settings.toml` lives in a different directory per platform (Stable
|
|
20593
21691
|
* channel). The home directory is resolved by the processor through
|
|
@@ -20623,8 +21721,16 @@ function warpSettingsDir() {
|
|
|
20623
21721
|
* patterns as regexes when targeting Warp (mirrors the Zed permissions
|
|
20624
21722
|
* adapter). Warp has no per-command "ask" list, so `ask` rules are dropped; and
|
|
20625
21723
|
* the command lists only model shell commands, so non-`bash` categories are
|
|
20626
|
-
* skipped (with a warning when they carry `deny` rules).
|
|
20627
|
-
*
|
|
21724
|
+
* skipped (with a warning when they carry `deny` rules).
|
|
21725
|
+
*
|
|
21726
|
+
* Warp's `[agents.profiles]` table also exposes file-read/read-only autonomy
|
|
21727
|
+
* knobs that do not fit the canonical `allow | ask | deny` per-command model:
|
|
21728
|
+
* `agent_mode_coding_permissions`, `agent_mode_coding_file_read_allowlist`, and
|
|
21729
|
+
* `agent_mode_execute_readonly_commands`. These are authored and round-tripped
|
|
21730
|
+
* through the `warp` override namespace (see `WarpPermissionsOverrideSchema`):
|
|
21731
|
+
* on **import** they are lifted from `settings.toml` into the override, and on
|
|
21732
|
+
* **export** they are merged back into `[agents.profiles]` (the override wins).
|
|
21733
|
+
* MCP allow/deny is a separate surface not modeled here.
|
|
20628
21734
|
*
|
|
20629
21735
|
* The `settings.toml` file holds all of Warp's settings, so the
|
|
20630
21736
|
* `[agents.profiles]` block is merged in place and the file is never deleted.
|
|
@@ -20669,12 +21775,15 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20669
21775
|
} catch (error) {
|
|
20670
21776
|
throw new Error(`Failed to parse existing Warp settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
20671
21777
|
}
|
|
21778
|
+
const config = rulesyncPermissions.getJson();
|
|
20672
21779
|
const { allow, deny } = convertRulesyncToWarpPermissions({
|
|
20673
|
-
config
|
|
21780
|
+
config,
|
|
20674
21781
|
logger
|
|
20675
21782
|
});
|
|
20676
21783
|
const agents = isRecord(settings.agents) ? { ...settings.agents } : {};
|
|
20677
21784
|
const profiles = isRecord(agents.profiles) ? { ...agents.profiles } : {};
|
|
21785
|
+
const override = config.warp;
|
|
21786
|
+
if (isRecord(override)) Object.assign(profiles, override);
|
|
20678
21787
|
const mergedAllow = (0, es_toolkit.uniq)(allow.toSorted());
|
|
20679
21788
|
const mergedDeny = (0, es_toolkit.uniq)(deny.toSorted());
|
|
20680
21789
|
if (mergedAllow.length > 0) profiles[ALLOWLIST_KEY] = mergedAllow;
|
|
@@ -20705,7 +21814,11 @@ var WarpPermissions = class WarpPermissions extends ToolPermissions {
|
|
|
20705
21814
|
allow: isStringArray(profiles[ALLOWLIST_KEY]) ? profiles[ALLOWLIST_KEY] : [],
|
|
20706
21815
|
deny: isStringArray(profiles[DENYLIST_KEY]) ? profiles[DENYLIST_KEY] : []
|
|
20707
21816
|
});
|
|
20708
|
-
|
|
21817
|
+
const warpOverride = {};
|
|
21818
|
+
for (const key of WARP_OVERRIDE_KEYS) if (profiles[key] !== void 0) warpOverride[key] = profiles[key];
|
|
21819
|
+
const result = { ...config };
|
|
21820
|
+
if (Object.keys(warpOverride).length > 0) result.warp = warpOverride;
|
|
21821
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
20709
21822
|
}
|
|
20710
21823
|
validate() {
|
|
20711
21824
|
return {
|
|
@@ -24071,8 +25184,9 @@ const DevinSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
24071
25184
|
* Represents a Devin (now Devin Desktop) skill directory.
|
|
24072
25185
|
* Devin supports skills in both project mode under .devin/skills/
|
|
24073
25186
|
* (preferred since the Devin Desktop rebrand; .devin/skills/ is the legacy
|
|
24074
|
-
* fallback the tool still reads) and global mode under
|
|
24075
|
-
* (
|
|
25187
|
+
* fallback the tool still reads) and global mode under the Devin-native
|
|
25188
|
+
* ~/.config/devin/skills/ (consistent with Devin's global agents/rules paths;
|
|
25189
|
+
* the legacy channel-dependent ~/.codeium/<channel>/skills/ is no longer emitted).
|
|
24076
25190
|
*/
|
|
24077
25191
|
var DevinSkill = class DevinSkill extends ToolSkill {
|
|
24078
25192
|
constructor({ outputRoot = process.cwd(), relativeDirPath = DevinSkill.getSettablePaths().relativeDirPath, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
|
|
@@ -24094,7 +25208,7 @@ var DevinSkill = class DevinSkill extends ToolSkill {
|
|
|
24094
25208
|
}
|
|
24095
25209
|
}
|
|
24096
25210
|
static getSettablePaths({ global = false } = {}) {
|
|
24097
|
-
if (global) return { relativeDirPath:
|
|
25211
|
+
if (global) return { relativeDirPath: DEVIN_GLOBAL_SKILLS_DIR_PATH };
|
|
24098
25212
|
return { relativeDirPath: DEVIN_SKILLS_DIR_PATH };
|
|
24099
25213
|
}
|
|
24100
25214
|
getFrontmatter() {
|
|
@@ -27153,7 +28267,7 @@ const QwencodeSubagentFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
27153
28267
|
disallowedTools: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
27154
28268
|
maxTurns: zod_mini.z.optional(zod_mini.z.number()),
|
|
27155
28269
|
color: zod_mini.z.optional(zod_mini.z.string()),
|
|
27156
|
-
mcpServers: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
28270
|
+
mcpServers: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.array(zod_mini.z.string()), zod_mini.z.record(zod_mini.z.string(), zod_mini.z.looseObject({}))])),
|
|
27157
28271
|
hooks: zod_mini.z.optional(zod_mini.z.unknown())
|
|
27158
28272
|
});
|
|
27159
28273
|
var QwencodeSubagent = class QwencodeSubagent extends ToolSubagent {
|
|
@@ -29180,14 +30294,21 @@ def register(ctx):
|
|
|
29180
30294
|
`;
|
|
29181
30295
|
}
|
|
29182
30296
|
function getEnabledPluginConfigContent(currentContent) {
|
|
29183
|
-
const config =
|
|
30297
|
+
const config = parseSharedConfig({
|
|
30298
|
+
format: "yaml",
|
|
30299
|
+
fileContent: currentContent
|
|
30300
|
+
});
|
|
29184
30301
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
29185
30302
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
29186
|
-
|
|
29187
|
-
|
|
29188
|
-
|
|
29189
|
-
|
|
29190
|
-
|
|
30303
|
+
return applySharedConfigPatch({
|
|
30304
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
30305
|
+
feature: "subagents",
|
|
30306
|
+
existingContent: currentContent,
|
|
30307
|
+
patch: { plugins: {
|
|
30308
|
+
...plugins,
|
|
30309
|
+
enabled: Array.from(/* @__PURE__ */ new Set([...enabled, "rulesync-subagents"]))
|
|
30310
|
+
} }
|
|
30311
|
+
});
|
|
29191
30312
|
}
|
|
29192
30313
|
function getSubagentSpec(rulesyncSubagent) {
|
|
29193
30314
|
const json = rulesyncSubagent.getFrontmatter();
|
|
@@ -29273,6 +30394,17 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
29273
30394
|
static getSettablePaths() {
|
|
29274
30395
|
return { relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH };
|
|
29275
30396
|
}
|
|
30397
|
+
/**
|
|
30398
|
+
* Beyond the subagent spec files, generation also read-modify-writes the
|
|
30399
|
+
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
30400
|
+
* so the write must be declared for the shared-file order derivation.
|
|
30401
|
+
*/
|
|
30402
|
+
static getExtraSharedWritePaths() {
|
|
30403
|
+
return [{
|
|
30404
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
30405
|
+
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
30406
|
+
}];
|
|
30407
|
+
}
|
|
29276
30408
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
29277
30409
|
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
29278
30410
|
}
|
|
@@ -31011,7 +32143,9 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
31011
32143
|
})),
|
|
31012
32144
|
kiro: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
31013
32145
|
inclusion: zod_mini.z.optional(zod_mini.z.string()),
|
|
31014
|
-
fileMatchPattern: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
32146
|
+
fileMatchPattern: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
32147
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
32148
|
+
description: zod_mini.z.optional(zod_mini.z.string())
|
|
31015
32149
|
})),
|
|
31016
32150
|
takt: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
31017
32151
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -33927,7 +35061,8 @@ function toFileMatchPattern(globs) {
|
|
|
33927
35061
|
*
|
|
33928
35062
|
* Precedence:
|
|
33929
35063
|
* 1. An explicit `kiro.inclusion` block round-trips as-is (with `fileMatchPattern`
|
|
33930
|
-
* taken from the block, or derived from `globs` when omitted for `fileMatch
|
|
35064
|
+
* taken from the block, or derived from `globs` when omitted for `fileMatch`;
|
|
35065
|
+
* and with the companion `name`/`description` carried through for `auto`).
|
|
33931
35066
|
* 2. Otherwise specific (non-wildcard) globs map to `fileMatch`, scoping the rule
|
|
33932
35067
|
* to matching files instead of leaving it implicitly always-on.
|
|
33933
35068
|
* 3. Otherwise the rule stays `always` — represented by omitting the block so the
|
|
@@ -33945,6 +35080,11 @@ function deriveKiroInclusion({ kiro, globs }) {
|
|
|
33945
35080
|
fileMatchPattern
|
|
33946
35081
|
} : { inclusion: "fileMatch" };
|
|
33947
35082
|
}
|
|
35083
|
+
if (kiro.inclusion === "auto") return {
|
|
35084
|
+
inclusion: "auto",
|
|
35085
|
+
...kiro.name !== void 0 ? { name: kiro.name } : {},
|
|
35086
|
+
...kiro.description !== void 0 ? { description: kiro.description } : {}
|
|
35087
|
+
};
|
|
33948
35088
|
return { inclusion: kiro.inclusion };
|
|
33949
35089
|
}
|
|
33950
35090
|
const fileMatchPattern = toFileMatchPattern(specificGlobs);
|
|
@@ -34016,6 +35156,8 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34016
35156
|
const rawPattern = frontmatter.fileMatchPattern;
|
|
34017
35157
|
const fileMatchPattern = Array.isArray(rawPattern) ? rawPattern.filter((p) => typeof p === "string") : typeof rawPattern === "string" ? rawPattern : void 0;
|
|
34018
35158
|
const globs = inclusion === "fileMatch" ? fileMatchPattern === void 0 ? [] : Array.isArray(fileMatchPattern) ? fileMatchPattern : [fileMatchPattern] : [];
|
|
35159
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name : void 0;
|
|
35160
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
34019
35161
|
return new RulesyncRule({
|
|
34020
35162
|
outputRoot: process.cwd(),
|
|
34021
35163
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -34026,7 +35168,9 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34026
35168
|
globs,
|
|
34027
35169
|
kiro: {
|
|
34028
35170
|
inclusion,
|
|
34029
|
-
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {}
|
|
35171
|
+
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {},
|
|
35172
|
+
...inclusion === "auto" && name !== void 0 ? { name } : {},
|
|
35173
|
+
...inclusion === "auto" && description !== void 0 ? { description } : {}
|
|
34030
35174
|
}
|
|
34031
35175
|
},
|
|
34032
35176
|
body
|
|
@@ -36200,6 +37344,197 @@ function buildPermissionsStrategy(ctx) {
|
|
|
36200
37344
|
};
|
|
36201
37345
|
}
|
|
36202
37346
|
//#endregion
|
|
37347
|
+
//#region src/types/processor-registry.ts
|
|
37348
|
+
const PROCESSOR_REGISTRY = [
|
|
37349
|
+
{
|
|
37350
|
+
feature: "rules",
|
|
37351
|
+
processor: RulesProcessor,
|
|
37352
|
+
schema: RulesProcessorToolTargetSchema,
|
|
37353
|
+
factory: toolRuleFactories
|
|
37354
|
+
},
|
|
37355
|
+
{
|
|
37356
|
+
feature: "ignore",
|
|
37357
|
+
processor: IgnoreProcessor,
|
|
37358
|
+
schema: IgnoreProcessorToolTargetSchema,
|
|
37359
|
+
factory: toolIgnoreFactories
|
|
37360
|
+
},
|
|
37361
|
+
{
|
|
37362
|
+
feature: "mcp",
|
|
37363
|
+
processor: McpProcessor,
|
|
37364
|
+
schema: McpProcessorToolTargetSchema,
|
|
37365
|
+
factory: toolMcpFactories
|
|
37366
|
+
},
|
|
37367
|
+
{
|
|
37368
|
+
feature: "commands",
|
|
37369
|
+
processor: CommandsProcessor,
|
|
37370
|
+
schema: CommandsProcessorToolTargetSchema,
|
|
37371
|
+
factory: toolCommandFactories
|
|
37372
|
+
},
|
|
37373
|
+
{
|
|
37374
|
+
feature: "subagents",
|
|
37375
|
+
processor: SubagentsProcessor,
|
|
37376
|
+
schema: SubagentsProcessorToolTargetSchema,
|
|
37377
|
+
factory: toolSubagentFactories
|
|
37378
|
+
},
|
|
37379
|
+
{
|
|
37380
|
+
feature: "skills",
|
|
37381
|
+
processor: SkillsProcessor,
|
|
37382
|
+
schema: SkillsProcessorToolTargetSchema,
|
|
37383
|
+
factory: toolSkillFactories
|
|
37384
|
+
},
|
|
37385
|
+
{
|
|
37386
|
+
feature: "hooks",
|
|
37387
|
+
processor: HooksProcessor,
|
|
37388
|
+
schema: HooksProcessorToolTargetSchema,
|
|
37389
|
+
factory: toolHooksFactories
|
|
37390
|
+
},
|
|
37391
|
+
{
|
|
37392
|
+
feature: "permissions",
|
|
37393
|
+
processor: PermissionsProcessor,
|
|
37394
|
+
schema: PermissionsProcessorToolTargetSchema,
|
|
37395
|
+
factory: toolPermissionsFactories
|
|
37396
|
+
}
|
|
37397
|
+
];
|
|
37398
|
+
const getProcessorRegistryEntry = (feature) => {
|
|
37399
|
+
const entry = PROCESSOR_REGISTRY.find((e) => e.feature === feature);
|
|
37400
|
+
if (!entry) throw new Error(`No processor registered for feature: ${feature}`);
|
|
37401
|
+
return entry;
|
|
37402
|
+
};
|
|
37403
|
+
//#endregion
|
|
37404
|
+
//#region src/lib/shared-file-derive.ts
|
|
37405
|
+
/**
|
|
37406
|
+
* The single declaration of the cross-feature write order for shared
|
|
37407
|
+
* (read-modify-write) config files: when two features write the same on-disk
|
|
37408
|
+
* file, the earlier one writes first and the later one merges on top, so the
|
|
37409
|
+
* later feature's conflict policy decides what survives (e.g. `permissions`
|
|
37410
|
+
* overriding `ignore`-derived `Read(...)` denies in `.claude/settings.json`).
|
|
37411
|
+
* The generation step graph's `dependsOn` edges are derived from this list
|
|
37412
|
+
* plus the registry's `getSettablePaths` declarations — adding a tool or a
|
|
37413
|
+
* shared path never requires touching the graph by hand.
|
|
37414
|
+
*/
|
|
37415
|
+
const SHARED_WRITE_FEATURE_ORDER = [
|
|
37416
|
+
"ignore",
|
|
37417
|
+
"subagents",
|
|
37418
|
+
"mcp",
|
|
37419
|
+
"hooks",
|
|
37420
|
+
"permissions",
|
|
37421
|
+
"rules"
|
|
37422
|
+
];
|
|
37423
|
+
const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
|
|
37424
|
+
const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
|
|
37425
|
+
const sharedFileKey = (path) => {
|
|
37426
|
+
const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
37427
|
+
const file = path.relativeFilePath.replace(/\\/g, "/");
|
|
37428
|
+
return dir === "" || dir === "." ? file : `${dir}/${file}`;
|
|
37429
|
+
};
|
|
37430
|
+
const settablePathsForScope = (cls, global) => {
|
|
37431
|
+
const paths = [];
|
|
37432
|
+
let settable;
|
|
37433
|
+
try {
|
|
37434
|
+
settable = cls.getSettablePaths?.({ global });
|
|
37435
|
+
} catch {
|
|
37436
|
+
return paths;
|
|
37437
|
+
}
|
|
37438
|
+
if (settable?.relativeFilePath) paths.push({
|
|
37439
|
+
relativeDirPath: settable.relativeDirPath ?? ".",
|
|
37440
|
+
relativeFilePath: settable.relativeFilePath
|
|
37441
|
+
});
|
|
37442
|
+
if (settable?.root) {
|
|
37443
|
+
paths.push(settable.root);
|
|
37444
|
+
for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
|
|
37445
|
+
}
|
|
37446
|
+
for (const path of cls.getExtraSharedWritePaths?.({ global }) ?? []) if (path.relativeFilePath) paths.push(path);
|
|
37447
|
+
return paths;
|
|
37448
|
+
};
|
|
37449
|
+
const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
|
|
37450
|
+
/**
|
|
37451
|
+
* Derive, from the processor registry, every on-disk file that two or more
|
|
37452
|
+
* features read-modify-write. Source of truth the generation step graph's
|
|
37453
|
+
* `writesSharedFile` declarations must match.
|
|
37454
|
+
*/
|
|
37455
|
+
const deriveSharedFileWriters = () => {
|
|
37456
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
37457
|
+
const pathByKey = /* @__PURE__ */ new Map();
|
|
37458
|
+
for (const entry of PROCESSOR_REGISTRY) {
|
|
37459
|
+
if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
|
|
37460
|
+
const factories = entry.factory;
|
|
37461
|
+
for (const [tool, factory] of factories) {
|
|
37462
|
+
if (TARGETS_NOT_DERIVED.has(tool)) continue;
|
|
37463
|
+
for (const path of collectFactoryPaths(factory)) {
|
|
37464
|
+
const key = sharedFileKey(path);
|
|
37465
|
+
if (!pathByKey.has(key)) pathByKey.set(key, path);
|
|
37466
|
+
let features = byKey.get(key);
|
|
37467
|
+
if (!features) {
|
|
37468
|
+
features = /* @__PURE__ */ new Map();
|
|
37469
|
+
byKey.set(key, features);
|
|
37470
|
+
}
|
|
37471
|
+
let tools = features.get(entry.feature);
|
|
37472
|
+
if (!tools) {
|
|
37473
|
+
tools = /* @__PURE__ */ new Set();
|
|
37474
|
+
features.set(entry.feature, tools);
|
|
37475
|
+
}
|
|
37476
|
+
tools.add(tool);
|
|
37477
|
+
}
|
|
37478
|
+
}
|
|
37479
|
+
}
|
|
37480
|
+
const writers = [];
|
|
37481
|
+
for (const [key, features] of byKey) {
|
|
37482
|
+
if (features.size < 2) continue;
|
|
37483
|
+
const path = pathByKey.get(key);
|
|
37484
|
+
const toolsByFeature = /* @__PURE__ */ new Map();
|
|
37485
|
+
for (const [feature, tools] of features) toolsByFeature.set(feature, [...tools].toSorted());
|
|
37486
|
+
writers.push({
|
|
37487
|
+
key,
|
|
37488
|
+
relativeDirPath: path.relativeDirPath,
|
|
37489
|
+
relativeFilePath: path.relativeFilePath,
|
|
37490
|
+
features: [...features.keys()].toSorted(),
|
|
37491
|
+
toolsByFeature
|
|
37492
|
+
});
|
|
37493
|
+
}
|
|
37494
|
+
return writers.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
37495
|
+
};
|
|
37496
|
+
/**
|
|
37497
|
+
* Derive, per shared-write feature, the shared files it writes and the
|
|
37498
|
+
* `dependsOn` edges that fix a safe write order: for every shared file, each
|
|
37499
|
+
* writer depends on all writers that precede it in
|
|
37500
|
+
* {@link SHARED_WRITE_FEATURE_ORDER}. This is the source the generation step
|
|
37501
|
+
* graph consumes, so registry changes (a new tool, a new settable path)
|
|
37502
|
+
* propagate into the execution order without a hand-maintained declaration.
|
|
37503
|
+
*
|
|
37504
|
+
* @throws Error if a feature writes a shared file but has no position in
|
|
37505
|
+
* `SHARED_WRITE_FEATURE_ORDER` — ordering it is a deliberate decision about
|
|
37506
|
+
* whose merge policy wins, so it must be made explicitly there.
|
|
37507
|
+
*/
|
|
37508
|
+
const deriveSharedWriteSteps = () => {
|
|
37509
|
+
const orderIndex = new Map(SHARED_WRITE_FEATURE_ORDER.map((feature, index) => [feature, index]));
|
|
37510
|
+
const filesByFeature = /* @__PURE__ */ new Map();
|
|
37511
|
+
const depsByFeature = /* @__PURE__ */ new Map();
|
|
37512
|
+
for (const writer of deriveSharedFileWriters()) {
|
|
37513
|
+
for (const feature of writer.features) if (!orderIndex.has(feature)) throw new Error(`Feature '${feature}' writes the shared file '${writer.key}' but has no position in SHARED_WRITE_FEATURE_ORDER. Decide where its writes merge relative to the other features and add it to the order.`);
|
|
37514
|
+
const ordered = [...writer.features].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b));
|
|
37515
|
+
for (const [position, feature] of ordered.entries()) {
|
|
37516
|
+
let files = filesByFeature.get(feature);
|
|
37517
|
+
if (!files) {
|
|
37518
|
+
files = /* @__PURE__ */ new Set();
|
|
37519
|
+
filesByFeature.set(feature, files);
|
|
37520
|
+
}
|
|
37521
|
+
files.add(writer.key);
|
|
37522
|
+
let deps = depsByFeature.get(feature);
|
|
37523
|
+
if (!deps) {
|
|
37524
|
+
deps = /* @__PURE__ */ new Set();
|
|
37525
|
+
depsByFeature.set(feature, deps);
|
|
37526
|
+
}
|
|
37527
|
+
for (const earlier of ordered.slice(0, position)) deps.add(earlier);
|
|
37528
|
+
}
|
|
37529
|
+
}
|
|
37530
|
+
const steps = /* @__PURE__ */ new Map();
|
|
37531
|
+
for (const [feature, files] of filesByFeature) steps.set(feature, {
|
|
37532
|
+
writesSharedFile: [...files].toSorted(),
|
|
37533
|
+
dependsOn: [...depsByFeature.get(feature) ?? []].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b))
|
|
37534
|
+
});
|
|
37535
|
+
return steps;
|
|
37536
|
+
};
|
|
37537
|
+
//#endregion
|
|
36203
37538
|
//#region src/lib/generate.ts
|
|
36204
37539
|
async function processFeatureGeneration(params) {
|
|
36205
37540
|
const { config, processor, toolFiles, skipFilePaths } = params;
|
|
@@ -36349,98 +37684,53 @@ function resolveExecutionOrder(steps) {
|
|
|
36349
37684
|
if (ordered.length !== steps.length) throw new Error("Generation steps contain a cyclic 'dependsOn' dependency.");
|
|
36350
37685
|
return ordered;
|
|
36351
37686
|
}
|
|
37687
|
+
const SHARED_WRITE_STEPS = deriveSharedWriteSteps();
|
|
37688
|
+
const sharedWriteMeta = (id) => {
|
|
37689
|
+
const step = SHARED_WRITE_STEPS.get(id);
|
|
37690
|
+
return step ? {
|
|
37691
|
+
writesSharedFile: step.writesSharedFile,
|
|
37692
|
+
dependsOn: step.dependsOn
|
|
37693
|
+
} : {};
|
|
37694
|
+
};
|
|
36352
37695
|
/**
|
|
36353
37696
|
* The static shape of the generation step graph: which steps write which shared
|
|
36354
37697
|
* (read-modify-write) config files, and the `dependsOn` edges that fix a safe order
|
|
36355
|
-
* for those writers.
|
|
36356
|
-
*
|
|
36357
|
-
*
|
|
36358
|
-
*
|
|
36359
|
-
*
|
|
37698
|
+
* for those writers. Both are derived from the processor registry's settable
|
|
37699
|
+
* paths and `SHARED_WRITE_FEATURE_ORDER` (see `shared-file-derive.ts`), so a new
|
|
37700
|
+
* tool or shared path never requires editing this graph. Exported (separately
|
|
37701
|
+
* from the `run` closures, which need a live `config`/`logger`) so
|
|
37702
|
+
* `resolveExecutionOrder`'s ordering guarantee can be tested directly against
|
|
37703
|
+
* the real graph rather than a hand-copied one. Readonly so a consumer can't
|
|
37704
|
+
* mutate this module-level singleton and affect every subsequent `generate()`
|
|
37705
|
+
* call in the process.
|
|
36360
37706
|
*/
|
|
36361
37707
|
const GENERATION_STEP_GRAPH = [
|
|
36362
37708
|
{
|
|
36363
37709
|
id: "ignore",
|
|
36364
|
-
|
|
37710
|
+
...sharedWriteMeta("ignore")
|
|
36365
37711
|
},
|
|
36366
37712
|
{
|
|
36367
37713
|
id: "mcp",
|
|
36368
|
-
|
|
36369
|
-
".amp/settings.json",
|
|
36370
|
-
".augment/settings.json",
|
|
36371
|
-
".codex/config.toml",
|
|
36372
|
-
".config/amp/settings.json",
|
|
36373
|
-
".config/devin/config.json",
|
|
36374
|
-
".config/opencode/opencode.json",
|
|
36375
|
-
".config/zed/settings.json",
|
|
36376
|
-
".devin/config.json",
|
|
36377
|
-
".grok/config.toml",
|
|
36378
|
-
".hermes/config.yaml",
|
|
36379
|
-
".qwen/settings.json",
|
|
36380
|
-
".reasonix/config.toml",
|
|
36381
|
-
".takt/config.yaml",
|
|
36382
|
-
".vibe/config.toml",
|
|
36383
|
-
".zed/settings.json",
|
|
36384
|
-
"kilo.json",
|
|
36385
|
-
"opencode.json",
|
|
36386
|
-
"reasonix.toml"
|
|
36387
|
-
],
|
|
36388
|
-
dependsOn: ["ignore"]
|
|
37714
|
+
...sharedWriteMeta("mcp")
|
|
36389
37715
|
},
|
|
36390
37716
|
{ id: "commands" },
|
|
36391
|
-
{
|
|
37717
|
+
{
|
|
37718
|
+
id: "subagents",
|
|
37719
|
+
...sharedWriteMeta("subagents")
|
|
37720
|
+
},
|
|
36392
37721
|
{ id: "skills" },
|
|
36393
37722
|
{
|
|
36394
37723
|
id: "hooks",
|
|
36395
|
-
|
|
36396
|
-
".augment/settings.json",
|
|
36397
|
-
".claude/settings.json",
|
|
36398
|
-
".codex/config.toml",
|
|
36399
|
-
".config/devin/config.json",
|
|
36400
|
-
".hermes/config.yaml",
|
|
36401
|
-
".kiro/agents/default.json",
|
|
36402
|
-
".qwen/settings.json",
|
|
36403
|
-
".vibe/config.toml"
|
|
36404
|
-
],
|
|
36405
|
-
dependsOn: ["ignore", "mcp"]
|
|
37724
|
+
...sharedWriteMeta("hooks")
|
|
36406
37725
|
},
|
|
36407
37726
|
{
|
|
36408
37727
|
id: "permissions",
|
|
36409
|
-
|
|
36410
|
-
".amp/settings.json",
|
|
36411
|
-
".augment/settings.json",
|
|
36412
|
-
".claude/settings.json",
|
|
36413
|
-
".codex/config.toml",
|
|
36414
|
-
".config/amp/settings.json",
|
|
36415
|
-
".config/devin/config.json",
|
|
36416
|
-
".config/opencode/opencode.json",
|
|
36417
|
-
".config/zed/settings.json",
|
|
36418
|
-
".devin/config.json",
|
|
36419
|
-
".grok/config.toml",
|
|
36420
|
-
".hermes/config.yaml",
|
|
36421
|
-
".kiro/agents/default.json",
|
|
36422
|
-
".qwen/settings.json",
|
|
36423
|
-
".reasonix/config.toml",
|
|
36424
|
-
".takt/config.yaml",
|
|
36425
|
-
".vibe/config.toml",
|
|
36426
|
-
".zed/settings.json",
|
|
36427
|
-
"opencode.json",
|
|
36428
|
-
"reasonix.toml"
|
|
36429
|
-
],
|
|
36430
|
-
dependsOn: [
|
|
36431
|
-
"ignore",
|
|
36432
|
-
"hooks",
|
|
36433
|
-
"mcp"
|
|
36434
|
-
]
|
|
37728
|
+
...sharedWriteMeta("permissions")
|
|
36435
37729
|
},
|
|
36436
37730
|
{
|
|
36437
37731
|
id: "rules",
|
|
36438
|
-
|
|
36439
|
-
dependsOn: [
|
|
36440
|
-
"mcp",
|
|
36441
|
-
"skills",
|
|
36442
|
-
"permissions"
|
|
36443
|
-
]
|
|
37732
|
+
...sharedWriteMeta("rules"),
|
|
37733
|
+
dependsOn: [...sharedWriteMeta("rules").dependsOn ?? [], "skills"]
|
|
36444
37734
|
}
|
|
36445
37735
|
];
|
|
36446
37736
|
/**
|
|
@@ -37198,12 +38488,6 @@ Object.defineProperty(exports, "CommandsProcessor", {
|
|
|
37198
38488
|
return CommandsProcessor;
|
|
37199
38489
|
}
|
|
37200
38490
|
});
|
|
37201
|
-
Object.defineProperty(exports, "CommandsProcessorToolTargetSchema", {
|
|
37202
|
-
enumerable: true,
|
|
37203
|
-
get: function() {
|
|
37204
|
-
return CommandsProcessorToolTargetSchema;
|
|
37205
|
-
}
|
|
37206
|
-
});
|
|
37207
38491
|
Object.defineProperty(exports, "ConfigResolver", {
|
|
37208
38492
|
enumerable: true,
|
|
37209
38493
|
get: function() {
|
|
@@ -37228,24 +38512,12 @@ Object.defineProperty(exports, "HooksProcessor", {
|
|
|
37228
38512
|
return HooksProcessor;
|
|
37229
38513
|
}
|
|
37230
38514
|
});
|
|
37231
|
-
Object.defineProperty(exports, "HooksProcessorToolTargetSchema", {
|
|
37232
|
-
enumerable: true,
|
|
37233
|
-
get: function() {
|
|
37234
|
-
return HooksProcessorToolTargetSchema;
|
|
37235
|
-
}
|
|
37236
|
-
});
|
|
37237
38515
|
Object.defineProperty(exports, "IgnoreProcessor", {
|
|
37238
38516
|
enumerable: true,
|
|
37239
38517
|
get: function() {
|
|
37240
38518
|
return IgnoreProcessor;
|
|
37241
38519
|
}
|
|
37242
38520
|
});
|
|
37243
|
-
Object.defineProperty(exports, "IgnoreProcessorToolTargetSchema", {
|
|
37244
|
-
enumerable: true,
|
|
37245
|
-
get: function() {
|
|
37246
|
-
return IgnoreProcessorToolTargetSchema;
|
|
37247
|
-
}
|
|
37248
|
-
});
|
|
37249
38521
|
Object.defineProperty(exports, "JsonLogger", {
|
|
37250
38522
|
enumerable: true,
|
|
37251
38523
|
get: function() {
|
|
@@ -37264,24 +38536,6 @@ Object.defineProperty(exports, "McpProcessor", {
|
|
|
37264
38536
|
return McpProcessor;
|
|
37265
38537
|
}
|
|
37266
38538
|
});
|
|
37267
|
-
Object.defineProperty(exports, "McpProcessorToolTargetSchema", {
|
|
37268
|
-
enumerable: true,
|
|
37269
|
-
get: function() {
|
|
37270
|
-
return McpProcessorToolTargetSchema;
|
|
37271
|
-
}
|
|
37272
|
-
});
|
|
37273
|
-
Object.defineProperty(exports, "PermissionsProcessor", {
|
|
37274
|
-
enumerable: true,
|
|
37275
|
-
get: function() {
|
|
37276
|
-
return PermissionsProcessor;
|
|
37277
|
-
}
|
|
37278
|
-
});
|
|
37279
|
-
Object.defineProperty(exports, "PermissionsProcessorToolTargetSchema", {
|
|
37280
|
-
enumerable: true,
|
|
37281
|
-
get: function() {
|
|
37282
|
-
return PermissionsProcessorToolTargetSchema;
|
|
37283
|
-
}
|
|
37284
|
-
});
|
|
37285
38539
|
Object.defineProperty(exports, "RULESYNC_AIIGNORE_FILE_NAME", {
|
|
37286
38540
|
enumerable: true,
|
|
37287
38541
|
get: function() {
|
|
@@ -37414,12 +38668,6 @@ Object.defineProperty(exports, "RulesProcessor", {
|
|
|
37414
38668
|
return RulesProcessor;
|
|
37415
38669
|
}
|
|
37416
38670
|
});
|
|
37417
|
-
Object.defineProperty(exports, "RulesProcessorToolTargetSchema", {
|
|
37418
|
-
enumerable: true,
|
|
37419
|
-
get: function() {
|
|
37420
|
-
return RulesProcessorToolTargetSchema;
|
|
37421
|
-
}
|
|
37422
|
-
});
|
|
37423
38671
|
Object.defineProperty(exports, "RulesyncCommand", {
|
|
37424
38672
|
enumerable: true,
|
|
37425
38673
|
get: function() {
|
|
@@ -37504,24 +38752,12 @@ Object.defineProperty(exports, "SkillsProcessor", {
|
|
|
37504
38752
|
return SkillsProcessor;
|
|
37505
38753
|
}
|
|
37506
38754
|
});
|
|
37507
|
-
Object.defineProperty(exports, "SkillsProcessorToolTargetSchema", {
|
|
37508
|
-
enumerable: true,
|
|
37509
|
-
get: function() {
|
|
37510
|
-
return SkillsProcessorToolTargetSchema;
|
|
37511
|
-
}
|
|
37512
|
-
});
|
|
37513
38755
|
Object.defineProperty(exports, "SubagentsProcessor", {
|
|
37514
38756
|
enumerable: true,
|
|
37515
38757
|
get: function() {
|
|
37516
38758
|
return SubagentsProcessor;
|
|
37517
38759
|
}
|
|
37518
38760
|
});
|
|
37519
|
-
Object.defineProperty(exports, "SubagentsProcessorToolTargetSchema", {
|
|
37520
|
-
enumerable: true,
|
|
37521
|
-
get: function() {
|
|
37522
|
-
return SubagentsProcessorToolTargetSchema;
|
|
37523
|
-
}
|
|
37524
|
-
});
|
|
37525
38761
|
Object.defineProperty(exports, "ToolTargetSchema", {
|
|
37526
38762
|
enumerable: true,
|
|
37527
38763
|
get: function() {
|
|
@@ -37618,6 +38854,12 @@ Object.defineProperty(exports, "getLocalSkillDirNames", {
|
|
|
37618
38854
|
return getLocalSkillDirNames;
|
|
37619
38855
|
}
|
|
37620
38856
|
});
|
|
38857
|
+
Object.defineProperty(exports, "getProcessorRegistryEntry", {
|
|
38858
|
+
enumerable: true,
|
|
38859
|
+
get: function() {
|
|
38860
|
+
return getProcessorRegistryEntry;
|
|
38861
|
+
}
|
|
38862
|
+
});
|
|
37621
38863
|
Object.defineProperty(exports, "importFromTool", {
|
|
37622
38864
|
enumerable: true,
|
|
37623
38865
|
get: function() {
|
|
@@ -37672,54 +38914,6 @@ Object.defineProperty(exports, "toPosixPath", {
|
|
|
37672
38914
|
return toPosixPath;
|
|
37673
38915
|
}
|
|
37674
38916
|
});
|
|
37675
|
-
Object.defineProperty(exports, "toolCommandFactories", {
|
|
37676
|
-
enumerable: true,
|
|
37677
|
-
get: function() {
|
|
37678
|
-
return toolCommandFactories;
|
|
37679
|
-
}
|
|
37680
|
-
});
|
|
37681
|
-
Object.defineProperty(exports, "toolHooksFactories", {
|
|
37682
|
-
enumerable: true,
|
|
37683
|
-
get: function() {
|
|
37684
|
-
return toolHooksFactories;
|
|
37685
|
-
}
|
|
37686
|
-
});
|
|
37687
|
-
Object.defineProperty(exports, "toolIgnoreFactories", {
|
|
37688
|
-
enumerable: true,
|
|
37689
|
-
get: function() {
|
|
37690
|
-
return toolIgnoreFactories;
|
|
37691
|
-
}
|
|
37692
|
-
});
|
|
37693
|
-
Object.defineProperty(exports, "toolMcpFactories", {
|
|
37694
|
-
enumerable: true,
|
|
37695
|
-
get: function() {
|
|
37696
|
-
return toolMcpFactories;
|
|
37697
|
-
}
|
|
37698
|
-
});
|
|
37699
|
-
Object.defineProperty(exports, "toolPermissionsFactories", {
|
|
37700
|
-
enumerable: true,
|
|
37701
|
-
get: function() {
|
|
37702
|
-
return toolPermissionsFactories;
|
|
37703
|
-
}
|
|
37704
|
-
});
|
|
37705
|
-
Object.defineProperty(exports, "toolRuleFactories", {
|
|
37706
|
-
enumerable: true,
|
|
37707
|
-
get: function() {
|
|
37708
|
-
return toolRuleFactories;
|
|
37709
|
-
}
|
|
37710
|
-
});
|
|
37711
|
-
Object.defineProperty(exports, "toolSkillFactories", {
|
|
37712
|
-
enumerable: true,
|
|
37713
|
-
get: function() {
|
|
37714
|
-
return toolSkillFactories;
|
|
37715
|
-
}
|
|
37716
|
-
});
|
|
37717
|
-
Object.defineProperty(exports, "toolSubagentFactories", {
|
|
37718
|
-
enumerable: true,
|
|
37719
|
-
get: function() {
|
|
37720
|
-
return toolSubagentFactories;
|
|
37721
|
-
}
|
|
37722
|
-
});
|
|
37723
38917
|
Object.defineProperty(exports, "writeFileContent", {
|
|
37724
38918
|
enumerable: true,
|
|
37725
38919
|
get: function() {
|