rulesync 9.4.0 → 9.6.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.
@@ -31,11 +31,11 @@ node_os = __toESM(node_os, 1);
31
31
  let es_toolkit = require("es-toolkit");
32
32
  let globby = require("globby");
33
33
  let node_util = require("node:util");
34
- let js_yaml = require("js-yaml");
35
34
  let smol_toml = require("smol-toml");
36
35
  smol_toml = __toESM(smol_toml, 1);
37
36
  let gray_matter = require("gray-matter");
38
37
  gray_matter = __toESM(gray_matter, 1);
38
+ let js_yaml = require("js-yaml");
39
39
  let es_toolkit_object = require("es-toolkit/object");
40
40
  let _toon_format_toon = require("@toon-format/toon");
41
41
  //#region src/types/features.ts
@@ -114,22 +114,22 @@ function formatError(error) {
114
114
  }
115
115
  //#endregion
116
116
  //#region src/constants/rulesync-paths.ts
117
- const { join: join$247 } = node_path.posix;
117
+ const { join: join$249 } = 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$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");
121
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "rules");
122
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "commands");
123
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "subagents");
124
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
125
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
126
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
127
127
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
128
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$247(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
128
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$249(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$247(RULESYNC_RELATIVE_DIR_PATH, "skills");
132
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$247(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
131
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$249(RULESYNC_RELATIVE_DIR_PATH, "skills");
132
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$249(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";
@@ -167,6 +167,7 @@ const rulesProcessorToolTargetTuple = [
167
167
  "opencode",
168
168
  "pi",
169
169
  "qwencode",
170
+ "reasonix",
170
171
  "replit",
171
172
  "roo",
172
173
  "rovodev",
@@ -314,6 +315,7 @@ const skillsProcessorToolTargetTuple = [
314
315
  "opencode",
315
316
  "pi",
316
317
  "qwencode",
318
+ "reasonix",
317
319
  "replit",
318
320
  "roo",
319
321
  "rovodev",
@@ -878,7 +880,7 @@ const SourceEntrySchema = zod_mini.z.object({
878
880
  scope: (0, zod_mini.optional)(zod_mini.z.enum(["project", "user"]))
879
881
  });
880
882
  const ConfigParamsSchema = zod_mini.z.object({
881
- outputRoots: zod_mini.z.array(zod_mini.z.string()),
883
+ 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
884
  targets: RulesyncConfigTargetsSchema,
883
885
  features: RulesyncFeaturesSchema,
884
886
  verbose: zod_mini.z.boolean(),
@@ -983,6 +985,7 @@ var Config = class Config {
983
985
  const resolvedTargets = targets ?? [];
984
986
  const resolvedFeatures = features ?? [];
985
987
  this.validateObjectFormTargetKeys(resolvedTargets);
988
+ this.validateObjectFormOutputRootKeys(outputRoots);
986
989
  this.validateConflictingTargets(resolvedTargets);
987
990
  if (dryRun && check) throw new Error("--dry-run and --check cannot be used together");
988
991
  this.outputRoots = outputRoots;
@@ -1020,6 +1023,11 @@ var Config = class Config {
1020
1023
  if (!validTargets.has(key)) throw new Error(`Unknown target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
1021
1024
  }
1022
1025
  }
1026
+ validateObjectFormOutputRootKeys(outputRoots) {
1027
+ if (Array.isArray(outputRoots)) return;
1028
+ const validTargets = new Set(ALL_TOOL_TARGETS);
1029
+ for (const key of Object.keys(outputRoots)) if (!validTargets.has(key)) throw new Error(`Unknown outputRoots target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
1030
+ }
1023
1031
  validateConflictingTargets(targets) {
1024
1032
  const has = (target) => {
1025
1033
  if (Array.isArray(targets)) return targets.includes(target);
@@ -1027,8 +1035,19 @@ var Config = class Config {
1027
1035
  };
1028
1036
  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
1037
  }
1030
- getOutputRoots() {
1031
- return this.outputRoots;
1038
+ getOutputRoots(target) {
1039
+ if (Array.isArray(this.outputRoots)) return this.outputRoots;
1040
+ if (target) {
1041
+ const targetOutputRoots = this.outputRoots[target];
1042
+ if (targetOutputRoots === void 0) return [];
1043
+ return Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
1044
+ }
1045
+ const allRoots = [];
1046
+ for (const value of Object.values(this.outputRoots)) {
1047
+ if (value === void 0) continue;
1048
+ allRoots.push(...Array.isArray(value) ? value : [value]);
1049
+ }
1050
+ return [...new Set(allRoots)];
1032
1051
  }
1033
1052
  /**
1034
1053
  * Filter an arbitrary string-key list down to the known `ToolTarget` set,
@@ -1390,10 +1409,21 @@ var ConfigResolver = class {
1390
1409
  };
1391
1410
  function getOutputRootsInLightOfGlobal({ outputRoots, global }) {
1392
1411
  if (global) return [getHomeDirectory()];
1393
- outputRoots.forEach((outputRoot) => {
1394
- validateOutputRoot(outputRoot);
1395
- });
1396
- return outputRoots.map((outputRoot) => (0, node_path.resolve)(outputRoot));
1412
+ if (Array.isArray(outputRoots)) {
1413
+ outputRoots.forEach((outputRoot) => {
1414
+ validateOutputRoot(outputRoot);
1415
+ });
1416
+ return outputRoots.map((outputRoot) => (0, node_path.resolve)(outputRoot));
1417
+ }
1418
+ const resolvedOutputRoots = {};
1419
+ for (const [target, targetOutputRoots] of Object.entries(outputRoots)) {
1420
+ const roots = Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
1421
+ roots.forEach((outputRoot) => {
1422
+ validateOutputRoot(outputRoot);
1423
+ });
1424
+ resolvedOutputRoots[target] = Array.isArray(targetOutputRoots) ? roots.map((outputRoot) => (0, node_path.resolve)(outputRoot)) : (0, node_path.resolve)(targetOutputRoots);
1425
+ }
1426
+ return resolvedOutputRoots;
1397
1427
  }
1398
1428
  function extractConfigFileTargets(targets) {
1399
1429
  if (targets === void 0) return void 0;
@@ -1422,7 +1452,7 @@ function isRecord(value) {
1422
1452
  * conversion, etc.) should reject inputs whose prototype could carry
1423
1453
  * malicious accessor descriptors.
1424
1454
  */
1425
- function isPlainObject(value) {
1455
+ function isPlainObject$1(value) {
1426
1456
  if (!isRecord(value)) return false;
1427
1457
  const proto = Object.getPrototypeOf(value);
1428
1458
  return proto === null || proto === Object.prototype;
@@ -1434,11 +1464,43 @@ function isStringArray(value) {
1434
1464
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1435
1465
  }
1436
1466
  //#endregion
1467
+ //#region src/utils/yaml.ts
1468
+ /**
1469
+ * js-yaml v5's `reason` for an empty document. `load("")` — and any
1470
+ * whitespace/comment-only input — throws a `YAMLException` with this reason
1471
+ * instead of returning `undefined` the way v4 did.
1472
+ */
1473
+ const EMPTY_INPUT_REASON = "expected a document, but the input is empty";
1474
+ /**
1475
+ * Load YAML content while preserving js-yaml v4's behavior of returning
1476
+ * `undefined` for empty documents (empty, whitespace-only, or comment-only
1477
+ * input).
1478
+ *
1479
+ * js-yaml v5 changed `load("")` to **throw** ("expected a document, but the
1480
+ * input is empty") instead of returning `undefined`. Many call sites here parse
1481
+ * config/lock/frontmatter files that can legitimately be empty and rely on the
1482
+ * old `undefined` sentinel (`load(input) ?? {}`, `if (loaded === undefined)`,
1483
+ * "an empty file parses to undefined/null"). Routing every load through this
1484
+ * wrapper keeps that contract in one place so a future js-yaml bump cannot
1485
+ * reintroduce the empty-input regression. Genuine parse errors still propagate.
1486
+ *
1487
+ * @see https://github.com/nodeca/js-yaml/blob/master/docs/migrate_v4_to_v5.md#empty-input-throws
1488
+ */
1489
+ function loadYaml(content) {
1490
+ if (content.trim() === "") return;
1491
+ try {
1492
+ return (0, js_yaml.load)(content);
1493
+ } catch (error) {
1494
+ if (error instanceof js_yaml.YAMLException && error.reason === EMPTY_INPUT_REASON) return;
1495
+ throw error;
1496
+ }
1497
+ }
1498
+ //#endregion
1437
1499
  //#region src/utils/frontmatter.ts
1438
1500
  function deepRemoveNullishValue(value) {
1439
1501
  if (value === null || value === void 0) return;
1440
1502
  if (Array.isArray(value)) return value.map((item) => deepRemoveNullishValue(item)).filter((item) => item !== void 0);
1441
- if (isPlainObject(value)) {
1503
+ if (isPlainObject$1(value)) {
1442
1504
  const result = {};
1443
1505
  for (const [key, val] of Object.entries(value)) {
1444
1506
  const cleaned = deepRemoveNullishValue(val);
@@ -1461,7 +1523,7 @@ function deepFlattenStringsValue(value) {
1461
1523
  if (value === null || value === void 0) return;
1462
1524
  if (typeof value === "string") return value.replace(/\n+/g, " ").trim();
1463
1525
  if (Array.isArray(value)) return value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
1464
- if (isPlainObject(value)) {
1526
+ if (isPlainObject$1(value)) {
1465
1527
  const result = {};
1466
1528
  for (const [key, val] of Object.entries(value)) {
1467
1529
  const cleaned = deepFlattenStringsValue(val);
@@ -1484,7 +1546,7 @@ function stringifyFrontmatter(body, frontmatter, options) {
1484
1546
  const { avoidBlockScalars = false } = options ?? {};
1485
1547
  const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
1486
1548
  if (avoidBlockScalars) return gray_matter.default.stringify(body, cleanFrontmatter, { engines: { yaml: {
1487
- parse: (input) => (0, js_yaml.load)(input) ?? {},
1549
+ parse: (input) => loadYaml(input) ?? {},
1488
1550
  stringify: (data) => (0, js_yaml.dump)(data, { lineWidth: -1 })
1489
1551
  } } });
1490
1552
  return gray_matter.default.stringify(body, cleanFrontmatter);
@@ -1523,7 +1585,7 @@ function tryJsonEquivalent(a, b) {
1523
1585
  }
1524
1586
  function tryYamlEquivalent(a, b) {
1525
1587
  try {
1526
- return (0, node_util.isDeepStrictEqual)((0, js_yaml.load)(a), (0, js_yaml.load)(b));
1588
+ return (0, node_util.isDeepStrictEqual)(loadYaml(a), loadYaml(b));
1527
1589
  } catch {
1528
1590
  return;
1529
1591
  }
@@ -3330,7 +3392,7 @@ var GooseCommand = class GooseCommand extends ToolCommand {
3330
3392
  const where = (0, node_path.join)(this.relativeDirPath, this.relativeFilePath);
3331
3393
  let parsed;
3332
3394
  try {
3333
- parsed = (0, js_yaml.load)(content);
3395
+ parsed = loadYaml(content);
3334
3396
  } catch (error) {
3335
3397
  throw new Error(`Failed to parse Goose recipe (${where}): ${formatError(error)}`, { cause: error });
3336
3398
  }
@@ -3984,7 +4046,7 @@ function omitPrototypePollutionKeys(record) {
3984
4046
  //#region src/features/shared/shared-config-gateway.ts
3985
4047
  function sanitizeSharedConfigValue(value) {
3986
4048
  if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
3987
- if (!isPlainObject(value)) return value;
4049
+ if (!isPlainObject$1(value)) return value;
3988
4050
  const result = {};
3989
4051
  for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
3990
4052
  return result;
@@ -4000,14 +4062,14 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
4000
4062
  const at = filePath === void 0 ? "" : ` at ${filePath}`;
4001
4063
  let parsed;
4002
4064
  try {
4003
- if (format === "yaml") parsed = (0, js_yaml.load)(fileContent);
4065
+ if (format === "yaml") parsed = loadYaml(fileContent);
4004
4066
  else if (format === "json") parsed = JSON.parse(fileContent);
4005
4067
  else parsed = (0, jsonc_parser.parse)(fileContent);
4006
4068
  } catch (error) {
4007
4069
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
4008
4070
  }
4009
4071
  if (parsed === void 0 || parsed === null) return {};
4010
- if (!isPlainObject(parsed)) {
4072
+ if (!isPlainObject$1(parsed)) {
4011
4073
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
4012
4074
  return {};
4013
4075
  }
@@ -4050,7 +4112,7 @@ function mergeSharedConfigDeep({ base, patch }) {
4050
4112
  for (const [key, patchValue] of Object.entries(patch)) {
4051
4113
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4052
4114
  const baseValue = result[key];
4053
- if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
4115
+ if (isPlainObject$1(baseValue) && isPlainObject$1(patchValue)) result[key] = mergeSharedConfigDeep({
4054
4116
  base: baseValue,
4055
4117
  patch: patchValue
4056
4118
  });
@@ -4684,6 +4746,8 @@ const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
4684
4746
  const REASONIX_DIR = REASONIX_GLOBAL_DIR;
4685
4747
  const REASONIX_SETTINGS_FILE_NAME = "settings.json";
4686
4748
  const REASONIX_COMMANDS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "commands");
4749
+ const REASONIX_RULE_FILE_NAME = "REASONIX.md";
4750
+ const REASONIX_SKILLS_DIR_PATH = (0, node_path.join)(REASONIX_DIR, "skills");
4687
4751
  //#endregion
4688
4752
  //#region src/features/commands/reasonix-command.ts
4689
4753
  /**
@@ -5089,8 +5153,8 @@ var RovodevCommand = class RovodevCommand extends ToolCommand {
5089
5153
  const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
5090
5154
  let existing = {};
5091
5155
  if (existingContent) try {
5092
- const parsed = (0, js_yaml.load)(existingContent);
5093
- if (isPlainObject(parsed)) existing = parsed;
5156
+ const parsed = loadYaml(existingContent);
5157
+ if (isPlainObject$1(parsed)) existing = parsed;
5094
5158
  } catch {}
5095
5159
  const prompts = rovodevCommands.map((command) => ({
5096
5160
  name: command.getName(),
@@ -5120,11 +5184,11 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
5120
5184
  if (!manifestContent) return "";
5121
5185
  let parsed;
5122
5186
  try {
5123
- parsed = (0, js_yaml.load)(manifestContent);
5187
+ parsed = loadYaml(manifestContent);
5124
5188
  } catch {
5125
5189
  return "";
5126
5190
  }
5127
- if (!isPlainObject(parsed) || !Array.isArray(parsed.prompts)) return "";
5191
+ if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
5128
5192
  const expectedContentFile = toPosixPath((0, node_path.join)("prompts", relativeFilePath));
5129
5193
  const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
5130
5194
  return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
@@ -6162,18 +6226,25 @@ const QWENCODE_HOOK_EVENTS = [
6162
6226
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6163
6227
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6164
6228
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6165
- * `SubagentStop`, `Notification`, `PreCompact`), but only the four events the
6166
- * upstream issue scoped in are mapped here: `PreToolUse`, `PostToolUse`,
6167
- * `UserPromptSubmit` ← `beforeSubmitPrompt`, and `Stop`. `match` (Reasonix's
6168
- * matcher field name) is honored only on `PreToolUse`/`PostToolUse`, matching
6169
- * the canonical `matcher` field's tool-event scoping used by other adapters.
6229
+ * `SubagentStop`, `Notification`, `PreCompact`). The eight events with a clean
6230
+ * canonical equivalent are mapped: `PreToolUse`, `PostToolUse`,
6231
+ * `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
6232
+ * `SessionEnd`, `SubagentStop`, and `PostLLMCall` `postModelInvocation`.
6233
+ * (`Notification` and `PreCompact` have no canonical event and are left out.)
6234
+ * `match` (Reasonix's matcher field name) is honored only on
6235
+ * `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
6236
+ * tool-event scoping used by other adapters.
6170
6237
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6171
6238
  */
6172
6239
  const REASONIX_HOOK_EVENTS = [
6173
6240
  "preToolUse",
6174
6241
  "postToolUse",
6175
6242
  "beforeSubmitPrompt",
6176
- "stop"
6243
+ "stop",
6244
+ "sessionStart",
6245
+ "sessionEnd",
6246
+ "subagentStop",
6247
+ "postModelInvocation"
6177
6248
  ];
6178
6249
  /**
6179
6250
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6609,14 +6680,18 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6609
6680
  /**
6610
6681
  * Map canonical camelCase event names to Reasonix PascalCase.
6611
6682
  * Reasonix explicitly mirrors Claude Code's hooks model, so it reuses the same
6612
- * PascalCase names for the four events rulesync maps.
6683
+ * PascalCase names for the events rulesync maps.
6613
6684
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6614
6685
  */
6615
6686
  const CANONICAL_TO_REASONIX_EVENT_NAMES = {
6616
6687
  preToolUse: "PreToolUse",
6617
6688
  postToolUse: "PostToolUse",
6618
6689
  beforeSubmitPrompt: "UserPromptSubmit",
6619
- stop: "Stop"
6690
+ stop: "Stop",
6691
+ sessionStart: "SessionStart",
6692
+ sessionEnd: "SessionEnd",
6693
+ subagentStop: "SubagentStop",
6694
+ postModelInvocation: "PostLLMCall"
6620
6695
  };
6621
6696
  /**
6622
6697
  * Map Reasonix PascalCase event names to canonical camelCase.
@@ -6665,6 +6740,21 @@ function applyCommandPrefix({ def, converterConfig }) {
6665
6740
  return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ? `"${converterConfig.projectDirVar}"/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
6666
6741
  }
6667
6742
  /**
6743
+ * Emit the configured boolean passthrough fields on the tool side, mapping each
6744
+ * canonical field name to its (possibly renamed) tool field name. Only boolean
6745
+ * values are carried through.
6746
+ */
6747
+ function emitBooleanPassthroughFields({ def, converterConfig }) {
6748
+ return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical }) => typeof def[canonical] === "boolean").map(({ canonical, tool }) => [tool, def[canonical]]));
6749
+ }
6750
+ /**
6751
+ * Import the configured boolean passthrough fields back into canonical fields,
6752
+ * reversing {@link emitBooleanPassthroughFields}. Only boolean values are read.
6753
+ */
6754
+ function importBooleanPassthroughFields({ h, converterConfig }) {
6755
+ return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
6756
+ }
6757
+ /**
6668
6758
  * Convert the definitions of a single matcher group into tool hook entries,
6669
6759
  * honoring supported hook types and passthrough fields.
6670
6760
  */
@@ -6678,6 +6768,10 @@ function buildToolHooks({ defs, converterConfig }) {
6678
6768
  converterConfig
6679
6769
  });
6680
6770
  hooks.push({
6771
+ ...emitBooleanPassthroughFields({
6772
+ def,
6773
+ converterConfig
6774
+ }),
6681
6775
  type: hookType,
6682
6776
  ...command !== void 0 && command !== null && { command },
6683
6777
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -6765,6 +6859,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
6765
6859
  ...prompt !== void 0 && prompt !== null && { prompt },
6766
6860
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
6767
6861
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
6862
+ ...importBooleanPassthroughFields({
6863
+ h,
6864
+ converterConfig
6865
+ }),
6768
6866
  ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
6769
6867
  };
6770
6868
  }
@@ -7059,7 +7157,7 @@ function combineAugmentSettings(base, local) {
7059
7157
  const baseValue = result[key];
7060
7158
  if (AUGMENTCODE_REPLACE_KEYS.has(key)) result[key] = localValue;
7061
7159
  else if (Array.isArray(localValue) && Array.isArray(baseValue)) result[key] = [...localValue, ...baseValue];
7062
- else if (isPlainObject(localValue) && isPlainObject(baseValue)) result[key] = combineAugmentSettings(baseValue, localValue);
7160
+ else if (isPlainObject$1(localValue) && isPlainObject$1(baseValue)) result[key] = combineAugmentSettings(baseValue, localValue);
7063
7161
  else result[key] = localValue;
7064
7162
  }
7065
7163
  return result;
@@ -7103,14 +7201,14 @@ async function readAugmentcodeSettingsWithLocalOverlay({ outputRoot, relativeDir
7103
7201
  } catch (error) {
7104
7202
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
7105
7203
  }
7106
- if (!isPlainObject(localParsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
7204
+ if (!isPlainObject$1(localParsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
7107
7205
  let baseParsed;
7108
7206
  try {
7109
7207
  baseParsed = JSON.parse(baseContent);
7110
7208
  } catch {
7111
7209
  return baseContent;
7112
7210
  }
7113
- const merged = combineAugmentSettings(isPlainObject(baseParsed) ? baseParsed : {}, localParsed);
7211
+ const merged = combineAugmentSettings(isPlainObject$1(baseParsed) ? baseParsed : {}, localParsed);
7114
7212
  return JSON.stringify(merged, null, 2);
7115
7213
  }
7116
7214
  //#endregion
@@ -8695,7 +8793,14 @@ const JUNIE_CONVERTER_CONFIG = {
8695
8793
  toolToCanonicalEventNames: JUNIE_TO_CANONICAL_EVENT_NAMES,
8696
8794
  projectDirVar: "",
8697
8795
  supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
8698
- noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"])
8796
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
8797
+ booleanPassthroughFields: [{
8798
+ canonical: "failClosed",
8799
+ tool: "blockOnError"
8800
+ }, {
8801
+ canonical: "async",
8802
+ tool: "async"
8803
+ }]
8699
8804
  };
8700
8805
  var JunieHooks = class JunieHooks extends ToolHooks {
8701
8806
  constructor(params) {
@@ -9731,8 +9836,9 @@ function reasonixHooksToCanonical(hooks) {
9731
9836
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
9732
9837
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
9733
9838
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
9734
- * Only the four events documented in the upstream issue are mapped:
9735
- * PreToolUse/PostToolUse/UserPromptSubmit/Stop (see REASONIX_HOOK_EVENTS).
9839
+ * The eight upstream events with a clean canonical equivalent are mapped:
9840
+ * PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
9841
+ * SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
9736
9842
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
9737
9843
  */
9738
9844
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -11811,7 +11917,7 @@ function parseAmpSettingsJsonc(fileContent) {
11811
11917
  const details = errors.map((error) => `${(0, jsonc_parser.printParseErrorCode)(error.error)} at offset ${error.offset}`).join(", ");
11812
11918
  throw new Error(`Failed to parse Amp settings: ${details}`);
11813
11919
  }
11814
- if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
11920
+ if (!isPlainObject$1(parsed)) throw new Error("Amp settings must be a JSON object");
11815
11921
  return parsed;
11816
11922
  }
11817
11923
  function filterMcpServers(mcpServers) {
@@ -12133,7 +12239,7 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
12133
12239
  } catch (error) {
12134
12240
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
12135
12241
  }
12136
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
12242
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
12137
12243
  return parsed;
12138
12244
  }
12139
12245
  /**
@@ -12359,7 +12465,7 @@ function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
12359
12465
  } catch (error) {
12360
12466
  throw new Error(`Failed to parse Cline MCP settings at ${configPath}: ${formatError(error)}`, { cause: error });
12361
12467
  }
12362
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Cline MCP settings at ${configPath}: expected a JSON object`);
12468
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Cline MCP settings at ${configPath}: expected a JSON object`);
12363
12469
  return parsed;
12364
12470
  }
12365
12471
  /**
@@ -12630,7 +12736,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
12630
12736
  for (const [key, value] of Object.entries(obj)) {
12631
12737
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
12632
12738
  if (value === null) continue;
12633
- if (isPlainObject(value)) {
12739
+ if (isPlainObject$1(value)) {
12634
12740
  const cleaned = this.removeEmptyEntries(value, depth + 1);
12635
12741
  if (Object.keys(cleaned).length === 0) continue;
12636
12742
  filtered[key] = cleaned;
@@ -13271,12 +13377,12 @@ function parseGooseConfig(fileContent, relativeDirPath, relativeFilePath) {
13271
13377
  const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
13272
13378
  let parsed;
13273
13379
  try {
13274
- parsed = (0, js_yaml.load)(fileContent);
13380
+ parsed = loadYaml(fileContent);
13275
13381
  } catch (error) {
13276
13382
  throw new Error(`Failed to parse Goose config at ${configPath}: ${formatError(error)}`, { cause: error });
13277
13383
  }
13278
13384
  if (parsed === void 0 || parsed === null) return {};
13279
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Goose config at ${configPath}: expected a YAML mapping`);
13385
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Goose config at ${configPath}: expected a YAML mapping`);
13280
13386
  return parsed;
13281
13387
  }
13282
13388
  /**
@@ -13321,7 +13427,7 @@ function applyGooseStdioFields(ext, config) {
13321
13427
  ext.cmd = command;
13322
13428
  if (isStringArray(config.args)) ext.args = config.args;
13323
13429
  }
13324
- if (isPlainObject(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13430
+ if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13325
13431
  }
13326
13432
  /**
13327
13433
  * Converts a single rulesync canonical MCP server into a Goose `extensions:` entry.
@@ -13336,7 +13442,7 @@ function convertServerToGooseExtension(name, config) {
13336
13442
  if (gooseType === "stdio") applyGooseStdioFields(ext, config);
13337
13443
  else if (gooseType === "sse" || gooseType === "streamable_http") {
13338
13444
  if (url !== void 0) ext.uri = url;
13339
- if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13445
+ if (isPlainObject$1(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13340
13446
  }
13341
13447
  ext.enabled = config.disabled !== true;
13342
13448
  const timeout = resolveGooseTimeout(config);
@@ -13378,9 +13484,9 @@ function convertFromGooseFormat(extensions) {
13378
13484
  else if (type === "stdio") server.type = "stdio";
13379
13485
  if (typeof ext.cmd === "string") server.command = ext.cmd;
13380
13486
  if (isStringArray(ext.args)) server.args = ext.args;
13381
- if (isPlainObject(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13487
+ if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13382
13488
  if (typeof ext.uri === "string") server.url = ext.uri;
13383
- if (isPlainObject(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13489
+ if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13384
13490
  if (ext.enabled === false) server.disabled = true;
13385
13491
  if (typeof ext.timeout === "number") server.timeout = ext.timeout;
13386
13492
  result[name] = server;
@@ -13403,7 +13509,7 @@ function buildGoosePluginStdioServer(config) {
13403
13509
  server.command = command;
13404
13510
  if (isStringArray(config.args)) server.args = config.args;
13405
13511
  }
13406
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13512
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13407
13513
  if (typeof config.cwd === "string") server.cwd = config.cwd;
13408
13514
  return server;
13409
13515
  }
@@ -13703,7 +13809,7 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
13703
13809
  for (const [key, value] of Object.entries(obj)) {
13704
13810
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
13705
13811
  if (value === null) continue;
13706
- if (isPlainObject(value)) {
13812
+ if (isPlainObject$1(value)) {
13707
13813
  const cleaned = this.removeEmptyEntries(value, depth + 1);
13708
13814
  if (Object.keys(cleaned).length === 0) continue;
13709
13815
  filtered[key] = cleaned;
@@ -13765,10 +13871,10 @@ function convertServerToHermes(config) {
13765
13871
  out.command = command;
13766
13872
  if (isStringArray(config.args)) out.args = config.args;
13767
13873
  }
13768
- if (isPlainObject(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13874
+ if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13769
13875
  } else if (url !== void 0) {
13770
13876
  out.url = url;
13771
- if (isPlainObject(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13877
+ if (isPlainObject$1(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13772
13878
  }
13773
13879
  if (config.disabled === true) out.enabled = false;
13774
13880
  const timeout = resolveHermesTimeout(config);
@@ -13813,9 +13919,9 @@ function convertFromHermesFormat(mcpServers) {
13813
13919
  const server = {};
13814
13920
  if (typeof config.command === "string") server.command = config.command;
13815
13921
  if (isStringArray(config.args)) server.args = config.args;
13816
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13922
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13817
13923
  if (typeof config.url === "string") server.url = config.url;
13818
- if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13924
+ if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13819
13925
  if (config.enabled === false) server.disabled = true;
13820
13926
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
13821
13927
  if (isRecord(config.tools)) {
@@ -14752,7 +14858,9 @@ const REASONIX_PLUGIN_FIELDS = [
14752
14858
  "env",
14753
14859
  "url",
14754
14860
  "headers",
14755
- "trusted_read_only_tools"
14861
+ "trusted_read_only_tools",
14862
+ "call_timeout_seconds",
14863
+ "tool_timeout_seconds"
14756
14864
  ];
14757
14865
  var ReasonixMcp = class ReasonixMcp extends ToolMcp {
14758
14866
  toml;
@@ -14984,7 +15092,7 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
14984
15092
  } catch (error) {
14985
15093
  throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: ${formatError(error)}`, { cause: error });
14986
15094
  }
14987
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: expected a JSON object`);
15095
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: expected a JSON object`);
14988
15096
  return parsed;
14989
15097
  }
14990
15098
  /**
@@ -16255,15 +16363,123 @@ const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
16255
16363
  enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
16256
16364
  });
16257
16365
  /**
16366
+ * Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
16367
+ * array supports "custom policy" entries the canonical allow/ask/deny model
16368
+ * cannot express: `permission.type` of `webhook-policy` / `script-policy`
16369
+ * (delegating the decision to a `webhookUrl` / `script`) and an `eventType` of
16370
+ * `tool-response` (a post-execution check rather than the default pre-execution
16371
+ * `tool-call`). These are authored here as verbatim `toolPermissions` entries
16372
+ * and prepended — ahead of the canonical-generated basic rules — into the shared
16373
+ * `.augment/settings.json`, so a webhook/script gate or tool-response check is
16374
+ * never shadowed by a regenerated allow/deny/ask entry under AugmentCode's
16375
+ * first-match-wins evaluation. The shared `permission` block continues to drive
16376
+ * the basic `allow` / `deny` / `ask-user` entries. Kept `looseObject` (verbatim
16377
+ * passthrough) so `shellInputRegex`, `eventType`, `webhookUrl`, `script`, and
16378
+ * any future policy field survive untouched. Both project and global scope are
16379
+ * supported.
16380
+ *
16381
+ * @example
16382
+ * { "toolPermissions": [
16383
+ * { "toolName": "github-api",
16384
+ * "permission": { "type": "webhook-policy", "webhookUrl": "https://api.example.com/validate" } },
16385
+ * { "toolName": "view", "eventType": "tool-response", "permission": { "type": "allow" } } ] }
16386
+ */
16387
+ const AugmentcodePermissionsOverrideSchema = zod_mini.z.looseObject({ toolPermissions: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.looseObject({
16388
+ toolName: zod_mini.z.string(),
16389
+ permission: zod_mini.z.looseObject({ type: zod_mini.z.string() })
16390
+ }))) });
16391
+ /**
16392
+ * Tool-scoped override block for Kiro. Kiro's agent config (`.kiro/agents/<name>.json`)
16393
+ * exposes per-tool `toolsSettings` knobs with no canonical allow/ask/deny
16394
+ * category: the shell auto-trust flags `shell.autoAllowReadonly` /
16395
+ * `shell.denyByDefault`, the `aws` built-in tool's `allowedServices` /
16396
+ * `deniedServices` (+ `autoAllowReadonly`), and the `web_fetch` domain trust
16397
+ * arrays `trusted` / `blocked` (regex host patterns; documented for `web_fetch`
16398
+ * only — `web_search` has no such surface). Fields placed here are deep-merged
16399
+ * (per `toolsSettings` key, override wins at the leaf) into the shared agent
16400
+ * config, while the canonical `permission` block continues to drive
16401
+ * `shell.{allowed,denied}Commands`, `read`/`write`/`grep`/`glob` paths, and the
16402
+ * `web_fetch`/`web_search` `allowedTools` toggles. Kept `looseObject` at every
16403
+ * level (verbatim passthrough) so future Kiro `toolsSettings` fields survive.
16404
+ *
16405
+ * Kiro's MCP `autoApprove` / `disabledTools` lists are intentionally NOT modeled
16406
+ * here: they live in a SEPARATE file (`.kiro/settings/mcp.json`, under
16407
+ * `mcpServers.<name>`), not the agent config this permissions translator writes,
16408
+ * and reconciling them with the canonical `mcp__*` model is a distinct design
16409
+ * question left out of scope.
16410
+ *
16411
+ * @example
16412
+ * { "toolsSettings": { "shell": { "autoAllowReadonly": true },
16413
+ * "aws": { "allowedServices": ["s3"], "deniedServices": ["eks"] },
16414
+ * "web_fetch": { "trusted": [".*github\\.com.*"] } } }
16415
+ */
16416
+ const KiroPermissionsOverrideSchema = zod_mini.z.looseObject({ toolsSettings: zod_mini.z.optional(zod_mini.z.looseObject({
16417
+ shell: zod_mini.z.optional(zod_mini.z.looseObject({
16418
+ autoAllowReadonly: zod_mini.z.optional(zod_mini.z.boolean()),
16419
+ denyByDefault: zod_mini.z.optional(zod_mini.z.boolean())
16420
+ })),
16421
+ aws: zod_mini.z.optional(zod_mini.z.looseObject({
16422
+ allowedServices: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
16423
+ deniedServices: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
16424
+ autoAllowReadonly: zod_mini.z.optional(zod_mini.z.boolean())
16425
+ })),
16426
+ web_fetch: zod_mini.z.optional(zod_mini.z.looseObject({
16427
+ trusted: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
16428
+ blocked: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string()))
16429
+ }))
16430
+ })) });
16431
+ /**
16432
+ * Codex CLI-scoped permission override.
16433
+ *
16434
+ * Codex CLI's permission surface is richer than the canonical allow/ask/deny
16435
+ * model: its approval workflow, classic sandbox system, and per-app tool gating
16436
+ * have no canonical category. Author them through a tool-scoped `codexcli`
16437
+ * override whose fields are written verbatim as top-level `.codex/config.toml`
16438
+ * keys (the override wins per key; existing sibling keys the user set directly
16439
+ * are preserved):
16440
+ * - `approval_policy` — `untrusted` | `on-request` | `never`, or a
16441
+ * `{ granular = { … } }` table (kept verbatim; the granular schema has
16442
+ * required fields that are brittle to model as typed keys).
16443
+ * - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
16444
+ * with the sibling `sandbox_workspace_write` table (`network_access`,
16445
+ * `writable_roots`, …).
16446
+ * - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
16447
+ * `.enabled`, `apps.<id>.default_tools_approval_mode`).
16448
+ * - `approvals_reviewer` — the reviewer-approval surface.
16449
+ *
16450
+ * Two surfaces are deliberately NOT authorable here so the override can never
16451
+ * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
16452
+ * MCP feature (`codexcli-mcp.ts` already writes the `mcp_servers` tables in the
16453
+ * same `config.toml`), and `permissions` / `default_permissions` are owned by
16454
+ * the canonical model. Any such key placed in the override is skipped with a
16455
+ * warning. Kept `looseObject` (verbatim passthrough) so future top-level Codex
16456
+ * config keys can be authored without Rulesync modeling each one.
16457
+ *
16458
+ * @see https://developers.openai.com/codex/config-reference
16459
+ * @see https://developers.openai.com/codex/permissions
16460
+ *
16461
+ * @example
16462
+ * { "approval_policy": "on-request", "sandbox_mode": "workspace-write",
16463
+ * "sandbox_workspace_write": { "network_access": true } }
16464
+ */
16465
+ const CodexcliPermissionsOverrideSchema = zod_mini.z.looseObject({
16466
+ approval_policy: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
16467
+ sandbox_mode: zod_mini.z.optional(zod_mini.z.string()),
16468
+ sandbox_workspace_write: zod_mini.z.optional(zod_mini.z.looseObject({})),
16469
+ apps: zod_mini.z.optional(zod_mini.z.looseObject({})),
16470
+ approvals_reviewer: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})]))
16471
+ });
16472
+ /**
16258
16473
  * Permissions configuration.
16259
16474
  * Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
16260
16475
  * Values are pattern-to-action mappings for that tool category.
16261
16476
  *
16262
16477
  * The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
16263
16478
  * `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
16264
- * `antigravity-cli` keys are tool-scoped overrides consumed only by their
16265
- * respective translator (see the matching `*PermissionsOverrideSchema`); every
16266
- * other tool reads the shared `permission` block and ignores them.
16479
+ * `antigravity-cli`/`augmentcode`/`kiro`/`codexcli` keys are tool-scoped
16480
+ * overrides consumed only by their respective translator (see the matching
16481
+ * `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
16482
+ * block and ignores them.
16267
16483
  *
16268
16484
  * @example
16269
16485
  * {
@@ -16287,7 +16503,10 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
16287
16503
  junie: zod_mini.z.optional(JuniePermissionsOverrideSchema),
16288
16504
  takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
16289
16505
  amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
16290
- "antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema)
16506
+ "antigravity-cli": zod_mini.z.optional(AntigravityCliPermissionsOverrideSchema),
16507
+ augmentcode: zod_mini.z.optional(AugmentcodePermissionsOverrideSchema),
16508
+ kiro: zod_mini.z.optional(KiroPermissionsOverrideSchema),
16509
+ codexcli: zod_mini.z.optional(CodexcliPermissionsOverrideSchema)
16291
16510
  });
16292
16511
  /**
16293
16512
  * Full permissions file schema including optional $schema field.
@@ -16439,7 +16658,7 @@ function parseAmpSettings(fileContent) {
16439
16658
  const details = errors.map((error) => `${(0, jsonc_parser.printParseErrorCode)(error.error)} at offset ${error.offset}`).join(", ");
16440
16659
  throw new Error(`Failed to parse Amp settings: ${details}`);
16441
16660
  }
16442
- if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
16661
+ if (!isPlainObject$1(parsed)) throw new Error("Amp settings must be a JSON object");
16443
16662
  return parsed;
16444
16663
  }
16445
16664
  function toDisableList(value) {
@@ -16466,7 +16685,7 @@ function toPermissionsList(value) {
16466
16685
  if (!Array.isArray(value)) return [];
16467
16686
  const entries = [];
16468
16687
  for (const raw of value) {
16469
- if (!isPlainObject(raw)) continue;
16688
+ if (!isPlainObject$1(raw)) continue;
16470
16689
  const { tool, action } = raw;
16471
16690
  if (typeof tool !== "string" || typeof action !== "string") continue;
16472
16691
  if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
@@ -16474,7 +16693,7 @@ function toPermissionsList(value) {
16474
16693
  ...raw,
16475
16694
  tool,
16476
16695
  action,
16477
- ...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
16696
+ ...isPlainObject$1(raw.matches) ? { matches: raw.matches } : {}
16478
16697
  });
16479
16698
  }
16480
16699
  return entries;
@@ -17334,6 +17553,55 @@ function isSpecialEntry(entry) {
17334
17553
  return false;
17335
17554
  }
17336
17555
  /**
17556
+ * A composite identity for a special entry, used to de-duplicate authored
17557
+ * (`augmentcode` override) and preserved (existing-file) special entries so the
17558
+ * same policy is not emitted twice into `toolPermissions`. Every field that can
17559
+ * distinguish two special entries is included.
17560
+ */
17561
+ function specialEntryKey(entry) {
17562
+ return JSON.stringify([
17563
+ entry.toolName,
17564
+ entry.shellInputRegex ?? "",
17565
+ entry.eventType ?? "",
17566
+ entry.permission.type,
17567
+ entry.permission.webhookUrl ?? "",
17568
+ entry.permission.script ?? ""
17569
+ ]);
17570
+ }
17571
+ /**
17572
+ * Stable de-duplication of special entries by {@link specialEntryKey}, keeping
17573
+ * the first occurrence (authored entries lead, so an authored policy wins over
17574
+ * an identical preserved one).
17575
+ */
17576
+ function dedupeSpecialEntries(entries) {
17577
+ const seen = /* @__PURE__ */ new Set();
17578
+ const out = [];
17579
+ for (const entry of entries) {
17580
+ const key = specialEntryKey(entry);
17581
+ if (seen.has(key)) continue;
17582
+ seen.add(key);
17583
+ out.push(entry);
17584
+ }
17585
+ return out;
17586
+ }
17587
+ /**
17588
+ * Validate/normalize the `augmentcode` override's `toolPermissions` array into
17589
+ * typed `AugmentToolPermission` entries, dropping any malformed item (with a
17590
+ * warning so a typo is not lost silently). The override schema is intentionally
17591
+ * loose (verbatim passthrough), so entries are re-parsed through the full entry
17592
+ * schema here to guarantee shape before they are written into the shared
17593
+ * settings file.
17594
+ */
17595
+ function coerceAuthoredEntries(entries, logger) {
17596
+ const out = [];
17597
+ for (const item of entries) {
17598
+ const parsed = AugmentToolPermissionSchema.safeParse(item);
17599
+ if (parsed.success) out.push(parsed.data);
17600
+ else logger?.warn(`AugmentCode permissions: dropping malformed 'augmentcode.toolPermissions' override entry: ${formatError(parsed.error)}.`);
17601
+ }
17602
+ return out;
17603
+ }
17604
+ /**
17337
17605
  * Convert a glob-like pattern into a regex string for AugmentCode's `shellInputRegex`.
17338
17606
  * Maps glob `*` to `.*`, `?` to `.`, escapes other regex metacharacters, and anchors at both ends.
17339
17607
  */
@@ -17464,12 +17732,18 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17464
17732
  } catch (error) {
17465
17733
  throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
17466
17734
  }
17735
+ const config = rulesyncPermissions.getJson();
17467
17736
  const generated = convertRulesyncToAugmentEntries({
17468
- config: rulesyncPermissions.getJson(),
17737
+ config,
17469
17738
  logger
17470
17739
  });
17471
17740
  const existingEntries = settings.toolPermissions ?? [];
17472
- const specialEntries = existingEntries.filter((entry) => isSpecialEntry(entry));
17741
+ const override = config.augmentcode;
17742
+ const authoredEntries = override?.toolPermissions ? coerceAuthoredEntries(override.toolPermissions, logger) : [];
17743
+ const authoredSpecials = authoredEntries.filter((entry) => isSpecialEntry(entry));
17744
+ const authoredBasics = authoredEntries.filter((entry) => !isSpecialEntry(entry));
17745
+ const preservedSpecials = override?.toolPermissions ? [] : existingEntries.filter((entry) => isSpecialEntry(entry));
17746
+ const specialEntries = dedupeSpecialEntries([...authoredSpecials, ...preservedSpecials]);
17473
17747
  const basicExistingEntries = existingEntries.filter((entry) => !isSpecialEntry(entry));
17474
17748
  const generatedKeys = new Set(generated.map((e) => `${e.toolName}|${e.shellInputRegex ?? ""}|${e.permission.type}`));
17475
17749
  const preservedBasicEntries = basicExistingEntries.filter((entry) => {
@@ -17480,7 +17754,11 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17480
17754
  }
17481
17755
  return false;
17482
17756
  });
17483
- const sortedBasic = sortAugmentEntries([...generated, ...preservedBasicEntries]);
17757
+ const sortedBasic = sortAugmentEntries([
17758
+ ...generated,
17759
+ ...preservedBasicEntries,
17760
+ ...authoredBasics
17761
+ ]);
17484
17762
  const merged = {
17485
17763
  ...settings,
17486
17764
  toolPermissions: [...specialEntries, ...sortedBasic]
@@ -17504,11 +17782,14 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17504
17782
  } catch (error) {
17505
17783
  throw new Error(`Failed to parse AugmentCode permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
17506
17784
  }
17507
- const config = convertAugmentToRulesyncPermissions({
17508
- entries: settings.toolPermissions ?? [],
17785
+ const allEntries = settings.toolPermissions ?? [];
17786
+ const specialEntries = allEntries.filter((entry) => isSpecialEntry(entry));
17787
+ const result = { ...convertAugmentToRulesyncPermissions({
17788
+ entries: allEntries.filter((entry) => !isSpecialEntry(entry)),
17509
17789
  logger: moduleLogger$1
17510
- });
17511
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
17790
+ }) };
17791
+ if (specialEntries.length > 0) result.augmentcode = { toolPermissions: specialEntries };
17792
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
17512
17793
  }
17513
17794
  validate() {
17514
17795
  try {
@@ -17642,10 +17923,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
17642
17923
  ]);
17643
17924
  const permission = {};
17644
17925
  for (const entry of entries) {
17645
- if (isSpecialEntry(entry)) {
17646
- logger?.warn(`AugmentCode permissions: skipping advanced entry for tool '${entry.toolName}' (type '${entry.permission.type}'${entry.eventType !== void 0 ? `, eventType '${entry.eventType}'` : ""}) on import; rulesync's permission model cannot represent custom policies, eventType, webhookUrl, or script. Such entries are preserved on generate but not imported.`);
17647
- continue;
17648
- }
17926
+ if (isSpecialEntry(entry)) continue;
17649
17927
  const type = entry.permission.type;
17650
17928
  if (!isBasicAugmentType(type)) continue;
17651
17929
  const canonical = toCanonicalToolName$5(entry.toolName);
@@ -18077,6 +18355,13 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
18077
18355
  ]);
18078
18356
  const CODEX_MINIMAL_KEY = ":minimal";
18079
18357
  const GLOBAL_WILDCARD_DOMAIN = "*";
18358
+ const CODEXCLI_OVERRIDE_KEYS = [
18359
+ "approval_policy",
18360
+ "sandbox_mode",
18361
+ "sandbox_workspace_write",
18362
+ "apps",
18363
+ "approvals_reviewer"
18364
+ ];
18080
18365
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18081
18366
  static getSettablePaths(_options = {}) {
18082
18367
  return {
@@ -18118,6 +18403,11 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18118
18403
  });
18119
18404
  parsed.permissions = permissionsTable;
18120
18405
  parsed.default_permissions = RULESYNC_PROFILE_NAME;
18406
+ applyCodexcliOverride({
18407
+ parsed,
18408
+ override: rulesyncPermissions.getJson().codexcli,
18409
+ logger
18410
+ });
18121
18411
  return new CodexcliPermissions({
18122
18412
  outputRoot,
18123
18413
  relativeDirPath: paths.relativeDirPath,
@@ -18142,7 +18432,12 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18142
18432
  profile,
18143
18433
  domainsHadUnknown
18144
18434
  });
18145
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
18435
+ const override = extractCodexcliOverride(table);
18436
+ const result = Object.keys(override).length > 0 ? {
18437
+ ...config,
18438
+ codexcli: override
18439
+ } : config;
18440
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
18146
18441
  }
18147
18442
  validate() {
18148
18443
  return {
@@ -18334,6 +18629,30 @@ function toMutableTable(value) {
18334
18629
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18335
18630
  return { ...value };
18336
18631
  }
18632
+ function isPlainObject(value) {
18633
+ return value !== null && typeof value === "object" && !Array.isArray(value);
18634
+ }
18635
+ function applyCodexcliOverride({ parsed, override, logger }) {
18636
+ if (!override) return;
18637
+ const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
18638
+ for (const [key, value] of Object.entries(override)) {
18639
+ if (!allowed.has(key)) {
18640
+ logger?.warn(`Codex CLI permission override key "${key}" is not managed and was skipped. "permissions"/"default_permissions" are owned by the canonical permission model and "mcp_servers" gating by the MCP feature.`);
18641
+ continue;
18642
+ }
18643
+ if (value === void 0) continue;
18644
+ const existing = parsed[key];
18645
+ parsed[key] = isPlainObject(existing) && isPlainObject(value) ? {
18646
+ ...existing,
18647
+ ...value
18648
+ } : value;
18649
+ }
18650
+ }
18651
+ function extractCodexcliOverride(table) {
18652
+ const override = {};
18653
+ for (const key of CODEXCLI_OVERRIDE_KEYS) if (table[key] !== void 0) override[key] = table[key];
18654
+ return override;
18655
+ }
18337
18656
  function toFilesystemRecord(value) {
18338
18657
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
18339
18658
  const result = {};
@@ -19248,7 +19567,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19248
19567
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19249
19568
  let parsed;
19250
19569
  try {
19251
- parsed = existingContent.trim() === "" ? {} : (0, js_yaml.load)(existingContent);
19570
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
19252
19571
  } catch (error) {
19253
19572
  throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
19254
19573
  }
@@ -19270,7 +19589,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19270
19589
  let parsed;
19271
19590
  try {
19272
19591
  const content = this.getFileContent();
19273
- parsed = content.trim() === "" ? {} : (0, js_yaml.load)(content);
19592
+ parsed = content.trim() === "" ? {} : loadYaml(content);
19274
19593
  } catch (error) {
19275
19594
  throw new Error(`Failed to parse Goose permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
19276
19595
  }
@@ -20177,6 +20496,13 @@ const KiroAgentSchema = zod_mini.z.looseObject({
20177
20496
  toolsSettings: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown()))
20178
20497
  });
20179
20498
  const UnknownRecordSchema = zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown());
20499
+ const CANONICAL_SHELL_KEYS = /* @__PURE__ */ new Set(["allowedCommands", "deniedCommands"]);
20500
+ const CANONICAL_TOOL_SETTINGS_KEYS = /* @__PURE__ */ new Set([
20501
+ "read",
20502
+ "write",
20503
+ "grep",
20504
+ "glob"
20505
+ ]);
20180
20506
  var KiroPermissions = class KiroPermissions extends ToolPermissions {
20181
20507
  static getSettablePaths(_options = {}) {
20182
20508
  return {
@@ -20240,7 +20566,10 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
20240
20566
  const allowedTools = new Set(parsed.allowedTools ?? []);
20241
20567
  if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
20242
20568
  if (allowedTools.has("web_search")) permission.websearch = { "*": "allow" };
20243
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
20569
+ const kiroOverride = extractKiroOverride(toolsSettings);
20570
+ const result = { permission };
20571
+ if (kiroOverride !== void 0) result.kiro = kiroOverride;
20572
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
20244
20573
  }
20245
20574
  validate() {
20246
20575
  return {
@@ -20290,19 +20619,71 @@ function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
20290
20619
  });
20291
20620
  else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
20292
20621
  }
20293
- nextToolsSettings.shell = shell;
20622
+ nextToolsSettings.shell = {
20623
+ ...preservedShellFlags(existing),
20624
+ ...shell
20625
+ };
20294
20626
  nextToolsSettings.read = pathTable(pathBuckets.read);
20295
20627
  nextToolsSettings.write = pathTable(pathBuckets.write);
20296
20628
  for (const key of ["grep", "glob"]) {
20297
20629
  const bucket = pathBuckets[key];
20298
20630
  if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
20299
20631
  }
20632
+ applyKiroOverride({
20633
+ override: config.kiro,
20634
+ nextToolsSettings,
20635
+ logger
20636
+ });
20300
20637
  return {
20301
20638
  ...existing,
20302
20639
  allowedTools: [...nextAllowedTools].toSorted(),
20303
20640
  toolsSettings: nextToolsSettings
20304
20641
  };
20305
20642
  }
20643
+ /**
20644
+ * Non-canonical `toolsSettings.shell` keys already present in the existing agent
20645
+ * config (everything except the canonical `allowed`/`deniedCommands`), so a
20646
+ * regenerate does not silently drop a hand-set `autoAllowReadonly` /
20647
+ * `denyByDefault` when no `kiro` override re-authors it.
20648
+ */
20649
+ function preservedShellFlags(existing) {
20650
+ const existingShell = asRecord$1(asRecord$1(existing.toolsSettings).shell);
20651
+ const flags = {};
20652
+ for (const [key, value] of Object.entries(existingShell)) if (!CANONICAL_SHELL_KEYS.has(key)) flags[key] = value;
20653
+ return flags;
20654
+ }
20655
+ /**
20656
+ * Deep-merge the `kiro` override's `toolsSettings` block into the generated
20657
+ * settings, one `toolsSettings` key at a time so the override's leaf fields win
20658
+ * without clobbering canonical-generated siblings.
20659
+ *
20660
+ * Guards, so the override can only author the non-canonical surfaces it is meant
20661
+ * for and can never weaken a canonical-generated deny:
20662
+ * - prototype-pollution keys are skipped before being used as object keys;
20663
+ * - fully-canonical `toolsSettings` keys (`read`/`write`/`grep`/`glob`) are
20664
+ * rejected outright with a warning (their paths are owned by the canonical
20665
+ * `permission` block);
20666
+ * - for `shell` (partly canonical), the canonical command-list leaves
20667
+ * (`allowed`/`deniedCommands`) are stripped from the override value so only the
20668
+ * auto-trust flags merge.
20669
+ */
20670
+ function applyKiroOverride({ override, nextToolsSettings, logger }) {
20671
+ const overrideToolsSettings = override?.toolsSettings;
20672
+ if (!isPlainObject$1(overrideToolsSettings)) return;
20673
+ for (const [key, value] of Object.entries(overrideToolsSettings)) {
20674
+ if (isPrototypePollutionKey(key)) continue;
20675
+ if (!isPlainObject$1(value)) continue;
20676
+ if (CANONICAL_TOOL_SETTINGS_KEYS.has(key)) {
20677
+ logger?.warn(`Kiro permissions: ignoring 'kiro.toolsSettings.${key}' override; '${key}' paths are driven by the canonical permission block.`);
20678
+ continue;
20679
+ }
20680
+ const mergeValue = key === "shell" ? Object.fromEntries(Object.entries(value).filter(([leaf]) => !CANONICAL_SHELL_KEYS.has(leaf))) : value;
20681
+ nextToolsSettings[key] = {
20682
+ ...asRecord$1(nextToolsSettings[key]),
20683
+ ...mergeValue
20684
+ };
20685
+ }
20686
+ }
20306
20687
  function pathTable(bucket) {
20307
20688
  return {
20308
20689
  allowedPaths: bucket?.allow ?? [],
@@ -20322,6 +20703,31 @@ function asRecord$1(value) {
20322
20703
  const result = UnknownRecordSchema.safeParse(value);
20323
20704
  return result.success ? result.data : {};
20324
20705
  }
20706
+ /**
20707
+ * Build the `kiro` permissions override from a parsed agent config's
20708
+ * `toolsSettings`, lifting the Kiro-specific knobs with no canonical category:
20709
+ * - `shell.*` flags other than the canonical `allowed`/`deniedCommands`
20710
+ * (e.g. `autoAllowReadonly`, `denyByDefault`), verbatim.
20711
+ * - the whole `aws` object (`allowedServices` / `deniedServices` / …), verbatim.
20712
+ * - the whole `web_fetch` object (`trusted` / `blocked`), verbatim.
20713
+ *
20714
+ * Returns `undefined` when none are present so the override key is omitted.
20715
+ */
20716
+ function extractKiroOverride(toolsSettings) {
20717
+ const overrideToolsSettings = {};
20718
+ const shellFlags = {};
20719
+ for (const [key, value] of Object.entries(asRecord$1(toolsSettings.shell))) {
20720
+ if (isPrototypePollutionKey(key)) continue;
20721
+ if (!CANONICAL_SHELL_KEYS.has(key)) shellFlags[key] = value;
20722
+ }
20723
+ if (Object.keys(shellFlags).length > 0) overrideToolsSettings.shell = shellFlags;
20724
+ const aws = asRecord$1(toolsSettings.aws);
20725
+ if (Object.keys(aws).length > 0) overrideToolsSettings.aws = aws;
20726
+ const webFetch = asRecord$1(toolsSettings.web_fetch);
20727
+ if (Object.keys(webFetch).length > 0) overrideToolsSettings.web_fetch = webFetch;
20728
+ if (Object.keys(overrideToolsSettings).length === 0) return void 0;
20729
+ return { toolsSettings: overrideToolsSettings };
20730
+ }
20325
20731
  function asStringArray(value) {
20326
20732
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
20327
20733
  }
@@ -21134,7 +21540,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21134
21540
  const existingContent = await readFileContentOrNull(filePath) ?? "";
21135
21541
  let parsed;
21136
21542
  try {
21137
- parsed = existingContent.trim() === "" ? {} : (0, js_yaml.load)(existingContent);
21543
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
21138
21544
  } catch (error) {
21139
21545
  throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
21140
21546
  }
@@ -21160,7 +21566,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21160
21566
  let parsed;
21161
21567
  try {
21162
21568
  const content = this.getFileContent();
21163
- parsed = content.trim() === "" ? {} : (0, js_yaml.load)(content);
21569
+ parsed = content.trim() === "" ? {} : loadYaml(content);
21164
21570
  } catch (error) {
21165
21571
  throw new Error(`Failed to parse Rovodev permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
21166
21572
  }
@@ -21355,9 +21761,9 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21355
21761
  const rulesyncJson = rulesyncPermissions.getJson();
21356
21762
  const provider = resolveActiveProvider(config);
21357
21763
  const mode = deriveTaktPermissionMode(rulesyncJson);
21358
- const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
21359
- const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21360
- const overrideProviderOptions = isPlainObject(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21764
+ const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
21765
+ const stepOverrides = isPlainObject$1(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21766
+ const overrideProviderOptions = isPlainObject$1(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21361
21767
  const patch = {
21362
21768
  [TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
21363
21769
  [TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
@@ -21388,12 +21794,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21388
21794
  invalidRootPolicy: "error"
21389
21795
  });
21390
21796
  const provider = resolveActiveProvider(config);
21391
- const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21392
- const profile = isPlainObject(profiles[provider]) ? profiles[provider] : {};
21797
+ const profiles = isPlainObject$1(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21798
+ const profile = isPlainObject$1(profiles[provider]) ? profiles[provider] : {};
21393
21799
  const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
21394
21800
  const rulesyncConfig = taktModeToRulesyncConfig(mode);
21395
- const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21396
- const providerOptions = isPlainObject(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21801
+ const stepOverrides = isPlainObject$1(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21802
+ const providerOptions = isPlainObject$1(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21397
21803
  const taktOverride = {};
21398
21804
  if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
21399
21805
  if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
@@ -21425,7 +21831,7 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21425
21831
  function resolveActiveProvider(config) {
21426
21832
  if (typeof config[TAKT_PROVIDER_KEY] === "string" && config[TAKT_PROVIDER_KEY].trim() !== "") return config[TAKT_PROVIDER_KEY];
21427
21833
  const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
21428
- if (isPlainObject(profiles)) {
21834
+ if (isPlainObject$1(profiles)) {
21429
21835
  const keys = Object.keys(profiles);
21430
21836
  if (keys.length === 1) return keys[0];
21431
21837
  }
@@ -24414,7 +24820,7 @@ function extractOpenaiYamlFile(otherFiles) {
24414
24820
  const rest = [];
24415
24821
  for (const file of otherFiles) {
24416
24822
  if (toPosixPath(file.relativeFilePathToDirPath) === target) try {
24417
- const loaded = (0, js_yaml.load)(file.fileBuffer.toString("utf-8"));
24823
+ const loaded = loadYaml(file.fileBuffer.toString("utf-8"));
24418
24824
  if (loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)) {
24419
24825
  parsed = loaded;
24420
24826
  continue;
@@ -26665,6 +27071,139 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
26665
27071
  }
26666
27072
  };
26667
27073
  //#endregion
27074
+ //#region src/features/skills/reasonix-skill.ts
27075
+ const ReasonixSkillFrontmatterSchema = zod_mini.z.looseObject({
27076
+ name: zod_mini.z.string(),
27077
+ description: zod_mini.z.string()
27078
+ });
27079
+ /**
27080
+ * Represents a DeepSeek-Reasonix skill directory.
27081
+ *
27082
+ * Reasonix discovers directory-layout skills (`<name>/SKILL.md`) under
27083
+ * `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the global
27084
+ * scope is served by the processor supplying the home directory as outputRoot.
27085
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md
27086
+ */
27087
+ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
27088
+ constructor({ outputRoot = process.cwd(), relativeDirPath = REASONIX_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
27089
+ super({
27090
+ outputRoot,
27091
+ relativeDirPath,
27092
+ dirName,
27093
+ mainFile: {
27094
+ name: SKILL_FILE_NAME,
27095
+ body,
27096
+ frontmatter: { ...frontmatter }
27097
+ },
27098
+ otherFiles,
27099
+ global
27100
+ });
27101
+ if (validate) {
27102
+ const result = this.validate();
27103
+ if (!result.success) throw result.error;
27104
+ }
27105
+ }
27106
+ static getSettablePaths({ global: _global = false } = {}) {
27107
+ return { relativeDirPath: REASONIX_SKILLS_DIR_PATH };
27108
+ }
27109
+ getFrontmatter() {
27110
+ return ReasonixSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
27111
+ }
27112
+ getBody() {
27113
+ return this.mainFile?.body ?? "";
27114
+ }
27115
+ validate() {
27116
+ if (this.mainFile === void 0) return {
27117
+ success: false,
27118
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27119
+ };
27120
+ const result = ReasonixSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27121
+ if (!result.success) return {
27122
+ success: false,
27123
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
27124
+ };
27125
+ return {
27126
+ success: true,
27127
+ error: null
27128
+ };
27129
+ }
27130
+ toRulesyncSkill() {
27131
+ const frontmatter = this.getFrontmatter();
27132
+ const rulesyncFrontmatter = {
27133
+ name: frontmatter.name,
27134
+ description: frontmatter.description,
27135
+ targets: ["*"]
27136
+ };
27137
+ return new RulesyncSkill({
27138
+ outputRoot: this.outputRoot,
27139
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
27140
+ dirName: this.getDirName(),
27141
+ frontmatter: rulesyncFrontmatter,
27142
+ body: this.getBody(),
27143
+ otherFiles: this.getOtherFiles(),
27144
+ validate: true,
27145
+ global: this.global
27146
+ });
27147
+ }
27148
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
27149
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
27150
+ const reasonixFrontmatter = {
27151
+ name: rulesyncFrontmatter.name,
27152
+ description: rulesyncFrontmatter.description
27153
+ };
27154
+ return new ReasonixSkill({
27155
+ outputRoot,
27156
+ relativeDirPath: ReasonixSkill.getSettablePaths({ global }).relativeDirPath,
27157
+ dirName: rulesyncSkill.getDirName(),
27158
+ frontmatter: reasonixFrontmatter,
27159
+ body: rulesyncSkill.getBody(),
27160
+ otherFiles: rulesyncSkill.getOtherFiles(),
27161
+ validate,
27162
+ global
27163
+ });
27164
+ }
27165
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
27166
+ const targets = rulesyncSkill.getFrontmatter().targets;
27167
+ return targets.includes("*") || targets.includes("reasonix");
27168
+ }
27169
+ static async fromDir(params) {
27170
+ const loaded = await this.loadSkillDirContent({
27171
+ ...params,
27172
+ getSettablePaths: ReasonixSkill.getSettablePaths
27173
+ });
27174
+ const result = ReasonixSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27175
+ if (!result.success) {
27176
+ const skillDirPath = (0, node_path.join)(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27177
+ throw new Error(`Invalid frontmatter in ${(0, node_path.join)(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27178
+ }
27179
+ return new ReasonixSkill({
27180
+ outputRoot: loaded.outputRoot,
27181
+ relativeDirPath: loaded.relativeDirPath,
27182
+ dirName: loaded.dirName,
27183
+ frontmatter: result.data,
27184
+ body: loaded.body,
27185
+ otherFiles: loaded.otherFiles,
27186
+ validate: true,
27187
+ global: loaded.global
27188
+ });
27189
+ }
27190
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
27191
+ return new ReasonixSkill({
27192
+ outputRoot,
27193
+ relativeDirPath,
27194
+ dirName,
27195
+ frontmatter: {
27196
+ name: "",
27197
+ description: ""
27198
+ },
27199
+ body: "",
27200
+ otherFiles: [],
27201
+ validate: false,
27202
+ global
27203
+ });
27204
+ }
27205
+ };
27206
+ //#endregion
26668
27207
  //#region src/constants/replit-paths.ts
26669
27208
  const REPLIT_RULE_FILE_NAME = "replit.md";
26670
27209
  const REPLIT_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
@@ -27759,6 +28298,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
27759
28298
  supportsGlobal: true
27760
28299
  }
27761
28300
  }],
28301
+ ["reasonix", {
28302
+ class: ReasonixSkill,
28303
+ meta: {
28304
+ supportsProject: true,
28305
+ supportsSimulated: false,
28306
+ supportsGlobal: true
28307
+ }
28308
+ }],
27762
28309
  ["replit", {
27763
28310
  class: ReplitSkill,
27764
28311
  meta: {
@@ -30042,7 +30589,7 @@ var GooseSubagent = class GooseSubagent extends ToolSubagent {
30042
30589
  const fileContent = await readFileContent(filePath);
30043
30590
  let parsed;
30044
30591
  try {
30045
- parsed = (0, js_yaml.load)(fileContent);
30592
+ parsed = loadYaml(fileContent);
30046
30593
  } catch (error) {
30047
30594
  throw new Error(`Failed to parse Goose recipe (${filePath}): ${formatError(error)}`, { cause: error });
30048
30595
  }
@@ -31430,7 +31977,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
31430
31977
  const fileContent = await readFileContent(filePath);
31431
31978
  let parsed;
31432
31979
  try {
31433
- parsed = (0, js_yaml.load)(fileContent);
31980
+ parsed = loadYaml(fileContent);
31434
31981
  } catch (error) {
31435
31982
  throw new Error(`Failed to parse .roomodes (${filePath}): ${formatError(error)}`, { cause: error });
31436
31983
  }
@@ -35623,6 +36170,72 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
35623
36170
  }
35624
36171
  };
35625
36172
  //#endregion
36173
+ //#region src/features/rules/reasonix-rule.ts
36174
+ var ReasonixRule = class ReasonixRule extends ToolRule {
36175
+ constructor({ fileContent, root, ...rest }) {
36176
+ super({
36177
+ ...rest,
36178
+ fileContent,
36179
+ root: root ?? false
36180
+ });
36181
+ }
36182
+ static getSettablePaths({ global = false } = {}) {
36183
+ return { root: {
36184
+ relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
36185
+ relativeFilePath: REASONIX_RULE_FILE_NAME
36186
+ } };
36187
+ }
36188
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
36189
+ const { root } = this.getSettablePaths({ global });
36190
+ const fileContent = await readFileContent((0, node_path.join)(outputRoot, (0, node_path.join)(root.relativeDirPath, root.relativeFilePath)));
36191
+ return new ReasonixRule({
36192
+ outputRoot,
36193
+ relativeDirPath: root.relativeDirPath,
36194
+ relativeFilePath: root.relativeFilePath,
36195
+ fileContent,
36196
+ validate,
36197
+ root: true
36198
+ });
36199
+ }
36200
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
36201
+ const { root } = this.getSettablePaths({ global });
36202
+ const isRoot = rulesyncRule.getFrontmatter().root ?? false;
36203
+ return new ReasonixRule({
36204
+ outputRoot,
36205
+ relativeDirPath: root.relativeDirPath,
36206
+ relativeFilePath: root.relativeFilePath,
36207
+ fileContent: rulesyncRule.getBody(),
36208
+ validate,
36209
+ root: isRoot
36210
+ });
36211
+ }
36212
+ toRulesyncRule() {
36213
+ return this.toRulesyncRuleDefault();
36214
+ }
36215
+ validate() {
36216
+ return {
36217
+ success: true,
36218
+ error: null
36219
+ };
36220
+ }
36221
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
36222
+ return new ReasonixRule({
36223
+ outputRoot,
36224
+ relativeDirPath,
36225
+ relativeFilePath,
36226
+ fileContent: "",
36227
+ validate: false,
36228
+ root: relativeFilePath === "REASONIX.md" && (relativeDirPath === "." || relativeDirPath === ".reasonix")
36229
+ });
36230
+ }
36231
+ static isTargetedByRulesyncRule(rulesyncRule) {
36232
+ return this.isTargetedByRulesyncRuleDefault({
36233
+ rulesyncRule,
36234
+ toolTarget: "reasonix"
36235
+ });
36236
+ }
36237
+ };
36238
+ //#endregion
35626
36239
  //#region src/features/rules/replit-rule.ts
35627
36240
  /**
35628
36241
  * Rule generator for Replit Agent
@@ -36535,6 +37148,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
36535
37148
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
36536
37149
  }
36537
37150
  }],
37151
+ ["reasonix", {
37152
+ class: ReasonixRule,
37153
+ meta: {
37154
+ extension: "md",
37155
+ supportsGlobal: true,
37156
+ ruleDiscoveryMode: "auto",
37157
+ foldsNonRootIntoRoot: true
37158
+ }
37159
+ }],
36538
37160
  ["replit", {
36539
37161
  class: ReplitRule,
36540
37162
  meta: {
@@ -37420,7 +38042,6 @@ const SHARED_WRITE_FEATURE_ORDER = [
37420
38042
  "permissions",
37421
38043
  "rules"
37422
38044
  ];
37423
- const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
37424
38045
  const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
37425
38046
  const sharedFileKey = (path) => {
37426
38047
  const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
@@ -37443,7 +38064,13 @@ const settablePathsForScope = (cls, global) => {
37443
38064
  paths.push(settable.root);
37444
38065
  for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
37445
38066
  }
37446
- for (const path of cls.getExtraSharedWritePaths?.({ global }) ?? []) if (path.relativeFilePath) paths.push(path);
38067
+ let extra;
38068
+ try {
38069
+ extra = cls.getExtraSharedWritePaths?.({ global }) ?? [];
38070
+ } catch {
38071
+ return paths;
38072
+ }
38073
+ for (const path of extra) if (path.relativeFilePath) paths.push(path);
37447
38074
  return paths;
37448
38075
  };
37449
38076
  const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
@@ -37456,7 +38083,6 @@ const deriveSharedFileWriters = () => {
37456
38083
  const byKey = /* @__PURE__ */ new Map();
37457
38084
  const pathByKey = /* @__PURE__ */ new Map();
37458
38085
  for (const entry of PROCESSOR_REGISTRY) {
37459
- if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
37460
38086
  const factories = entry.factory;
37461
38087
  for (const [tool, factory] of factories) {
37462
38088
  if (TARGETS_NOT_DERIVED.has(tool)) continue;
@@ -37845,7 +38471,7 @@ async function generateRulesCore(params) {
37845
38471
  targets: config.getConfigFileTargets(),
37846
38472
  global: config.getGlobal()
37847
38473
  }) : /* @__PURE__ */ new Map();
37848
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38474
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37849
38475
  if (!config.getFeatures(toolTarget).includes("rules")) continue;
37850
38476
  const processor = new RulesProcessor({
37851
38477
  outputRoot,
@@ -37900,7 +38526,7 @@ async function generateIgnoreCore(params) {
37900
38526
  let hasDiff = false;
37901
38527
  for (const toolTarget of (0, es_toolkit.intersection)(config.getTargets(), supportedIgnoreTargets)) {
37902
38528
  if (!config.getFeatures(toolTarget).includes("ignore")) continue;
37903
- for (const outputRoot of config.getOutputRoots()) try {
38529
+ for (const outputRoot of config.getOutputRoots(toolTarget)) try {
37904
38530
  const processor = new IgnoreProcessor({
37905
38531
  outputRoot,
37906
38532
  inputRoot: config.getInputRoot(),
@@ -37941,7 +38567,7 @@ async function generateMcpCore(params) {
37941
38567
  featureName: "mcp",
37942
38568
  logger
37943
38569
  });
37944
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38570
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37945
38571
  if (!config.getFeatures(toolTarget).includes("mcp")) continue;
37946
38572
  const processor = new McpProcessor({
37947
38573
  outputRoot,
@@ -37983,7 +38609,7 @@ async function generateCommandsCore(params) {
37983
38609
  featureName: "commands",
37984
38610
  logger
37985
38611
  });
37986
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38612
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37987
38613
  if (!config.getFeatures(toolTarget).includes("commands")) continue;
37988
38614
  const processor = new CommandsProcessor({
37989
38615
  outputRoot,
@@ -38025,7 +38651,7 @@ async function generateSubagentsCore(params) {
38025
38651
  featureName: "subagents",
38026
38652
  logger
38027
38653
  });
38028
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38654
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38029
38655
  if (!config.getFeatures(toolTarget).includes("subagents")) continue;
38030
38656
  const processor = new SubagentsProcessor({
38031
38657
  outputRoot,
@@ -38068,7 +38694,7 @@ async function generateSkillsCore(params) {
38068
38694
  featureName: "skills",
38069
38695
  logger
38070
38696
  });
38071
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38697
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38072
38698
  if (!config.getFeatures(toolTarget).includes("skills")) continue;
38073
38699
  const processor = new SkillsProcessor({
38074
38700
  outputRoot,
@@ -38109,7 +38735,7 @@ async function generateHooksCore(params) {
38109
38735
  featureName: "hooks",
38110
38736
  logger
38111
38737
  });
38112
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38738
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38113
38739
  if (!config.getFeatures(toolTarget).includes("hooks")) continue;
38114
38740
  const processor = new HooksProcessor({
38115
38741
  outputRoot,
@@ -38146,7 +38772,7 @@ async function generatePermissionsCore(params) {
38146
38772
  let totalCount = 0;
38147
38773
  const allPaths = [];
38148
38774
  let hasDiff = false;
38149
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of (0, es_toolkit.intersection)(config.getTargets(), supportedPermissionsTargets)) {
38775
+ for (const toolTarget of (0, es_toolkit.intersection)(config.getTargets(), supportedPermissionsTargets)) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38150
38776
  if (!config.getFeatures(toolTarget).includes("permissions")) continue;
38151
38777
  try {
38152
38778
  const processor = new PermissionsProcessor({
@@ -38878,6 +39504,12 @@ Object.defineProperty(exports, "listDirectoryFiles", {
38878
39504
  return listDirectoryFiles;
38879
39505
  }
38880
39506
  });
39507
+ Object.defineProperty(exports, "loadYaml", {
39508
+ enumerable: true,
39509
+ get: function() {
39510
+ return loadYaml;
39511
+ }
39512
+ });
38881
39513
  Object.defineProperty(exports, "readFileContent", {
38882
39514
  enumerable: true,
38883
39515
  get: function() {