rulesync 9.3.0 → 9.5.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 +4 -61
- package/dist/cli/index.js +2 -59
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-B7VzSVUK.js → import-BdJG1fyb.js} +973 -390
- package/dist/import-BdJG1fyb.js.map +1 -0
- package/dist/{import-CE3Rx3Wt.cjs → import-DngV1i49.cjs} +987 -500
- 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-B7VzSVUK.js.map +0 -1
|
@@ -852,7 +852,7 @@ const SourceEntrySchema = z.object({
|
|
|
852
852
|
scope: optional(z.enum(["project", "user"]))
|
|
853
853
|
});
|
|
854
854
|
const ConfigParamsSchema = z.object({
|
|
855
|
-
outputRoots: z.array(z.string()),
|
|
855
|
+
outputRoots: z.union([z.array(z.string()), z.record(z.string(), z.union([z.string(), z.array(z.string())]))]),
|
|
856
856
|
targets: RulesyncConfigTargetsSchema,
|
|
857
857
|
features: RulesyncFeaturesSchema,
|
|
858
858
|
verbose: z.boolean(),
|
|
@@ -957,6 +957,7 @@ var Config = class Config {
|
|
|
957
957
|
const resolvedTargets = targets ?? [];
|
|
958
958
|
const resolvedFeatures = features ?? [];
|
|
959
959
|
this.validateObjectFormTargetKeys(resolvedTargets);
|
|
960
|
+
this.validateObjectFormOutputRootKeys(outputRoots);
|
|
960
961
|
this.validateConflictingTargets(resolvedTargets);
|
|
961
962
|
if (dryRun && check) throw new Error("--dry-run and --check cannot be used together");
|
|
962
963
|
this.outputRoots = outputRoots;
|
|
@@ -994,6 +995,11 @@ var Config = class Config {
|
|
|
994
995
|
if (!validTargets.has(key)) throw new Error(`Unknown target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
|
|
995
996
|
}
|
|
996
997
|
}
|
|
998
|
+
validateObjectFormOutputRootKeys(outputRoots) {
|
|
999
|
+
if (Array.isArray(outputRoots)) return;
|
|
1000
|
+
const validTargets = new Set(ALL_TOOL_TARGETS);
|
|
1001
|
+
for (const key of Object.keys(outputRoots)) if (!validTargets.has(key)) throw new Error(`Unknown outputRoots target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
|
|
1002
|
+
}
|
|
997
1003
|
validateConflictingTargets(targets) {
|
|
998
1004
|
const has = (target) => {
|
|
999
1005
|
if (Array.isArray(targets)) return targets.includes(target);
|
|
@@ -1001,8 +1007,19 @@ var Config = class Config {
|
|
|
1001
1007
|
};
|
|
1002
1008
|
for (const [target1, target2] of CONFLICTING_TARGET_PAIRS) if (has(target1) && has(target2)) throw new Error(`Conflicting targets: '${target1}' and '${target2}' cannot be used together. Please choose one.`);
|
|
1003
1009
|
}
|
|
1004
|
-
getOutputRoots() {
|
|
1005
|
-
return this.outputRoots;
|
|
1010
|
+
getOutputRoots(target) {
|
|
1011
|
+
if (Array.isArray(this.outputRoots)) return this.outputRoots;
|
|
1012
|
+
if (target) {
|
|
1013
|
+
const targetOutputRoots = this.outputRoots[target];
|
|
1014
|
+
if (targetOutputRoots === void 0) return [];
|
|
1015
|
+
return Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
|
|
1016
|
+
}
|
|
1017
|
+
const allRoots = [];
|
|
1018
|
+
for (const value of Object.values(this.outputRoots)) {
|
|
1019
|
+
if (value === void 0) continue;
|
|
1020
|
+
allRoots.push(...Array.isArray(value) ? value : [value]);
|
|
1021
|
+
}
|
|
1022
|
+
return [...new Set(allRoots)];
|
|
1006
1023
|
}
|
|
1007
1024
|
/**
|
|
1008
1025
|
* Filter an arbitrary string-key list down to the known `ToolTarget` set,
|
|
@@ -1364,10 +1381,21 @@ var ConfigResolver = class {
|
|
|
1364
1381
|
};
|
|
1365
1382
|
function getOutputRootsInLightOfGlobal({ outputRoots, global }) {
|
|
1366
1383
|
if (global) return [getHomeDirectory()];
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1384
|
+
if (Array.isArray(outputRoots)) {
|
|
1385
|
+
outputRoots.forEach((outputRoot) => {
|
|
1386
|
+
validateOutputRoot(outputRoot);
|
|
1387
|
+
});
|
|
1388
|
+
return outputRoots.map((outputRoot) => resolve(outputRoot));
|
|
1389
|
+
}
|
|
1390
|
+
const resolvedOutputRoots = {};
|
|
1391
|
+
for (const [target, targetOutputRoots] of Object.entries(outputRoots)) {
|
|
1392
|
+
const roots = Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
|
|
1393
|
+
roots.forEach((outputRoot) => {
|
|
1394
|
+
validateOutputRoot(outputRoot);
|
|
1395
|
+
});
|
|
1396
|
+
resolvedOutputRoots[target] = Array.isArray(targetOutputRoots) ? roots.map((outputRoot) => resolve(outputRoot)) : resolve(targetOutputRoots);
|
|
1397
|
+
}
|
|
1398
|
+
return resolvedOutputRoots;
|
|
1371
1399
|
}
|
|
1372
1400
|
function extractConfigFileTargets(targets) {
|
|
1373
1401
|
if (targets === void 0) return void 0;
|
|
@@ -3892,6 +3920,297 @@ const OPENCODE_JSON_FILE_NAME = "opencode.json";
|
|
|
3892
3920
|
const OPENCODE_RULE_FILE_NAME = "AGENTS.md";
|
|
3893
3921
|
const OPENCODE_HOOKS_FILE_NAME = "rulesync-hooks.js";
|
|
3894
3922
|
//#endregion
|
|
3923
|
+
//#region src/constants/takt-paths.ts
|
|
3924
|
+
const TAKT_DIR = ".takt";
|
|
3925
|
+
const TAKT_FACETS_SUBDIR = "facets";
|
|
3926
|
+
const TAKT_FACETS_DIR_PATH = join(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
3927
|
+
const TAKT_RULES_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "policies");
|
|
3928
|
+
const TAKT_COMMANDS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "instructions");
|
|
3929
|
+
const TAKT_SKILLS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
3930
|
+
const TAKT_SUBAGENTS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "personas");
|
|
3931
|
+
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
3932
|
+
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
3933
|
+
/**
|
|
3934
|
+
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
3935
|
+
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
3936
|
+
* profiles (including permission modes), and other Takt settings.
|
|
3937
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3938
|
+
*/
|
|
3939
|
+
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
3940
|
+
/**
|
|
3941
|
+
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
3942
|
+
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
3943
|
+
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
3944
|
+
* server using it is permitted to run.
|
|
3945
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3946
|
+
*/
|
|
3947
|
+
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
3948
|
+
//#endregion
|
|
3949
|
+
//#region src/utils/prototype-pollution.ts
|
|
3950
|
+
/**
|
|
3951
|
+
* Keys that, if walked into when constructing or merging objects from
|
|
3952
|
+
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
3953
|
+
* chain) and propagate state to every other object in the runtime. Any code
|
|
3954
|
+
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
3955
|
+
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
3956
|
+
*/
|
|
3957
|
+
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
3958
|
+
"__proto__",
|
|
3959
|
+
"constructor",
|
|
3960
|
+
"prototype"
|
|
3961
|
+
]);
|
|
3962
|
+
function isPrototypePollutionKey(key) {
|
|
3963
|
+
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Returns a shallow copy of a record's own entries with every
|
|
3967
|
+
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
3968
|
+
*
|
|
3969
|
+
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
3970
|
+
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
3971
|
+
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
3972
|
+
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
3973
|
+
* prototype). Walking the entries through this helper severs that path while
|
|
3974
|
+
* preserving every legitimate key.
|
|
3975
|
+
*/
|
|
3976
|
+
function omitPrototypePollutionKeys(record) {
|
|
3977
|
+
const sanitized = {};
|
|
3978
|
+
for (const [key, value] of Object.entries(record)) {
|
|
3979
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
3980
|
+
sanitized[key] = value;
|
|
3981
|
+
}
|
|
3982
|
+
return sanitized;
|
|
3983
|
+
}
|
|
3984
|
+
//#endregion
|
|
3985
|
+
//#region src/features/shared/shared-config-gateway.ts
|
|
3986
|
+
function sanitizeSharedConfigValue(value) {
|
|
3987
|
+
if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
|
|
3988
|
+
if (!isPlainObject(value)) return value;
|
|
3989
|
+
const result = {};
|
|
3990
|
+
for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
|
|
3991
|
+
return result;
|
|
3992
|
+
}
|
|
3993
|
+
/**
|
|
3994
|
+
* Parse a shared config file into a plain document: an empty/whitespace file
|
|
3995
|
+
* is `{}`, prototype-pollution keys are dropped recursively, and a non-mapping
|
|
3996
|
+
* root follows `invalidRootPolicy`. Syntax errors are wrapped with the file
|
|
3997
|
+
* path when one is given.
|
|
3998
|
+
*/
|
|
3999
|
+
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty" }) {
|
|
4000
|
+
if (fileContent.trim() === "") return {};
|
|
4001
|
+
const at = filePath === void 0 ? "" : ` at ${filePath}`;
|
|
4002
|
+
let parsed;
|
|
4003
|
+
try {
|
|
4004
|
+
if (format === "yaml") parsed = load(fileContent);
|
|
4005
|
+
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
4006
|
+
else parsed = parse(fileContent);
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
|
|
4009
|
+
}
|
|
4010
|
+
if (parsed === void 0 || parsed === null) return {};
|
|
4011
|
+
if (!isPlainObject(parsed)) {
|
|
4012
|
+
if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
|
|
4013
|
+
return {};
|
|
4014
|
+
}
|
|
4015
|
+
return sanitizeSharedConfigValue(parsed);
|
|
4016
|
+
}
|
|
4017
|
+
/**
|
|
4018
|
+
* Serialize a shared config document. YAML output always ends with exactly one
|
|
4019
|
+
* newline; JSON output matches the 2-space `JSON.stringify` shape the JSON
|
|
4020
|
+
* writers have always emitted (no trailing newline).
|
|
4021
|
+
*/
|
|
4022
|
+
function stringifySharedConfig({ format, document }) {
|
|
4023
|
+
if (format === "yaml") return dump(document, {
|
|
4024
|
+
noRefs: true,
|
|
4025
|
+
sortKeys: false
|
|
4026
|
+
}).trimEnd() + "\n";
|
|
4027
|
+
return JSON.stringify(document, null, 2);
|
|
4028
|
+
}
|
|
4029
|
+
/**
|
|
4030
|
+
* Shallow merge: every top-level key in `patch` replaces the base key
|
|
4031
|
+
* wholesale; all other base keys are preserved. The policy for a feature that
|
|
4032
|
+
* owns a fixed set of top-level keys.
|
|
4033
|
+
*/
|
|
4034
|
+
function mergeSharedConfigShallow({ base, patch }) {
|
|
4035
|
+
return {
|
|
4036
|
+
...base,
|
|
4037
|
+
...sanitizeSharedConfigValue(patch)
|
|
4038
|
+
};
|
|
4039
|
+
}
|
|
4040
|
+
/**
|
|
4041
|
+
* Deep merge (`patch` wins): nested plain objects are merged key-by-key; every
|
|
4042
|
+
* other value (arrays, scalars) is replaced wholesale. The policy for a
|
|
4043
|
+
* feature whose contribution interleaves with user-authored siblings at any
|
|
4044
|
+
* depth (e.g. permissions overlays onto `approvals`/`security` structures, or
|
|
4045
|
+
* per-provider option tables) — nested sibling keys are preserved by
|
|
4046
|
+
* construction instead of by per-tool re-implementation. Prototype-pollution
|
|
4047
|
+
* keys are dropped.
|
|
4048
|
+
*/
|
|
4049
|
+
function mergeSharedConfigDeep({ base, patch }) {
|
|
4050
|
+
const result = { ...base };
|
|
4051
|
+
for (const [key, patchValue] of Object.entries(patch)) {
|
|
4052
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4053
|
+
const baseValue = result[key];
|
|
4054
|
+
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
|
|
4055
|
+
base: baseValue,
|
|
4056
|
+
patch: patchValue
|
|
4057
|
+
});
|
|
4058
|
+
else result[key] = sanitizeSharedConfigValue(patchValue);
|
|
4059
|
+
}
|
|
4060
|
+
return result;
|
|
4061
|
+
}
|
|
4062
|
+
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
4063
|
+
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
4064
|
+
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
4065
|
+
/**
|
|
4066
|
+
* Who owns what in each gateway-managed shared config file, and which policy
|
|
4067
|
+
* resolves conflicts. Keys are `dir/file` tokens matching
|
|
4068
|
+
* `deriveSharedFileWriters()`; a test keeps each entry's feature set in
|
|
4069
|
+
* lock-step with the writers derived from the processor registry, so an
|
|
4070
|
+
* undeclared writer fails CI instead of merging by accident.
|
|
4071
|
+
*/
|
|
4072
|
+
const SHARED_CONFIG_OWNERSHIP = {
|
|
4073
|
+
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
4074
|
+
format: "json",
|
|
4075
|
+
features: {
|
|
4076
|
+
ignore: {
|
|
4077
|
+
kind: "custom",
|
|
4078
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
4079
|
+
},
|
|
4080
|
+
hooks: {
|
|
4081
|
+
kind: "replace-owned-keys",
|
|
4082
|
+
ownedKeys: ["hooks"]
|
|
4083
|
+
},
|
|
4084
|
+
permissions: {
|
|
4085
|
+
kind: "custom",
|
|
4086
|
+
policyFunction: "applyPermissions"
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
},
|
|
4090
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: {
|
|
4091
|
+
format: "yaml",
|
|
4092
|
+
features: {
|
|
4093
|
+
subagents: {
|
|
4094
|
+
kind: "replace-owned-keys",
|
|
4095
|
+
ownedKeys: ["plugins"]
|
|
4096
|
+
},
|
|
4097
|
+
mcp: {
|
|
4098
|
+
kind: "replace-owned-keys",
|
|
4099
|
+
ownedKeys: ["mcp_servers"]
|
|
4100
|
+
},
|
|
4101
|
+
hooks: {
|
|
4102
|
+
kind: "replace-owned-keys",
|
|
4103
|
+
ownedKeys: ["hooks"]
|
|
4104
|
+
},
|
|
4105
|
+
permissions: {
|
|
4106
|
+
kind: "deep-merge",
|
|
4107
|
+
replaceKeys: ["permissions"]
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
},
|
|
4111
|
+
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
4112
|
+
format: "yaml",
|
|
4113
|
+
invalidRootPolicy: "error",
|
|
4114
|
+
features: {
|
|
4115
|
+
mcp: {
|
|
4116
|
+
kind: "replace-owned-keys",
|
|
4117
|
+
ownedKeys: [TAKT_WORKFLOW_MCP_SERVERS_KEY]
|
|
4118
|
+
},
|
|
4119
|
+
permissions: { kind: "deep-merge" }
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
};
|
|
4123
|
+
/**
|
|
4124
|
+
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
4125
|
+
* the existing content, merge the patch under the feature's declared policy,
|
|
4126
|
+
* and serialize. Throws when the file or feature is undeclared, when a
|
|
4127
|
+
* `replace-owned-keys` patch strays outside its owned keys, or when the
|
|
4128
|
+
* feature's policy is `custom` (those calls go to the named policy function
|
|
4129
|
+
* instead).
|
|
4130
|
+
*/
|
|
4131
|
+
function applySharedConfigPatch({ fileKey, feature, existingContent, patch, filePath }) {
|
|
4132
|
+
const declaration = SHARED_CONFIG_OWNERSHIP[fileKey];
|
|
4133
|
+
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.`);
|
|
4134
|
+
const policy = declaration.features[feature];
|
|
4135
|
+
if (!policy) throw new Error(`Feature '${feature}' declares no ownership of '${fileKey}'; add it to SHARED_CONFIG_OWNERSHIP before writing.`);
|
|
4136
|
+
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.`);
|
|
4137
|
+
const base = parseSharedConfig({
|
|
4138
|
+
format: declaration.format,
|
|
4139
|
+
fileContent: existingContent,
|
|
4140
|
+
filePath,
|
|
4141
|
+
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy }
|
|
4142
|
+
});
|
|
4143
|
+
if (policy.kind === "replace-owned-keys") {
|
|
4144
|
+
const unowned = Object.keys(patch).filter((key) => !policy.ownedKeys.includes(key));
|
|
4145
|
+
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.`);
|
|
4146
|
+
return stringifySharedConfig({
|
|
4147
|
+
format: declaration.format,
|
|
4148
|
+
document: mergeSharedConfigShallow({
|
|
4149
|
+
base,
|
|
4150
|
+
patch
|
|
4151
|
+
})
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
const merged = mergeSharedConfigDeep({
|
|
4155
|
+
base,
|
|
4156
|
+
patch
|
|
4157
|
+
});
|
|
4158
|
+
for (const key of policy.replaceKeys ?? []) if (patch[key] !== void 0) merged[key] = sanitizeSharedConfigValue(patch[key]);
|
|
4159
|
+
return stringifySharedConfig({
|
|
4160
|
+
format: declaration.format,
|
|
4161
|
+
document: merged
|
|
4162
|
+
});
|
|
4163
|
+
}
|
|
4164
|
+
const READ_TOOL_NAME = "Read";
|
|
4165
|
+
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
4166
|
+
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
4167
|
+
const parsePermissionsBlock = (settings) => {
|
|
4168
|
+
const permissions = settings.permissions ?? {};
|
|
4169
|
+
return {
|
|
4170
|
+
allow: permissions.allow ?? [],
|
|
4171
|
+
ask: permissions.ask ?? [],
|
|
4172
|
+
deny: permissions.deny ?? []
|
|
4173
|
+
};
|
|
4174
|
+
};
|
|
4175
|
+
const withPermissions = (settings, next) => {
|
|
4176
|
+
const permissions = { ...settings.permissions };
|
|
4177
|
+
const assign = (key, values) => {
|
|
4178
|
+
if (values.length > 0) permissions[key] = values;
|
|
4179
|
+
else delete permissions[key];
|
|
4180
|
+
};
|
|
4181
|
+
assign("allow", next.allow);
|
|
4182
|
+
assign("ask", next.ask);
|
|
4183
|
+
assign("deny", next.deny);
|
|
4184
|
+
return {
|
|
4185
|
+
...settings,
|
|
4186
|
+
permissions
|
|
4187
|
+
};
|
|
4188
|
+
};
|
|
4189
|
+
const applyIgnoreReadDenies = (params) => {
|
|
4190
|
+
const { settings, readDenies } = params;
|
|
4191
|
+
const current = parsePermissionsBlock(settings);
|
|
4192
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
4193
|
+
return withPermissions(settings, {
|
|
4194
|
+
allow: current.allow,
|
|
4195
|
+
ask: current.ask,
|
|
4196
|
+
deny: uniq([...preservedDeny, ...readDenies].toSorted())
|
|
4197
|
+
});
|
|
4198
|
+
};
|
|
4199
|
+
const applyPermissions = (params) => {
|
|
4200
|
+
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
4201
|
+
const current = parsePermissionsBlock(settings);
|
|
4202
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
4203
|
+
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
4204
|
+
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
4205
|
+
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.`);
|
|
4206
|
+
}
|
|
4207
|
+
return withPermissions(settings, {
|
|
4208
|
+
allow: uniq([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
4209
|
+
ask: uniq([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
4210
|
+
deny: uniq([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
4211
|
+
});
|
|
4212
|
+
};
|
|
4213
|
+
//#endregion
|
|
3895
4214
|
//#region src/features/opencode-config.ts
|
|
3896
4215
|
/**
|
|
3897
4216
|
* Reads and parses the OpenCode config (`opencode.jsonc` preferred, then
|
|
@@ -3916,9 +4235,10 @@ async function readOpencodeConfig({ outputRoot, global = false }) {
|
|
|
3916
4235
|
});
|
|
3917
4236
|
const fileContent = await readFileContentOrNull(join(configDir, "opencode.jsonc")) ?? await readFileContentOrNull(join(configDir, "opencode.json"));
|
|
3918
4237
|
if (!fileContent) return {};
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
4238
|
+
return parseSharedConfig({
|
|
4239
|
+
format: "jsonc",
|
|
4240
|
+
fileContent
|
|
4241
|
+
});
|
|
3922
4242
|
}
|
|
3923
4243
|
/**
|
|
3924
4244
|
* Narrows an unknown value to a plain record of entries keyed by name, as used
|
|
@@ -4827,32 +5147,6 @@ var RovodevPromptsManifest = class extends ToolFile {
|
|
|
4827
5147
|
}
|
|
4828
5148
|
};
|
|
4829
5149
|
//#endregion
|
|
4830
|
-
//#region src/constants/takt-paths.ts
|
|
4831
|
-
const TAKT_DIR = ".takt";
|
|
4832
|
-
const TAKT_FACETS_SUBDIR = "facets";
|
|
4833
|
-
const TAKT_FACETS_DIR_PATH = join(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
4834
|
-
const TAKT_RULES_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "policies");
|
|
4835
|
-
const TAKT_COMMANDS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "instructions");
|
|
4836
|
-
const TAKT_SKILLS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
4837
|
-
const TAKT_SUBAGENTS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "personas");
|
|
4838
|
-
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = join(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
4839
|
-
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
4840
|
-
/**
|
|
4841
|
-
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
4842
|
-
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
4843
|
-
* profiles (including permission modes), and other Takt settings.
|
|
4844
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4845
|
-
*/
|
|
4846
|
-
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
4847
|
-
/**
|
|
4848
|
-
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
4849
|
-
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
4850
|
-
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
4851
|
-
* server using it is permitted to run.
|
|
4852
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4853
|
-
*/
|
|
4854
|
-
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
4855
|
-
//#endregion
|
|
4856
5150
|
//#region src/features/takt-shared.ts
|
|
4857
5151
|
/**
|
|
4858
5152
|
* Shared utilities for all TAKT-* tool file classes.
|
|
@@ -6330,42 +6624,6 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
6330
6624
|
*/
|
|
6331
6625
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6332
6626
|
//#endregion
|
|
6333
|
-
//#region src/utils/prototype-pollution.ts
|
|
6334
|
-
/**
|
|
6335
|
-
* Keys that, if walked into when constructing or merging objects from
|
|
6336
|
-
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
6337
|
-
* chain) and propagate state to every other object in the runtime. Any code
|
|
6338
|
-
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
6339
|
-
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
6340
|
-
*/
|
|
6341
|
-
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
6342
|
-
"__proto__",
|
|
6343
|
-
"constructor",
|
|
6344
|
-
"prototype"
|
|
6345
|
-
]);
|
|
6346
|
-
function isPrototypePollutionKey(key) {
|
|
6347
|
-
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
6348
|
-
}
|
|
6349
|
-
/**
|
|
6350
|
-
* Returns a shallow copy of a record's own entries with every
|
|
6351
|
-
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
6352
|
-
*
|
|
6353
|
-
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
6354
|
-
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
6355
|
-
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
6356
|
-
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
6357
|
-
* prototype). Walking the entries through this helper severs that path while
|
|
6358
|
-
* preserving every legitimate key.
|
|
6359
|
-
*/
|
|
6360
|
-
function omitPrototypePollutionKeys(record) {
|
|
6361
|
-
const sanitized = {};
|
|
6362
|
-
for (const [key, value] of Object.entries(record)) {
|
|
6363
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
6364
|
-
sanitized[key] = value;
|
|
6365
|
-
}
|
|
6366
|
-
return sanitized;
|
|
6367
|
-
}
|
|
6368
|
-
//#endregion
|
|
6369
6627
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
6370
6628
|
function isToolMatcherEntry(x) {
|
|
6371
6629
|
if (x === null || typeof x !== "object") return false;
|
|
@@ -7024,24 +7282,19 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
|
7024
7282
|
const paths = ClaudecodeHooks.getSettablePaths({ global });
|
|
7025
7283
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7026
7284
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
7027
|
-
let settings;
|
|
7028
|
-
try {
|
|
7029
|
-
settings = JSON.parse(existingContent);
|
|
7030
|
-
} catch (error) {
|
|
7031
|
-
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7032
|
-
}
|
|
7033
7285
|
const config = rulesyncHooks.getJson();
|
|
7034
|
-
const
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
|
|
7286
|
+
const fileContent = applySharedConfigPatch({
|
|
7287
|
+
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
7288
|
+
feature: "hooks",
|
|
7289
|
+
existingContent,
|
|
7290
|
+
patch: { hooks: canonicalToToolHooks({
|
|
7291
|
+
config,
|
|
7292
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
7293
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
7294
|
+
logger
|
|
7295
|
+
}) },
|
|
7296
|
+
filePath
|
|
7039
7297
|
});
|
|
7040
|
-
const merged = {
|
|
7041
|
-
...settings,
|
|
7042
|
-
hooks: claudeHooks
|
|
7043
|
-
};
|
|
7044
|
-
const fileContent = JSON.stringify(merged, null, 2);
|
|
7045
7298
|
return new ClaudecodeHooks({
|
|
7046
7299
|
outputRoot,
|
|
7047
7300
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -8273,55 +8526,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8273
8526
|
}
|
|
8274
8527
|
};
|
|
8275
8528
|
//#endregion
|
|
8276
|
-
//#region src/features/hermes-config.ts
|
|
8277
|
-
function sanitizeHermesConfigValue(value) {
|
|
8278
|
-
if (Array.isArray(value)) return value.map(sanitizeHermesConfigValue);
|
|
8279
|
-
if (!isPlainObject(value)) return value;
|
|
8280
|
-
const sanitized = omitPrototypePollutionKeys(value);
|
|
8281
|
-
const result = {};
|
|
8282
|
-
for (const [key, nestedValue] of Object.entries(sanitized)) {
|
|
8283
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8284
|
-
result[key] = sanitizeHermesConfigValue(nestedValue);
|
|
8285
|
-
}
|
|
8286
|
-
return result;
|
|
8287
|
-
}
|
|
8288
|
-
function parseHermesConfig(fileContent) {
|
|
8289
|
-
if (!fileContent.trim()) return {};
|
|
8290
|
-
const parsed = load(fileContent);
|
|
8291
|
-
if (isPlainObject(parsed)) return sanitizeHermesConfigValue(parsed);
|
|
8292
|
-
return {};
|
|
8293
|
-
}
|
|
8294
|
-
function stringifyHermesConfig(config) {
|
|
8295
|
-
return dump(config, {
|
|
8296
|
-
noRefs: true,
|
|
8297
|
-
sortKeys: false
|
|
8298
|
-
}).trimEnd() + "\n";
|
|
8299
|
-
}
|
|
8300
|
-
function mergeHermesConfig(fileContent, patch) {
|
|
8301
|
-
return stringifyHermesConfig({
|
|
8302
|
-
...parseHermesConfig(fileContent),
|
|
8303
|
-
...patch
|
|
8304
|
-
});
|
|
8305
|
-
}
|
|
8306
|
-
/**
|
|
8307
|
-
* Recursively merge `patch` into `base` (patch wins). Nested plain objects are
|
|
8308
|
-
* merged key-by-key; every other value (arrays, scalars) is replaced wholesale.
|
|
8309
|
-
* Used to overlay the Hermes-scoped permission override onto the natively
|
|
8310
|
-
* emitted `approvals`/`security` structures without one clobbering the other
|
|
8311
|
-
* (e.g. `approvals.deny` from canonical deny rules coexisting with an
|
|
8312
|
-
* `approvals.mode` from the override). Prototype-pollution keys are dropped.
|
|
8313
|
-
*/
|
|
8314
|
-
function deepMergeHermesConfig(base, patch) {
|
|
8315
|
-
const result = { ...base };
|
|
8316
|
-
for (const [key, patchValue] of Object.entries(patch)) {
|
|
8317
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8318
|
-
const baseValue = result[key];
|
|
8319
|
-
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = deepMergeHermesConfig(baseValue, patchValue);
|
|
8320
|
-
else result[key] = sanitizeHermesConfigValue(patchValue);
|
|
8321
|
-
}
|
|
8322
|
-
return result;
|
|
8323
|
-
}
|
|
8324
|
-
//#endregion
|
|
8325
8529
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8326
8530
|
/**
|
|
8327
8531
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -8449,10 +8653,21 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8449
8653
|
return true;
|
|
8450
8654
|
}
|
|
8451
8655
|
setFileContent(fileContent) {
|
|
8452
|
-
this.fileContent =
|
|
8656
|
+
this.fileContent = applySharedConfigPatch({
|
|
8657
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
8658
|
+
feature: "hooks",
|
|
8659
|
+
existingContent: fileContent,
|
|
8660
|
+
patch: parseSharedConfig({
|
|
8661
|
+
format: "yaml",
|
|
8662
|
+
fileContent: this.fileContent
|
|
8663
|
+
})
|
|
8664
|
+
});
|
|
8453
8665
|
}
|
|
8454
8666
|
toRulesyncHooks() {
|
|
8455
|
-
const hooks = hermesHooksToCanonical(
|
|
8667
|
+
const hooks = hermesHooksToCanonical(parseSharedConfig({
|
|
8668
|
+
format: "yaml",
|
|
8669
|
+
fileContent: this.getFileContent()
|
|
8670
|
+
}).hooks);
|
|
8456
8671
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
8457
8672
|
version: 1,
|
|
8458
8673
|
hooks
|
|
@@ -8462,11 +8677,14 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8462
8677
|
const config = rulesyncHooks.getJson();
|
|
8463
8678
|
return new HermesagentHooks({
|
|
8464
8679
|
outputRoot,
|
|
8465
|
-
fileContent:
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8680
|
+
fileContent: stringifySharedConfig({
|
|
8681
|
+
format: "yaml",
|
|
8682
|
+
document: { hooks: canonicalToHermesHooks({
|
|
8683
|
+
config,
|
|
8684
|
+
toolOverrideHooks: config.hermesagent?.hooks,
|
|
8685
|
+
logger
|
|
8686
|
+
}) }
|
|
8687
|
+
})
|
|
8470
8688
|
});
|
|
8471
8689
|
}
|
|
8472
8690
|
};
|
|
@@ -10486,66 +10704,6 @@ var AugmentcodeIgnore = class AugmentcodeIgnore extends ToolIgnore {
|
|
|
10486
10704
|
}
|
|
10487
10705
|
};
|
|
10488
10706
|
//#endregion
|
|
10489
|
-
//#region src/features/claudecode-settings-gateway.ts
|
|
10490
|
-
/**
|
|
10491
|
-
* Single owner of the `.claude/settings.json` `permissions` block, which both
|
|
10492
|
-
* `ignore` (writes `Read(...)` into `permissions.deny`) and `permissions`
|
|
10493
|
-
* (writes the whole `allow`/`ask`/`deny`) read-modify-write. The entry format,
|
|
10494
|
-
* the merge, and the cross-feature ownership rule (permissions' explicit `Read`
|
|
10495
|
-
* rules win over ignore-derived `Read` denies) used to be duplicated across both
|
|
10496
|
-
* feature files; they live here once so each feature just states its intent and
|
|
10497
|
-
* never reasons about the other's existence.
|
|
10498
|
-
*/
|
|
10499
|
-
const READ_TOOL_NAME = "Read";
|
|
10500
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
10501
|
-
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
10502
|
-
const parsePermissionsBlock = (settings) => {
|
|
10503
|
-
const permissions = settings.permissions ?? {};
|
|
10504
|
-
return {
|
|
10505
|
-
allow: permissions.allow ?? [],
|
|
10506
|
-
ask: permissions.ask ?? [],
|
|
10507
|
-
deny: permissions.deny ?? []
|
|
10508
|
-
};
|
|
10509
|
-
};
|
|
10510
|
-
const withPermissions = (settings, next) => {
|
|
10511
|
-
const permissions = { ...settings.permissions };
|
|
10512
|
-
const assign = (key, values) => {
|
|
10513
|
-
if (values.length > 0) permissions[key] = values;
|
|
10514
|
-
else delete permissions[key];
|
|
10515
|
-
};
|
|
10516
|
-
assign("allow", next.allow);
|
|
10517
|
-
assign("ask", next.ask);
|
|
10518
|
-
assign("deny", next.deny);
|
|
10519
|
-
return {
|
|
10520
|
-
...settings,
|
|
10521
|
-
permissions
|
|
10522
|
-
};
|
|
10523
|
-
};
|
|
10524
|
-
const applyIgnoreReadDenies = (params) => {
|
|
10525
|
-
const { settings, readDenies } = params;
|
|
10526
|
-
const current = parsePermissionsBlock(settings);
|
|
10527
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
10528
|
-
return withPermissions(settings, {
|
|
10529
|
-
allow: current.allow,
|
|
10530
|
-
ask: current.ask,
|
|
10531
|
-
deny: uniq([...preservedDeny, ...readDenies].toSorted())
|
|
10532
|
-
});
|
|
10533
|
-
};
|
|
10534
|
-
const applyPermissions = (params) => {
|
|
10535
|
-
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
10536
|
-
const current = parsePermissionsBlock(settings);
|
|
10537
|
-
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
10538
|
-
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
10539
|
-
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
10540
|
-
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.`);
|
|
10541
|
-
}
|
|
10542
|
-
return withPermissions(settings, {
|
|
10543
|
-
allow: uniq([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
10544
|
-
ask: uniq([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
10545
|
-
deny: uniq([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
10546
|
-
});
|
|
10547
|
-
};
|
|
10548
|
-
//#endregion
|
|
10549
10707
|
//#region src/features/ignore/claudecode-ignore.ts
|
|
10550
10708
|
const DEFAULT_FILE_MODE = "shared";
|
|
10551
10709
|
/**
|
|
@@ -11968,7 +12126,6 @@ var AntigravityIdeMcp = class extends AntigravityMcp {
|
|
|
11968
12126
|
};
|
|
11969
12127
|
//#endregion
|
|
11970
12128
|
//#region src/features/mcp/augmentcode-mcp.ts
|
|
11971
|
-
const AUGMENTCODE_GLOBAL_ONLY_MESSAGE = "AugmentCode MCP is global-only; use --global to sync ~/.augment/settings.json";
|
|
11972
12129
|
function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
11973
12130
|
const configPath = join(relativeDirPath, relativeFilePath);
|
|
11974
12131
|
let parsed;
|
|
@@ -11983,13 +12140,15 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
|
|
|
11983
12140
|
/**
|
|
11984
12141
|
* AugmentCode (Auggie CLI) MCP servers.
|
|
11985
12142
|
*
|
|
11986
|
-
* MCP servers are persisted in the shared
|
|
11987
|
-
*
|
|
11988
|
-
*
|
|
12143
|
+
* MCP servers are persisted in the shared settings file `.augment/settings.json`
|
|
12144
|
+
* at either scope: the committed workspace file for team-shared servers (project)
|
|
12145
|
+
* or `~/.augment/settings.json` (global). That same file also holds `hooks` and
|
|
11989
12146
|
* `toolPermissions`, so generation merges the `mcpServers` block into the
|
|
11990
|
-
* existing settings instead of overwriting it, and the file is never deleted.
|
|
12147
|
+
* existing settings instead of overwriting it, and the file is never deleted. On
|
|
12148
|
+
* project-scope import the gitignored `.augment/settings.local.json` overrides
|
|
12149
|
+
* file is overlaid on top (the same layering the hooks/permissions adapters use).
|
|
11991
12150
|
*
|
|
11992
|
-
* @see https://docs.augmentcode.com/cli/
|
|
12151
|
+
* @see https://docs.augmentcode.com/cli/config
|
|
11993
12152
|
*/
|
|
11994
12153
|
var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
11995
12154
|
json;
|
|
@@ -12011,9 +12170,14 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12011
12170
|
};
|
|
12012
12171
|
}
|
|
12013
12172
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
12014
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12015
12173
|
const paths = this.getSettablePaths({ global });
|
|
12016
|
-
const json = parseAugmentcodeSettings(await
|
|
12174
|
+
const json = parseAugmentcodeSettings(await readAugmentcodeSettingsWithLocalOverlay({
|
|
12175
|
+
outputRoot,
|
|
12176
|
+
relativeDirPath: paths.relativeDirPath,
|
|
12177
|
+
baseFileName: paths.relativeFilePath,
|
|
12178
|
+
baseFallbackContent: "{}",
|
|
12179
|
+
includeLocalOverlay: !global
|
|
12180
|
+
}), paths.relativeDirPath, paths.relativeFilePath);
|
|
12017
12181
|
const newJson = {
|
|
12018
12182
|
...json,
|
|
12019
12183
|
mcpServers: json.mcpServers ?? {}
|
|
@@ -12028,7 +12192,6 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12028
12192
|
});
|
|
12029
12193
|
}
|
|
12030
12194
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12031
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12032
12195
|
const paths = this.getSettablePaths({ global });
|
|
12033
12196
|
const merged = {
|
|
12034
12197
|
...parseAugmentcodeSettings(await readOrInitializeFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath), JSON.stringify({}, null, 2)), paths.relativeDirPath, paths.relativeFilePath),
|
|
@@ -13678,7 +13841,10 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13678
13841
|
config;
|
|
13679
13842
|
constructor(params) {
|
|
13680
13843
|
super(params);
|
|
13681
|
-
this.config = this.fileContent !== void 0 ?
|
|
13844
|
+
this.config = this.fileContent !== void 0 ? parseSharedConfig({
|
|
13845
|
+
format: "yaml",
|
|
13846
|
+
fileContent: this.fileContent
|
|
13847
|
+
}) : {};
|
|
13682
13848
|
}
|
|
13683
13849
|
getConfig() {
|
|
13684
13850
|
return this.config;
|
|
@@ -13687,9 +13853,17 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13687
13853
|
return true;
|
|
13688
13854
|
}
|
|
13689
13855
|
setFileContent(fileContent) {
|
|
13690
|
-
const merged = mergeHermesMcpServers(
|
|
13856
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13857
|
+
format: "yaml",
|
|
13858
|
+
fileContent
|
|
13859
|
+
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
13691
13860
|
this.config = merged;
|
|
13692
|
-
super.setFileContent(
|
|
13861
|
+
super.setFileContent(applySharedConfigPatch({
|
|
13862
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13863
|
+
feature: "mcp",
|
|
13864
|
+
existingContent: fileContent,
|
|
13865
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13866
|
+
}));
|
|
13693
13867
|
}
|
|
13694
13868
|
isDeletable() {
|
|
13695
13869
|
return false;
|
|
@@ -13716,12 +13890,21 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13716
13890
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13717
13891
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
13718
13892
|
const paths = this.getSettablePaths({ global });
|
|
13719
|
-
const
|
|
13893
|
+
const fileContent = await readOrInitializeFileContent(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath), "");
|
|
13894
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13895
|
+
format: "yaml",
|
|
13896
|
+
fileContent
|
|
13897
|
+
}), convertToHermesFormat(rulesyncMcp.getMcpServers()));
|
|
13720
13898
|
return new HermesagentMcp({
|
|
13721
13899
|
outputRoot,
|
|
13722
13900
|
relativeDirPath: paths.relativeDirPath,
|
|
13723
13901
|
relativeFilePath: paths.relativeFilePath,
|
|
13724
|
-
fileContent:
|
|
13902
|
+
fileContent: applySharedConfigPatch({
|
|
13903
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13904
|
+
feature: "mcp",
|
|
13905
|
+
existingContent: fileContent,
|
|
13906
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13907
|
+
}),
|
|
13725
13908
|
validate,
|
|
13726
13909
|
global
|
|
13727
13910
|
});
|
|
@@ -14886,28 +15069,6 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
14886
15069
|
}
|
|
14887
15070
|
};
|
|
14888
15071
|
//#endregion
|
|
14889
|
-
//#region src/features/shared/takt-config.ts
|
|
14890
|
-
/**
|
|
14891
|
-
* Parse a Takt `config.yaml` into a plain object, treating an empty file as `{}`.
|
|
14892
|
-
*
|
|
14893
|
-
* Shared by the Takt adapters that read-modify-write the same `config.yaml`
|
|
14894
|
-
* (mcp, permissions, ...). Uses `isPlainObject` (not `isRecord`) so class
|
|
14895
|
-
* instances are rejected for prototype-pollution hardening; a YAML mapping
|
|
14896
|
-
* always parses to a plain object.
|
|
14897
|
-
*/
|
|
14898
|
-
function parseTaktConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
14899
|
-
const configPath = join(relativeDirPath, relativeFilePath);
|
|
14900
|
-
let parsed;
|
|
14901
|
-
try {
|
|
14902
|
-
parsed = fileContent.trim() === "" ? {} : load(fileContent);
|
|
14903
|
-
} catch (error) {
|
|
14904
|
-
throw new Error(`Failed to parse Takt config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
14905
|
-
}
|
|
14906
|
-
if (parsed === void 0 || parsed === null) return {};
|
|
14907
|
-
if (!isPlainObject(parsed)) throw new Error(`Failed to parse Takt config at ${configPath}: expected a YAML mapping`);
|
|
14908
|
-
return parsed;
|
|
14909
|
-
}
|
|
14910
|
-
//#endregion
|
|
14911
15072
|
//#region src/features/mcp/takt-mcp.ts
|
|
14912
15073
|
/**
|
|
14913
15074
|
* MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -14973,17 +15134,20 @@ var TaktMcp = class TaktMcp extends ToolMcp {
|
|
|
14973
15134
|
}
|
|
14974
15135
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
14975
15136
|
const paths = TaktMcp.getSettablePaths({ global });
|
|
14976
|
-
const
|
|
15137
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15138
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
14977
15139
|
const allowlist = deriveTransportAllowlist(rulesyncMcp.getMcpServers());
|
|
14978
|
-
const merged = {
|
|
14979
|
-
...config,
|
|
14980
|
-
[TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist
|
|
14981
|
-
};
|
|
14982
15140
|
return new TaktMcp({
|
|
14983
15141
|
outputRoot,
|
|
14984
15142
|
relativeDirPath: paths.relativeDirPath,
|
|
14985
15143
|
relativeFilePath: paths.relativeFilePath,
|
|
14986
|
-
fileContent:
|
|
15144
|
+
fileContent: applySharedConfigPatch({
|
|
15145
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
15146
|
+
feature: "mcp",
|
|
15147
|
+
existingContent,
|
|
15148
|
+
patch: { [TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist },
|
|
15149
|
+
filePath
|
|
15150
|
+
}),
|
|
14987
15151
|
validate,
|
|
14988
15152
|
global
|
|
14989
15153
|
});
|
|
@@ -15428,7 +15592,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
15428
15592
|
["augmentcode", {
|
|
15429
15593
|
class: AugmentcodeMcp,
|
|
15430
15594
|
meta: {
|
|
15431
|
-
supportsProject:
|
|
15595
|
+
supportsProject: true,
|
|
15432
15596
|
supportsGlobal: true,
|
|
15433
15597
|
supportsEnabledTools: false,
|
|
15434
15598
|
supportsDisabledTools: false
|
|
@@ -16010,15 +16174,97 @@ const JuniePermissionsOverrideSchema = z.looseObject({
|
|
|
16010
16174
|
defaultBehavior: z.optional(z.string())
|
|
16011
16175
|
});
|
|
16012
16176
|
/**
|
|
16177
|
+
* Tool-scoped override block for Takt. Takt's `config.yaml` carries two
|
|
16178
|
+
* permission surfaces the canonical coarse-mode mapping can't express:
|
|
16179
|
+
* `step_permission_overrides` — a per-workflow-step map (`<step>` →
|
|
16180
|
+
* `readonly`/`edit`/`full`) that lives inside the active provider profile
|
|
16181
|
+
* alongside `default_permission_mode` and layers on top of it at that step; and
|
|
16182
|
+
* `provider_options` — a top-level, per-provider table of sandbox/network knobs
|
|
16183
|
+
* orthogonal to the permission mode (e.g. `codex.network_access`,
|
|
16184
|
+
* `claude.sandbox.allow_unsandboxed_commands`, `opencode.allowed_tools`). Fields
|
|
16185
|
+
* placed here are merged into `config.yaml` and emitted only for Takt, while the
|
|
16186
|
+
* shared `permission` block continues to drive `default_permission_mode`. Kept
|
|
16187
|
+
* `looseObject` (verbatim passthrough); Takt validates its own value sets (e.g.
|
|
16188
|
+
* `provider_options.<p>.base_url` must be loopback). Both project and global
|
|
16189
|
+
* scope are supported.
|
|
16190
|
+
*
|
|
16191
|
+
* Note: Takt's config loader hard-rejects unknown top-level keys, so only keys
|
|
16192
|
+
* Takt actually recognizes belong here. `required_permission_mode` is NOT one —
|
|
16193
|
+
* it is a per-step field of the workflow YAML (not `config.yaml`), so it is out
|
|
16194
|
+
* of scope for this override.
|
|
16195
|
+
*
|
|
16196
|
+
* @example
|
|
16197
|
+
* { "step_permission_overrides": { "ai_review": "readonly" }, "provider_options": { "codex": { "network_access": true } } }
|
|
16198
|
+
*/
|
|
16199
|
+
const TaktPermissionsOverrideSchema = z.looseObject({
|
|
16200
|
+
step_permission_overrides: z.optional(z.record(z.string(), z.string())),
|
|
16201
|
+
provider_options: z.optional(z.looseObject({}))
|
|
16202
|
+
});
|
|
16203
|
+
/**
|
|
16204
|
+
* Tool-scoped override block for Amp. Amp's `amp.permissions` array and sibling
|
|
16205
|
+
* settings carry shapes the canonical per-command allow/ask/deny model can't
|
|
16206
|
+
* express, so they are authored here and merged into the shared Amp settings
|
|
16207
|
+
* file, while the shared `permission` block continues to drive the canonical
|
|
16208
|
+
* `amp.permissions` (allow/ask/reject) + `amp.tools.disable` entries:
|
|
16209
|
+
* - `permissions` — extra `amp.permissions` entries with non-`cmd` matchers
|
|
16210
|
+
* (`path`/`url`/`query`/…), regex/array match values, `context`
|
|
16211
|
+
* (`thread`/`subagent`), `delegate` (+`to`), or `reject` (+`message`). These
|
|
16212
|
+
* are appended AFTER the canonical-generated entries (Amp is first-match-wins,
|
|
16213
|
+
* so generated allow/ask/reject rules take precedence; authored entries act as
|
|
16214
|
+
* later fallbacks), preserving author order.
|
|
16215
|
+
* - `mcpPermissions` — Amp's `amp.mcpPermissions` array (`{ matches, action }`).
|
|
16216
|
+
* - `guardedFiles` — `amp.guardedFiles.allowlist` (globs allowed without
|
|
16217
|
+
* confirmation).
|
|
16218
|
+
* - `dangerouslyAllowAll` — `amp.dangerouslyAllowAll` (disable all confirmation).
|
|
16219
|
+
* Kept `looseObject` (verbatim passthrough). Both project and global scope are
|
|
16220
|
+
* supported.
|
|
16221
|
+
*
|
|
16222
|
+
* @example
|
|
16223
|
+
* { "dangerouslyAllowAll": false, "guardedFiles": { "allowlist": ["docs/**"] },
|
|
16224
|
+
* "permissions": [{ "tool": "Bash", "action": "delegate", "to": "approve.sh" }] }
|
|
16225
|
+
*/
|
|
16226
|
+
const AmpPermissionsOverrideSchema = z.looseObject({
|
|
16227
|
+
permissions: z.optional(z.array(z.looseObject({
|
|
16228
|
+
tool: z.string(),
|
|
16229
|
+
action: z.string()
|
|
16230
|
+
}))),
|
|
16231
|
+
mcpPermissions: z.optional(z.array(z.looseObject({}))),
|
|
16232
|
+
guardedFiles: z.optional(z.looseObject({ allowlist: z.optional(z.array(z.string())) })),
|
|
16233
|
+
dangerouslyAllowAll: z.optional(z.boolean())
|
|
16234
|
+
});
|
|
16235
|
+
/**
|
|
16236
|
+
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
16237
|
+
* `settings.json` carries two global autonomy/sandbox knobs outside the
|
|
16238
|
+
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
16239
|
+
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
16240
|
+
* `always-proceed` / `strict`) and `enableTerminalSandbox` (a boolean confining
|
|
16241
|
+
* agent-run commands to OS containment). Antigravity applies the allow/deny
|
|
16242
|
+
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
16243
|
+
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
16244
|
+
* Fields placed here are merged onto the top level of
|
|
16245
|
+
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
16246
|
+
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
16247
|
+
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
16248
|
+
* Verified against https://antigravity.google/docs/cli/reference and
|
|
16249
|
+
* https://antigravity.google/docs/cli/sandbox.
|
|
16250
|
+
*
|
|
16251
|
+
* @example
|
|
16252
|
+
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
16253
|
+
*/
|
|
16254
|
+
const AntigravityCliPermissionsOverrideSchema = z.looseObject({
|
|
16255
|
+
toolPermission: z.optional(z.string()),
|
|
16256
|
+
enableTerminalSandbox: z.optional(z.boolean())
|
|
16257
|
+
});
|
|
16258
|
+
/**
|
|
16013
16259
|
* Permissions configuration.
|
|
16014
16260
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
16015
16261
|
* Values are pattern-to-action mappings for that tool category.
|
|
16016
16262
|
*
|
|
16017
16263
|
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
16018
|
-
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie
|
|
16019
|
-
* overrides consumed only by their
|
|
16020
|
-
* `*PermissionsOverrideSchema`); every
|
|
16021
|
-
* block and ignores them.
|
|
16264
|
+
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
|
|
16265
|
+
* `antigravity-cli` keys are tool-scoped overrides consumed only by their
|
|
16266
|
+
* respective translator (see the matching `*PermissionsOverrideSchema`); every
|
|
16267
|
+
* other tool reads the shared `permission` block and ignores them.
|
|
16022
16268
|
*
|
|
16023
16269
|
* @example
|
|
16024
16270
|
* {
|
|
@@ -16039,7 +16285,10 @@ const PermissionsConfigSchema = z.looseObject({
|
|
|
16039
16285
|
reasonix: z.optional(ReasonixPermissionsOverrideSchema),
|
|
16040
16286
|
factorydroid: z.optional(FactorydroidPermissionsOverrideSchema),
|
|
16041
16287
|
warp: z.optional(WarpPermissionsOverrideSchema),
|
|
16042
|
-
junie: z.optional(JuniePermissionsOverrideSchema)
|
|
16288
|
+
junie: z.optional(JuniePermissionsOverrideSchema),
|
|
16289
|
+
takt: z.optional(TaktPermissionsOverrideSchema),
|
|
16290
|
+
amp: z.optional(AmpPermissionsOverrideSchema),
|
|
16291
|
+
"antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema)
|
|
16043
16292
|
});
|
|
16044
16293
|
/**
|
|
16045
16294
|
* Full permissions file schema including optional $schema field.
|
|
@@ -16151,6 +16400,39 @@ const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
|
16151
16400
|
* Reference: https://ampcode.com/manual ("amp.permissions").
|
|
16152
16401
|
*/
|
|
16153
16402
|
const AMP_PERMISSIONS_KEY = "amp.permissions";
|
|
16403
|
+
/**
|
|
16404
|
+
* The `amp.guardedFiles.allowlist` array (file globs allowed without
|
|
16405
|
+
* confirmation), `amp.dangerouslyAllowAll` boolean (disable all confirmation),
|
|
16406
|
+
* and `amp.mcpPermissions` array — sibling settings authored through the `amp`
|
|
16407
|
+
* permissions override. Reference: https://ampcode.com/manual.
|
|
16408
|
+
*/
|
|
16409
|
+
const AMP_GUARDED_FILES_ALLOWLIST_KEY = "amp.guardedFiles.allowlist";
|
|
16410
|
+
const AMP_DANGEROUSLY_ALLOW_ALL_KEY = "amp.dangerouslyAllowAll";
|
|
16411
|
+
const AMP_MCP_PERMISSIONS_KEY = "amp.mcpPermissions";
|
|
16412
|
+
/**
|
|
16413
|
+
* The read-only `cmd` string of an entry's `matches`, or `undefined` when the
|
|
16414
|
+
* matcher is absent or uses a non-`cmd` key / non-string value.
|
|
16415
|
+
*/
|
|
16416
|
+
function ampMatchesCmd(entry) {
|
|
16417
|
+
const cmd = entry.matches?.cmd;
|
|
16418
|
+
return typeof cmd === "string" ? cmd : void 0;
|
|
16419
|
+
}
|
|
16420
|
+
/**
|
|
16421
|
+
* Whether an `amp.permissions` entry is fully expressible in the canonical
|
|
16422
|
+
* per-command allow/ask/deny model: a plain `allow`/`ask`/`reject` action with
|
|
16423
|
+
* no `context`/`message`/`to` and a matcher that is either absent or exactly a
|
|
16424
|
+
* single string `cmd`. Everything else (non-`cmd` matchers, regex/array match
|
|
16425
|
+
* values, `delegate`, `reject`+`message`, `context`) is routed to the `amp`
|
|
16426
|
+
* override verbatim instead of being flattened with loss or dropped.
|
|
16427
|
+
*/
|
|
16428
|
+
function isCanonicalAmpEntry(entry) {
|
|
16429
|
+
if (entry.action === "delegate") return false;
|
|
16430
|
+
if (entry.context !== void 0 || entry.message !== void 0 || entry.to !== void 0) return false;
|
|
16431
|
+
const matches = entry.matches;
|
|
16432
|
+
if (matches === void 0) return true;
|
|
16433
|
+
const keys = Object.keys(matches);
|
|
16434
|
+
return keys.length === 0 || keys.length === 1 && typeof matches.cmd === "string";
|
|
16435
|
+
}
|
|
16154
16436
|
function parseAmpSettings(fileContent) {
|
|
16155
16437
|
const errors = [];
|
|
16156
16438
|
const parsed = parse(fileContent || "{}", errors, { allowTrailingComma: true });
|
|
@@ -16166,10 +16448,20 @@ function toDisableList(value) {
|
|
|
16166
16448
|
return value.filter((entry) => typeof entry === "string");
|
|
16167
16449
|
}
|
|
16168
16450
|
/**
|
|
16451
|
+
* Read `amp.guardedFiles.allowlist` (an array of file glob strings) from parsed
|
|
16452
|
+
* settings, returning `undefined` when the key is absent or not an array so the
|
|
16453
|
+
* override omits it.
|
|
16454
|
+
*/
|
|
16455
|
+
function extractAmpGuardedAllowlist(value) {
|
|
16456
|
+
if (!Array.isArray(value)) return void 0;
|
|
16457
|
+
return value.filter((entry) => typeof entry === "string");
|
|
16458
|
+
}
|
|
16459
|
+
/**
|
|
16169
16460
|
* Read an `amp.permissions` array from untrusted parsed settings. Only entries
|
|
16170
|
-
* whose
|
|
16171
|
-
*
|
|
16172
|
-
*
|
|
16461
|
+
* whose `tool`/`action` rulesync understands are retained; every other key —
|
|
16462
|
+
* including the full `matches` object (so non-`cmd` matchers, regex/array
|
|
16463
|
+
* values, `context`, `to`, `message`) — is kept verbatim so it survives a
|
|
16464
|
+
* round-trip.
|
|
16173
16465
|
*/
|
|
16174
16466
|
function toPermissionsList(value) {
|
|
16175
16467
|
if (!Array.isArray(value)) return [];
|
|
@@ -16179,14 +16471,11 @@ function toPermissionsList(value) {
|
|
|
16179
16471
|
const { tool, action } = raw;
|
|
16180
16472
|
if (typeof tool !== "string" || typeof action !== "string") continue;
|
|
16181
16473
|
if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
|
|
16182
|
-
const matches = raw.matches;
|
|
16183
|
-
let normalizedMatches;
|
|
16184
|
-
if (isPlainObject(matches) && typeof matches.cmd === "string") normalizedMatches = { cmd: matches.cmd };
|
|
16185
16474
|
entries.push({
|
|
16186
16475
|
...raw,
|
|
16187
16476
|
tool,
|
|
16188
16477
|
action,
|
|
16189
|
-
...
|
|
16478
|
+
...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
|
|
16190
16479
|
});
|
|
16191
16480
|
}
|
|
16192
16481
|
return entries;
|
|
@@ -16211,6 +16500,13 @@ function toPermissionsList(value) {
|
|
|
16211
16500
|
* The settings file is shared with the MCP feature (`amp.mcpServers`), so reads
|
|
16212
16501
|
* and writes merge into the existing JSON rather than overwriting it, and the
|
|
16213
16502
|
* file is never deleted.
|
|
16503
|
+
*
|
|
16504
|
+
* Amp shapes with no canonical category are authored and round-tripped through
|
|
16505
|
+
* the `amp` permissions override (see `AmpPermissionsOverrideSchema`): extra
|
|
16506
|
+
* `amp.permissions` entries with non-`cmd` matchers / `context` / `delegate` /
|
|
16507
|
+
* `reject`+`message` (appended after the generated canonical entries), plus the
|
|
16508
|
+
* sibling `amp.mcpPermissions`, `amp.guardedFiles.allowlist`, and
|
|
16509
|
+
* `amp.dangerouslyAllowAll` settings.
|
|
16214
16510
|
*/
|
|
16215
16511
|
var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
16216
16512
|
constructor(params) {
|
|
@@ -16280,15 +16576,25 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16280
16576
|
const jsonDir = join(outputRoot, basePaths.relativeDirPath);
|
|
16281
16577
|
const { fileContent, relativeFilePath } = await this.resolveSettingsFile(jsonDir);
|
|
16282
16578
|
const json = fileContent ? parseAmpSettings(fileContent) : {};
|
|
16283
|
-
const
|
|
16284
|
-
const
|
|
16579
|
+
const config = rulesyncPermissions.getJson();
|
|
16580
|
+
const { disable, permissions } = convertRulesyncToAmp(config);
|
|
16581
|
+
const override = config.amp;
|
|
16582
|
+
const authoredExtras = override?.permissions ? toPermissionsList(override.permissions) : [];
|
|
16583
|
+
const preservedDelegates = override?.permissions ? [] : toPermissionsList(json[AMP_PERMISSIONS_KEY]).filter((entry) => entry.action === "delegate");
|
|
16285
16584
|
const newJson = {
|
|
16286
16585
|
...json,
|
|
16287
16586
|
[AMP_TOOLS_DISABLE_KEY]: disable
|
|
16288
16587
|
};
|
|
16289
|
-
const mergedPermissions = [
|
|
16588
|
+
const mergedPermissions = mergeAmpPermissions([
|
|
16589
|
+
...permissions,
|
|
16590
|
+
...authoredExtras,
|
|
16591
|
+
...preservedDelegates
|
|
16592
|
+
]);
|
|
16290
16593
|
if (mergedPermissions.length > 0) newJson[AMP_PERMISSIONS_KEY] = mergedPermissions;
|
|
16291
16594
|
else delete newJson[AMP_PERMISSIONS_KEY];
|
|
16595
|
+
if (override?.guardedFiles?.allowlist !== void 0) newJson[AMP_GUARDED_FILES_ALLOWLIST_KEY] = override.guardedFiles.allowlist;
|
|
16596
|
+
if (override?.dangerouslyAllowAll !== void 0) newJson[AMP_DANGEROUSLY_ALLOW_ALL_KEY] = override.dangerouslyAllowAll;
|
|
16597
|
+
if (override?.mcpPermissions !== void 0) newJson[AMP_MCP_PERMISSIONS_KEY] = override.mcpPermissions;
|
|
16292
16598
|
return new AmpPermissions({
|
|
16293
16599
|
outputRoot,
|
|
16294
16600
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -16299,11 +16605,22 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16299
16605
|
}
|
|
16300
16606
|
toRulesyncPermissions() {
|
|
16301
16607
|
const json = parseAmpSettings(this.getFileContent());
|
|
16608
|
+
const allPermissions = toPermissionsList(json[AMP_PERMISSIONS_KEY]);
|
|
16609
|
+
const canonicalEntries = allPermissions.filter(isCanonicalAmpEntry);
|
|
16610
|
+
const overrideEntries = allPermissions.filter((entry) => !isCanonicalAmpEntry(entry));
|
|
16302
16611
|
const config = convertAmpToRulesync({
|
|
16303
16612
|
disable: toDisableList(json[AMP_TOOLS_DISABLE_KEY]),
|
|
16304
|
-
permissions:
|
|
16305
|
-
});
|
|
16306
|
-
|
|
16613
|
+
permissions: canonicalEntries
|
|
16614
|
+
});
|
|
16615
|
+
const ampOverride = {};
|
|
16616
|
+
if (overrideEntries.length > 0) ampOverride.permissions = overrideEntries;
|
|
16617
|
+
if (Array.isArray(json[AMP_MCP_PERMISSIONS_KEY])) ampOverride.mcpPermissions = json[AMP_MCP_PERMISSIONS_KEY];
|
|
16618
|
+
const allowlist = extractAmpGuardedAllowlist(json[AMP_GUARDED_FILES_ALLOWLIST_KEY]);
|
|
16619
|
+
if (allowlist !== void 0) ampOverride.guardedFiles = { allowlist };
|
|
16620
|
+
if (typeof json[AMP_DANGEROUSLY_ALLOW_ALL_KEY] === "boolean") ampOverride.dangerouslyAllowAll = json[AMP_DANGEROUSLY_ALLOW_ALL_KEY];
|
|
16621
|
+
const result = { ...config };
|
|
16622
|
+
if (Object.keys(ampOverride).length > 0) result.amp = ampOverride;
|
|
16623
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16307
16624
|
}
|
|
16308
16625
|
validate() {
|
|
16309
16626
|
try {
|
|
@@ -16350,6 +16667,37 @@ const ACTION_PRIORITY = {
|
|
|
16350
16667
|
allow: 2
|
|
16351
16668
|
};
|
|
16352
16669
|
/**
|
|
16670
|
+
* Fail-closed action priority for merging the full `amp.permissions` list
|
|
16671
|
+
* (generated + authored/preserved). `reject` leads so no `allow` can shadow it
|
|
16672
|
+
* under first-match-wins; `delegate` (which defers to an external approver, so
|
|
16673
|
+
* it never auto-allows) trails as the final fallback.
|
|
16674
|
+
*/
|
|
16675
|
+
const MERGE_ACTION_PRIORITY = {
|
|
16676
|
+
reject: 0,
|
|
16677
|
+
ask: 1,
|
|
16678
|
+
allow: 2,
|
|
16679
|
+
delegate: 3
|
|
16680
|
+
};
|
|
16681
|
+
/**
|
|
16682
|
+
* Stable fail-closed merge of generated and authored `amp.permissions` entries:
|
|
16683
|
+
* order by {@link MERGE_ACTION_PRIORITY} only, preserving each source's relative
|
|
16684
|
+
* order within an action class. Guarantees an authored `reject` cannot be
|
|
16685
|
+
* shadowed by a generated catch-all `allow`.
|
|
16686
|
+
*/
|
|
16687
|
+
function mergeAmpPermissions(entries) {
|
|
16688
|
+
const decorated = entries.map((entry, index) => ({
|
|
16689
|
+
entry,
|
|
16690
|
+
index
|
|
16691
|
+
}));
|
|
16692
|
+
decorated.sort((a, b) => {
|
|
16693
|
+
const ap = MERGE_ACTION_PRIORITY[a.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16694
|
+
const bp = MERGE_ACTION_PRIORITY[b.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16695
|
+
if (ap !== bp) return ap - bp;
|
|
16696
|
+
return a.index - b.index;
|
|
16697
|
+
});
|
|
16698
|
+
return decorated.map((d) => d.entry);
|
|
16699
|
+
}
|
|
16700
|
+
/**
|
|
16353
16701
|
* Convert a rulesync permissions config into Amp's two permission surfaces.
|
|
16354
16702
|
*
|
|
16355
16703
|
* For each `(category, pattern, action)` — where the category name IS the Amp
|
|
@@ -16414,14 +16762,14 @@ function sortAmpPermissions(entries) {
|
|
|
16414
16762
|
const ap = ACTION_PRIORITY[a.entry.action] ?? 0;
|
|
16415
16763
|
const bp = ACTION_PRIORITY[b.entry.action] ?? 0;
|
|
16416
16764
|
if (ap !== bp) return ap - bp;
|
|
16417
|
-
const aHasCmd = a.entry
|
|
16418
|
-
const bHasCmd = b.entry
|
|
16765
|
+
const aHasCmd = ampMatchesCmd(a.entry) !== void 0 ? 0 : 1;
|
|
16766
|
+
const bHasCmd = ampMatchesCmd(b.entry) !== void 0 ? 0 : 1;
|
|
16419
16767
|
if (aHasCmd !== bHasCmd) return aHasCmd - bHasCmd;
|
|
16420
16768
|
const at = a.entry.tool;
|
|
16421
16769
|
const bt = b.entry.tool;
|
|
16422
16770
|
if (at !== bt) return at < bt ? -1 : 1;
|
|
16423
|
-
const ac = a.entry
|
|
16424
|
-
const bc = b.entry
|
|
16771
|
+
const ac = ampMatchesCmd(a.entry) ?? "";
|
|
16772
|
+
const bc = ampMatchesCmd(b.entry) ?? "";
|
|
16425
16773
|
if (ac !== bc) return ac < bc ? -1 : 1;
|
|
16426
16774
|
return a.index - b.index;
|
|
16427
16775
|
});
|
|
@@ -16460,7 +16808,7 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
16460
16808
|
for (const tool of disable) assign(tool, "*", "deny");
|
|
16461
16809
|
for (const entry of permissions) {
|
|
16462
16810
|
if (entry.action === "delegate") continue;
|
|
16463
|
-
const pattern = entry
|
|
16811
|
+
const pattern = ampMatchesCmd(entry) ?? "*";
|
|
16464
16812
|
const action = entry.action === "reject" ? "deny" : entry.action === "ask" ? "ask" : "allow";
|
|
16465
16813
|
assign(entry.tool, pattern, action);
|
|
16466
16814
|
}
|
|
@@ -16542,6 +16890,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
16542
16890
|
* Permissions are written to the global `~/.gemini/antigravity-cli/settings.json`
|
|
16543
16891
|
* file (global scope only). The file holds other CLI settings besides
|
|
16544
16892
|
* permissions, so it is never deleted.
|
|
16893
|
+
*
|
|
16894
|
+
* Two CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
16895
|
+
* `toolPermission` (the global autonomy preset) and `enableTerminalSandbox` — are
|
|
16896
|
+
* authored and round-tripped through the `antigravity-cli` permissions override
|
|
16897
|
+
* (see `AntigravityCliPermissionsOverrideSchema`).
|
|
16545
16898
|
*/
|
|
16546
16899
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
16547
16900
|
constructor(params) {
|
|
@@ -16602,6 +16955,9 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16602
16955
|
...settings,
|
|
16603
16956
|
permissions: mergedPermissions
|
|
16604
16957
|
};
|
|
16958
|
+
const override = config["antigravity-cli"];
|
|
16959
|
+
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
16960
|
+
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
16605
16961
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
16606
16962
|
return new AntigravityCliPermissions({
|
|
16607
16963
|
outputRoot,
|
|
@@ -16625,7 +16981,12 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16625
16981
|
ask: permissions.ask ?? [],
|
|
16626
16982
|
deny: permissions.deny ?? []
|
|
16627
16983
|
});
|
|
16628
|
-
|
|
16984
|
+
const override = {};
|
|
16985
|
+
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
16986
|
+
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
16987
|
+
const result = { ...config };
|
|
16988
|
+
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
16989
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16629
16990
|
}
|
|
16630
16991
|
validate() {
|
|
16631
16992
|
return {
|
|
@@ -19311,14 +19672,21 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
19311
19672
|
return true;
|
|
19312
19673
|
}
|
|
19313
19674
|
setFileContent(fileContent) {
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19675
|
+
this.fileContent = applySharedConfigPatch({
|
|
19676
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
19677
|
+
feature: "permissions",
|
|
19678
|
+
existingContent: fileContent,
|
|
19679
|
+
patch: parseSharedConfig({
|
|
19680
|
+
format: "yaml",
|
|
19681
|
+
fileContent: this.fileContent
|
|
19682
|
+
})
|
|
19683
|
+
});
|
|
19319
19684
|
}
|
|
19320
19685
|
toRulesyncPermissions() {
|
|
19321
|
-
const config =
|
|
19686
|
+
const config = parseSharedConfig({
|
|
19687
|
+
format: "yaml",
|
|
19688
|
+
fileContent: this.getFileContent()
|
|
19689
|
+
});
|
|
19322
19690
|
const permissions = config.permissions && typeof config.permissions === "object" ? config.permissions.rulesync : {};
|
|
19323
19691
|
return new RulesyncPermissions({
|
|
19324
19692
|
relativeDirPath: "",
|
|
@@ -19339,11 +19707,17 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
19339
19707
|
enabled: true,
|
|
19340
19708
|
domains: webfetchDeny
|
|
19341
19709
|
} };
|
|
19342
|
-
if (permissions.hermes && typeof permissions.hermes === "object") config =
|
|
19710
|
+
if (permissions.hermes && typeof permissions.hermes === "object") config = mergeSharedConfigDeep({
|
|
19711
|
+
base: config,
|
|
19712
|
+
patch: permissions.hermes
|
|
19713
|
+
});
|
|
19343
19714
|
config.permissions = { rulesync: permissions };
|
|
19344
19715
|
return new HermesagentPermissions({
|
|
19345
19716
|
outputRoot,
|
|
19346
|
-
fileContent:
|
|
19717
|
+
fileContent: stringifySharedConfig({
|
|
19718
|
+
format: "yaml",
|
|
19719
|
+
document: config
|
|
19720
|
+
})
|
|
19347
19721
|
});
|
|
19348
19722
|
}
|
|
19349
19723
|
};
|
|
@@ -20899,6 +21273,8 @@ function isPermissionAction(value) {
|
|
|
20899
21273
|
const TAKT_PROVIDER_KEY = "provider";
|
|
20900
21274
|
const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
|
|
20901
21275
|
const TAKT_DEFAULT_PERMISSION_MODE_KEY = "default_permission_mode";
|
|
21276
|
+
const TAKT_STEP_PERMISSION_OVERRIDES_KEY = "step_permission_overrides";
|
|
21277
|
+
const TAKT_PROVIDER_OPTIONS_KEY = "provider_options";
|
|
20902
21278
|
const TAKT_DEFAULT_PROVIDER = "claude";
|
|
20903
21279
|
const CATCH_ALL_PATTERN = "*";
|
|
20904
21280
|
/**
|
|
@@ -20928,10 +21304,16 @@ const CATCH_ALL_PATTERN = "*";
|
|
|
20928
21304
|
* `bash: { "*": "deny" }`. These round-trip the generate mapping.
|
|
20929
21305
|
*
|
|
20930
21306
|
* Both project and global scope are supported. The shared config is merged in
|
|
20931
|
-
* place:
|
|
20932
|
-
*
|
|
20933
|
-
*
|
|
20934
|
-
*
|
|
21307
|
+
* place: `provider_profiles.<provider>.default_permission_mode` is set from the
|
|
21308
|
+
* derived mode; every other provider profile and all other top-level keys are
|
|
21309
|
+
* preserved. The file is never deleted.
|
|
21310
|
+
*
|
|
21311
|
+
* Two Takt-specific surfaces with no canonical category round-trip through the
|
|
21312
|
+
* `takt` override (see `TaktPermissionsOverrideSchema`):
|
|
21313
|
+
* `step_permission_overrides` (a per-step mode map inside the active provider
|
|
21314
|
+
* profile, layered on top of `default_permission_mode`) and `provider_options`
|
|
21315
|
+
* (a top-level per-provider sandbox/network table). Both are authored on
|
|
21316
|
+
* generate and re-extracted on import.
|
|
20935
21317
|
*/
|
|
20936
21318
|
var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
20937
21319
|
constructor(params) {
|
|
@@ -20963,37 +21345,62 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
20963
21345
|
}
|
|
20964
21346
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
20965
21347
|
const paths = TaktPermissions.getSettablePaths({ global });
|
|
20966
|
-
const
|
|
21348
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
21349
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
21350
|
+
const config = parseSharedConfig({
|
|
21351
|
+
format: "yaml",
|
|
21352
|
+
fileContent: existingContent,
|
|
21353
|
+
filePath,
|
|
21354
|
+
invalidRootPolicy: "error"
|
|
21355
|
+
});
|
|
21356
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20967
21357
|
const provider = resolveActiveProvider(config);
|
|
20968
|
-
const mode = deriveTaktPermissionMode(
|
|
20969
|
-
const
|
|
20970
|
-
const
|
|
20971
|
-
const
|
|
20972
|
-
|
|
20973
|
-
[TAKT_PROVIDER_PROFILES_KEY]: {
|
|
20974
|
-
|
|
20975
|
-
[
|
|
20976
|
-
|
|
20977
|
-
|
|
20978
|
-
}
|
|
20979
|
-
}
|
|
21358
|
+
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
21359
|
+
const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
21360
|
+
const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21361
|
+
const overrideProviderOptions = isPlainObject(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21362
|
+
const patch = {
|
|
21363
|
+
[TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
|
|
21364
|
+
[TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
|
|
21365
|
+
...stepOverrides !== void 0 && { [TAKT_STEP_PERMISSION_OVERRIDES_KEY]: stepOverrides }
|
|
21366
|
+
} },
|
|
21367
|
+
...overrideProviderOptions !== void 0 && { [TAKT_PROVIDER_OPTIONS_KEY]: overrideProviderOptions }
|
|
20980
21368
|
};
|
|
20981
21369
|
return new TaktPermissions({
|
|
20982
21370
|
outputRoot,
|
|
20983
21371
|
relativeDirPath: paths.relativeDirPath,
|
|
20984
21372
|
relativeFilePath: paths.relativeFilePath,
|
|
20985
|
-
fileContent:
|
|
21373
|
+
fileContent: applySharedConfigPatch({
|
|
21374
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
21375
|
+
feature: "permissions",
|
|
21376
|
+
existingContent,
|
|
21377
|
+
patch,
|
|
21378
|
+
filePath
|
|
21379
|
+
}),
|
|
20986
21380
|
validate: true,
|
|
20987
21381
|
global
|
|
20988
21382
|
});
|
|
20989
21383
|
}
|
|
20990
21384
|
toRulesyncPermissions() {
|
|
20991
|
-
const config =
|
|
21385
|
+
const config = parseSharedConfig({
|
|
21386
|
+
format: "yaml",
|
|
21387
|
+
fileContent: this.getFileContent(),
|
|
21388
|
+
filePath: join(this.getRelativeDirPath(), this.getRelativeFilePath()),
|
|
21389
|
+
invalidRootPolicy: "error"
|
|
21390
|
+
});
|
|
20992
21391
|
const provider = resolveActiveProvider(config);
|
|
20993
21392
|
const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
|
|
20994
|
-
const
|
|
21393
|
+
const profile = isPlainObject(profiles[provider]) ? profiles[provider] : {};
|
|
21394
|
+
const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
|
|
20995
21395
|
const rulesyncConfig = taktModeToRulesyncConfig(mode);
|
|
20996
|
-
|
|
21396
|
+
const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21397
|
+
const providerOptions = isPlainObject(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21398
|
+
const taktOverride = {};
|
|
21399
|
+
if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
|
|
21400
|
+
if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
|
|
21401
|
+
const result = { ...rulesyncConfig };
|
|
21402
|
+
if (Object.keys(taktOverride).length > 0) result.takt = taktOverride;
|
|
21403
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
20997
21404
|
}
|
|
20998
21405
|
validate() {
|
|
20999
21406
|
return {
|
|
@@ -29888,14 +30295,21 @@ def register(ctx):
|
|
|
29888
30295
|
`;
|
|
29889
30296
|
}
|
|
29890
30297
|
function getEnabledPluginConfigContent(currentContent) {
|
|
29891
|
-
const config =
|
|
30298
|
+
const config = parseSharedConfig({
|
|
30299
|
+
format: "yaml",
|
|
30300
|
+
fileContent: currentContent
|
|
30301
|
+
});
|
|
29892
30302
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
29893
30303
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
29894
|
-
|
|
29895
|
-
|
|
29896
|
-
|
|
29897
|
-
|
|
29898
|
-
|
|
30304
|
+
return applySharedConfigPatch({
|
|
30305
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
30306
|
+
feature: "subagents",
|
|
30307
|
+
existingContent: currentContent,
|
|
30308
|
+
patch: { plugins: {
|
|
30309
|
+
...plugins,
|
|
30310
|
+
enabled: Array.from(/* @__PURE__ */ new Set([...enabled, "rulesync-subagents"]))
|
|
30311
|
+
} }
|
|
30312
|
+
});
|
|
29899
30313
|
}
|
|
29900
30314
|
function getSubagentSpec(rulesyncSubagent) {
|
|
29901
30315
|
const json = rulesyncSubagent.getFrontmatter();
|
|
@@ -29981,6 +30395,17 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
29981
30395
|
static getSettablePaths() {
|
|
29982
30396
|
return { relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH };
|
|
29983
30397
|
}
|
|
30398
|
+
/**
|
|
30399
|
+
* Beyond the subagent spec files, generation also read-modify-writes the
|
|
30400
|
+
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
30401
|
+
* so the write must be declared for the shared-file order derivation.
|
|
30402
|
+
*/
|
|
30403
|
+
static getExtraSharedWritePaths() {
|
|
30404
|
+
return [{
|
|
30405
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
30406
|
+
relativeFilePath: basename(HERMESAGENT_CONFIG_FILE_PATH)
|
|
30407
|
+
}];
|
|
30408
|
+
}
|
|
29984
30409
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
29985
30410
|
return [join(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
29986
30411
|
}
|
|
@@ -31719,7 +32144,9 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
31719
32144
|
})),
|
|
31720
32145
|
kiro: z.optional(z.looseObject({
|
|
31721
32146
|
inclusion: z.optional(z.string()),
|
|
31722
|
-
fileMatchPattern: z.optional(z.union([z.string(), z.array(z.string())]))
|
|
32147
|
+
fileMatchPattern: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
32148
|
+
name: z.optional(z.string()),
|
|
32149
|
+
description: z.optional(z.string())
|
|
31723
32150
|
})),
|
|
31724
32151
|
takt: z.optional(z.looseObject({
|
|
31725
32152
|
name: z.optional(z.string()),
|
|
@@ -34635,7 +35062,8 @@ function toFileMatchPattern(globs) {
|
|
|
34635
35062
|
*
|
|
34636
35063
|
* Precedence:
|
|
34637
35064
|
* 1. An explicit `kiro.inclusion` block round-trips as-is (with `fileMatchPattern`
|
|
34638
|
-
* taken from the block, or derived from `globs` when omitted for `fileMatch
|
|
35065
|
+
* taken from the block, or derived from `globs` when omitted for `fileMatch`;
|
|
35066
|
+
* and with the companion `name`/`description` carried through for `auto`).
|
|
34639
35067
|
* 2. Otherwise specific (non-wildcard) globs map to `fileMatch`, scoping the rule
|
|
34640
35068
|
* to matching files instead of leaving it implicitly always-on.
|
|
34641
35069
|
* 3. Otherwise the rule stays `always` — represented by omitting the block so the
|
|
@@ -34653,6 +35081,11 @@ function deriveKiroInclusion({ kiro, globs }) {
|
|
|
34653
35081
|
fileMatchPattern
|
|
34654
35082
|
} : { inclusion: "fileMatch" };
|
|
34655
35083
|
}
|
|
35084
|
+
if (kiro.inclusion === "auto") return {
|
|
35085
|
+
inclusion: "auto",
|
|
35086
|
+
...kiro.name !== void 0 ? { name: kiro.name } : {},
|
|
35087
|
+
...kiro.description !== void 0 ? { description: kiro.description } : {}
|
|
35088
|
+
};
|
|
34656
35089
|
return { inclusion: kiro.inclusion };
|
|
34657
35090
|
}
|
|
34658
35091
|
const fileMatchPattern = toFileMatchPattern(specificGlobs);
|
|
@@ -34724,6 +35157,8 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34724
35157
|
const rawPattern = frontmatter.fileMatchPattern;
|
|
34725
35158
|
const fileMatchPattern = Array.isArray(rawPattern) ? rawPattern.filter((p) => typeof p === "string") : typeof rawPattern === "string" ? rawPattern : void 0;
|
|
34726
35159
|
const globs = inclusion === "fileMatch" ? fileMatchPattern === void 0 ? [] : Array.isArray(fileMatchPattern) ? fileMatchPattern : [fileMatchPattern] : [];
|
|
35160
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name : void 0;
|
|
35161
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
34727
35162
|
return new RulesyncRule({
|
|
34728
35163
|
outputRoot: process.cwd(),
|
|
34729
35164
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -34734,7 +35169,9 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34734
35169
|
globs,
|
|
34735
35170
|
kiro: {
|
|
34736
35171
|
inclusion,
|
|
34737
|
-
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {}
|
|
35172
|
+
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {},
|
|
35173
|
+
...inclusion === "auto" && name !== void 0 ? { name } : {},
|
|
35174
|
+
...inclusion === "auto" && description !== void 0 ? { description } : {}
|
|
34738
35175
|
}
|
|
34739
35176
|
},
|
|
34740
35177
|
body
|
|
@@ -36908,6 +37345,197 @@ function buildPermissionsStrategy(ctx) {
|
|
|
36908
37345
|
};
|
|
36909
37346
|
}
|
|
36910
37347
|
//#endregion
|
|
37348
|
+
//#region src/types/processor-registry.ts
|
|
37349
|
+
const PROCESSOR_REGISTRY = [
|
|
37350
|
+
{
|
|
37351
|
+
feature: "rules",
|
|
37352
|
+
processor: RulesProcessor,
|
|
37353
|
+
schema: RulesProcessorToolTargetSchema,
|
|
37354
|
+
factory: toolRuleFactories
|
|
37355
|
+
},
|
|
37356
|
+
{
|
|
37357
|
+
feature: "ignore",
|
|
37358
|
+
processor: IgnoreProcessor,
|
|
37359
|
+
schema: IgnoreProcessorToolTargetSchema,
|
|
37360
|
+
factory: toolIgnoreFactories
|
|
37361
|
+
},
|
|
37362
|
+
{
|
|
37363
|
+
feature: "mcp",
|
|
37364
|
+
processor: McpProcessor,
|
|
37365
|
+
schema: McpProcessorToolTargetSchema,
|
|
37366
|
+
factory: toolMcpFactories
|
|
37367
|
+
},
|
|
37368
|
+
{
|
|
37369
|
+
feature: "commands",
|
|
37370
|
+
processor: CommandsProcessor,
|
|
37371
|
+
schema: CommandsProcessorToolTargetSchema,
|
|
37372
|
+
factory: toolCommandFactories
|
|
37373
|
+
},
|
|
37374
|
+
{
|
|
37375
|
+
feature: "subagents",
|
|
37376
|
+
processor: SubagentsProcessor,
|
|
37377
|
+
schema: SubagentsProcessorToolTargetSchema,
|
|
37378
|
+
factory: toolSubagentFactories
|
|
37379
|
+
},
|
|
37380
|
+
{
|
|
37381
|
+
feature: "skills",
|
|
37382
|
+
processor: SkillsProcessor,
|
|
37383
|
+
schema: SkillsProcessorToolTargetSchema,
|
|
37384
|
+
factory: toolSkillFactories
|
|
37385
|
+
},
|
|
37386
|
+
{
|
|
37387
|
+
feature: "hooks",
|
|
37388
|
+
processor: HooksProcessor,
|
|
37389
|
+
schema: HooksProcessorToolTargetSchema,
|
|
37390
|
+
factory: toolHooksFactories
|
|
37391
|
+
},
|
|
37392
|
+
{
|
|
37393
|
+
feature: "permissions",
|
|
37394
|
+
processor: PermissionsProcessor,
|
|
37395
|
+
schema: PermissionsProcessorToolTargetSchema,
|
|
37396
|
+
factory: toolPermissionsFactories
|
|
37397
|
+
}
|
|
37398
|
+
];
|
|
37399
|
+
const getProcessorRegistryEntry = (feature) => {
|
|
37400
|
+
const entry = PROCESSOR_REGISTRY.find((e) => e.feature === feature);
|
|
37401
|
+
if (!entry) throw new Error(`No processor registered for feature: ${feature}`);
|
|
37402
|
+
return entry;
|
|
37403
|
+
};
|
|
37404
|
+
//#endregion
|
|
37405
|
+
//#region src/lib/shared-file-derive.ts
|
|
37406
|
+
/**
|
|
37407
|
+
* The single declaration of the cross-feature write order for shared
|
|
37408
|
+
* (read-modify-write) config files: when two features write the same on-disk
|
|
37409
|
+
* file, the earlier one writes first and the later one merges on top, so the
|
|
37410
|
+
* later feature's conflict policy decides what survives (e.g. `permissions`
|
|
37411
|
+
* overriding `ignore`-derived `Read(...)` denies in `.claude/settings.json`).
|
|
37412
|
+
* The generation step graph's `dependsOn` edges are derived from this list
|
|
37413
|
+
* plus the registry's `getSettablePaths` declarations — adding a tool or a
|
|
37414
|
+
* shared path never requires touching the graph by hand.
|
|
37415
|
+
*/
|
|
37416
|
+
const SHARED_WRITE_FEATURE_ORDER = [
|
|
37417
|
+
"ignore",
|
|
37418
|
+
"subagents",
|
|
37419
|
+
"mcp",
|
|
37420
|
+
"hooks",
|
|
37421
|
+
"permissions",
|
|
37422
|
+
"rules"
|
|
37423
|
+
];
|
|
37424
|
+
const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
|
|
37425
|
+
const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
|
|
37426
|
+
const sharedFileKey = (path) => {
|
|
37427
|
+
const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
37428
|
+
const file = path.relativeFilePath.replace(/\\/g, "/");
|
|
37429
|
+
return dir === "" || dir === "." ? file : `${dir}/${file}`;
|
|
37430
|
+
};
|
|
37431
|
+
const settablePathsForScope = (cls, global) => {
|
|
37432
|
+
const paths = [];
|
|
37433
|
+
let settable;
|
|
37434
|
+
try {
|
|
37435
|
+
settable = cls.getSettablePaths?.({ global });
|
|
37436
|
+
} catch {
|
|
37437
|
+
return paths;
|
|
37438
|
+
}
|
|
37439
|
+
if (settable?.relativeFilePath) paths.push({
|
|
37440
|
+
relativeDirPath: settable.relativeDirPath ?? ".",
|
|
37441
|
+
relativeFilePath: settable.relativeFilePath
|
|
37442
|
+
});
|
|
37443
|
+
if (settable?.root) {
|
|
37444
|
+
paths.push(settable.root);
|
|
37445
|
+
for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
|
|
37446
|
+
}
|
|
37447
|
+
for (const path of cls.getExtraSharedWritePaths?.({ global }) ?? []) if (path.relativeFilePath) paths.push(path);
|
|
37448
|
+
return paths;
|
|
37449
|
+
};
|
|
37450
|
+
const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
|
|
37451
|
+
/**
|
|
37452
|
+
* Derive, from the processor registry, every on-disk file that two or more
|
|
37453
|
+
* features read-modify-write. Source of truth the generation step graph's
|
|
37454
|
+
* `writesSharedFile` declarations must match.
|
|
37455
|
+
*/
|
|
37456
|
+
const deriveSharedFileWriters = () => {
|
|
37457
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
37458
|
+
const pathByKey = /* @__PURE__ */ new Map();
|
|
37459
|
+
for (const entry of PROCESSOR_REGISTRY) {
|
|
37460
|
+
if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
|
|
37461
|
+
const factories = entry.factory;
|
|
37462
|
+
for (const [tool, factory] of factories) {
|
|
37463
|
+
if (TARGETS_NOT_DERIVED.has(tool)) continue;
|
|
37464
|
+
for (const path of collectFactoryPaths(factory)) {
|
|
37465
|
+
const key = sharedFileKey(path);
|
|
37466
|
+
if (!pathByKey.has(key)) pathByKey.set(key, path);
|
|
37467
|
+
let features = byKey.get(key);
|
|
37468
|
+
if (!features) {
|
|
37469
|
+
features = /* @__PURE__ */ new Map();
|
|
37470
|
+
byKey.set(key, features);
|
|
37471
|
+
}
|
|
37472
|
+
let tools = features.get(entry.feature);
|
|
37473
|
+
if (!tools) {
|
|
37474
|
+
tools = /* @__PURE__ */ new Set();
|
|
37475
|
+
features.set(entry.feature, tools);
|
|
37476
|
+
}
|
|
37477
|
+
tools.add(tool);
|
|
37478
|
+
}
|
|
37479
|
+
}
|
|
37480
|
+
}
|
|
37481
|
+
const writers = [];
|
|
37482
|
+
for (const [key, features] of byKey) {
|
|
37483
|
+
if (features.size < 2) continue;
|
|
37484
|
+
const path = pathByKey.get(key);
|
|
37485
|
+
const toolsByFeature = /* @__PURE__ */ new Map();
|
|
37486
|
+
for (const [feature, tools] of features) toolsByFeature.set(feature, [...tools].toSorted());
|
|
37487
|
+
writers.push({
|
|
37488
|
+
key,
|
|
37489
|
+
relativeDirPath: path.relativeDirPath,
|
|
37490
|
+
relativeFilePath: path.relativeFilePath,
|
|
37491
|
+
features: [...features.keys()].toSorted(),
|
|
37492
|
+
toolsByFeature
|
|
37493
|
+
});
|
|
37494
|
+
}
|
|
37495
|
+
return writers.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
37496
|
+
};
|
|
37497
|
+
/**
|
|
37498
|
+
* Derive, per shared-write feature, the shared files it writes and the
|
|
37499
|
+
* `dependsOn` edges that fix a safe write order: for every shared file, each
|
|
37500
|
+
* writer depends on all writers that precede it in
|
|
37501
|
+
* {@link SHARED_WRITE_FEATURE_ORDER}. This is the source the generation step
|
|
37502
|
+
* graph consumes, so registry changes (a new tool, a new settable path)
|
|
37503
|
+
* propagate into the execution order without a hand-maintained declaration.
|
|
37504
|
+
*
|
|
37505
|
+
* @throws Error if a feature writes a shared file but has no position in
|
|
37506
|
+
* `SHARED_WRITE_FEATURE_ORDER` — ordering it is a deliberate decision about
|
|
37507
|
+
* whose merge policy wins, so it must be made explicitly there.
|
|
37508
|
+
*/
|
|
37509
|
+
const deriveSharedWriteSteps = () => {
|
|
37510
|
+
const orderIndex = new Map(SHARED_WRITE_FEATURE_ORDER.map((feature, index) => [feature, index]));
|
|
37511
|
+
const filesByFeature = /* @__PURE__ */ new Map();
|
|
37512
|
+
const depsByFeature = /* @__PURE__ */ new Map();
|
|
37513
|
+
for (const writer of deriveSharedFileWriters()) {
|
|
37514
|
+
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.`);
|
|
37515
|
+
const ordered = [...writer.features].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b));
|
|
37516
|
+
for (const [position, feature] of ordered.entries()) {
|
|
37517
|
+
let files = filesByFeature.get(feature);
|
|
37518
|
+
if (!files) {
|
|
37519
|
+
files = /* @__PURE__ */ new Set();
|
|
37520
|
+
filesByFeature.set(feature, files);
|
|
37521
|
+
}
|
|
37522
|
+
files.add(writer.key);
|
|
37523
|
+
let deps = depsByFeature.get(feature);
|
|
37524
|
+
if (!deps) {
|
|
37525
|
+
deps = /* @__PURE__ */ new Set();
|
|
37526
|
+
depsByFeature.set(feature, deps);
|
|
37527
|
+
}
|
|
37528
|
+
for (const earlier of ordered.slice(0, position)) deps.add(earlier);
|
|
37529
|
+
}
|
|
37530
|
+
}
|
|
37531
|
+
const steps = /* @__PURE__ */ new Map();
|
|
37532
|
+
for (const [feature, files] of filesByFeature) steps.set(feature, {
|
|
37533
|
+
writesSharedFile: [...files].toSorted(),
|
|
37534
|
+
dependsOn: [...depsByFeature.get(feature) ?? []].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b))
|
|
37535
|
+
});
|
|
37536
|
+
return steps;
|
|
37537
|
+
};
|
|
37538
|
+
//#endregion
|
|
36911
37539
|
//#region src/lib/generate.ts
|
|
36912
37540
|
async function processFeatureGeneration(params) {
|
|
36913
37541
|
const { config, processor, toolFiles, skipFilePaths } = params;
|
|
@@ -37057,98 +37685,53 @@ function resolveExecutionOrder(steps) {
|
|
|
37057
37685
|
if (ordered.length !== steps.length) throw new Error("Generation steps contain a cyclic 'dependsOn' dependency.");
|
|
37058
37686
|
return ordered;
|
|
37059
37687
|
}
|
|
37688
|
+
const SHARED_WRITE_STEPS = deriveSharedWriteSteps();
|
|
37689
|
+
const sharedWriteMeta = (id) => {
|
|
37690
|
+
const step = SHARED_WRITE_STEPS.get(id);
|
|
37691
|
+
return step ? {
|
|
37692
|
+
writesSharedFile: step.writesSharedFile,
|
|
37693
|
+
dependsOn: step.dependsOn
|
|
37694
|
+
} : {};
|
|
37695
|
+
};
|
|
37060
37696
|
/**
|
|
37061
37697
|
* The static shape of the generation step graph: which steps write which shared
|
|
37062
37698
|
* (read-modify-write) config files, and the `dependsOn` edges that fix a safe order
|
|
37063
|
-
* for those writers.
|
|
37064
|
-
*
|
|
37065
|
-
*
|
|
37066
|
-
*
|
|
37067
|
-
*
|
|
37699
|
+
* for those writers. Both are derived from the processor registry's settable
|
|
37700
|
+
* paths and `SHARED_WRITE_FEATURE_ORDER` (see `shared-file-derive.ts`), so a new
|
|
37701
|
+
* tool or shared path never requires editing this graph. Exported (separately
|
|
37702
|
+
* from the `run` closures, which need a live `config`/`logger`) so
|
|
37703
|
+
* `resolveExecutionOrder`'s ordering guarantee can be tested directly against
|
|
37704
|
+
* the real graph rather than a hand-copied one. Readonly so a consumer can't
|
|
37705
|
+
* mutate this module-level singleton and affect every subsequent `generate()`
|
|
37706
|
+
* call in the process.
|
|
37068
37707
|
*/
|
|
37069
37708
|
const GENERATION_STEP_GRAPH = [
|
|
37070
37709
|
{
|
|
37071
37710
|
id: "ignore",
|
|
37072
|
-
|
|
37711
|
+
...sharedWriteMeta("ignore")
|
|
37073
37712
|
},
|
|
37074
37713
|
{
|
|
37075
37714
|
id: "mcp",
|
|
37076
|
-
|
|
37077
|
-
".amp/settings.json",
|
|
37078
|
-
".augment/settings.json",
|
|
37079
|
-
".codex/config.toml",
|
|
37080
|
-
".config/amp/settings.json",
|
|
37081
|
-
".config/devin/config.json",
|
|
37082
|
-
".config/opencode/opencode.json",
|
|
37083
|
-
".config/zed/settings.json",
|
|
37084
|
-
".devin/config.json",
|
|
37085
|
-
".grok/config.toml",
|
|
37086
|
-
".hermes/config.yaml",
|
|
37087
|
-
".qwen/settings.json",
|
|
37088
|
-
".reasonix/config.toml",
|
|
37089
|
-
".takt/config.yaml",
|
|
37090
|
-
".vibe/config.toml",
|
|
37091
|
-
".zed/settings.json",
|
|
37092
|
-
"kilo.json",
|
|
37093
|
-
"opencode.json",
|
|
37094
|
-
"reasonix.toml"
|
|
37095
|
-
],
|
|
37096
|
-
dependsOn: ["ignore"]
|
|
37715
|
+
...sharedWriteMeta("mcp")
|
|
37097
37716
|
},
|
|
37098
37717
|
{ id: "commands" },
|
|
37099
|
-
{
|
|
37718
|
+
{
|
|
37719
|
+
id: "subagents",
|
|
37720
|
+
...sharedWriteMeta("subagents")
|
|
37721
|
+
},
|
|
37100
37722
|
{ id: "skills" },
|
|
37101
37723
|
{
|
|
37102
37724
|
id: "hooks",
|
|
37103
|
-
|
|
37104
|
-
".augment/settings.json",
|
|
37105
|
-
".claude/settings.json",
|
|
37106
|
-
".codex/config.toml",
|
|
37107
|
-
".config/devin/config.json",
|
|
37108
|
-
".hermes/config.yaml",
|
|
37109
|
-
".kiro/agents/default.json",
|
|
37110
|
-
".qwen/settings.json",
|
|
37111
|
-
".vibe/config.toml"
|
|
37112
|
-
],
|
|
37113
|
-
dependsOn: ["ignore", "mcp"]
|
|
37725
|
+
...sharedWriteMeta("hooks")
|
|
37114
37726
|
},
|
|
37115
37727
|
{
|
|
37116
37728
|
id: "permissions",
|
|
37117
|
-
|
|
37118
|
-
".amp/settings.json",
|
|
37119
|
-
".augment/settings.json",
|
|
37120
|
-
".claude/settings.json",
|
|
37121
|
-
".codex/config.toml",
|
|
37122
|
-
".config/amp/settings.json",
|
|
37123
|
-
".config/devin/config.json",
|
|
37124
|
-
".config/opencode/opencode.json",
|
|
37125
|
-
".config/zed/settings.json",
|
|
37126
|
-
".devin/config.json",
|
|
37127
|
-
".grok/config.toml",
|
|
37128
|
-
".hermes/config.yaml",
|
|
37129
|
-
".kiro/agents/default.json",
|
|
37130
|
-
".qwen/settings.json",
|
|
37131
|
-
".reasonix/config.toml",
|
|
37132
|
-
".takt/config.yaml",
|
|
37133
|
-
".vibe/config.toml",
|
|
37134
|
-
".zed/settings.json",
|
|
37135
|
-
"opencode.json",
|
|
37136
|
-
"reasonix.toml"
|
|
37137
|
-
],
|
|
37138
|
-
dependsOn: [
|
|
37139
|
-
"ignore",
|
|
37140
|
-
"hooks",
|
|
37141
|
-
"mcp"
|
|
37142
|
-
]
|
|
37729
|
+
...sharedWriteMeta("permissions")
|
|
37143
37730
|
},
|
|
37144
37731
|
{
|
|
37145
37732
|
id: "rules",
|
|
37146
|
-
|
|
37147
|
-
dependsOn: [
|
|
37148
|
-
"mcp",
|
|
37149
|
-
"skills",
|
|
37150
|
-
"permissions"
|
|
37151
|
-
]
|
|
37733
|
+
...sharedWriteMeta("rules"),
|
|
37734
|
+
dependsOn: [...sharedWriteMeta("rules").dependsOn ?? [], "skills"]
|
|
37152
37735
|
}
|
|
37153
37736
|
];
|
|
37154
37737
|
/**
|
|
@@ -37263,7 +37846,7 @@ async function generateRulesCore(params) {
|
|
|
37263
37846
|
targets: config.getConfigFileTargets(),
|
|
37264
37847
|
global: config.getGlobal()
|
|
37265
37848
|
}) : /* @__PURE__ */ new Map();
|
|
37266
|
-
for (const
|
|
37849
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37267
37850
|
if (!config.getFeatures(toolTarget).includes("rules")) continue;
|
|
37268
37851
|
const processor = new RulesProcessor({
|
|
37269
37852
|
outputRoot,
|
|
@@ -37318,7 +37901,7 @@ async function generateIgnoreCore(params) {
|
|
|
37318
37901
|
let hasDiff = false;
|
|
37319
37902
|
for (const toolTarget of intersection(config.getTargets(), supportedIgnoreTargets)) {
|
|
37320
37903
|
if (!config.getFeatures(toolTarget).includes("ignore")) continue;
|
|
37321
|
-
for (const outputRoot of config.getOutputRoots()) try {
|
|
37904
|
+
for (const outputRoot of config.getOutputRoots(toolTarget)) try {
|
|
37322
37905
|
const processor = new IgnoreProcessor({
|
|
37323
37906
|
outputRoot,
|
|
37324
37907
|
inputRoot: config.getInputRoot(),
|
|
@@ -37359,7 +37942,7 @@ async function generateMcpCore(params) {
|
|
|
37359
37942
|
featureName: "mcp",
|
|
37360
37943
|
logger
|
|
37361
37944
|
});
|
|
37362
|
-
for (const
|
|
37945
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37363
37946
|
if (!config.getFeatures(toolTarget).includes("mcp")) continue;
|
|
37364
37947
|
const processor = new McpProcessor({
|
|
37365
37948
|
outputRoot,
|
|
@@ -37401,7 +37984,7 @@ async function generateCommandsCore(params) {
|
|
|
37401
37984
|
featureName: "commands",
|
|
37402
37985
|
logger
|
|
37403
37986
|
});
|
|
37404
|
-
for (const
|
|
37987
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37405
37988
|
if (!config.getFeatures(toolTarget).includes("commands")) continue;
|
|
37406
37989
|
const processor = new CommandsProcessor({
|
|
37407
37990
|
outputRoot,
|
|
@@ -37443,7 +38026,7 @@ async function generateSubagentsCore(params) {
|
|
|
37443
38026
|
featureName: "subagents",
|
|
37444
38027
|
logger
|
|
37445
38028
|
});
|
|
37446
|
-
for (const
|
|
38029
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37447
38030
|
if (!config.getFeatures(toolTarget).includes("subagents")) continue;
|
|
37448
38031
|
const processor = new SubagentsProcessor({
|
|
37449
38032
|
outputRoot,
|
|
@@ -37486,7 +38069,7 @@ async function generateSkillsCore(params) {
|
|
|
37486
38069
|
featureName: "skills",
|
|
37487
38070
|
logger
|
|
37488
38071
|
});
|
|
37489
|
-
for (const
|
|
38072
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37490
38073
|
if (!config.getFeatures(toolTarget).includes("skills")) continue;
|
|
37491
38074
|
const processor = new SkillsProcessor({
|
|
37492
38075
|
outputRoot,
|
|
@@ -37527,7 +38110,7 @@ async function generateHooksCore(params) {
|
|
|
37527
38110
|
featureName: "hooks",
|
|
37528
38111
|
logger
|
|
37529
38112
|
});
|
|
37530
|
-
for (const
|
|
38113
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37531
38114
|
if (!config.getFeatures(toolTarget).includes("hooks")) continue;
|
|
37532
38115
|
const processor = new HooksProcessor({
|
|
37533
38116
|
outputRoot,
|
|
@@ -37564,7 +38147,7 @@ async function generatePermissionsCore(params) {
|
|
|
37564
38147
|
let totalCount = 0;
|
|
37565
38148
|
const allPaths = [];
|
|
37566
38149
|
let hasDiff = false;
|
|
37567
|
-
for (const
|
|
38150
|
+
for (const toolTarget of intersection(config.getTargets(), supportedPermissionsTargets)) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37568
38151
|
if (!config.getFeatures(toolTarget).includes("permissions")) continue;
|
|
37569
38152
|
try {
|
|
37570
38153
|
const processor = new PermissionsProcessor({
|
|
@@ -37840,6 +38423,6 @@ async function importPermissionsCore(params) {
|
|
|
37840
38423
|
return writtenCount;
|
|
37841
38424
|
}
|
|
37842
38425
|
//#endregion
|
|
37843
|
-
export {
|
|
38426
|
+
export { toPosixPath as $, RulesyncCommand as A, createTempDirectory as B, RulesyncHooks as C, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, ALL_FEATURES as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, formatError as Et, ConsoleLogger as F, getFileSize as G, ensureDir as H, JsonLogger as I, listDirectoryFiles as J, getHomeDirectory as K, CLIError as L, stringifyFrontmatter as M, ConfigResolver as N, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES_WITH_WILDCARD as Ot, findControlCharacter as P, removeTempDirectory as Q, ErrorCodes as R, HooksProcessor as S, RULESYNC_RULES_RELATIVE_DIR_PATH as St, CLAUDECODE_DIR as T, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Tt, fileExists as U, directoryExists as V, findFilesByGlobs as W, removeDirectory as X, readFileContent as Y, removeFile as Z, RulesyncPermissions as _, RULESYNC_MCP_SCHEMA_URL as _t, convertFromTool as a, RULESYNC_AIIGNORE_FILE_NAME as at, IgnoreProcessor as b, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_HOOKS_FILE_NAME as dt, writeFileContent as et, SkillsProcessor as f, RULESYNC_HOOKS_RELATIVE_FILE_PATH as ft, SKILL_FILE_NAME as g, RULESYNC_MCP_RELATIVE_FILE_PATH as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_MCP_FILE_NAME as ht, getProcessorRegistryEntry as i, MAX_FILE_SIZE as it, RulesyncCommandFrontmatterSchema as j, CLAUDECODE_SKILLS_DIR_PATH as k, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as lt, RulesyncSkill as m, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as mt, checkRulesyncDirExists as n, ALL_TOOL_TARGETS_WITH_WILDCARD as nt, RulesProcessor as o, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ot, getLocalSkillDirNames as p, RULESYNC_IGNORE_RELATIVE_FILE_PATH as pt, isSymlink as q, generate as r, ToolTargetSchema as rt, RulesyncRule as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as st, importFromTool as t, ALL_TOOL_TARGETS as tt, RulesyncSubagent as u, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as ut, McpProcessor as v, RULESYNC_OVERVIEW_FILE_NAME as vt, CommandsProcessor as w, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as wt, RulesyncIgnore as x, RULESYNC_RELATIVE_DIR_PATH as xt, RulesyncMcp as y, RULESYNC_PERMISSIONS_FILE_NAME as yt, checkPathTraversal as z };
|
|
37844
38427
|
|
|
37845
|
-
//# sourceMappingURL=import-
|
|
38428
|
+
//# sourceMappingURL=import-BdJG1fyb.js.map
|