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
|
@@ -114,22 +114,22 @@ function formatError(error) {
|
|
|
114
114
|
}
|
|
115
115
|
//#endregion
|
|
116
116
|
//#region src/constants/rulesync-paths.ts
|
|
117
|
-
const { join: join$
|
|
117
|
+
const { join: join$247 } = node_path.posix;
|
|
118
118
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
119
119
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
120
120
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
121
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
122
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
123
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
124
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
125
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
126
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
121
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "rules");
|
|
122
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "commands");
|
|
123
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "subagents");
|
|
124
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
125
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
126
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
127
127
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
128
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
128
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
129
129
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
130
130
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
131
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
132
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
131
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, "skills");
|
|
132
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$247(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
|
|
133
133
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
134
134
|
const RULESYNC_MCP_FILE_NAME = "mcp.json";
|
|
135
135
|
const RULESYNC_HOOKS_FILE_NAME = "hooks.json";
|
|
@@ -878,7 +878,7 @@ const SourceEntrySchema = zod_mini.z.object({
|
|
|
878
878
|
scope: (0, zod_mini.optional)(zod_mini.z.enum(["project", "user"]))
|
|
879
879
|
});
|
|
880
880
|
const ConfigParamsSchema = zod_mini.z.object({
|
|
881
|
-
outputRoots: zod_mini.z.array(zod_mini.z.string()),
|
|
881
|
+
outputRoots: zod_mini.z.union([zod_mini.z.array(zod_mini.z.string()), zod_mini.z.record(zod_mini.z.string(), zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))]),
|
|
882
882
|
targets: RulesyncConfigTargetsSchema,
|
|
883
883
|
features: RulesyncFeaturesSchema,
|
|
884
884
|
verbose: zod_mini.z.boolean(),
|
|
@@ -983,6 +983,7 @@ var Config = class Config {
|
|
|
983
983
|
const resolvedTargets = targets ?? [];
|
|
984
984
|
const resolvedFeatures = features ?? [];
|
|
985
985
|
this.validateObjectFormTargetKeys(resolvedTargets);
|
|
986
|
+
this.validateObjectFormOutputRootKeys(outputRoots);
|
|
986
987
|
this.validateConflictingTargets(resolvedTargets);
|
|
987
988
|
if (dryRun && check) throw new Error("--dry-run and --check cannot be used together");
|
|
988
989
|
this.outputRoots = outputRoots;
|
|
@@ -1020,6 +1021,11 @@ var Config = class Config {
|
|
|
1020
1021
|
if (!validTargets.has(key)) throw new Error(`Unknown target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
|
|
1021
1022
|
}
|
|
1022
1023
|
}
|
|
1024
|
+
validateObjectFormOutputRootKeys(outputRoots) {
|
|
1025
|
+
if (Array.isArray(outputRoots)) return;
|
|
1026
|
+
const validTargets = new Set(ALL_TOOL_TARGETS);
|
|
1027
|
+
for (const key of Object.keys(outputRoots)) if (!validTargets.has(key)) throw new Error(`Unknown outputRoots target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
|
|
1028
|
+
}
|
|
1023
1029
|
validateConflictingTargets(targets) {
|
|
1024
1030
|
const has = (target) => {
|
|
1025
1031
|
if (Array.isArray(targets)) return targets.includes(target);
|
|
@@ -1027,8 +1033,19 @@ var Config = class Config {
|
|
|
1027
1033
|
};
|
|
1028
1034
|
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.`);
|
|
1029
1035
|
}
|
|
1030
|
-
getOutputRoots() {
|
|
1031
|
-
return this.outputRoots;
|
|
1036
|
+
getOutputRoots(target) {
|
|
1037
|
+
if (Array.isArray(this.outputRoots)) return this.outputRoots;
|
|
1038
|
+
if (target) {
|
|
1039
|
+
const targetOutputRoots = this.outputRoots[target];
|
|
1040
|
+
if (targetOutputRoots === void 0) return [];
|
|
1041
|
+
return Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
|
|
1042
|
+
}
|
|
1043
|
+
const allRoots = [];
|
|
1044
|
+
for (const value of Object.values(this.outputRoots)) {
|
|
1045
|
+
if (value === void 0) continue;
|
|
1046
|
+
allRoots.push(...Array.isArray(value) ? value : [value]);
|
|
1047
|
+
}
|
|
1048
|
+
return [...new Set(allRoots)];
|
|
1032
1049
|
}
|
|
1033
1050
|
/**
|
|
1034
1051
|
* Filter an arbitrary string-key list down to the known `ToolTarget` set,
|
|
@@ -1390,10 +1407,21 @@ var ConfigResolver = class {
|
|
|
1390
1407
|
};
|
|
1391
1408
|
function getOutputRootsInLightOfGlobal({ outputRoots, global }) {
|
|
1392
1409
|
if (global) return [getHomeDirectory()];
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1410
|
+
if (Array.isArray(outputRoots)) {
|
|
1411
|
+
outputRoots.forEach((outputRoot) => {
|
|
1412
|
+
validateOutputRoot(outputRoot);
|
|
1413
|
+
});
|
|
1414
|
+
return outputRoots.map((outputRoot) => (0, node_path.resolve)(outputRoot));
|
|
1415
|
+
}
|
|
1416
|
+
const resolvedOutputRoots = {};
|
|
1417
|
+
for (const [target, targetOutputRoots] of Object.entries(outputRoots)) {
|
|
1418
|
+
const roots = Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
|
|
1419
|
+
roots.forEach((outputRoot) => {
|
|
1420
|
+
validateOutputRoot(outputRoot);
|
|
1421
|
+
});
|
|
1422
|
+
resolvedOutputRoots[target] = Array.isArray(targetOutputRoots) ? roots.map((outputRoot) => (0, node_path.resolve)(outputRoot)) : (0, node_path.resolve)(targetOutputRoots);
|
|
1423
|
+
}
|
|
1424
|
+
return resolvedOutputRoots;
|
|
1397
1425
|
}
|
|
1398
1426
|
function extractConfigFileTargets(targets) {
|
|
1399
1427
|
if (targets === void 0) return void 0;
|
|
@@ -3919,6 +3947,297 @@ const OPENCODE_JSON_FILE_NAME = "opencode.json";
|
|
|
3919
3947
|
const OPENCODE_RULE_FILE_NAME = "AGENTS.md";
|
|
3920
3948
|
const OPENCODE_HOOKS_FILE_NAME = "rulesync-hooks.js";
|
|
3921
3949
|
//#endregion
|
|
3950
|
+
//#region src/constants/takt-paths.ts
|
|
3951
|
+
const TAKT_DIR = ".takt";
|
|
3952
|
+
const TAKT_FACETS_SUBDIR = "facets";
|
|
3953
|
+
const TAKT_FACETS_DIR_PATH = (0, node_path.join)(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
3954
|
+
const TAKT_RULES_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "policies");
|
|
3955
|
+
const TAKT_COMMANDS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "instructions");
|
|
3956
|
+
const TAKT_SKILLS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
3957
|
+
const TAKT_SUBAGENTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "personas");
|
|
3958
|
+
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
3959
|
+
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
3960
|
+
/**
|
|
3961
|
+
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
3962
|
+
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
3963
|
+
* profiles (including permission modes), and other Takt settings.
|
|
3964
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3965
|
+
*/
|
|
3966
|
+
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
3967
|
+
/**
|
|
3968
|
+
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
3969
|
+
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
3970
|
+
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
3971
|
+
* server using it is permitted to run.
|
|
3972
|
+
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
3973
|
+
*/
|
|
3974
|
+
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
3975
|
+
//#endregion
|
|
3976
|
+
//#region src/utils/prototype-pollution.ts
|
|
3977
|
+
/**
|
|
3978
|
+
* Keys that, if walked into when constructing or merging objects from
|
|
3979
|
+
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
3980
|
+
* chain) and propagate state to every other object in the runtime. Any code
|
|
3981
|
+
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
3982
|
+
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
3983
|
+
*/
|
|
3984
|
+
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
3985
|
+
"__proto__",
|
|
3986
|
+
"constructor",
|
|
3987
|
+
"prototype"
|
|
3988
|
+
]);
|
|
3989
|
+
function isPrototypePollutionKey(key) {
|
|
3990
|
+
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
3991
|
+
}
|
|
3992
|
+
/**
|
|
3993
|
+
* Returns a shallow copy of a record's own entries with every
|
|
3994
|
+
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
3995
|
+
*
|
|
3996
|
+
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
3997
|
+
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
3998
|
+
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
3999
|
+
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
4000
|
+
* prototype). Walking the entries through this helper severs that path while
|
|
4001
|
+
* preserving every legitimate key.
|
|
4002
|
+
*/
|
|
4003
|
+
function omitPrototypePollutionKeys(record) {
|
|
4004
|
+
const sanitized = {};
|
|
4005
|
+
for (const [key, value] of Object.entries(record)) {
|
|
4006
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4007
|
+
sanitized[key] = value;
|
|
4008
|
+
}
|
|
4009
|
+
return sanitized;
|
|
4010
|
+
}
|
|
4011
|
+
//#endregion
|
|
4012
|
+
//#region src/features/shared/shared-config-gateway.ts
|
|
4013
|
+
function sanitizeSharedConfigValue(value) {
|
|
4014
|
+
if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
|
|
4015
|
+
if (!isPlainObject(value)) return value;
|
|
4016
|
+
const result = {};
|
|
4017
|
+
for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
|
|
4018
|
+
return result;
|
|
4019
|
+
}
|
|
4020
|
+
/**
|
|
4021
|
+
* Parse a shared config file into a plain document: an empty/whitespace file
|
|
4022
|
+
* is `{}`, prototype-pollution keys are dropped recursively, and a non-mapping
|
|
4023
|
+
* root follows `invalidRootPolicy`. Syntax errors are wrapped with the file
|
|
4024
|
+
* path when one is given.
|
|
4025
|
+
*/
|
|
4026
|
+
function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy = "coerce-empty" }) {
|
|
4027
|
+
if (fileContent.trim() === "") return {};
|
|
4028
|
+
const at = filePath === void 0 ? "" : ` at ${filePath}`;
|
|
4029
|
+
let parsed;
|
|
4030
|
+
try {
|
|
4031
|
+
if (format === "yaml") parsed = (0, js_yaml.load)(fileContent);
|
|
4032
|
+
else if (format === "json") parsed = JSON.parse(fileContent);
|
|
4033
|
+
else parsed = (0, jsonc_parser.parse)(fileContent);
|
|
4034
|
+
} catch (error) {
|
|
4035
|
+
throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
|
|
4036
|
+
}
|
|
4037
|
+
if (parsed === void 0 || parsed === null) return {};
|
|
4038
|
+
if (!isPlainObject(parsed)) {
|
|
4039
|
+
if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
|
|
4040
|
+
return {};
|
|
4041
|
+
}
|
|
4042
|
+
return sanitizeSharedConfigValue(parsed);
|
|
4043
|
+
}
|
|
4044
|
+
/**
|
|
4045
|
+
* Serialize a shared config document. YAML output always ends with exactly one
|
|
4046
|
+
* newline; JSON output matches the 2-space `JSON.stringify` shape the JSON
|
|
4047
|
+
* writers have always emitted (no trailing newline).
|
|
4048
|
+
*/
|
|
4049
|
+
function stringifySharedConfig({ format, document }) {
|
|
4050
|
+
if (format === "yaml") return (0, js_yaml.dump)(document, {
|
|
4051
|
+
noRefs: true,
|
|
4052
|
+
sortKeys: false
|
|
4053
|
+
}).trimEnd() + "\n";
|
|
4054
|
+
return JSON.stringify(document, null, 2);
|
|
4055
|
+
}
|
|
4056
|
+
/**
|
|
4057
|
+
* Shallow merge: every top-level key in `patch` replaces the base key
|
|
4058
|
+
* wholesale; all other base keys are preserved. The policy for a feature that
|
|
4059
|
+
* owns a fixed set of top-level keys.
|
|
4060
|
+
*/
|
|
4061
|
+
function mergeSharedConfigShallow({ base, patch }) {
|
|
4062
|
+
return {
|
|
4063
|
+
...base,
|
|
4064
|
+
...sanitizeSharedConfigValue(patch)
|
|
4065
|
+
};
|
|
4066
|
+
}
|
|
4067
|
+
/**
|
|
4068
|
+
* Deep merge (`patch` wins): nested plain objects are merged key-by-key; every
|
|
4069
|
+
* other value (arrays, scalars) is replaced wholesale. The policy for a
|
|
4070
|
+
* feature whose contribution interleaves with user-authored siblings at any
|
|
4071
|
+
* depth (e.g. permissions overlays onto `approvals`/`security` structures, or
|
|
4072
|
+
* per-provider option tables) — nested sibling keys are preserved by
|
|
4073
|
+
* construction instead of by per-tool re-implementation. Prototype-pollution
|
|
4074
|
+
* keys are dropped.
|
|
4075
|
+
*/
|
|
4076
|
+
function mergeSharedConfigDeep({ base, patch }) {
|
|
4077
|
+
const result = { ...base };
|
|
4078
|
+
for (const [key, patchValue] of Object.entries(patch)) {
|
|
4079
|
+
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
4080
|
+
const baseValue = result[key];
|
|
4081
|
+
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
|
|
4082
|
+
base: baseValue,
|
|
4083
|
+
patch: patchValue
|
|
4084
|
+
});
|
|
4085
|
+
else result[key] = sanitizeSharedConfigValue(patchValue);
|
|
4086
|
+
}
|
|
4087
|
+
return result;
|
|
4088
|
+
}
|
|
4089
|
+
const CLAUDE_SETTINGS_SHARED_FILE_KEY = ".claude/settings.json";
|
|
4090
|
+
const HERMES_CONFIG_SHARED_FILE_KEY = ".hermes/config.yaml";
|
|
4091
|
+
const TAKT_CONFIG_SHARED_FILE_KEY = ".takt/config.yaml";
|
|
4092
|
+
/**
|
|
4093
|
+
* Who owns what in each gateway-managed shared config file, and which policy
|
|
4094
|
+
* resolves conflicts. Keys are `dir/file` tokens matching
|
|
4095
|
+
* `deriveSharedFileWriters()`; a test keeps each entry's feature set in
|
|
4096
|
+
* lock-step with the writers derived from the processor registry, so an
|
|
4097
|
+
* undeclared writer fails CI instead of merging by accident.
|
|
4098
|
+
*/
|
|
4099
|
+
const SHARED_CONFIG_OWNERSHIP = {
|
|
4100
|
+
[CLAUDE_SETTINGS_SHARED_FILE_KEY]: {
|
|
4101
|
+
format: "json",
|
|
4102
|
+
features: {
|
|
4103
|
+
ignore: {
|
|
4104
|
+
kind: "custom",
|
|
4105
|
+
policyFunction: "applyIgnoreReadDenies"
|
|
4106
|
+
},
|
|
4107
|
+
hooks: {
|
|
4108
|
+
kind: "replace-owned-keys",
|
|
4109
|
+
ownedKeys: ["hooks"]
|
|
4110
|
+
},
|
|
4111
|
+
permissions: {
|
|
4112
|
+
kind: "custom",
|
|
4113
|
+
policyFunction: "applyPermissions"
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
},
|
|
4117
|
+
[HERMES_CONFIG_SHARED_FILE_KEY]: {
|
|
4118
|
+
format: "yaml",
|
|
4119
|
+
features: {
|
|
4120
|
+
subagents: {
|
|
4121
|
+
kind: "replace-owned-keys",
|
|
4122
|
+
ownedKeys: ["plugins"]
|
|
4123
|
+
},
|
|
4124
|
+
mcp: {
|
|
4125
|
+
kind: "replace-owned-keys",
|
|
4126
|
+
ownedKeys: ["mcp_servers"]
|
|
4127
|
+
},
|
|
4128
|
+
hooks: {
|
|
4129
|
+
kind: "replace-owned-keys",
|
|
4130
|
+
ownedKeys: ["hooks"]
|
|
4131
|
+
},
|
|
4132
|
+
permissions: {
|
|
4133
|
+
kind: "deep-merge",
|
|
4134
|
+
replaceKeys: ["permissions"]
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
},
|
|
4138
|
+
[TAKT_CONFIG_SHARED_FILE_KEY]: {
|
|
4139
|
+
format: "yaml",
|
|
4140
|
+
invalidRootPolicy: "error",
|
|
4141
|
+
features: {
|
|
4142
|
+
mcp: {
|
|
4143
|
+
kind: "replace-owned-keys",
|
|
4144
|
+
ownedKeys: [TAKT_WORKFLOW_MCP_SERVERS_KEY]
|
|
4145
|
+
},
|
|
4146
|
+
permissions: { kind: "deep-merge" }
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
};
|
|
4150
|
+
/**
|
|
4151
|
+
* Execute a feature's declared write to a gateway-managed shared file: parse
|
|
4152
|
+
* the existing content, merge the patch under the feature's declared policy,
|
|
4153
|
+
* and serialize. Throws when the file or feature is undeclared, when a
|
|
4154
|
+
* `replace-owned-keys` patch strays outside its owned keys, or when the
|
|
4155
|
+
* feature's policy is `custom` (those calls go to the named policy function
|
|
4156
|
+
* instead).
|
|
4157
|
+
*/
|
|
4158
|
+
function applySharedConfigPatch({ fileKey, feature, existingContent, patch, filePath }) {
|
|
4159
|
+
const declaration = SHARED_CONFIG_OWNERSHIP[fileKey];
|
|
4160
|
+
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.`);
|
|
4161
|
+
const policy = declaration.features[feature];
|
|
4162
|
+
if (!policy) throw new Error(`Feature '${feature}' declares no ownership of '${fileKey}'; add it to SHARED_CONFIG_OWNERSHIP before writing.`);
|
|
4163
|
+
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.`);
|
|
4164
|
+
const base = parseSharedConfig({
|
|
4165
|
+
format: declaration.format,
|
|
4166
|
+
fileContent: existingContent,
|
|
4167
|
+
filePath,
|
|
4168
|
+
...declaration.invalidRootPolicy !== void 0 && { invalidRootPolicy: declaration.invalidRootPolicy }
|
|
4169
|
+
});
|
|
4170
|
+
if (policy.kind === "replace-owned-keys") {
|
|
4171
|
+
const unowned = Object.keys(patch).filter((key) => !policy.ownedKeys.includes(key));
|
|
4172
|
+
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.`);
|
|
4173
|
+
return stringifySharedConfig({
|
|
4174
|
+
format: declaration.format,
|
|
4175
|
+
document: mergeSharedConfigShallow({
|
|
4176
|
+
base,
|
|
4177
|
+
patch
|
|
4178
|
+
})
|
|
4179
|
+
});
|
|
4180
|
+
}
|
|
4181
|
+
const merged = mergeSharedConfigDeep({
|
|
4182
|
+
base,
|
|
4183
|
+
patch
|
|
4184
|
+
});
|
|
4185
|
+
for (const key of policy.replaceKeys ?? []) if (patch[key] !== void 0) merged[key] = sanitizeSharedConfigValue(patch[key]);
|
|
4186
|
+
return stringifySharedConfig({
|
|
4187
|
+
format: declaration.format,
|
|
4188
|
+
document: merged
|
|
4189
|
+
});
|
|
4190
|
+
}
|
|
4191
|
+
const READ_TOOL_NAME = "Read";
|
|
4192
|
+
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
4193
|
+
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
4194
|
+
const parsePermissionsBlock = (settings) => {
|
|
4195
|
+
const permissions = settings.permissions ?? {};
|
|
4196
|
+
return {
|
|
4197
|
+
allow: permissions.allow ?? [],
|
|
4198
|
+
ask: permissions.ask ?? [],
|
|
4199
|
+
deny: permissions.deny ?? []
|
|
4200
|
+
};
|
|
4201
|
+
};
|
|
4202
|
+
const withPermissions = (settings, next) => {
|
|
4203
|
+
const permissions = { ...settings.permissions };
|
|
4204
|
+
const assign = (key, values) => {
|
|
4205
|
+
if (values.length > 0) permissions[key] = values;
|
|
4206
|
+
else delete permissions[key];
|
|
4207
|
+
};
|
|
4208
|
+
assign("allow", next.allow);
|
|
4209
|
+
assign("ask", next.ask);
|
|
4210
|
+
assign("deny", next.deny);
|
|
4211
|
+
return {
|
|
4212
|
+
...settings,
|
|
4213
|
+
permissions
|
|
4214
|
+
};
|
|
4215
|
+
};
|
|
4216
|
+
const applyIgnoreReadDenies = (params) => {
|
|
4217
|
+
const { settings, readDenies } = params;
|
|
4218
|
+
const current = parsePermissionsBlock(settings);
|
|
4219
|
+
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
4220
|
+
return withPermissions(settings, {
|
|
4221
|
+
allow: current.allow,
|
|
4222
|
+
ask: current.ask,
|
|
4223
|
+
deny: (0, es_toolkit.uniq)([...preservedDeny, ...readDenies].toSorted())
|
|
4224
|
+
});
|
|
4225
|
+
};
|
|
4226
|
+
const applyPermissions = (params) => {
|
|
4227
|
+
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
4228
|
+
const current = parsePermissionsBlock(settings);
|
|
4229
|
+
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
4230
|
+
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
4231
|
+
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
4232
|
+
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.`);
|
|
4233
|
+
}
|
|
4234
|
+
return withPermissions(settings, {
|
|
4235
|
+
allow: (0, es_toolkit.uniq)([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
4236
|
+
ask: (0, es_toolkit.uniq)([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
4237
|
+
deny: (0, es_toolkit.uniq)([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
4238
|
+
});
|
|
4239
|
+
};
|
|
4240
|
+
//#endregion
|
|
3922
4241
|
//#region src/features/opencode-config.ts
|
|
3923
4242
|
/**
|
|
3924
4243
|
* Reads and parses the OpenCode config (`opencode.jsonc` preferred, then
|
|
@@ -3943,9 +4262,10 @@ async function readOpencodeConfig({ outputRoot, global = false }) {
|
|
|
3943
4262
|
});
|
|
3944
4263
|
const fileContent = await readFileContentOrNull((0, node_path.join)(configDir, "opencode.jsonc")) ?? await readFileContentOrNull((0, node_path.join)(configDir, "opencode.json"));
|
|
3945
4264
|
if (!fileContent) return {};
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4265
|
+
return parseSharedConfig({
|
|
4266
|
+
format: "jsonc",
|
|
4267
|
+
fileContent
|
|
4268
|
+
});
|
|
3949
4269
|
}
|
|
3950
4270
|
/**
|
|
3951
4271
|
* Narrows an unknown value to a plain record of entries keyed by name, as used
|
|
@@ -4854,32 +5174,6 @@ var RovodevPromptsManifest = class extends ToolFile {
|
|
|
4854
5174
|
}
|
|
4855
5175
|
};
|
|
4856
5176
|
//#endregion
|
|
4857
|
-
//#region src/constants/takt-paths.ts
|
|
4858
|
-
const TAKT_DIR = ".takt";
|
|
4859
|
-
const TAKT_FACETS_SUBDIR = "facets";
|
|
4860
|
-
const TAKT_FACETS_DIR_PATH = (0, node_path.join)(TAKT_DIR, TAKT_FACETS_SUBDIR);
|
|
4861
|
-
const TAKT_RULES_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "policies");
|
|
4862
|
-
const TAKT_COMMANDS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "instructions");
|
|
4863
|
-
const TAKT_SKILLS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "knowledge");
|
|
4864
|
-
const TAKT_SUBAGENTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "personas");
|
|
4865
|
-
const TAKT_OUTPUT_CONTRACTS_DIR_PATH = (0, node_path.join)(TAKT_FACETS_DIR_PATH, "output-contracts");
|
|
4866
|
-
const TAKT_RULE_OVERVIEW_FILE_NAME = "overview.md";
|
|
4867
|
-
/**
|
|
4868
|
-
* Takt's shared config file. Lives at `.takt/config.yaml` (project) and
|
|
4869
|
-
* `~/.takt/config.yaml` (global); it holds the active provider, provider
|
|
4870
|
-
* profiles (including permission modes), and other Takt settings.
|
|
4871
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4872
|
-
*/
|
|
4873
|
-
const TAKT_CONFIG_FILE_NAME = "config.yaml";
|
|
4874
|
-
/**
|
|
4875
|
-
* Top-level key in Takt's `config.yaml` holding the workflow MCP transport
|
|
4876
|
-
* allowlist (`stdio` / `sse` / `http` booleans). Takt is default-deny: a
|
|
4877
|
-
* transport must be explicitly enabled here before any workflow-defined MCP
|
|
4878
|
-
* server using it is permitted to run.
|
|
4879
|
-
* @see https://github.com/nrslib/takt/blob/main/docs/configuration.md
|
|
4880
|
-
*/
|
|
4881
|
-
const TAKT_WORKFLOW_MCP_SERVERS_KEY = "workflow_mcp_servers";
|
|
4882
|
-
//#endregion
|
|
4883
5177
|
//#region src/features/takt-shared.ts
|
|
4884
5178
|
/**
|
|
4885
5179
|
* Shared utilities for all TAKT-* tool file classes.
|
|
@@ -6357,42 +6651,6 @@ const CANONICAL_TO_REASONIX_EVENT_NAMES = {
|
|
|
6357
6651
|
*/
|
|
6358
6652
|
const REASONIX_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_REASONIX_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
6359
6653
|
//#endregion
|
|
6360
|
-
//#region src/utils/prototype-pollution.ts
|
|
6361
|
-
/**
|
|
6362
|
-
* Keys that, if walked into when constructing or merging objects from
|
|
6363
|
-
* untrusted input, can mutate `Object.prototype` (or otherwise the prototype
|
|
6364
|
-
* chain) and propagate state to every other object in the runtime. Any code
|
|
6365
|
-
* that copies arbitrary user-supplied keys into a fresh object — frontmatter
|
|
6366
|
-
* parsing, MCP config conversion, settings round-trip — should skip these.
|
|
6367
|
-
*/
|
|
6368
|
-
const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
|
|
6369
|
-
"__proto__",
|
|
6370
|
-
"constructor",
|
|
6371
|
-
"prototype"
|
|
6372
|
-
]);
|
|
6373
|
-
function isPrototypePollutionKey(key) {
|
|
6374
|
-
return PROTOTYPE_POLLUTION_KEYS.has(key);
|
|
6375
|
-
}
|
|
6376
|
-
/**
|
|
6377
|
-
* Returns a shallow copy of a record's own entries with every
|
|
6378
|
-
* prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
|
|
6379
|
-
*
|
|
6380
|
-
* Use when copying a nested, user-supplied string map — an MCP server's `env`
|
|
6381
|
-
* or `headers` table — into freshly generated config. Carrying such a map by
|
|
6382
|
-
* reference, or re-assigning its keys via bracket notation, would let a literal
|
|
6383
|
-
* `__proto__` key ride along (and re-assigning it would mutate the target's
|
|
6384
|
-
* prototype). Walking the entries through this helper severs that path while
|
|
6385
|
-
* preserving every legitimate key.
|
|
6386
|
-
*/
|
|
6387
|
-
function omitPrototypePollutionKeys(record) {
|
|
6388
|
-
const sanitized = {};
|
|
6389
|
-
for (const [key, value] of Object.entries(record)) {
|
|
6390
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
6391
|
-
sanitized[key] = value;
|
|
6392
|
-
}
|
|
6393
|
-
return sanitized;
|
|
6394
|
-
}
|
|
6395
|
-
//#endregion
|
|
6396
6654
|
//#region src/features/hooks/tool-hooks-converter.ts
|
|
6397
6655
|
function isToolMatcherEntry(x) {
|
|
6398
6656
|
if (x === null || typeof x !== "object") return false;
|
|
@@ -7051,24 +7309,19 @@ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
|
7051
7309
|
const paths = ClaudecodeHooks.getSettablePaths({ global });
|
|
7052
7310
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
7053
7311
|
const existingContent = await readOrInitializeFileContent(filePath, JSON.stringify({}, null, 2));
|
|
7054
|
-
let settings;
|
|
7055
|
-
try {
|
|
7056
|
-
settings = JSON.parse(existingContent);
|
|
7057
|
-
} catch (error) {
|
|
7058
|
-
throw new Error(`Failed to parse existing Claude settings at ${filePath}: ${formatError(error)}`, { cause: error });
|
|
7059
|
-
}
|
|
7060
7312
|
const config = rulesyncHooks.getJson();
|
|
7061
|
-
const
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7313
|
+
const fileContent = applySharedConfigPatch({
|
|
7314
|
+
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
7315
|
+
feature: "hooks",
|
|
7316
|
+
existingContent,
|
|
7317
|
+
patch: { hooks: canonicalToToolHooks({
|
|
7318
|
+
config,
|
|
7319
|
+
toolOverrideHooks: config.claudecode?.hooks,
|
|
7320
|
+
converterConfig: CLAUDE_CONVERTER_CONFIG,
|
|
7321
|
+
logger
|
|
7322
|
+
}) },
|
|
7323
|
+
filePath
|
|
7066
7324
|
});
|
|
7067
|
-
const merged = {
|
|
7068
|
-
...settings,
|
|
7069
|
-
hooks: claudeHooks
|
|
7070
|
-
};
|
|
7071
|
-
const fileContent = JSON.stringify(merged, null, 2);
|
|
7072
7325
|
return new ClaudecodeHooks({
|
|
7073
7326
|
outputRoot,
|
|
7074
7327
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -8300,55 +8553,6 @@ var GooseHooks = class GooseHooks extends ToolHooks {
|
|
|
8300
8553
|
}
|
|
8301
8554
|
};
|
|
8302
8555
|
//#endregion
|
|
8303
|
-
//#region src/features/hermes-config.ts
|
|
8304
|
-
function sanitizeHermesConfigValue(value) {
|
|
8305
|
-
if (Array.isArray(value)) return value.map(sanitizeHermesConfigValue);
|
|
8306
|
-
if (!isPlainObject(value)) return value;
|
|
8307
|
-
const sanitized = omitPrototypePollutionKeys(value);
|
|
8308
|
-
const result = {};
|
|
8309
|
-
for (const [key, nestedValue] of Object.entries(sanitized)) {
|
|
8310
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8311
|
-
result[key] = sanitizeHermesConfigValue(nestedValue);
|
|
8312
|
-
}
|
|
8313
|
-
return result;
|
|
8314
|
-
}
|
|
8315
|
-
function parseHermesConfig(fileContent) {
|
|
8316
|
-
if (!fileContent.trim()) return {};
|
|
8317
|
-
const parsed = (0, js_yaml.load)(fileContent);
|
|
8318
|
-
if (isPlainObject(parsed)) return sanitizeHermesConfigValue(parsed);
|
|
8319
|
-
return {};
|
|
8320
|
-
}
|
|
8321
|
-
function stringifyHermesConfig(config) {
|
|
8322
|
-
return (0, js_yaml.dump)(config, {
|
|
8323
|
-
noRefs: true,
|
|
8324
|
-
sortKeys: false
|
|
8325
|
-
}).trimEnd() + "\n";
|
|
8326
|
-
}
|
|
8327
|
-
function mergeHermesConfig(fileContent, patch) {
|
|
8328
|
-
return stringifyHermesConfig({
|
|
8329
|
-
...parseHermesConfig(fileContent),
|
|
8330
|
-
...patch
|
|
8331
|
-
});
|
|
8332
|
-
}
|
|
8333
|
-
/**
|
|
8334
|
-
* Recursively merge `patch` into `base` (patch wins). Nested plain objects are
|
|
8335
|
-
* merged key-by-key; every other value (arrays, scalars) is replaced wholesale.
|
|
8336
|
-
* Used to overlay the Hermes-scoped permission override onto the natively
|
|
8337
|
-
* emitted `approvals`/`security` structures without one clobbering the other
|
|
8338
|
-
* (e.g. `approvals.deny` from canonical deny rules coexisting with an
|
|
8339
|
-
* `approvals.mode` from the override). Prototype-pollution keys are dropped.
|
|
8340
|
-
*/
|
|
8341
|
-
function deepMergeHermesConfig(base, patch) {
|
|
8342
|
-
const result = { ...base };
|
|
8343
|
-
for (const [key, patchValue] of Object.entries(patch)) {
|
|
8344
|
-
if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
|
|
8345
|
-
const baseValue = result[key];
|
|
8346
|
-
if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = deepMergeHermesConfig(baseValue, patchValue);
|
|
8347
|
-
else result[key] = sanitizeHermesConfigValue(patchValue);
|
|
8348
|
-
}
|
|
8349
|
-
return result;
|
|
8350
|
-
}
|
|
8351
|
-
//#endregion
|
|
8352
8556
|
//#region src/features/hooks/hermesagent-hooks.ts
|
|
8353
8557
|
/**
|
|
8354
8558
|
* Canonical events that map to a Hermes tool-call event (`pre_tool_call` /
|
|
@@ -8476,10 +8680,21 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8476
8680
|
return true;
|
|
8477
8681
|
}
|
|
8478
8682
|
setFileContent(fileContent) {
|
|
8479
|
-
this.fileContent =
|
|
8683
|
+
this.fileContent = applySharedConfigPatch({
|
|
8684
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
8685
|
+
feature: "hooks",
|
|
8686
|
+
existingContent: fileContent,
|
|
8687
|
+
patch: parseSharedConfig({
|
|
8688
|
+
format: "yaml",
|
|
8689
|
+
fileContent: this.fileContent
|
|
8690
|
+
})
|
|
8691
|
+
});
|
|
8480
8692
|
}
|
|
8481
8693
|
toRulesyncHooks() {
|
|
8482
|
-
const hooks = hermesHooksToCanonical(
|
|
8694
|
+
const hooks = hermesHooksToCanonical(parseSharedConfig({
|
|
8695
|
+
format: "yaml",
|
|
8696
|
+
fileContent: this.getFileContent()
|
|
8697
|
+
}).hooks);
|
|
8483
8698
|
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify({
|
|
8484
8699
|
version: 1,
|
|
8485
8700
|
hooks
|
|
@@ -8489,11 +8704,14 @@ var HermesagentHooks = class HermesagentHooks extends ToolHooks {
|
|
|
8489
8704
|
const config = rulesyncHooks.getJson();
|
|
8490
8705
|
return new HermesagentHooks({
|
|
8491
8706
|
outputRoot,
|
|
8492
|
-
fileContent:
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8707
|
+
fileContent: stringifySharedConfig({
|
|
8708
|
+
format: "yaml",
|
|
8709
|
+
document: { hooks: canonicalToHermesHooks({
|
|
8710
|
+
config,
|
|
8711
|
+
toolOverrideHooks: config.hermesagent?.hooks,
|
|
8712
|
+
logger
|
|
8713
|
+
}) }
|
|
8714
|
+
})
|
|
8497
8715
|
});
|
|
8498
8716
|
}
|
|
8499
8717
|
};
|
|
@@ -10513,66 +10731,6 @@ var AugmentcodeIgnore = class AugmentcodeIgnore extends ToolIgnore {
|
|
|
10513
10731
|
}
|
|
10514
10732
|
};
|
|
10515
10733
|
//#endregion
|
|
10516
|
-
//#region src/features/claudecode-settings-gateway.ts
|
|
10517
|
-
/**
|
|
10518
|
-
* Single owner of the `.claude/settings.json` `permissions` block, which both
|
|
10519
|
-
* `ignore` (writes `Read(...)` into `permissions.deny`) and `permissions`
|
|
10520
|
-
* (writes the whole `allow`/`ask`/`deny`) read-modify-write. The entry format,
|
|
10521
|
-
* the merge, and the cross-feature ownership rule (permissions' explicit `Read`
|
|
10522
|
-
* rules win over ignore-derived `Read` denies) used to be duplicated across both
|
|
10523
|
-
* feature files; they live here once so each feature just states its intent and
|
|
10524
|
-
* never reasons about the other's existence.
|
|
10525
|
-
*/
|
|
10526
|
-
const READ_TOOL_NAME = "Read";
|
|
10527
|
-
const isReadDenyEntry = (entry) => entry.startsWith(`${READ_TOOL_NAME}(`) && entry.endsWith(")");
|
|
10528
|
-
const buildReadDenyEntry = (pattern) => `${READ_TOOL_NAME}(${pattern})`;
|
|
10529
|
-
const parsePermissionsBlock = (settings) => {
|
|
10530
|
-
const permissions = settings.permissions ?? {};
|
|
10531
|
-
return {
|
|
10532
|
-
allow: permissions.allow ?? [],
|
|
10533
|
-
ask: permissions.ask ?? [],
|
|
10534
|
-
deny: permissions.deny ?? []
|
|
10535
|
-
};
|
|
10536
|
-
};
|
|
10537
|
-
const withPermissions = (settings, next) => {
|
|
10538
|
-
const permissions = { ...settings.permissions };
|
|
10539
|
-
const assign = (key, values) => {
|
|
10540
|
-
if (values.length > 0) permissions[key] = values;
|
|
10541
|
-
else delete permissions[key];
|
|
10542
|
-
};
|
|
10543
|
-
assign("allow", next.allow);
|
|
10544
|
-
assign("ask", next.ask);
|
|
10545
|
-
assign("deny", next.deny);
|
|
10546
|
-
return {
|
|
10547
|
-
...settings,
|
|
10548
|
-
permissions
|
|
10549
|
-
};
|
|
10550
|
-
};
|
|
10551
|
-
const applyIgnoreReadDenies = (params) => {
|
|
10552
|
-
const { settings, readDenies } = params;
|
|
10553
|
-
const current = parsePermissionsBlock(settings);
|
|
10554
|
-
const preservedDeny = current.deny.filter((entry) => !isReadDenyEntry(entry) || readDenies.includes(entry));
|
|
10555
|
-
return withPermissions(settings, {
|
|
10556
|
-
allow: current.allow,
|
|
10557
|
-
ask: current.ask,
|
|
10558
|
-
deny: (0, es_toolkit.uniq)([...preservedDeny, ...readDenies].toSorted())
|
|
10559
|
-
});
|
|
10560
|
-
};
|
|
10561
|
-
const applyPermissions = (params) => {
|
|
10562
|
-
const { settings, managedToolNames, toolNameOf, allow, ask, deny, logger } = params;
|
|
10563
|
-
const current = parsePermissionsBlock(settings);
|
|
10564
|
-
const keepUnmanaged = (entries) => entries.filter((entry) => !managedToolNames.has(toolNameOf(entry)));
|
|
10565
|
-
if (logger && managedToolNames.has(READ_TOOL_NAME)) {
|
|
10566
|
-
const overwrittenReadDenies = current.deny.filter((entry) => toolNameOf(entry) === READ_TOOL_NAME);
|
|
10567
|
-
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.`);
|
|
10568
|
-
}
|
|
10569
|
-
return withPermissions(settings, {
|
|
10570
|
-
allow: (0, es_toolkit.uniq)([...keepUnmanaged(current.allow), ...allow].toSorted()),
|
|
10571
|
-
ask: (0, es_toolkit.uniq)([...keepUnmanaged(current.ask), ...ask].toSorted()),
|
|
10572
|
-
deny: (0, es_toolkit.uniq)([...keepUnmanaged(current.deny), ...deny].toSorted())
|
|
10573
|
-
});
|
|
10574
|
-
};
|
|
10575
|
-
//#endregion
|
|
10576
10734
|
//#region src/features/ignore/claudecode-ignore.ts
|
|
10577
10735
|
const DEFAULT_FILE_MODE = "shared";
|
|
10578
10736
|
/**
|
|
@@ -11995,7 +12153,6 @@ var AntigravityIdeMcp = class extends AntigravityMcp {
|
|
|
11995
12153
|
};
|
|
11996
12154
|
//#endregion
|
|
11997
12155
|
//#region src/features/mcp/augmentcode-mcp.ts
|
|
11998
|
-
const AUGMENTCODE_GLOBAL_ONLY_MESSAGE = "AugmentCode MCP is global-only; use --global to sync ~/.augment/settings.json";
|
|
11999
12156
|
function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath) {
|
|
12000
12157
|
const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
12001
12158
|
let parsed;
|
|
@@ -12010,13 +12167,15 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
|
|
|
12010
12167
|
/**
|
|
12011
12168
|
* AugmentCode (Auggie CLI) MCP servers.
|
|
12012
12169
|
*
|
|
12013
|
-
* MCP servers are persisted in the shared
|
|
12014
|
-
*
|
|
12015
|
-
*
|
|
12170
|
+
* MCP servers are persisted in the shared settings file `.augment/settings.json`
|
|
12171
|
+
* at either scope: the committed workspace file for team-shared servers (project)
|
|
12172
|
+
* or `~/.augment/settings.json` (global). That same file also holds `hooks` and
|
|
12016
12173
|
* `toolPermissions`, so generation merges the `mcpServers` block into the
|
|
12017
|
-
* existing settings instead of overwriting it, and the file is never deleted.
|
|
12174
|
+
* existing settings instead of overwriting it, and the file is never deleted. On
|
|
12175
|
+
* project-scope import the gitignored `.augment/settings.local.json` overrides
|
|
12176
|
+
* file is overlaid on top (the same layering the hooks/permissions adapters use).
|
|
12018
12177
|
*
|
|
12019
|
-
* @see https://docs.augmentcode.com/cli/
|
|
12178
|
+
* @see https://docs.augmentcode.com/cli/config
|
|
12020
12179
|
*/
|
|
12021
12180
|
var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
12022
12181
|
json;
|
|
@@ -12038,9 +12197,14 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12038
12197
|
};
|
|
12039
12198
|
}
|
|
12040
12199
|
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
12041
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12042
12200
|
const paths = this.getSettablePaths({ global });
|
|
12043
|
-
const json = parseAugmentcodeSettings(await
|
|
12201
|
+
const json = parseAugmentcodeSettings(await readAugmentcodeSettingsWithLocalOverlay({
|
|
12202
|
+
outputRoot,
|
|
12203
|
+
relativeDirPath: paths.relativeDirPath,
|
|
12204
|
+
baseFileName: paths.relativeFilePath,
|
|
12205
|
+
baseFallbackContent: "{}",
|
|
12206
|
+
includeLocalOverlay: !global
|
|
12207
|
+
}), paths.relativeDirPath, paths.relativeFilePath);
|
|
12044
12208
|
const newJson = {
|
|
12045
12209
|
...json,
|
|
12046
12210
|
mcpServers: json.mcpServers ?? {}
|
|
@@ -12055,7 +12219,6 @@ var AugmentcodeMcp = class AugmentcodeMcp extends ToolMcp {
|
|
|
12055
12219
|
});
|
|
12056
12220
|
}
|
|
12057
12221
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
12058
|
-
if (!global) throw new Error(AUGMENTCODE_GLOBAL_ONLY_MESSAGE);
|
|
12059
12222
|
const paths = this.getSettablePaths({ global });
|
|
12060
12223
|
const merged = {
|
|
12061
12224
|
...parseAugmentcodeSettings(await readOrInitializeFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath), JSON.stringify({}, null, 2)), paths.relativeDirPath, paths.relativeFilePath),
|
|
@@ -13705,7 +13868,10 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13705
13868
|
config;
|
|
13706
13869
|
constructor(params) {
|
|
13707
13870
|
super(params);
|
|
13708
|
-
this.config = this.fileContent !== void 0 ?
|
|
13871
|
+
this.config = this.fileContent !== void 0 ? parseSharedConfig({
|
|
13872
|
+
format: "yaml",
|
|
13873
|
+
fileContent: this.fileContent
|
|
13874
|
+
}) : {};
|
|
13709
13875
|
}
|
|
13710
13876
|
getConfig() {
|
|
13711
13877
|
return this.config;
|
|
@@ -13714,9 +13880,17 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13714
13880
|
return true;
|
|
13715
13881
|
}
|
|
13716
13882
|
setFileContent(fileContent) {
|
|
13717
|
-
const merged = mergeHermesMcpServers(
|
|
13883
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13884
|
+
format: "yaml",
|
|
13885
|
+
fileContent
|
|
13886
|
+
}), isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
|
|
13718
13887
|
this.config = merged;
|
|
13719
|
-
super.setFileContent(
|
|
13888
|
+
super.setFileContent(applySharedConfigPatch({
|
|
13889
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13890
|
+
feature: "mcp",
|
|
13891
|
+
existingContent: fileContent,
|
|
13892
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13893
|
+
}));
|
|
13720
13894
|
}
|
|
13721
13895
|
isDeletable() {
|
|
13722
13896
|
return false;
|
|
@@ -13743,12 +13917,21 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
|
|
|
13743
13917
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
13744
13918
|
if (!global) throw new Error(HERMESAGENT_GLOBAL_ONLY_MESSAGE);
|
|
13745
13919
|
const paths = this.getSettablePaths({ global });
|
|
13746
|
-
const
|
|
13920
|
+
const fileContent = await readOrInitializeFileContent((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath), "");
|
|
13921
|
+
const merged = mergeHermesMcpServers(parseSharedConfig({
|
|
13922
|
+
format: "yaml",
|
|
13923
|
+
fileContent
|
|
13924
|
+
}), convertToHermesFormat(rulesyncMcp.getMcpServers()));
|
|
13747
13925
|
return new HermesagentMcp({
|
|
13748
13926
|
outputRoot,
|
|
13749
13927
|
relativeDirPath: paths.relativeDirPath,
|
|
13750
13928
|
relativeFilePath: paths.relativeFilePath,
|
|
13751
|
-
fileContent:
|
|
13929
|
+
fileContent: applySharedConfigPatch({
|
|
13930
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
13931
|
+
feature: "mcp",
|
|
13932
|
+
existingContent: fileContent,
|
|
13933
|
+
patch: { mcp_servers: merged.mcp_servers }
|
|
13934
|
+
}),
|
|
13752
13935
|
validate,
|
|
13753
13936
|
global
|
|
13754
13937
|
});
|
|
@@ -14913,28 +15096,6 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
14913
15096
|
}
|
|
14914
15097
|
};
|
|
14915
15098
|
//#endregion
|
|
14916
|
-
//#region src/features/shared/takt-config.ts
|
|
14917
|
-
/**
|
|
14918
|
-
* Parse a Takt `config.yaml` into a plain object, treating an empty file as `{}`.
|
|
14919
|
-
*
|
|
14920
|
-
* Shared by the Takt adapters that read-modify-write the same `config.yaml`
|
|
14921
|
-
* (mcp, permissions, ...). Uses `isPlainObject` (not `isRecord`) so class
|
|
14922
|
-
* instances are rejected for prototype-pollution hardening; a YAML mapping
|
|
14923
|
-
* always parses to a plain object.
|
|
14924
|
-
*/
|
|
14925
|
-
function parseTaktConfig(fileContent, relativeDirPath, relativeFilePath) {
|
|
14926
|
-
const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
|
|
14927
|
-
let parsed;
|
|
14928
|
-
try {
|
|
14929
|
-
parsed = fileContent.trim() === "" ? {} : (0, js_yaml.load)(fileContent);
|
|
14930
|
-
} catch (error) {
|
|
14931
|
-
throw new Error(`Failed to parse Takt config at ${configPath}: ${formatError(error)}`, { cause: error });
|
|
14932
|
-
}
|
|
14933
|
-
if (parsed === void 0 || parsed === null) return {};
|
|
14934
|
-
if (!isPlainObject(parsed)) throw new Error(`Failed to parse Takt config at ${configPath}: expected a YAML mapping`);
|
|
14935
|
-
return parsed;
|
|
14936
|
-
}
|
|
14937
|
-
//#endregion
|
|
14938
15099
|
//#region src/features/mcp/takt-mcp.ts
|
|
14939
15100
|
/**
|
|
14940
15101
|
* MCP adapter for Takt (`.takt/config.yaml` project / `~/.takt/config.yaml`
|
|
@@ -15000,17 +15161,20 @@ var TaktMcp = class TaktMcp extends ToolMcp {
|
|
|
15000
15161
|
}
|
|
15001
15162
|
static async fromRulesyncMcp({ outputRoot = process.cwd(), rulesyncMcp, validate = true, global = false }) {
|
|
15002
15163
|
const paths = TaktMcp.getSettablePaths({ global });
|
|
15003
|
-
const
|
|
15164
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
15165
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
15004
15166
|
const allowlist = deriveTransportAllowlist(rulesyncMcp.getMcpServers());
|
|
15005
|
-
const merged = {
|
|
15006
|
-
...config,
|
|
15007
|
-
[TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist
|
|
15008
|
-
};
|
|
15009
15167
|
return new TaktMcp({
|
|
15010
15168
|
outputRoot,
|
|
15011
15169
|
relativeDirPath: paths.relativeDirPath,
|
|
15012
15170
|
relativeFilePath: paths.relativeFilePath,
|
|
15013
|
-
fileContent: (
|
|
15171
|
+
fileContent: applySharedConfigPatch({
|
|
15172
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
15173
|
+
feature: "mcp",
|
|
15174
|
+
existingContent,
|
|
15175
|
+
patch: { [TAKT_WORKFLOW_MCP_SERVERS_KEY]: allowlist },
|
|
15176
|
+
filePath
|
|
15177
|
+
}),
|
|
15014
15178
|
validate,
|
|
15015
15179
|
global
|
|
15016
15180
|
});
|
|
@@ -15455,7 +15619,7 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
|
|
|
15455
15619
|
["augmentcode", {
|
|
15456
15620
|
class: AugmentcodeMcp,
|
|
15457
15621
|
meta: {
|
|
15458
|
-
supportsProject:
|
|
15622
|
+
supportsProject: true,
|
|
15459
15623
|
supportsGlobal: true,
|
|
15460
15624
|
supportsEnabledTools: false,
|
|
15461
15625
|
supportsDisabledTools: false
|
|
@@ -16037,15 +16201,97 @@ const JuniePermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
|
16037
16201
|
defaultBehavior: zod_mini.z.optional(zod_mini.z.string())
|
|
16038
16202
|
});
|
|
16039
16203
|
/**
|
|
16204
|
+
* Tool-scoped override block for Takt. Takt's `config.yaml` carries two
|
|
16205
|
+
* permission surfaces the canonical coarse-mode mapping can't express:
|
|
16206
|
+
* `step_permission_overrides` — a per-workflow-step map (`<step>` →
|
|
16207
|
+
* `readonly`/`edit`/`full`) that lives inside the active provider profile
|
|
16208
|
+
* alongside `default_permission_mode` and layers on top of it at that step; and
|
|
16209
|
+
* `provider_options` — a top-level, per-provider table of sandbox/network knobs
|
|
16210
|
+
* orthogonal to the permission mode (e.g. `codex.network_access`,
|
|
16211
|
+
* `claude.sandbox.allow_unsandboxed_commands`, `opencode.allowed_tools`). Fields
|
|
16212
|
+
* placed here are merged into `config.yaml` and emitted only for Takt, while the
|
|
16213
|
+
* shared `permission` block continues to drive `default_permission_mode`. Kept
|
|
16214
|
+
* `looseObject` (verbatim passthrough); Takt validates its own value sets (e.g.
|
|
16215
|
+
* `provider_options.<p>.base_url` must be loopback). Both project and global
|
|
16216
|
+
* scope are supported.
|
|
16217
|
+
*
|
|
16218
|
+
* Note: Takt's config loader hard-rejects unknown top-level keys, so only keys
|
|
16219
|
+
* Takt actually recognizes belong here. `required_permission_mode` is NOT one —
|
|
16220
|
+
* it is a per-step field of the workflow YAML (not `config.yaml`), so it is out
|
|
16221
|
+
* of scope for this override.
|
|
16222
|
+
*
|
|
16223
|
+
* @example
|
|
16224
|
+
* { "step_permission_overrides": { "ai_review": "readonly" }, "provider_options": { "codex": { "network_access": true } } }
|
|
16225
|
+
*/
|
|
16226
|
+
const TaktPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16227
|
+
step_permission_overrides: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.string())),
|
|
16228
|
+
provider_options: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
16229
|
+
});
|
|
16230
|
+
/**
|
|
16231
|
+
* Tool-scoped override block for Amp. Amp's `amp.permissions` array and sibling
|
|
16232
|
+
* settings carry shapes the canonical per-command allow/ask/deny model can't
|
|
16233
|
+
* express, so they are authored here and merged into the shared Amp settings
|
|
16234
|
+
* file, while the shared `permission` block continues to drive the canonical
|
|
16235
|
+
* `amp.permissions` (allow/ask/reject) + `amp.tools.disable` entries:
|
|
16236
|
+
* - `permissions` — extra `amp.permissions` entries with non-`cmd` matchers
|
|
16237
|
+
* (`path`/`url`/`query`/…), regex/array match values, `context`
|
|
16238
|
+
* (`thread`/`subagent`), `delegate` (+`to`), or `reject` (+`message`). These
|
|
16239
|
+
* are appended AFTER the canonical-generated entries (Amp is first-match-wins,
|
|
16240
|
+
* so generated allow/ask/reject rules take precedence; authored entries act as
|
|
16241
|
+
* later fallbacks), preserving author order.
|
|
16242
|
+
* - `mcpPermissions` — Amp's `amp.mcpPermissions` array (`{ matches, action }`).
|
|
16243
|
+
* - `guardedFiles` — `amp.guardedFiles.allowlist` (globs allowed without
|
|
16244
|
+
* confirmation).
|
|
16245
|
+
* - `dangerouslyAllowAll` — `amp.dangerouslyAllowAll` (disable all confirmation).
|
|
16246
|
+
* Kept `looseObject` (verbatim passthrough). Both project and global scope are
|
|
16247
|
+
* supported.
|
|
16248
|
+
*
|
|
16249
|
+
* @example
|
|
16250
|
+
* { "dangerouslyAllowAll": false, "guardedFiles": { "allowlist": ["docs/**"] },
|
|
16251
|
+
* "permissions": [{ "tool": "Bash", "action": "delegate", "to": "approve.sh" }] }
|
|
16252
|
+
*/
|
|
16253
|
+
const AmpPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16254
|
+
permissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
|
|
16255
|
+
tool: zod_mini.z.string(),
|
|
16256
|
+
action: zod_mini.z.string()
|
|
16257
|
+
}))),
|
|
16258
|
+
mcpPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({}))),
|
|
16259
|
+
guardedFiles: zod_mini.z.optional(zod_mini.z.looseObject({ allowlist: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
|
|
16260
|
+
dangerouslyAllowAll: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16261
|
+
});
|
|
16262
|
+
/**
|
|
16263
|
+
* Tool-scoped override block for the Google Antigravity CLI. Antigravity's CLI
|
|
16264
|
+
* `settings.json` carries two global autonomy/sandbox knobs outside the
|
|
16265
|
+
* `permissions.allow/ask/deny` arrays rulesync manages: `toolPermission` (the
|
|
16266
|
+
* global autonomy preset — `request-review` (default) / `proceed-in-sandbox` /
|
|
16267
|
+
* `always-proceed` / `strict`) and `enableTerminalSandbox` (a boolean confining
|
|
16268
|
+
* agent-run commands to OS containment). Antigravity applies the allow/deny
|
|
16269
|
+
* lists as per-rule exceptions to the preset at runtime, so rulesync only
|
|
16270
|
+
* authors these keys verbatim — no precedence modeling is needed on our side.
|
|
16271
|
+
* Fields placed here are merged onto the top level of
|
|
16272
|
+
* `~/.gemini/antigravity-cli/settings.json` (global-only) and emitted only for
|
|
16273
|
+
* the CLI. The Antigravity IDE exposes the same concepts through a GUI (no
|
|
16274
|
+
* documented JSON schema), so this override does NOT apply to `antigravity-ide`.
|
|
16275
|
+
* Verified against https://antigravity.google/docs/cli/reference and
|
|
16276
|
+
* https://antigravity.google/docs/cli/sandbox.
|
|
16277
|
+
*
|
|
16278
|
+
* @example
|
|
16279
|
+
* { "toolPermission": "strict", "enableTerminalSandbox": true }
|
|
16280
|
+
*/
|
|
16281
|
+
const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
16282
|
+
toolPermission: zod_mini.z.optional(zod_mini.z.string()),
|
|
16283
|
+
enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
|
|
16284
|
+
});
|
|
16285
|
+
/**
|
|
16040
16286
|
* Permissions configuration.
|
|
16041
16287
|
* Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
|
|
16042
16288
|
* Values are pattern-to-action mappings for that tool category.
|
|
16043
16289
|
*
|
|
16044
16290
|
* The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
|
|
16045
|
-
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie
|
|
16046
|
-
* overrides consumed only by their
|
|
16047
|
-
* `*PermissionsOverrideSchema`); every
|
|
16048
|
-
* block and ignores them.
|
|
16291
|
+
* `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
|
|
16292
|
+
* `antigravity-cli` keys are tool-scoped overrides consumed only by their
|
|
16293
|
+
* respective translator (see the matching `*PermissionsOverrideSchema`); every
|
|
16294
|
+
* other tool reads the shared `permission` block and ignores them.
|
|
16049
16295
|
*
|
|
16050
16296
|
* @example
|
|
16051
16297
|
* {
|
|
@@ -16066,7 +16312,10 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
|
|
|
16066
16312
|
reasonix: zod_mini.z.optional(ReasonixPermissionsOverrideSchema),
|
|
16067
16313
|
factorydroid: zod_mini.z.optional(FactorydroidPermissionsOverrideSchema),
|
|
16068
16314
|
warp: zod_mini.z.optional(WarpPermissionsOverrideSchema),
|
|
16069
|
-
junie: zod_mini.z.optional(JuniePermissionsOverrideSchema)
|
|
16315
|
+
junie: zod_mini.z.optional(JuniePermissionsOverrideSchema),
|
|
16316
|
+
takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
|
|
16317
|
+
amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
|
|
16318
|
+
"antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema)
|
|
16070
16319
|
});
|
|
16071
16320
|
/**
|
|
16072
16321
|
* Full permissions file schema including optional $schema field.
|
|
@@ -16178,6 +16427,39 @@ const AMP_TOOLS_DISABLE_KEY = "amp.tools.disable";
|
|
|
16178
16427
|
* Reference: https://ampcode.com/manual ("amp.permissions").
|
|
16179
16428
|
*/
|
|
16180
16429
|
const AMP_PERMISSIONS_KEY = "amp.permissions";
|
|
16430
|
+
/**
|
|
16431
|
+
* The `amp.guardedFiles.allowlist` array (file globs allowed without
|
|
16432
|
+
* confirmation), `amp.dangerouslyAllowAll` boolean (disable all confirmation),
|
|
16433
|
+
* and `amp.mcpPermissions` array — sibling settings authored through the `amp`
|
|
16434
|
+
* permissions override. Reference: https://ampcode.com/manual.
|
|
16435
|
+
*/
|
|
16436
|
+
const AMP_GUARDED_FILES_ALLOWLIST_KEY = "amp.guardedFiles.allowlist";
|
|
16437
|
+
const AMP_DANGEROUSLY_ALLOW_ALL_KEY = "amp.dangerouslyAllowAll";
|
|
16438
|
+
const AMP_MCP_PERMISSIONS_KEY = "amp.mcpPermissions";
|
|
16439
|
+
/**
|
|
16440
|
+
* The read-only `cmd` string of an entry's `matches`, or `undefined` when the
|
|
16441
|
+
* matcher is absent or uses a non-`cmd` key / non-string value.
|
|
16442
|
+
*/
|
|
16443
|
+
function ampMatchesCmd(entry) {
|
|
16444
|
+
const cmd = entry.matches?.cmd;
|
|
16445
|
+
return typeof cmd === "string" ? cmd : void 0;
|
|
16446
|
+
}
|
|
16447
|
+
/**
|
|
16448
|
+
* Whether an `amp.permissions` entry is fully expressible in the canonical
|
|
16449
|
+
* per-command allow/ask/deny model: a plain `allow`/`ask`/`reject` action with
|
|
16450
|
+
* no `context`/`message`/`to` and a matcher that is either absent or exactly a
|
|
16451
|
+
* single string `cmd`. Everything else (non-`cmd` matchers, regex/array match
|
|
16452
|
+
* values, `delegate`, `reject`+`message`, `context`) is routed to the `amp`
|
|
16453
|
+
* override verbatim instead of being flattened with loss or dropped.
|
|
16454
|
+
*/
|
|
16455
|
+
function isCanonicalAmpEntry(entry) {
|
|
16456
|
+
if (entry.action === "delegate") return false;
|
|
16457
|
+
if (entry.context !== void 0 || entry.message !== void 0 || entry.to !== void 0) return false;
|
|
16458
|
+
const matches = entry.matches;
|
|
16459
|
+
if (matches === void 0) return true;
|
|
16460
|
+
const keys = Object.keys(matches);
|
|
16461
|
+
return keys.length === 0 || keys.length === 1 && typeof matches.cmd === "string";
|
|
16462
|
+
}
|
|
16181
16463
|
function parseAmpSettings(fileContent) {
|
|
16182
16464
|
const errors = [];
|
|
16183
16465
|
const parsed = (0, jsonc_parser.parse)(fileContent || "{}", errors, { allowTrailingComma: true });
|
|
@@ -16193,10 +16475,20 @@ function toDisableList(value) {
|
|
|
16193
16475
|
return value.filter((entry) => typeof entry === "string");
|
|
16194
16476
|
}
|
|
16195
16477
|
/**
|
|
16478
|
+
* Read `amp.guardedFiles.allowlist` (an array of file glob strings) from parsed
|
|
16479
|
+
* settings, returning `undefined` when the key is absent or not an array so the
|
|
16480
|
+
* override omits it.
|
|
16481
|
+
*/
|
|
16482
|
+
function extractAmpGuardedAllowlist(value) {
|
|
16483
|
+
if (!Array.isArray(value)) return void 0;
|
|
16484
|
+
return value.filter((entry) => typeof entry === "string");
|
|
16485
|
+
}
|
|
16486
|
+
/**
|
|
16196
16487
|
* Read an `amp.permissions` array from untrusted parsed settings. Only entries
|
|
16197
|
-
* whose
|
|
16198
|
-
*
|
|
16199
|
-
*
|
|
16488
|
+
* whose `tool`/`action` rulesync understands are retained; every other key —
|
|
16489
|
+
* including the full `matches` object (so non-`cmd` matchers, regex/array
|
|
16490
|
+
* values, `context`, `to`, `message`) — is kept verbatim so it survives a
|
|
16491
|
+
* round-trip.
|
|
16200
16492
|
*/
|
|
16201
16493
|
function toPermissionsList(value) {
|
|
16202
16494
|
if (!Array.isArray(value)) return [];
|
|
@@ -16206,14 +16498,11 @@ function toPermissionsList(value) {
|
|
|
16206
16498
|
const { tool, action } = raw;
|
|
16207
16499
|
if (typeof tool !== "string" || typeof action !== "string") continue;
|
|
16208
16500
|
if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
|
|
16209
|
-
const matches = raw.matches;
|
|
16210
|
-
let normalizedMatches;
|
|
16211
|
-
if (isPlainObject(matches) && typeof matches.cmd === "string") normalizedMatches = { cmd: matches.cmd };
|
|
16212
16501
|
entries.push({
|
|
16213
16502
|
...raw,
|
|
16214
16503
|
tool,
|
|
16215
16504
|
action,
|
|
16216
|
-
...
|
|
16505
|
+
...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
|
|
16217
16506
|
});
|
|
16218
16507
|
}
|
|
16219
16508
|
return entries;
|
|
@@ -16238,6 +16527,13 @@ function toPermissionsList(value) {
|
|
|
16238
16527
|
* The settings file is shared with the MCP feature (`amp.mcpServers`), so reads
|
|
16239
16528
|
* and writes merge into the existing JSON rather than overwriting it, and the
|
|
16240
16529
|
* file is never deleted.
|
|
16530
|
+
*
|
|
16531
|
+
* Amp shapes with no canonical category are authored and round-tripped through
|
|
16532
|
+
* the `amp` permissions override (see `AmpPermissionsOverrideSchema`): extra
|
|
16533
|
+
* `amp.permissions` entries with non-`cmd` matchers / `context` / `delegate` /
|
|
16534
|
+
* `reject`+`message` (appended after the generated canonical entries), plus the
|
|
16535
|
+
* sibling `amp.mcpPermissions`, `amp.guardedFiles.allowlist`, and
|
|
16536
|
+
* `amp.dangerouslyAllowAll` settings.
|
|
16241
16537
|
*/
|
|
16242
16538
|
var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
16243
16539
|
constructor(params) {
|
|
@@ -16307,15 +16603,25 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16307
16603
|
const jsonDir = (0, node_path.join)(outputRoot, basePaths.relativeDirPath);
|
|
16308
16604
|
const { fileContent, relativeFilePath } = await this.resolveSettingsFile(jsonDir);
|
|
16309
16605
|
const json = fileContent ? parseAmpSettings(fileContent) : {};
|
|
16310
|
-
const
|
|
16311
|
-
const
|
|
16606
|
+
const config = rulesyncPermissions.getJson();
|
|
16607
|
+
const { disable, permissions } = convertRulesyncToAmp(config);
|
|
16608
|
+
const override = config.amp;
|
|
16609
|
+
const authoredExtras = override?.permissions ? toPermissionsList(override.permissions) : [];
|
|
16610
|
+
const preservedDelegates = override?.permissions ? [] : toPermissionsList(json[AMP_PERMISSIONS_KEY]).filter((entry) => entry.action === "delegate");
|
|
16312
16611
|
const newJson = {
|
|
16313
16612
|
...json,
|
|
16314
16613
|
[AMP_TOOLS_DISABLE_KEY]: disable
|
|
16315
16614
|
};
|
|
16316
|
-
const mergedPermissions = [
|
|
16615
|
+
const mergedPermissions = mergeAmpPermissions([
|
|
16616
|
+
...permissions,
|
|
16617
|
+
...authoredExtras,
|
|
16618
|
+
...preservedDelegates
|
|
16619
|
+
]);
|
|
16317
16620
|
if (mergedPermissions.length > 0) newJson[AMP_PERMISSIONS_KEY] = mergedPermissions;
|
|
16318
16621
|
else delete newJson[AMP_PERMISSIONS_KEY];
|
|
16622
|
+
if (override?.guardedFiles?.allowlist !== void 0) newJson[AMP_GUARDED_FILES_ALLOWLIST_KEY] = override.guardedFiles.allowlist;
|
|
16623
|
+
if (override?.dangerouslyAllowAll !== void 0) newJson[AMP_DANGEROUSLY_ALLOW_ALL_KEY] = override.dangerouslyAllowAll;
|
|
16624
|
+
if (override?.mcpPermissions !== void 0) newJson[AMP_MCP_PERMISSIONS_KEY] = override.mcpPermissions;
|
|
16319
16625
|
return new AmpPermissions({
|
|
16320
16626
|
outputRoot,
|
|
16321
16627
|
relativeDirPath: basePaths.relativeDirPath,
|
|
@@ -16326,11 +16632,22 @@ var AmpPermissions = class AmpPermissions extends ToolPermissions {
|
|
|
16326
16632
|
}
|
|
16327
16633
|
toRulesyncPermissions() {
|
|
16328
16634
|
const json = parseAmpSettings(this.getFileContent());
|
|
16635
|
+
const allPermissions = toPermissionsList(json[AMP_PERMISSIONS_KEY]);
|
|
16636
|
+
const canonicalEntries = allPermissions.filter(isCanonicalAmpEntry);
|
|
16637
|
+
const overrideEntries = allPermissions.filter((entry) => !isCanonicalAmpEntry(entry));
|
|
16329
16638
|
const config = convertAmpToRulesync({
|
|
16330
16639
|
disable: toDisableList(json[AMP_TOOLS_DISABLE_KEY]),
|
|
16331
|
-
permissions:
|
|
16332
|
-
});
|
|
16333
|
-
|
|
16640
|
+
permissions: canonicalEntries
|
|
16641
|
+
});
|
|
16642
|
+
const ampOverride = {};
|
|
16643
|
+
if (overrideEntries.length > 0) ampOverride.permissions = overrideEntries;
|
|
16644
|
+
if (Array.isArray(json[AMP_MCP_PERMISSIONS_KEY])) ampOverride.mcpPermissions = json[AMP_MCP_PERMISSIONS_KEY];
|
|
16645
|
+
const allowlist = extractAmpGuardedAllowlist(json[AMP_GUARDED_FILES_ALLOWLIST_KEY]);
|
|
16646
|
+
if (allowlist !== void 0) ampOverride.guardedFiles = { allowlist };
|
|
16647
|
+
if (typeof json[AMP_DANGEROUSLY_ALLOW_ALL_KEY] === "boolean") ampOverride.dangerouslyAllowAll = json[AMP_DANGEROUSLY_ALLOW_ALL_KEY];
|
|
16648
|
+
const result = { ...config };
|
|
16649
|
+
if (Object.keys(ampOverride).length > 0) result.amp = ampOverride;
|
|
16650
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16334
16651
|
}
|
|
16335
16652
|
validate() {
|
|
16336
16653
|
try {
|
|
@@ -16377,6 +16694,37 @@ const ACTION_PRIORITY = {
|
|
|
16377
16694
|
allow: 2
|
|
16378
16695
|
};
|
|
16379
16696
|
/**
|
|
16697
|
+
* Fail-closed action priority for merging the full `amp.permissions` list
|
|
16698
|
+
* (generated + authored/preserved). `reject` leads so no `allow` can shadow it
|
|
16699
|
+
* under first-match-wins; `delegate` (which defers to an external approver, so
|
|
16700
|
+
* it never auto-allows) trails as the final fallback.
|
|
16701
|
+
*/
|
|
16702
|
+
const MERGE_ACTION_PRIORITY = {
|
|
16703
|
+
reject: 0,
|
|
16704
|
+
ask: 1,
|
|
16705
|
+
allow: 2,
|
|
16706
|
+
delegate: 3
|
|
16707
|
+
};
|
|
16708
|
+
/**
|
|
16709
|
+
* Stable fail-closed merge of generated and authored `amp.permissions` entries:
|
|
16710
|
+
* order by {@link MERGE_ACTION_PRIORITY} only, preserving each source's relative
|
|
16711
|
+
* order within an action class. Guarantees an authored `reject` cannot be
|
|
16712
|
+
* shadowed by a generated catch-all `allow`.
|
|
16713
|
+
*/
|
|
16714
|
+
function mergeAmpPermissions(entries) {
|
|
16715
|
+
const decorated = entries.map((entry, index) => ({
|
|
16716
|
+
entry,
|
|
16717
|
+
index
|
|
16718
|
+
}));
|
|
16719
|
+
decorated.sort((a, b) => {
|
|
16720
|
+
const ap = MERGE_ACTION_PRIORITY[a.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16721
|
+
const bp = MERGE_ACTION_PRIORITY[b.entry.action] ?? MERGE_ACTION_PRIORITY.allow;
|
|
16722
|
+
if (ap !== bp) return ap - bp;
|
|
16723
|
+
return a.index - b.index;
|
|
16724
|
+
});
|
|
16725
|
+
return decorated.map((d) => d.entry);
|
|
16726
|
+
}
|
|
16727
|
+
/**
|
|
16380
16728
|
* Convert a rulesync permissions config into Amp's two permission surfaces.
|
|
16381
16729
|
*
|
|
16382
16730
|
* For each `(category, pattern, action)` — where the category name IS the Amp
|
|
@@ -16441,14 +16789,14 @@ function sortAmpPermissions(entries) {
|
|
|
16441
16789
|
const ap = ACTION_PRIORITY[a.entry.action] ?? 0;
|
|
16442
16790
|
const bp = ACTION_PRIORITY[b.entry.action] ?? 0;
|
|
16443
16791
|
if (ap !== bp) return ap - bp;
|
|
16444
|
-
const aHasCmd = a.entry
|
|
16445
|
-
const bHasCmd = b.entry
|
|
16792
|
+
const aHasCmd = ampMatchesCmd(a.entry) !== void 0 ? 0 : 1;
|
|
16793
|
+
const bHasCmd = ampMatchesCmd(b.entry) !== void 0 ? 0 : 1;
|
|
16446
16794
|
if (aHasCmd !== bHasCmd) return aHasCmd - bHasCmd;
|
|
16447
16795
|
const at = a.entry.tool;
|
|
16448
16796
|
const bt = b.entry.tool;
|
|
16449
16797
|
if (at !== bt) return at < bt ? -1 : 1;
|
|
16450
|
-
const ac = a.entry
|
|
16451
|
-
const bc = b.entry
|
|
16798
|
+
const ac = ampMatchesCmd(a.entry) ?? "";
|
|
16799
|
+
const bc = ampMatchesCmd(b.entry) ?? "";
|
|
16452
16800
|
if (ac !== bc) return ac < bc ? -1 : 1;
|
|
16453
16801
|
return a.index - b.index;
|
|
16454
16802
|
});
|
|
@@ -16487,7 +16835,7 @@ function convertAmpToRulesync({ disable, permissions }) {
|
|
|
16487
16835
|
for (const tool of disable) assign(tool, "*", "deny");
|
|
16488
16836
|
for (const entry of permissions) {
|
|
16489
16837
|
if (entry.action === "delegate") continue;
|
|
16490
|
-
const pattern = entry
|
|
16838
|
+
const pattern = ampMatchesCmd(entry) ?? "*";
|
|
16491
16839
|
const action = entry.action === "reject" ? "deny" : entry.action === "ask" ? "ask" : "allow";
|
|
16492
16840
|
assign(entry.tool, pattern, action);
|
|
16493
16841
|
}
|
|
@@ -16569,6 +16917,11 @@ function buildPermissionEntry$1(toolName, pattern) {
|
|
|
16569
16917
|
* Permissions are written to the global `~/.gemini/antigravity-cli/settings.json`
|
|
16570
16918
|
* file (global scope only). The file holds other CLI settings besides
|
|
16571
16919
|
* permissions, so it is never deleted.
|
|
16920
|
+
*
|
|
16921
|
+
* Two CLI-only autonomy/sandbox knobs outside the allow/ask/deny arrays —
|
|
16922
|
+
* `toolPermission` (the global autonomy preset) and `enableTerminalSandbox` — are
|
|
16923
|
+
* authored and round-tripped through the `antigravity-cli` permissions override
|
|
16924
|
+
* (see `AntigravityCliPermissionsOverrideSchema`).
|
|
16572
16925
|
*/
|
|
16573
16926
|
var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPermissions {
|
|
16574
16927
|
constructor(params) {
|
|
@@ -16629,6 +16982,9 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16629
16982
|
...settings,
|
|
16630
16983
|
permissions: mergedPermissions
|
|
16631
16984
|
};
|
|
16985
|
+
const override = config["antigravity-cli"];
|
|
16986
|
+
if (override?.toolPermission !== void 0) merged.toolPermission = override.toolPermission;
|
|
16987
|
+
if (override?.enableTerminalSandbox !== void 0) merged.enableTerminalSandbox = override.enableTerminalSandbox;
|
|
16632
16988
|
const fileContent = JSON.stringify(merged, null, 2);
|
|
16633
16989
|
return new AntigravityCliPermissions({
|
|
16634
16990
|
outputRoot,
|
|
@@ -16652,7 +17008,12 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
|
|
|
16652
17008
|
ask: permissions.ask ?? [],
|
|
16653
17009
|
deny: permissions.deny ?? []
|
|
16654
17010
|
});
|
|
16655
|
-
|
|
17011
|
+
const override = {};
|
|
17012
|
+
if (typeof settings.toolPermission === "string") override.toolPermission = settings.toolPermission;
|
|
17013
|
+
if (typeof settings.enableTerminalSandbox === "boolean") override.enableTerminalSandbox = settings.enableTerminalSandbox;
|
|
17014
|
+
const result = { ...config };
|
|
17015
|
+
if (Object.keys(override).length > 0) result["antigravity-cli"] = override;
|
|
17016
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
16656
17017
|
}
|
|
16657
17018
|
validate() {
|
|
16658
17019
|
return {
|
|
@@ -19338,14 +19699,21 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
19338
19699
|
return true;
|
|
19339
19700
|
}
|
|
19340
19701
|
setFileContent(fileContent) {
|
|
19341
|
-
|
|
19342
|
-
|
|
19343
|
-
|
|
19344
|
-
|
|
19345
|
-
|
|
19702
|
+
this.fileContent = applySharedConfigPatch({
|
|
19703
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
19704
|
+
feature: "permissions",
|
|
19705
|
+
existingContent: fileContent,
|
|
19706
|
+
patch: parseSharedConfig({
|
|
19707
|
+
format: "yaml",
|
|
19708
|
+
fileContent: this.fileContent
|
|
19709
|
+
})
|
|
19710
|
+
});
|
|
19346
19711
|
}
|
|
19347
19712
|
toRulesyncPermissions() {
|
|
19348
|
-
const config =
|
|
19713
|
+
const config = parseSharedConfig({
|
|
19714
|
+
format: "yaml",
|
|
19715
|
+
fileContent: this.getFileContent()
|
|
19716
|
+
});
|
|
19349
19717
|
const permissions = config.permissions && typeof config.permissions === "object" ? config.permissions.rulesync : {};
|
|
19350
19718
|
return new RulesyncPermissions({
|
|
19351
19719
|
relativeDirPath: "",
|
|
@@ -19366,11 +19734,17 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
|
|
|
19366
19734
|
enabled: true,
|
|
19367
19735
|
domains: webfetchDeny
|
|
19368
19736
|
} };
|
|
19369
|
-
if (permissions.hermes && typeof permissions.hermes === "object") config =
|
|
19737
|
+
if (permissions.hermes && typeof permissions.hermes === "object") config = mergeSharedConfigDeep({
|
|
19738
|
+
base: config,
|
|
19739
|
+
patch: permissions.hermes
|
|
19740
|
+
});
|
|
19370
19741
|
config.permissions = { rulesync: permissions };
|
|
19371
19742
|
return new HermesagentPermissions({
|
|
19372
19743
|
outputRoot,
|
|
19373
|
-
fileContent:
|
|
19744
|
+
fileContent: stringifySharedConfig({
|
|
19745
|
+
format: "yaml",
|
|
19746
|
+
document: config
|
|
19747
|
+
})
|
|
19374
19748
|
});
|
|
19375
19749
|
}
|
|
19376
19750
|
};
|
|
@@ -20926,6 +21300,8 @@ function isPermissionAction(value) {
|
|
|
20926
21300
|
const TAKT_PROVIDER_KEY = "provider";
|
|
20927
21301
|
const TAKT_PROVIDER_PROFILES_KEY = "provider_profiles";
|
|
20928
21302
|
const TAKT_DEFAULT_PERMISSION_MODE_KEY = "default_permission_mode";
|
|
21303
|
+
const TAKT_STEP_PERMISSION_OVERRIDES_KEY = "step_permission_overrides";
|
|
21304
|
+
const TAKT_PROVIDER_OPTIONS_KEY = "provider_options";
|
|
20929
21305
|
const TAKT_DEFAULT_PROVIDER = "claude";
|
|
20930
21306
|
const CATCH_ALL_PATTERN = "*";
|
|
20931
21307
|
/**
|
|
@@ -20955,10 +21331,16 @@ const CATCH_ALL_PATTERN = "*";
|
|
|
20955
21331
|
* `bash: { "*": "deny" }`. These round-trip the generate mapping.
|
|
20956
21332
|
*
|
|
20957
21333
|
* Both project and global scope are supported. The shared config is merged in
|
|
20958
|
-
* place:
|
|
20959
|
-
*
|
|
20960
|
-
*
|
|
20961
|
-
*
|
|
21334
|
+
* place: `provider_profiles.<provider>.default_permission_mode` is set from the
|
|
21335
|
+
* derived mode; every other provider profile and all other top-level keys are
|
|
21336
|
+
* preserved. The file is never deleted.
|
|
21337
|
+
*
|
|
21338
|
+
* Two Takt-specific surfaces with no canonical category round-trip through the
|
|
21339
|
+
* `takt` override (see `TaktPermissionsOverrideSchema`):
|
|
21340
|
+
* `step_permission_overrides` (a per-step mode map inside the active provider
|
|
21341
|
+
* profile, layered on top of `default_permission_mode`) and `provider_options`
|
|
21342
|
+
* (a top-level per-provider sandbox/network table). Both are authored on
|
|
21343
|
+
* generate and re-extracted on import.
|
|
20962
21344
|
*/
|
|
20963
21345
|
var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
20964
21346
|
constructor(params) {
|
|
@@ -20990,37 +21372,62 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
|
|
|
20990
21372
|
}
|
|
20991
21373
|
static async fromRulesyncPermissions({ outputRoot = process.cwd(), rulesyncPermissions, global = false }) {
|
|
20992
21374
|
const paths = TaktPermissions.getSettablePaths({ global });
|
|
20993
|
-
const
|
|
21375
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
21376
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "";
|
|
21377
|
+
const config = parseSharedConfig({
|
|
21378
|
+
format: "yaml",
|
|
21379
|
+
fileContent: existingContent,
|
|
21380
|
+
filePath,
|
|
21381
|
+
invalidRootPolicy: "error"
|
|
21382
|
+
});
|
|
21383
|
+
const rulesyncJson = rulesyncPermissions.getJson();
|
|
20994
21384
|
const provider = resolveActiveProvider(config);
|
|
20995
|
-
const mode = deriveTaktPermissionMode(
|
|
20996
|
-
const
|
|
20997
|
-
const
|
|
20998
|
-
const
|
|
20999
|
-
|
|
21000
|
-
[TAKT_PROVIDER_PROFILES_KEY]: {
|
|
21001
|
-
|
|
21002
|
-
[
|
|
21003
|
-
|
|
21004
|
-
|
|
21005
|
-
}
|
|
21006
|
-
}
|
|
21385
|
+
const mode = deriveTaktPermissionMode(rulesyncJson);
|
|
21386
|
+
const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
|
|
21387
|
+
const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21388
|
+
const overrideProviderOptions = isPlainObject(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21389
|
+
const patch = {
|
|
21390
|
+
[TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
|
|
21391
|
+
[TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
|
|
21392
|
+
...stepOverrides !== void 0 && { [TAKT_STEP_PERMISSION_OVERRIDES_KEY]: stepOverrides }
|
|
21393
|
+
} },
|
|
21394
|
+
...overrideProviderOptions !== void 0 && { [TAKT_PROVIDER_OPTIONS_KEY]: overrideProviderOptions }
|
|
21007
21395
|
};
|
|
21008
21396
|
return new TaktPermissions({
|
|
21009
21397
|
outputRoot,
|
|
21010
21398
|
relativeDirPath: paths.relativeDirPath,
|
|
21011
21399
|
relativeFilePath: paths.relativeFilePath,
|
|
21012
|
-
fileContent: (
|
|
21400
|
+
fileContent: applySharedConfigPatch({
|
|
21401
|
+
fileKey: TAKT_CONFIG_SHARED_FILE_KEY,
|
|
21402
|
+
feature: "permissions",
|
|
21403
|
+
existingContent,
|
|
21404
|
+
patch,
|
|
21405
|
+
filePath
|
|
21406
|
+
}),
|
|
21013
21407
|
validate: true,
|
|
21014
21408
|
global
|
|
21015
21409
|
});
|
|
21016
21410
|
}
|
|
21017
21411
|
toRulesyncPermissions() {
|
|
21018
|
-
const config =
|
|
21412
|
+
const config = parseSharedConfig({
|
|
21413
|
+
format: "yaml",
|
|
21414
|
+
fileContent: this.getFileContent(),
|
|
21415
|
+
filePath: (0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath()),
|
|
21416
|
+
invalidRootPolicy: "error"
|
|
21417
|
+
});
|
|
21019
21418
|
const provider = resolveActiveProvider(config);
|
|
21020
21419
|
const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
|
|
21021
|
-
const
|
|
21420
|
+
const profile = isPlainObject(profiles[provider]) ? profiles[provider] : {};
|
|
21421
|
+
const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
|
|
21022
21422
|
const rulesyncConfig = taktModeToRulesyncConfig(mode);
|
|
21023
|
-
|
|
21423
|
+
const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
|
|
21424
|
+
const providerOptions = isPlainObject(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
|
|
21425
|
+
const taktOverride = {};
|
|
21426
|
+
if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
|
|
21427
|
+
if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
|
|
21428
|
+
const result = { ...rulesyncConfig };
|
|
21429
|
+
if (Object.keys(taktOverride).length > 0) result.takt = taktOverride;
|
|
21430
|
+
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
21024
21431
|
}
|
|
21025
21432
|
validate() {
|
|
21026
21433
|
return {
|
|
@@ -29915,14 +30322,21 @@ def register(ctx):
|
|
|
29915
30322
|
`;
|
|
29916
30323
|
}
|
|
29917
30324
|
function getEnabledPluginConfigContent(currentContent) {
|
|
29918
|
-
const config =
|
|
30325
|
+
const config = parseSharedConfig({
|
|
30326
|
+
format: "yaml",
|
|
30327
|
+
fileContent: currentContent
|
|
30328
|
+
});
|
|
29919
30329
|
const plugins = config.plugins && typeof config.plugins === "object" ? config.plugins : {};
|
|
29920
30330
|
const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
|
|
29921
|
-
|
|
29922
|
-
|
|
29923
|
-
|
|
29924
|
-
|
|
29925
|
-
|
|
30331
|
+
return applySharedConfigPatch({
|
|
30332
|
+
fileKey: HERMES_CONFIG_SHARED_FILE_KEY,
|
|
30333
|
+
feature: "subagents",
|
|
30334
|
+
existingContent: currentContent,
|
|
30335
|
+
patch: { plugins: {
|
|
30336
|
+
...plugins,
|
|
30337
|
+
enabled: Array.from(/* @__PURE__ */ new Set([...enabled, "rulesync-subagents"]))
|
|
30338
|
+
} }
|
|
30339
|
+
});
|
|
29926
30340
|
}
|
|
29927
30341
|
function getSubagentSpec(rulesyncSubagent) {
|
|
29928
30342
|
const json = rulesyncSubagent.getFrontmatter();
|
|
@@ -30008,6 +30422,17 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
|
|
|
30008
30422
|
static getSettablePaths() {
|
|
30009
30423
|
return { relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH };
|
|
30010
30424
|
}
|
|
30425
|
+
/**
|
|
30426
|
+
* Beyond the subagent spec files, generation also read-modify-writes the
|
|
30427
|
+
* shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
|
|
30428
|
+
* so the write must be declared for the shared-file order derivation.
|
|
30429
|
+
*/
|
|
30430
|
+
static getExtraSharedWritePaths() {
|
|
30431
|
+
return [{
|
|
30432
|
+
relativeDirPath: HERMESAGENT_GLOBAL_DIR,
|
|
30433
|
+
relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
|
|
30434
|
+
}];
|
|
30435
|
+
}
|
|
30011
30436
|
static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
|
|
30012
30437
|
return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
|
|
30013
30438
|
}
|
|
@@ -31746,7 +32171,9 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
31746
32171
|
})),
|
|
31747
32172
|
kiro: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
31748
32173
|
inclusion: zod_mini.z.optional(zod_mini.z.string()),
|
|
31749
|
-
fileMatchPattern: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
|
|
32174
|
+
fileMatchPattern: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
32175
|
+
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
32176
|
+
description: zod_mini.z.optional(zod_mini.z.string())
|
|
31750
32177
|
})),
|
|
31751
32178
|
takt: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
31752
32179
|
name: zod_mini.z.optional(zod_mini.z.string()),
|
|
@@ -34662,7 +35089,8 @@ function toFileMatchPattern(globs) {
|
|
|
34662
35089
|
*
|
|
34663
35090
|
* Precedence:
|
|
34664
35091
|
* 1. An explicit `kiro.inclusion` block round-trips as-is (with `fileMatchPattern`
|
|
34665
|
-
* taken from the block, or derived from `globs` when omitted for `fileMatch
|
|
35092
|
+
* taken from the block, or derived from `globs` when omitted for `fileMatch`;
|
|
35093
|
+
* and with the companion `name`/`description` carried through for `auto`).
|
|
34666
35094
|
* 2. Otherwise specific (non-wildcard) globs map to `fileMatch`, scoping the rule
|
|
34667
35095
|
* to matching files instead of leaving it implicitly always-on.
|
|
34668
35096
|
* 3. Otherwise the rule stays `always` — represented by omitting the block so the
|
|
@@ -34680,6 +35108,11 @@ function deriveKiroInclusion({ kiro, globs }) {
|
|
|
34680
35108
|
fileMatchPattern
|
|
34681
35109
|
} : { inclusion: "fileMatch" };
|
|
34682
35110
|
}
|
|
35111
|
+
if (kiro.inclusion === "auto") return {
|
|
35112
|
+
inclusion: "auto",
|
|
35113
|
+
...kiro.name !== void 0 ? { name: kiro.name } : {},
|
|
35114
|
+
...kiro.description !== void 0 ? { description: kiro.description } : {}
|
|
35115
|
+
};
|
|
34683
35116
|
return { inclusion: kiro.inclusion };
|
|
34684
35117
|
}
|
|
34685
35118
|
const fileMatchPattern = toFileMatchPattern(specificGlobs);
|
|
@@ -34751,6 +35184,8 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34751
35184
|
const rawPattern = frontmatter.fileMatchPattern;
|
|
34752
35185
|
const fileMatchPattern = Array.isArray(rawPattern) ? rawPattern.filter((p) => typeof p === "string") : typeof rawPattern === "string" ? rawPattern : void 0;
|
|
34753
35186
|
const globs = inclusion === "fileMatch" ? fileMatchPattern === void 0 ? [] : Array.isArray(fileMatchPattern) ? fileMatchPattern : [fileMatchPattern] : [];
|
|
35187
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name : void 0;
|
|
35188
|
+
const description = typeof frontmatter.description === "string" ? frontmatter.description : void 0;
|
|
34754
35189
|
return new RulesyncRule({
|
|
34755
35190
|
outputRoot: process.cwd(),
|
|
34756
35191
|
relativeDirPath: RulesyncRule.getSettablePaths().recommended.relativeDirPath,
|
|
@@ -34761,7 +35196,9 @@ var KiroRule = class KiroRule extends ToolRule {
|
|
|
34761
35196
|
globs,
|
|
34762
35197
|
kiro: {
|
|
34763
35198
|
inclusion,
|
|
34764
|
-
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {}
|
|
35199
|
+
...fileMatchPattern !== void 0 ? { fileMatchPattern } : {},
|
|
35200
|
+
...inclusion === "auto" && name !== void 0 ? { name } : {},
|
|
35201
|
+
...inclusion === "auto" && description !== void 0 ? { description } : {}
|
|
34765
35202
|
}
|
|
34766
35203
|
},
|
|
34767
35204
|
body
|
|
@@ -36935,6 +37372,197 @@ function buildPermissionsStrategy(ctx) {
|
|
|
36935
37372
|
};
|
|
36936
37373
|
}
|
|
36937
37374
|
//#endregion
|
|
37375
|
+
//#region src/types/processor-registry.ts
|
|
37376
|
+
const PROCESSOR_REGISTRY = [
|
|
37377
|
+
{
|
|
37378
|
+
feature: "rules",
|
|
37379
|
+
processor: RulesProcessor,
|
|
37380
|
+
schema: RulesProcessorToolTargetSchema,
|
|
37381
|
+
factory: toolRuleFactories
|
|
37382
|
+
},
|
|
37383
|
+
{
|
|
37384
|
+
feature: "ignore",
|
|
37385
|
+
processor: IgnoreProcessor,
|
|
37386
|
+
schema: IgnoreProcessorToolTargetSchema,
|
|
37387
|
+
factory: toolIgnoreFactories
|
|
37388
|
+
},
|
|
37389
|
+
{
|
|
37390
|
+
feature: "mcp",
|
|
37391
|
+
processor: McpProcessor,
|
|
37392
|
+
schema: McpProcessorToolTargetSchema,
|
|
37393
|
+
factory: toolMcpFactories
|
|
37394
|
+
},
|
|
37395
|
+
{
|
|
37396
|
+
feature: "commands",
|
|
37397
|
+
processor: CommandsProcessor,
|
|
37398
|
+
schema: CommandsProcessorToolTargetSchema,
|
|
37399
|
+
factory: toolCommandFactories
|
|
37400
|
+
},
|
|
37401
|
+
{
|
|
37402
|
+
feature: "subagents",
|
|
37403
|
+
processor: SubagentsProcessor,
|
|
37404
|
+
schema: SubagentsProcessorToolTargetSchema,
|
|
37405
|
+
factory: toolSubagentFactories
|
|
37406
|
+
},
|
|
37407
|
+
{
|
|
37408
|
+
feature: "skills",
|
|
37409
|
+
processor: SkillsProcessor,
|
|
37410
|
+
schema: SkillsProcessorToolTargetSchema,
|
|
37411
|
+
factory: toolSkillFactories
|
|
37412
|
+
},
|
|
37413
|
+
{
|
|
37414
|
+
feature: "hooks",
|
|
37415
|
+
processor: HooksProcessor,
|
|
37416
|
+
schema: HooksProcessorToolTargetSchema,
|
|
37417
|
+
factory: toolHooksFactories
|
|
37418
|
+
},
|
|
37419
|
+
{
|
|
37420
|
+
feature: "permissions",
|
|
37421
|
+
processor: PermissionsProcessor,
|
|
37422
|
+
schema: PermissionsProcessorToolTargetSchema,
|
|
37423
|
+
factory: toolPermissionsFactories
|
|
37424
|
+
}
|
|
37425
|
+
];
|
|
37426
|
+
const getProcessorRegistryEntry = (feature) => {
|
|
37427
|
+
const entry = PROCESSOR_REGISTRY.find((e) => e.feature === feature);
|
|
37428
|
+
if (!entry) throw new Error(`No processor registered for feature: ${feature}`);
|
|
37429
|
+
return entry;
|
|
37430
|
+
};
|
|
37431
|
+
//#endregion
|
|
37432
|
+
//#region src/lib/shared-file-derive.ts
|
|
37433
|
+
/**
|
|
37434
|
+
* The single declaration of the cross-feature write order for shared
|
|
37435
|
+
* (read-modify-write) config files: when two features write the same on-disk
|
|
37436
|
+
* file, the earlier one writes first and the later one merges on top, so the
|
|
37437
|
+
* later feature's conflict policy decides what survives (e.g. `permissions`
|
|
37438
|
+
* overriding `ignore`-derived `Read(...)` denies in `.claude/settings.json`).
|
|
37439
|
+
* The generation step graph's `dependsOn` edges are derived from this list
|
|
37440
|
+
* plus the registry's `getSettablePaths` declarations — adding a tool or a
|
|
37441
|
+
* shared path never requires touching the graph by hand.
|
|
37442
|
+
*/
|
|
37443
|
+
const SHARED_WRITE_FEATURE_ORDER = [
|
|
37444
|
+
"ignore",
|
|
37445
|
+
"subagents",
|
|
37446
|
+
"mcp",
|
|
37447
|
+
"hooks",
|
|
37448
|
+
"permissions",
|
|
37449
|
+
"rules"
|
|
37450
|
+
];
|
|
37451
|
+
const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
|
|
37452
|
+
const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
|
|
37453
|
+
const sharedFileKey = (path) => {
|
|
37454
|
+
const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
37455
|
+
const file = path.relativeFilePath.replace(/\\/g, "/");
|
|
37456
|
+
return dir === "" || dir === "." ? file : `${dir}/${file}`;
|
|
37457
|
+
};
|
|
37458
|
+
const settablePathsForScope = (cls, global) => {
|
|
37459
|
+
const paths = [];
|
|
37460
|
+
let settable;
|
|
37461
|
+
try {
|
|
37462
|
+
settable = cls.getSettablePaths?.({ global });
|
|
37463
|
+
} catch {
|
|
37464
|
+
return paths;
|
|
37465
|
+
}
|
|
37466
|
+
if (settable?.relativeFilePath) paths.push({
|
|
37467
|
+
relativeDirPath: settable.relativeDirPath ?? ".",
|
|
37468
|
+
relativeFilePath: settable.relativeFilePath
|
|
37469
|
+
});
|
|
37470
|
+
if (settable?.root) {
|
|
37471
|
+
paths.push(settable.root);
|
|
37472
|
+
for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
|
|
37473
|
+
}
|
|
37474
|
+
for (const path of cls.getExtraSharedWritePaths?.({ global }) ?? []) if (path.relativeFilePath) paths.push(path);
|
|
37475
|
+
return paths;
|
|
37476
|
+
};
|
|
37477
|
+
const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
|
|
37478
|
+
/**
|
|
37479
|
+
* Derive, from the processor registry, every on-disk file that two or more
|
|
37480
|
+
* features read-modify-write. Source of truth the generation step graph's
|
|
37481
|
+
* `writesSharedFile` declarations must match.
|
|
37482
|
+
*/
|
|
37483
|
+
const deriveSharedFileWriters = () => {
|
|
37484
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
37485
|
+
const pathByKey = /* @__PURE__ */ new Map();
|
|
37486
|
+
for (const entry of PROCESSOR_REGISTRY) {
|
|
37487
|
+
if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
|
|
37488
|
+
const factories = entry.factory;
|
|
37489
|
+
for (const [tool, factory] of factories) {
|
|
37490
|
+
if (TARGETS_NOT_DERIVED.has(tool)) continue;
|
|
37491
|
+
for (const path of collectFactoryPaths(factory)) {
|
|
37492
|
+
const key = sharedFileKey(path);
|
|
37493
|
+
if (!pathByKey.has(key)) pathByKey.set(key, path);
|
|
37494
|
+
let features = byKey.get(key);
|
|
37495
|
+
if (!features) {
|
|
37496
|
+
features = /* @__PURE__ */ new Map();
|
|
37497
|
+
byKey.set(key, features);
|
|
37498
|
+
}
|
|
37499
|
+
let tools = features.get(entry.feature);
|
|
37500
|
+
if (!tools) {
|
|
37501
|
+
tools = /* @__PURE__ */ new Set();
|
|
37502
|
+
features.set(entry.feature, tools);
|
|
37503
|
+
}
|
|
37504
|
+
tools.add(tool);
|
|
37505
|
+
}
|
|
37506
|
+
}
|
|
37507
|
+
}
|
|
37508
|
+
const writers = [];
|
|
37509
|
+
for (const [key, features] of byKey) {
|
|
37510
|
+
if (features.size < 2) continue;
|
|
37511
|
+
const path = pathByKey.get(key);
|
|
37512
|
+
const toolsByFeature = /* @__PURE__ */ new Map();
|
|
37513
|
+
for (const [feature, tools] of features) toolsByFeature.set(feature, [...tools].toSorted());
|
|
37514
|
+
writers.push({
|
|
37515
|
+
key,
|
|
37516
|
+
relativeDirPath: path.relativeDirPath,
|
|
37517
|
+
relativeFilePath: path.relativeFilePath,
|
|
37518
|
+
features: [...features.keys()].toSorted(),
|
|
37519
|
+
toolsByFeature
|
|
37520
|
+
});
|
|
37521
|
+
}
|
|
37522
|
+
return writers.toSorted((a, b) => a.key.localeCompare(b.key));
|
|
37523
|
+
};
|
|
37524
|
+
/**
|
|
37525
|
+
* Derive, per shared-write feature, the shared files it writes and the
|
|
37526
|
+
* `dependsOn` edges that fix a safe write order: for every shared file, each
|
|
37527
|
+
* writer depends on all writers that precede it in
|
|
37528
|
+
* {@link SHARED_WRITE_FEATURE_ORDER}. This is the source the generation step
|
|
37529
|
+
* graph consumes, so registry changes (a new tool, a new settable path)
|
|
37530
|
+
* propagate into the execution order without a hand-maintained declaration.
|
|
37531
|
+
*
|
|
37532
|
+
* @throws Error if a feature writes a shared file but has no position in
|
|
37533
|
+
* `SHARED_WRITE_FEATURE_ORDER` — ordering it is a deliberate decision about
|
|
37534
|
+
* whose merge policy wins, so it must be made explicitly there.
|
|
37535
|
+
*/
|
|
37536
|
+
const deriveSharedWriteSteps = () => {
|
|
37537
|
+
const orderIndex = new Map(SHARED_WRITE_FEATURE_ORDER.map((feature, index) => [feature, index]));
|
|
37538
|
+
const filesByFeature = /* @__PURE__ */ new Map();
|
|
37539
|
+
const depsByFeature = /* @__PURE__ */ new Map();
|
|
37540
|
+
for (const writer of deriveSharedFileWriters()) {
|
|
37541
|
+
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.`);
|
|
37542
|
+
const ordered = [...writer.features].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b));
|
|
37543
|
+
for (const [position, feature] of ordered.entries()) {
|
|
37544
|
+
let files = filesByFeature.get(feature);
|
|
37545
|
+
if (!files) {
|
|
37546
|
+
files = /* @__PURE__ */ new Set();
|
|
37547
|
+
filesByFeature.set(feature, files);
|
|
37548
|
+
}
|
|
37549
|
+
files.add(writer.key);
|
|
37550
|
+
let deps = depsByFeature.get(feature);
|
|
37551
|
+
if (!deps) {
|
|
37552
|
+
deps = /* @__PURE__ */ new Set();
|
|
37553
|
+
depsByFeature.set(feature, deps);
|
|
37554
|
+
}
|
|
37555
|
+
for (const earlier of ordered.slice(0, position)) deps.add(earlier);
|
|
37556
|
+
}
|
|
37557
|
+
}
|
|
37558
|
+
const steps = /* @__PURE__ */ new Map();
|
|
37559
|
+
for (const [feature, files] of filesByFeature) steps.set(feature, {
|
|
37560
|
+
writesSharedFile: [...files].toSorted(),
|
|
37561
|
+
dependsOn: [...depsByFeature.get(feature) ?? []].toSorted((a, b) => orderIndex.get(a) - orderIndex.get(b))
|
|
37562
|
+
});
|
|
37563
|
+
return steps;
|
|
37564
|
+
};
|
|
37565
|
+
//#endregion
|
|
36938
37566
|
//#region src/lib/generate.ts
|
|
36939
37567
|
async function processFeatureGeneration(params) {
|
|
36940
37568
|
const { config, processor, toolFiles, skipFilePaths } = params;
|
|
@@ -37084,98 +37712,53 @@ function resolveExecutionOrder(steps) {
|
|
|
37084
37712
|
if (ordered.length !== steps.length) throw new Error("Generation steps contain a cyclic 'dependsOn' dependency.");
|
|
37085
37713
|
return ordered;
|
|
37086
37714
|
}
|
|
37715
|
+
const SHARED_WRITE_STEPS = deriveSharedWriteSteps();
|
|
37716
|
+
const sharedWriteMeta = (id) => {
|
|
37717
|
+
const step = SHARED_WRITE_STEPS.get(id);
|
|
37718
|
+
return step ? {
|
|
37719
|
+
writesSharedFile: step.writesSharedFile,
|
|
37720
|
+
dependsOn: step.dependsOn
|
|
37721
|
+
} : {};
|
|
37722
|
+
};
|
|
37087
37723
|
/**
|
|
37088
37724
|
* The static shape of the generation step graph: which steps write which shared
|
|
37089
37725
|
* (read-modify-write) config files, and the `dependsOn` edges that fix a safe order
|
|
37090
|
-
* for those writers.
|
|
37091
|
-
*
|
|
37092
|
-
*
|
|
37093
|
-
*
|
|
37094
|
-
*
|
|
37726
|
+
* for those writers. Both are derived from the processor registry's settable
|
|
37727
|
+
* paths and `SHARED_WRITE_FEATURE_ORDER` (see `shared-file-derive.ts`), so a new
|
|
37728
|
+
* tool or shared path never requires editing this graph. Exported (separately
|
|
37729
|
+
* from the `run` closures, which need a live `config`/`logger`) so
|
|
37730
|
+
* `resolveExecutionOrder`'s ordering guarantee can be tested directly against
|
|
37731
|
+
* the real graph rather than a hand-copied one. Readonly so a consumer can't
|
|
37732
|
+
* mutate this module-level singleton and affect every subsequent `generate()`
|
|
37733
|
+
* call in the process.
|
|
37095
37734
|
*/
|
|
37096
37735
|
const GENERATION_STEP_GRAPH = [
|
|
37097
37736
|
{
|
|
37098
37737
|
id: "ignore",
|
|
37099
|
-
|
|
37738
|
+
...sharedWriteMeta("ignore")
|
|
37100
37739
|
},
|
|
37101
37740
|
{
|
|
37102
37741
|
id: "mcp",
|
|
37103
|
-
|
|
37104
|
-
".amp/settings.json",
|
|
37105
|
-
".augment/settings.json",
|
|
37106
|
-
".codex/config.toml",
|
|
37107
|
-
".config/amp/settings.json",
|
|
37108
|
-
".config/devin/config.json",
|
|
37109
|
-
".config/opencode/opencode.json",
|
|
37110
|
-
".config/zed/settings.json",
|
|
37111
|
-
".devin/config.json",
|
|
37112
|
-
".grok/config.toml",
|
|
37113
|
-
".hermes/config.yaml",
|
|
37114
|
-
".qwen/settings.json",
|
|
37115
|
-
".reasonix/config.toml",
|
|
37116
|
-
".takt/config.yaml",
|
|
37117
|
-
".vibe/config.toml",
|
|
37118
|
-
".zed/settings.json",
|
|
37119
|
-
"kilo.json",
|
|
37120
|
-
"opencode.json",
|
|
37121
|
-
"reasonix.toml"
|
|
37122
|
-
],
|
|
37123
|
-
dependsOn: ["ignore"]
|
|
37742
|
+
...sharedWriteMeta("mcp")
|
|
37124
37743
|
},
|
|
37125
37744
|
{ id: "commands" },
|
|
37126
|
-
{
|
|
37745
|
+
{
|
|
37746
|
+
id: "subagents",
|
|
37747
|
+
...sharedWriteMeta("subagents")
|
|
37748
|
+
},
|
|
37127
37749
|
{ id: "skills" },
|
|
37128
37750
|
{
|
|
37129
37751
|
id: "hooks",
|
|
37130
|
-
|
|
37131
|
-
".augment/settings.json",
|
|
37132
|
-
".claude/settings.json",
|
|
37133
|
-
".codex/config.toml",
|
|
37134
|
-
".config/devin/config.json",
|
|
37135
|
-
".hermes/config.yaml",
|
|
37136
|
-
".kiro/agents/default.json",
|
|
37137
|
-
".qwen/settings.json",
|
|
37138
|
-
".vibe/config.toml"
|
|
37139
|
-
],
|
|
37140
|
-
dependsOn: ["ignore", "mcp"]
|
|
37752
|
+
...sharedWriteMeta("hooks")
|
|
37141
37753
|
},
|
|
37142
37754
|
{
|
|
37143
37755
|
id: "permissions",
|
|
37144
|
-
|
|
37145
|
-
".amp/settings.json",
|
|
37146
|
-
".augment/settings.json",
|
|
37147
|
-
".claude/settings.json",
|
|
37148
|
-
".codex/config.toml",
|
|
37149
|
-
".config/amp/settings.json",
|
|
37150
|
-
".config/devin/config.json",
|
|
37151
|
-
".config/opencode/opencode.json",
|
|
37152
|
-
".config/zed/settings.json",
|
|
37153
|
-
".devin/config.json",
|
|
37154
|
-
".grok/config.toml",
|
|
37155
|
-
".hermes/config.yaml",
|
|
37156
|
-
".kiro/agents/default.json",
|
|
37157
|
-
".qwen/settings.json",
|
|
37158
|
-
".reasonix/config.toml",
|
|
37159
|
-
".takt/config.yaml",
|
|
37160
|
-
".vibe/config.toml",
|
|
37161
|
-
".zed/settings.json",
|
|
37162
|
-
"opencode.json",
|
|
37163
|
-
"reasonix.toml"
|
|
37164
|
-
],
|
|
37165
|
-
dependsOn: [
|
|
37166
|
-
"ignore",
|
|
37167
|
-
"hooks",
|
|
37168
|
-
"mcp"
|
|
37169
|
-
]
|
|
37756
|
+
...sharedWriteMeta("permissions")
|
|
37170
37757
|
},
|
|
37171
37758
|
{
|
|
37172
37759
|
id: "rules",
|
|
37173
|
-
|
|
37174
|
-
dependsOn: [
|
|
37175
|
-
"mcp",
|
|
37176
|
-
"skills",
|
|
37177
|
-
"permissions"
|
|
37178
|
-
]
|
|
37760
|
+
...sharedWriteMeta("rules"),
|
|
37761
|
+
dependsOn: [...sharedWriteMeta("rules").dependsOn ?? [], "skills"]
|
|
37179
37762
|
}
|
|
37180
37763
|
];
|
|
37181
37764
|
/**
|
|
@@ -37290,7 +37873,7 @@ async function generateRulesCore(params) {
|
|
|
37290
37873
|
targets: config.getConfigFileTargets(),
|
|
37291
37874
|
global: config.getGlobal()
|
|
37292
37875
|
}) : /* @__PURE__ */ new Map();
|
|
37293
|
-
for (const
|
|
37876
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37294
37877
|
if (!config.getFeatures(toolTarget).includes("rules")) continue;
|
|
37295
37878
|
const processor = new RulesProcessor({
|
|
37296
37879
|
outputRoot,
|
|
@@ -37345,7 +37928,7 @@ async function generateIgnoreCore(params) {
|
|
|
37345
37928
|
let hasDiff = false;
|
|
37346
37929
|
for (const toolTarget of (0, es_toolkit.intersection)(config.getTargets(), supportedIgnoreTargets)) {
|
|
37347
37930
|
if (!config.getFeatures(toolTarget).includes("ignore")) continue;
|
|
37348
|
-
for (const outputRoot of config.getOutputRoots()) try {
|
|
37931
|
+
for (const outputRoot of config.getOutputRoots(toolTarget)) try {
|
|
37349
37932
|
const processor = new IgnoreProcessor({
|
|
37350
37933
|
outputRoot,
|
|
37351
37934
|
inputRoot: config.getInputRoot(),
|
|
@@ -37386,7 +37969,7 @@ async function generateMcpCore(params) {
|
|
|
37386
37969
|
featureName: "mcp",
|
|
37387
37970
|
logger
|
|
37388
37971
|
});
|
|
37389
|
-
for (const
|
|
37972
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37390
37973
|
if (!config.getFeatures(toolTarget).includes("mcp")) continue;
|
|
37391
37974
|
const processor = new McpProcessor({
|
|
37392
37975
|
outputRoot,
|
|
@@ -37428,7 +38011,7 @@ async function generateCommandsCore(params) {
|
|
|
37428
38011
|
featureName: "commands",
|
|
37429
38012
|
logger
|
|
37430
38013
|
});
|
|
37431
|
-
for (const
|
|
38014
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37432
38015
|
if (!config.getFeatures(toolTarget).includes("commands")) continue;
|
|
37433
38016
|
const processor = new CommandsProcessor({
|
|
37434
38017
|
outputRoot,
|
|
@@ -37470,7 +38053,7 @@ async function generateSubagentsCore(params) {
|
|
|
37470
38053
|
featureName: "subagents",
|
|
37471
38054
|
logger
|
|
37472
38055
|
});
|
|
37473
|
-
for (const
|
|
38056
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37474
38057
|
if (!config.getFeatures(toolTarget).includes("subagents")) continue;
|
|
37475
38058
|
const processor = new SubagentsProcessor({
|
|
37476
38059
|
outputRoot,
|
|
@@ -37513,7 +38096,7 @@ async function generateSkillsCore(params) {
|
|
|
37513
38096
|
featureName: "skills",
|
|
37514
38097
|
logger
|
|
37515
38098
|
});
|
|
37516
|
-
for (const
|
|
38099
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37517
38100
|
if (!config.getFeatures(toolTarget).includes("skills")) continue;
|
|
37518
38101
|
const processor = new SkillsProcessor({
|
|
37519
38102
|
outputRoot,
|
|
@@ -37554,7 +38137,7 @@ async function generateHooksCore(params) {
|
|
|
37554
38137
|
featureName: "hooks",
|
|
37555
38138
|
logger
|
|
37556
38139
|
});
|
|
37557
|
-
for (const
|
|
38140
|
+
for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37558
38141
|
if (!config.getFeatures(toolTarget).includes("hooks")) continue;
|
|
37559
38142
|
const processor = new HooksProcessor({
|
|
37560
38143
|
outputRoot,
|
|
@@ -37591,7 +38174,7 @@ async function generatePermissionsCore(params) {
|
|
|
37591
38174
|
let totalCount = 0;
|
|
37592
38175
|
const allPaths = [];
|
|
37593
38176
|
let hasDiff = false;
|
|
37594
|
-
for (const
|
|
38177
|
+
for (const toolTarget of (0, es_toolkit.intersection)(config.getTargets(), supportedPermissionsTargets)) for (const outputRoot of config.getOutputRoots(toolTarget)) {
|
|
37595
38178
|
if (!config.getFeatures(toolTarget).includes("permissions")) continue;
|
|
37596
38179
|
try {
|
|
37597
38180
|
const processor = new PermissionsProcessor({
|
|
@@ -37933,12 +38516,6 @@ Object.defineProperty(exports, "CommandsProcessor", {
|
|
|
37933
38516
|
return CommandsProcessor;
|
|
37934
38517
|
}
|
|
37935
38518
|
});
|
|
37936
|
-
Object.defineProperty(exports, "CommandsProcessorToolTargetSchema", {
|
|
37937
|
-
enumerable: true,
|
|
37938
|
-
get: function() {
|
|
37939
|
-
return CommandsProcessorToolTargetSchema;
|
|
37940
|
-
}
|
|
37941
|
-
});
|
|
37942
38519
|
Object.defineProperty(exports, "ConfigResolver", {
|
|
37943
38520
|
enumerable: true,
|
|
37944
38521
|
get: function() {
|
|
@@ -37963,24 +38540,12 @@ Object.defineProperty(exports, "HooksProcessor", {
|
|
|
37963
38540
|
return HooksProcessor;
|
|
37964
38541
|
}
|
|
37965
38542
|
});
|
|
37966
|
-
Object.defineProperty(exports, "HooksProcessorToolTargetSchema", {
|
|
37967
|
-
enumerable: true,
|
|
37968
|
-
get: function() {
|
|
37969
|
-
return HooksProcessorToolTargetSchema;
|
|
37970
|
-
}
|
|
37971
|
-
});
|
|
37972
38543
|
Object.defineProperty(exports, "IgnoreProcessor", {
|
|
37973
38544
|
enumerable: true,
|
|
37974
38545
|
get: function() {
|
|
37975
38546
|
return IgnoreProcessor;
|
|
37976
38547
|
}
|
|
37977
38548
|
});
|
|
37978
|
-
Object.defineProperty(exports, "IgnoreProcessorToolTargetSchema", {
|
|
37979
|
-
enumerable: true,
|
|
37980
|
-
get: function() {
|
|
37981
|
-
return IgnoreProcessorToolTargetSchema;
|
|
37982
|
-
}
|
|
37983
|
-
});
|
|
37984
38549
|
Object.defineProperty(exports, "JsonLogger", {
|
|
37985
38550
|
enumerable: true,
|
|
37986
38551
|
get: function() {
|
|
@@ -37999,24 +38564,6 @@ Object.defineProperty(exports, "McpProcessor", {
|
|
|
37999
38564
|
return McpProcessor;
|
|
38000
38565
|
}
|
|
38001
38566
|
});
|
|
38002
|
-
Object.defineProperty(exports, "McpProcessorToolTargetSchema", {
|
|
38003
|
-
enumerable: true,
|
|
38004
|
-
get: function() {
|
|
38005
|
-
return McpProcessorToolTargetSchema;
|
|
38006
|
-
}
|
|
38007
|
-
});
|
|
38008
|
-
Object.defineProperty(exports, "PermissionsProcessor", {
|
|
38009
|
-
enumerable: true,
|
|
38010
|
-
get: function() {
|
|
38011
|
-
return PermissionsProcessor;
|
|
38012
|
-
}
|
|
38013
|
-
});
|
|
38014
|
-
Object.defineProperty(exports, "PermissionsProcessorToolTargetSchema", {
|
|
38015
|
-
enumerable: true,
|
|
38016
|
-
get: function() {
|
|
38017
|
-
return PermissionsProcessorToolTargetSchema;
|
|
38018
|
-
}
|
|
38019
|
-
});
|
|
38020
38567
|
Object.defineProperty(exports, "RULESYNC_AIIGNORE_FILE_NAME", {
|
|
38021
38568
|
enumerable: true,
|
|
38022
38569
|
get: function() {
|
|
@@ -38149,12 +38696,6 @@ Object.defineProperty(exports, "RulesProcessor", {
|
|
|
38149
38696
|
return RulesProcessor;
|
|
38150
38697
|
}
|
|
38151
38698
|
});
|
|
38152
|
-
Object.defineProperty(exports, "RulesProcessorToolTargetSchema", {
|
|
38153
|
-
enumerable: true,
|
|
38154
|
-
get: function() {
|
|
38155
|
-
return RulesProcessorToolTargetSchema;
|
|
38156
|
-
}
|
|
38157
|
-
});
|
|
38158
38699
|
Object.defineProperty(exports, "RulesyncCommand", {
|
|
38159
38700
|
enumerable: true,
|
|
38160
38701
|
get: function() {
|
|
@@ -38239,24 +38780,12 @@ Object.defineProperty(exports, "SkillsProcessor", {
|
|
|
38239
38780
|
return SkillsProcessor;
|
|
38240
38781
|
}
|
|
38241
38782
|
});
|
|
38242
|
-
Object.defineProperty(exports, "SkillsProcessorToolTargetSchema", {
|
|
38243
|
-
enumerable: true,
|
|
38244
|
-
get: function() {
|
|
38245
|
-
return SkillsProcessorToolTargetSchema;
|
|
38246
|
-
}
|
|
38247
|
-
});
|
|
38248
38783
|
Object.defineProperty(exports, "SubagentsProcessor", {
|
|
38249
38784
|
enumerable: true,
|
|
38250
38785
|
get: function() {
|
|
38251
38786
|
return SubagentsProcessor;
|
|
38252
38787
|
}
|
|
38253
38788
|
});
|
|
38254
|
-
Object.defineProperty(exports, "SubagentsProcessorToolTargetSchema", {
|
|
38255
|
-
enumerable: true,
|
|
38256
|
-
get: function() {
|
|
38257
|
-
return SubagentsProcessorToolTargetSchema;
|
|
38258
|
-
}
|
|
38259
|
-
});
|
|
38260
38789
|
Object.defineProperty(exports, "ToolTargetSchema", {
|
|
38261
38790
|
enumerable: true,
|
|
38262
38791
|
get: function() {
|
|
@@ -38353,6 +38882,12 @@ Object.defineProperty(exports, "getLocalSkillDirNames", {
|
|
|
38353
38882
|
return getLocalSkillDirNames;
|
|
38354
38883
|
}
|
|
38355
38884
|
});
|
|
38885
|
+
Object.defineProperty(exports, "getProcessorRegistryEntry", {
|
|
38886
|
+
enumerable: true,
|
|
38887
|
+
get: function() {
|
|
38888
|
+
return getProcessorRegistryEntry;
|
|
38889
|
+
}
|
|
38890
|
+
});
|
|
38356
38891
|
Object.defineProperty(exports, "importFromTool", {
|
|
38357
38892
|
enumerable: true,
|
|
38358
38893
|
get: function() {
|
|
@@ -38407,54 +38942,6 @@ Object.defineProperty(exports, "toPosixPath", {
|
|
|
38407
38942
|
return toPosixPath;
|
|
38408
38943
|
}
|
|
38409
38944
|
});
|
|
38410
|
-
Object.defineProperty(exports, "toolCommandFactories", {
|
|
38411
|
-
enumerable: true,
|
|
38412
|
-
get: function() {
|
|
38413
|
-
return toolCommandFactories;
|
|
38414
|
-
}
|
|
38415
|
-
});
|
|
38416
|
-
Object.defineProperty(exports, "toolHooksFactories", {
|
|
38417
|
-
enumerable: true,
|
|
38418
|
-
get: function() {
|
|
38419
|
-
return toolHooksFactories;
|
|
38420
|
-
}
|
|
38421
|
-
});
|
|
38422
|
-
Object.defineProperty(exports, "toolIgnoreFactories", {
|
|
38423
|
-
enumerable: true,
|
|
38424
|
-
get: function() {
|
|
38425
|
-
return toolIgnoreFactories;
|
|
38426
|
-
}
|
|
38427
|
-
});
|
|
38428
|
-
Object.defineProperty(exports, "toolMcpFactories", {
|
|
38429
|
-
enumerable: true,
|
|
38430
|
-
get: function() {
|
|
38431
|
-
return toolMcpFactories;
|
|
38432
|
-
}
|
|
38433
|
-
});
|
|
38434
|
-
Object.defineProperty(exports, "toolPermissionsFactories", {
|
|
38435
|
-
enumerable: true,
|
|
38436
|
-
get: function() {
|
|
38437
|
-
return toolPermissionsFactories;
|
|
38438
|
-
}
|
|
38439
|
-
});
|
|
38440
|
-
Object.defineProperty(exports, "toolRuleFactories", {
|
|
38441
|
-
enumerable: true,
|
|
38442
|
-
get: function() {
|
|
38443
|
-
return toolRuleFactories;
|
|
38444
|
-
}
|
|
38445
|
-
});
|
|
38446
|
-
Object.defineProperty(exports, "toolSkillFactories", {
|
|
38447
|
-
enumerable: true,
|
|
38448
|
-
get: function() {
|
|
38449
|
-
return toolSkillFactories;
|
|
38450
|
-
}
|
|
38451
|
-
});
|
|
38452
|
-
Object.defineProperty(exports, "toolSubagentFactories", {
|
|
38453
|
-
enumerable: true,
|
|
38454
|
-
get: function() {
|
|
38455
|
-
return toolSubagentFactories;
|
|
38456
|
-
}
|
|
38457
|
-
});
|
|
38458
38945
|
Object.defineProperty(exports, "writeFileContent", {
|
|
38459
38946
|
enumerable: true,
|
|
38460
38947
|
get: function() {
|