rulesync 9.5.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",
@@ -1450,7 +1452,7 @@ function isRecord(value) {
1450
1452
  * conversion, etc.) should reject inputs whose prototype could carry
1451
1453
  * malicious accessor descriptors.
1452
1454
  */
1453
- function isPlainObject(value) {
1455
+ function isPlainObject$1(value) {
1454
1456
  if (!isRecord(value)) return false;
1455
1457
  const proto = Object.getPrototypeOf(value);
1456
1458
  return proto === null || proto === Object.prototype;
@@ -1462,11 +1464,43 @@ function isStringArray(value) {
1462
1464
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1463
1465
  }
1464
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
1465
1499
  //#region src/utils/frontmatter.ts
1466
1500
  function deepRemoveNullishValue(value) {
1467
1501
  if (value === null || value === void 0) return;
1468
1502
  if (Array.isArray(value)) return value.map((item) => deepRemoveNullishValue(item)).filter((item) => item !== void 0);
1469
- if (isPlainObject(value)) {
1503
+ if (isPlainObject$1(value)) {
1470
1504
  const result = {};
1471
1505
  for (const [key, val] of Object.entries(value)) {
1472
1506
  const cleaned = deepRemoveNullishValue(val);
@@ -1489,7 +1523,7 @@ function deepFlattenStringsValue(value) {
1489
1523
  if (value === null || value === void 0) return;
1490
1524
  if (typeof value === "string") return value.replace(/\n+/g, " ").trim();
1491
1525
  if (Array.isArray(value)) return value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
1492
- if (isPlainObject(value)) {
1526
+ if (isPlainObject$1(value)) {
1493
1527
  const result = {};
1494
1528
  for (const [key, val] of Object.entries(value)) {
1495
1529
  const cleaned = deepFlattenStringsValue(val);
@@ -1512,7 +1546,7 @@ function stringifyFrontmatter(body, frontmatter, options) {
1512
1546
  const { avoidBlockScalars = false } = options ?? {};
1513
1547
  const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
1514
1548
  if (avoidBlockScalars) return gray_matter.default.stringify(body, cleanFrontmatter, { engines: { yaml: {
1515
- parse: (input) => (0, js_yaml.load)(input) ?? {},
1549
+ parse: (input) => loadYaml(input) ?? {},
1516
1550
  stringify: (data) => (0, js_yaml.dump)(data, { lineWidth: -1 })
1517
1551
  } } });
1518
1552
  return gray_matter.default.stringify(body, cleanFrontmatter);
@@ -1551,7 +1585,7 @@ function tryJsonEquivalent(a, b) {
1551
1585
  }
1552
1586
  function tryYamlEquivalent(a, b) {
1553
1587
  try {
1554
- 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));
1555
1589
  } catch {
1556
1590
  return;
1557
1591
  }
@@ -3358,7 +3392,7 @@ var GooseCommand = class GooseCommand extends ToolCommand {
3358
3392
  const where = (0, node_path.join)(this.relativeDirPath, this.relativeFilePath);
3359
3393
  let parsed;
3360
3394
  try {
3361
- parsed = (0, js_yaml.load)(content);
3395
+ parsed = loadYaml(content);
3362
3396
  } catch (error) {
3363
3397
  throw new Error(`Failed to parse Goose recipe (${where}): ${formatError(error)}`, { cause: error });
3364
3398
  }
@@ -4012,7 +4046,7 @@ function omitPrototypePollutionKeys(record) {
4012
4046
  //#region src/features/shared/shared-config-gateway.ts
4013
4047
  function sanitizeSharedConfigValue(value) {
4014
4048
  if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
4015
- if (!isPlainObject(value)) return value;
4049
+ if (!isPlainObject$1(value)) return value;
4016
4050
  const result = {};
4017
4051
  for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
4018
4052
  return result;
@@ -4028,14 +4062,14 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
4028
4062
  const at = filePath === void 0 ? "" : ` at ${filePath}`;
4029
4063
  let parsed;
4030
4064
  try {
4031
- if (format === "yaml") parsed = (0, js_yaml.load)(fileContent);
4065
+ if (format === "yaml") parsed = loadYaml(fileContent);
4032
4066
  else if (format === "json") parsed = JSON.parse(fileContent);
4033
4067
  else parsed = (0, jsonc_parser.parse)(fileContent);
4034
4068
  } catch (error) {
4035
4069
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
4036
4070
  }
4037
4071
  if (parsed === void 0 || parsed === null) return {};
4038
- if (!isPlainObject(parsed)) {
4072
+ if (!isPlainObject$1(parsed)) {
4039
4073
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
4040
4074
  return {};
4041
4075
  }
@@ -4078,7 +4112,7 @@ function mergeSharedConfigDeep({ base, patch }) {
4078
4112
  for (const [key, patchValue] of Object.entries(patch)) {
4079
4113
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4080
4114
  const baseValue = result[key];
4081
- if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
4115
+ if (isPlainObject$1(baseValue) && isPlainObject$1(patchValue)) result[key] = mergeSharedConfigDeep({
4082
4116
  base: baseValue,
4083
4117
  patch: patchValue
4084
4118
  });
@@ -4712,6 +4746,8 @@ const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
4712
4746
  const REASONIX_DIR = REASONIX_GLOBAL_DIR;
4713
4747
  const REASONIX_SETTINGS_FILE_NAME = "settings.json";
4714
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");
4715
4751
  //#endregion
4716
4752
  //#region src/features/commands/reasonix-command.ts
4717
4753
  /**
@@ -5117,8 +5153,8 @@ var RovodevCommand = class RovodevCommand extends ToolCommand {
5117
5153
  const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
5118
5154
  let existing = {};
5119
5155
  if (existingContent) try {
5120
- const parsed = (0, js_yaml.load)(existingContent);
5121
- if (isPlainObject(parsed)) existing = parsed;
5156
+ const parsed = loadYaml(existingContent);
5157
+ if (isPlainObject$1(parsed)) existing = parsed;
5122
5158
  } catch {}
5123
5159
  const prompts = rovodevCommands.map((command) => ({
5124
5160
  name: command.getName(),
@@ -5148,11 +5184,11 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
5148
5184
  if (!manifestContent) return "";
5149
5185
  let parsed;
5150
5186
  try {
5151
- parsed = (0, js_yaml.load)(manifestContent);
5187
+ parsed = loadYaml(manifestContent);
5152
5188
  } catch {
5153
5189
  return "";
5154
5190
  }
5155
- if (!isPlainObject(parsed) || !Array.isArray(parsed.prompts)) return "";
5191
+ if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
5156
5192
  const expectedContentFile = toPosixPath((0, node_path.join)("prompts", relativeFilePath));
5157
5193
  const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
5158
5194
  return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
@@ -6190,18 +6226,25 @@ const QWENCODE_HOOK_EVENTS = [
6190
6226
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6191
6227
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6192
6228
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6193
- * `SubagentStop`, `Notification`, `PreCompact`), but only the four events the
6194
- * upstream issue scoped in are mapped here: `PreToolUse`, `PostToolUse`,
6195
- * `UserPromptSubmit` ← `beforeSubmitPrompt`, and `Stop`. `match` (Reasonix's
6196
- * matcher field name) is honored only on `PreToolUse`/`PostToolUse`, matching
6197
- * 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.
6198
6237
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6199
6238
  */
6200
6239
  const REASONIX_HOOK_EVENTS = [
6201
6240
  "preToolUse",
6202
6241
  "postToolUse",
6203
6242
  "beforeSubmitPrompt",
6204
- "stop"
6243
+ "stop",
6244
+ "sessionStart",
6245
+ "sessionEnd",
6246
+ "subagentStop",
6247
+ "postModelInvocation"
6205
6248
  ];
6206
6249
  /**
6207
6250
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6637,14 +6680,18 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6637
6680
  /**
6638
6681
  * Map canonical camelCase event names to Reasonix PascalCase.
6639
6682
  * Reasonix explicitly mirrors Claude Code's hooks model, so it reuses the same
6640
- * PascalCase names for the four events rulesync maps.
6683
+ * PascalCase names for the events rulesync maps.
6641
6684
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6642
6685
  */
6643
6686
  const CANONICAL_TO_REASONIX_EVENT_NAMES = {
6644
6687
  preToolUse: "PreToolUse",
6645
6688
  postToolUse: "PostToolUse",
6646
6689
  beforeSubmitPrompt: "UserPromptSubmit",
6647
- stop: "Stop"
6690
+ stop: "Stop",
6691
+ sessionStart: "SessionStart",
6692
+ sessionEnd: "SessionEnd",
6693
+ subagentStop: "SubagentStop",
6694
+ postModelInvocation: "PostLLMCall"
6648
6695
  };
6649
6696
  /**
6650
6697
  * Map Reasonix PascalCase event names to canonical camelCase.
@@ -6693,6 +6740,21 @@ function applyCommandPrefix({ def, converterConfig }) {
6693
6740
  return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ? `"${converterConfig.projectDirVar}"/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
6694
6741
  }
6695
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
+ /**
6696
6758
  * Convert the definitions of a single matcher group into tool hook entries,
6697
6759
  * honoring supported hook types and passthrough fields.
6698
6760
  */
@@ -6706,6 +6768,10 @@ function buildToolHooks({ defs, converterConfig }) {
6706
6768
  converterConfig
6707
6769
  });
6708
6770
  hooks.push({
6771
+ ...emitBooleanPassthroughFields({
6772
+ def,
6773
+ converterConfig
6774
+ }),
6709
6775
  type: hookType,
6710
6776
  ...command !== void 0 && command !== null && { command },
6711
6777
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -6793,6 +6859,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
6793
6859
  ...prompt !== void 0 && prompt !== null && { prompt },
6794
6860
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
6795
6861
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
6862
+ ...importBooleanPassthroughFields({
6863
+ h,
6864
+ converterConfig
6865
+ }),
6796
6866
  ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
6797
6867
  };
6798
6868
  }
@@ -7087,7 +7157,7 @@ function combineAugmentSettings(base, local) {
7087
7157
  const baseValue = result[key];
7088
7158
  if (AUGMENTCODE_REPLACE_KEYS.has(key)) result[key] = localValue;
7089
7159
  else if (Array.isArray(localValue) && Array.isArray(baseValue)) result[key] = [...localValue, ...baseValue];
7090
- 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);
7091
7161
  else result[key] = localValue;
7092
7162
  }
7093
7163
  return result;
@@ -7131,14 +7201,14 @@ async function readAugmentcodeSettingsWithLocalOverlay({ outputRoot, relativeDir
7131
7201
  } catch (error) {
7132
7202
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
7133
7203
  }
7134
- 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`);
7135
7205
  let baseParsed;
7136
7206
  try {
7137
7207
  baseParsed = JSON.parse(baseContent);
7138
7208
  } catch {
7139
7209
  return baseContent;
7140
7210
  }
7141
- const merged = combineAugmentSettings(isPlainObject(baseParsed) ? baseParsed : {}, localParsed);
7211
+ const merged = combineAugmentSettings(isPlainObject$1(baseParsed) ? baseParsed : {}, localParsed);
7142
7212
  return JSON.stringify(merged, null, 2);
7143
7213
  }
7144
7214
  //#endregion
@@ -8723,7 +8793,14 @@ const JUNIE_CONVERTER_CONFIG = {
8723
8793
  toolToCanonicalEventNames: JUNIE_TO_CANONICAL_EVENT_NAMES,
8724
8794
  projectDirVar: "",
8725
8795
  supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
8726
- 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
+ }]
8727
8804
  };
8728
8805
  var JunieHooks = class JunieHooks extends ToolHooks {
8729
8806
  constructor(params) {
@@ -9759,8 +9836,9 @@ function reasonixHooksToCanonical(hooks) {
9759
9836
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
9760
9837
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
9761
9838
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
9762
- * Only the four events documented in the upstream issue are mapped:
9763
- * 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).
9764
9842
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
9765
9843
  */
9766
9844
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -11839,7 +11917,7 @@ function parseAmpSettingsJsonc(fileContent) {
11839
11917
  const details = errors.map((error) => `${(0, jsonc_parser.printParseErrorCode)(error.error)} at offset ${error.offset}`).join(", ");
11840
11918
  throw new Error(`Failed to parse Amp settings: ${details}`);
11841
11919
  }
11842
- 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");
11843
11921
  return parsed;
11844
11922
  }
11845
11923
  function filterMcpServers(mcpServers) {
@@ -12161,7 +12239,7 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
12161
12239
  } catch (error) {
12162
12240
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
12163
12241
  }
12164
- 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`);
12165
12243
  return parsed;
12166
12244
  }
12167
12245
  /**
@@ -12387,7 +12465,7 @@ function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
12387
12465
  } catch (error) {
12388
12466
  throw new Error(`Failed to parse Cline MCP settings at ${configPath}: ${formatError(error)}`, { cause: error });
12389
12467
  }
12390
- 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`);
12391
12469
  return parsed;
12392
12470
  }
12393
12471
  /**
@@ -12658,7 +12736,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
12658
12736
  for (const [key, value] of Object.entries(obj)) {
12659
12737
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
12660
12738
  if (value === null) continue;
12661
- if (isPlainObject(value)) {
12739
+ if (isPlainObject$1(value)) {
12662
12740
  const cleaned = this.removeEmptyEntries(value, depth + 1);
12663
12741
  if (Object.keys(cleaned).length === 0) continue;
12664
12742
  filtered[key] = cleaned;
@@ -13299,12 +13377,12 @@ function parseGooseConfig(fileContent, relativeDirPath, relativeFilePath) {
13299
13377
  const configPath = (0, node_path.join)(relativeDirPath, relativeFilePath);
13300
13378
  let parsed;
13301
13379
  try {
13302
- parsed = (0, js_yaml.load)(fileContent);
13380
+ parsed = loadYaml(fileContent);
13303
13381
  } catch (error) {
13304
13382
  throw new Error(`Failed to parse Goose config at ${configPath}: ${formatError(error)}`, { cause: error });
13305
13383
  }
13306
13384
  if (parsed === void 0 || parsed === null) return {};
13307
- 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`);
13308
13386
  return parsed;
13309
13387
  }
13310
13388
  /**
@@ -13349,7 +13427,7 @@ function applyGooseStdioFields(ext, config) {
13349
13427
  ext.cmd = command;
13350
13428
  if (isStringArray(config.args)) ext.args = config.args;
13351
13429
  }
13352
- if (isPlainObject(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13430
+ if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13353
13431
  }
13354
13432
  /**
13355
13433
  * Converts a single rulesync canonical MCP server into a Goose `extensions:` entry.
@@ -13364,7 +13442,7 @@ function convertServerToGooseExtension(name, config) {
13364
13442
  if (gooseType === "stdio") applyGooseStdioFields(ext, config);
13365
13443
  else if (gooseType === "sse" || gooseType === "streamable_http") {
13366
13444
  if (url !== void 0) ext.uri = url;
13367
- if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13445
+ if (isPlainObject$1(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13368
13446
  }
13369
13447
  ext.enabled = config.disabled !== true;
13370
13448
  const timeout = resolveGooseTimeout(config);
@@ -13406,9 +13484,9 @@ function convertFromGooseFormat(extensions) {
13406
13484
  else if (type === "stdio") server.type = "stdio";
13407
13485
  if (typeof ext.cmd === "string") server.command = ext.cmd;
13408
13486
  if (isStringArray(ext.args)) server.args = ext.args;
13409
- if (isPlainObject(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13487
+ if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13410
13488
  if (typeof ext.uri === "string") server.url = ext.uri;
13411
- if (isPlainObject(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13489
+ if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13412
13490
  if (ext.enabled === false) server.disabled = true;
13413
13491
  if (typeof ext.timeout === "number") server.timeout = ext.timeout;
13414
13492
  result[name] = server;
@@ -13431,7 +13509,7 @@ function buildGoosePluginStdioServer(config) {
13431
13509
  server.command = command;
13432
13510
  if (isStringArray(config.args)) server.args = config.args;
13433
13511
  }
13434
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13512
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13435
13513
  if (typeof config.cwd === "string") server.cwd = config.cwd;
13436
13514
  return server;
13437
13515
  }
@@ -13731,7 +13809,7 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
13731
13809
  for (const [key, value] of Object.entries(obj)) {
13732
13810
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
13733
13811
  if (value === null) continue;
13734
- if (isPlainObject(value)) {
13812
+ if (isPlainObject$1(value)) {
13735
13813
  const cleaned = this.removeEmptyEntries(value, depth + 1);
13736
13814
  if (Object.keys(cleaned).length === 0) continue;
13737
13815
  filtered[key] = cleaned;
@@ -13793,10 +13871,10 @@ function convertServerToHermes(config) {
13793
13871
  out.command = command;
13794
13872
  if (isStringArray(config.args)) out.args = config.args;
13795
13873
  }
13796
- if (isPlainObject(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13874
+ if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13797
13875
  } else if (url !== void 0) {
13798
13876
  out.url = url;
13799
- if (isPlainObject(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13877
+ if (isPlainObject$1(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13800
13878
  }
13801
13879
  if (config.disabled === true) out.enabled = false;
13802
13880
  const timeout = resolveHermesTimeout(config);
@@ -13841,9 +13919,9 @@ function convertFromHermesFormat(mcpServers) {
13841
13919
  const server = {};
13842
13920
  if (typeof config.command === "string") server.command = config.command;
13843
13921
  if (isStringArray(config.args)) server.args = config.args;
13844
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13922
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13845
13923
  if (typeof config.url === "string") server.url = config.url;
13846
- if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13924
+ if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13847
13925
  if (config.enabled === false) server.disabled = true;
13848
13926
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
13849
13927
  if (isRecord(config.tools)) {
@@ -14780,7 +14858,9 @@ const REASONIX_PLUGIN_FIELDS = [
14780
14858
  "env",
14781
14859
  "url",
14782
14860
  "headers",
14783
- "trusted_read_only_tools"
14861
+ "trusted_read_only_tools",
14862
+ "call_timeout_seconds",
14863
+ "tool_timeout_seconds"
14784
14864
  ];
14785
14865
  var ReasonixMcp = class ReasonixMcp extends ToolMcp {
14786
14866
  toml;
@@ -15012,7 +15092,7 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
15012
15092
  } catch (error) {
15013
15093
  throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: ${formatError(error)}`, { cause: error });
15014
15094
  }
15015
- 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`);
15016
15096
  return parsed;
15017
15097
  }
15018
15098
  /**
@@ -16283,15 +16363,123 @@ const AntigravityCliPermissionsOverrideSchema = zod_mini.z.looseObject({
16283
16363
  enableTerminalSandbox: zod_mini.z.optional(zod_mini.z.boolean())
16284
16364
  });
16285
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
+ /**
16286
16473
  * Permissions configuration.
16287
16474
  * Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
16288
16475
  * Values are pattern-to-action mappings for that tool category.
16289
16476
  *
16290
16477
  * The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
16291
16478
  * `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.
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.
16295
16483
  *
16296
16484
  * @example
16297
16485
  * {
@@ -16315,7 +16503,10 @@ const PermissionsConfigSchema = zod_mini.z.looseObject({
16315
16503
  junie: zod_mini.z.optional(JuniePermissionsOverrideSchema),
16316
16504
  takt: zod_mini.z.optional(TaktPermissionsOverrideSchema),
16317
16505
  amp: zod_mini.z.optional(AmpPermissionsOverrideSchema),
16318
- "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)
16319
16510
  });
16320
16511
  /**
16321
16512
  * Full permissions file schema including optional $schema field.
@@ -16467,7 +16658,7 @@ function parseAmpSettings(fileContent) {
16467
16658
  const details = errors.map((error) => `${(0, jsonc_parser.printParseErrorCode)(error.error)} at offset ${error.offset}`).join(", ");
16468
16659
  throw new Error(`Failed to parse Amp settings: ${details}`);
16469
16660
  }
16470
- 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");
16471
16662
  return parsed;
16472
16663
  }
16473
16664
  function toDisableList(value) {
@@ -16494,7 +16685,7 @@ function toPermissionsList(value) {
16494
16685
  if (!Array.isArray(value)) return [];
16495
16686
  const entries = [];
16496
16687
  for (const raw of value) {
16497
- if (!isPlainObject(raw)) continue;
16688
+ if (!isPlainObject$1(raw)) continue;
16498
16689
  const { tool, action } = raw;
16499
16690
  if (typeof tool !== "string" || typeof action !== "string") continue;
16500
16691
  if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
@@ -16502,7 +16693,7 @@ function toPermissionsList(value) {
16502
16693
  ...raw,
16503
16694
  tool,
16504
16695
  action,
16505
- ...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
16696
+ ...isPlainObject$1(raw.matches) ? { matches: raw.matches } : {}
16506
16697
  });
16507
16698
  }
16508
16699
  return entries;
@@ -17362,6 +17553,55 @@ function isSpecialEntry(entry) {
17362
17553
  return false;
17363
17554
  }
17364
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
+ /**
17365
17605
  * Convert a glob-like pattern into a regex string for AugmentCode's `shellInputRegex`.
17366
17606
  * Maps glob `*` to `.*`, `?` to `.`, escapes other regex metacharacters, and anchors at both ends.
17367
17607
  */
@@ -17492,12 +17732,18 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17492
17732
  } catch (error) {
17493
17733
  throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
17494
17734
  }
17735
+ const config = rulesyncPermissions.getJson();
17495
17736
  const generated = convertRulesyncToAugmentEntries({
17496
- config: rulesyncPermissions.getJson(),
17737
+ config,
17497
17738
  logger
17498
17739
  });
17499
17740
  const existingEntries = settings.toolPermissions ?? [];
17500
- 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]);
17501
17747
  const basicExistingEntries = existingEntries.filter((entry) => !isSpecialEntry(entry));
17502
17748
  const generatedKeys = new Set(generated.map((e) => `${e.toolName}|${e.shellInputRegex ?? ""}|${e.permission.type}`));
17503
17749
  const preservedBasicEntries = basicExistingEntries.filter((entry) => {
@@ -17508,7 +17754,11 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17508
17754
  }
17509
17755
  return false;
17510
17756
  });
17511
- const sortedBasic = sortAugmentEntries([...generated, ...preservedBasicEntries]);
17757
+ const sortedBasic = sortAugmentEntries([
17758
+ ...generated,
17759
+ ...preservedBasicEntries,
17760
+ ...authoredBasics
17761
+ ]);
17512
17762
  const merged = {
17513
17763
  ...settings,
17514
17764
  toolPermissions: [...specialEntries, ...sortedBasic]
@@ -17532,11 +17782,14 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17532
17782
  } catch (error) {
17533
17783
  throw new Error(`Failed to parse AugmentCode permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
17534
17784
  }
17535
- const config = convertAugmentToRulesyncPermissions({
17536
- 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)),
17537
17789
  logger: moduleLogger$1
17538
- });
17539
- 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) });
17540
17793
  }
17541
17794
  validate() {
17542
17795
  try {
@@ -17670,10 +17923,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
17670
17923
  ]);
17671
17924
  const permission = {};
17672
17925
  for (const entry of entries) {
17673
- if (isSpecialEntry(entry)) {
17674
- 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.`);
17675
- continue;
17676
- }
17926
+ if (isSpecialEntry(entry)) continue;
17677
17927
  const type = entry.permission.type;
17678
17928
  if (!isBasicAugmentType(type)) continue;
17679
17929
  const canonical = toCanonicalToolName$5(entry.toolName);
@@ -18105,6 +18355,13 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
18105
18355
  ]);
18106
18356
  const CODEX_MINIMAL_KEY = ":minimal";
18107
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
+ ];
18108
18365
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18109
18366
  static getSettablePaths(_options = {}) {
18110
18367
  return {
@@ -18146,6 +18403,11 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18146
18403
  });
18147
18404
  parsed.permissions = permissionsTable;
18148
18405
  parsed.default_permissions = RULESYNC_PROFILE_NAME;
18406
+ applyCodexcliOverride({
18407
+ parsed,
18408
+ override: rulesyncPermissions.getJson().codexcli,
18409
+ logger
18410
+ });
18149
18411
  return new CodexcliPermissions({
18150
18412
  outputRoot,
18151
18413
  relativeDirPath: paths.relativeDirPath,
@@ -18170,7 +18432,12 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18170
18432
  profile,
18171
18433
  domainsHadUnknown
18172
18434
  });
18173
- 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) });
18174
18441
  }
18175
18442
  validate() {
18176
18443
  return {
@@ -18362,6 +18629,30 @@ function toMutableTable(value) {
18362
18629
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18363
18630
  return { ...value };
18364
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
+ }
18365
18656
  function toFilesystemRecord(value) {
18366
18657
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
18367
18658
  const result = {};
@@ -19276,7 +19567,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19276
19567
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19277
19568
  let parsed;
19278
19569
  try {
19279
- parsed = existingContent.trim() === "" ? {} : (0, js_yaml.load)(existingContent);
19570
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
19280
19571
  } catch (error) {
19281
19572
  throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
19282
19573
  }
@@ -19298,7 +19589,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19298
19589
  let parsed;
19299
19590
  try {
19300
19591
  const content = this.getFileContent();
19301
- parsed = content.trim() === "" ? {} : (0, js_yaml.load)(content);
19592
+ parsed = content.trim() === "" ? {} : loadYaml(content);
19302
19593
  } catch (error) {
19303
19594
  throw new Error(`Failed to parse Goose permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
19304
19595
  }
@@ -20205,6 +20496,13 @@ const KiroAgentSchema = zod_mini.z.looseObject({
20205
20496
  toolsSettings: zod_mini.z.optional(zod_mini.z.record(zod_mini.z.string(), zod_mini.z.unknown()))
20206
20497
  });
20207
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
+ ]);
20208
20506
  var KiroPermissions = class KiroPermissions extends ToolPermissions {
20209
20507
  static getSettablePaths(_options = {}) {
20210
20508
  return {
@@ -20268,7 +20566,10 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
20268
20566
  const allowedTools = new Set(parsed.allowedTools ?? []);
20269
20567
  if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
20270
20568
  if (allowedTools.has("web_search")) permission.websearch = { "*": "allow" };
20271
- 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) });
20272
20573
  }
20273
20574
  validate() {
20274
20575
  return {
@@ -20318,19 +20619,71 @@ function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
20318
20619
  });
20319
20620
  else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
20320
20621
  }
20321
- nextToolsSettings.shell = shell;
20622
+ nextToolsSettings.shell = {
20623
+ ...preservedShellFlags(existing),
20624
+ ...shell
20625
+ };
20322
20626
  nextToolsSettings.read = pathTable(pathBuckets.read);
20323
20627
  nextToolsSettings.write = pathTable(pathBuckets.write);
20324
20628
  for (const key of ["grep", "glob"]) {
20325
20629
  const bucket = pathBuckets[key];
20326
20630
  if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
20327
20631
  }
20632
+ applyKiroOverride({
20633
+ override: config.kiro,
20634
+ nextToolsSettings,
20635
+ logger
20636
+ });
20328
20637
  return {
20329
20638
  ...existing,
20330
20639
  allowedTools: [...nextAllowedTools].toSorted(),
20331
20640
  toolsSettings: nextToolsSettings
20332
20641
  };
20333
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
+ }
20334
20687
  function pathTable(bucket) {
20335
20688
  return {
20336
20689
  allowedPaths: bucket?.allow ?? [],
@@ -20350,6 +20703,31 @@ function asRecord$1(value) {
20350
20703
  const result = UnknownRecordSchema.safeParse(value);
20351
20704
  return result.success ? result.data : {};
20352
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
+ }
20353
20731
  function asStringArray(value) {
20354
20732
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
20355
20733
  }
@@ -21162,7 +21540,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21162
21540
  const existingContent = await readFileContentOrNull(filePath) ?? "";
21163
21541
  let parsed;
21164
21542
  try {
21165
- parsed = existingContent.trim() === "" ? {} : (0, js_yaml.load)(existingContent);
21543
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
21166
21544
  } catch (error) {
21167
21545
  throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
21168
21546
  }
@@ -21188,7 +21566,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21188
21566
  let parsed;
21189
21567
  try {
21190
21568
  const content = this.getFileContent();
21191
- parsed = content.trim() === "" ? {} : (0, js_yaml.load)(content);
21569
+ parsed = content.trim() === "" ? {} : loadYaml(content);
21192
21570
  } catch (error) {
21193
21571
  throw new Error(`Failed to parse Rovodev permissions content in ${(0, node_path.join)(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
21194
21572
  }
@@ -21383,9 +21761,9 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21383
21761
  const rulesyncJson = rulesyncPermissions.getJson();
21384
21762
  const provider = resolveActiveProvider(config);
21385
21763
  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;
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;
21389
21767
  const patch = {
21390
21768
  [TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
21391
21769
  [TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
@@ -21416,12 +21794,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21416
21794
  invalidRootPolicy: "error"
21417
21795
  });
21418
21796
  const provider = resolveActiveProvider(config);
21419
- const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21420
- 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] : {};
21421
21799
  const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
21422
21800
  const rulesyncConfig = taktModeToRulesyncConfig(mode);
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;
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;
21425
21803
  const taktOverride = {};
21426
21804
  if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
21427
21805
  if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
@@ -21453,7 +21831,7 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21453
21831
  function resolveActiveProvider(config) {
21454
21832
  if (typeof config[TAKT_PROVIDER_KEY] === "string" && config[TAKT_PROVIDER_KEY].trim() !== "") return config[TAKT_PROVIDER_KEY];
21455
21833
  const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
21456
- if (isPlainObject(profiles)) {
21834
+ if (isPlainObject$1(profiles)) {
21457
21835
  const keys = Object.keys(profiles);
21458
21836
  if (keys.length === 1) return keys[0];
21459
21837
  }
@@ -24442,7 +24820,7 @@ function extractOpenaiYamlFile(otherFiles) {
24442
24820
  const rest = [];
24443
24821
  for (const file of otherFiles) {
24444
24822
  if (toPosixPath(file.relativeFilePathToDirPath) === target) try {
24445
- const loaded = (0, js_yaml.load)(file.fileBuffer.toString("utf-8"));
24823
+ const loaded = loadYaml(file.fileBuffer.toString("utf-8"));
24446
24824
  if (loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)) {
24447
24825
  parsed = loaded;
24448
24826
  continue;
@@ -26693,6 +27071,139 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
26693
27071
  }
26694
27072
  };
26695
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
26696
27207
  //#region src/constants/replit-paths.ts
26697
27208
  const REPLIT_RULE_FILE_NAME = "replit.md";
26698
27209
  const REPLIT_SKILLS_DIR_PATH = (0, node_path.join)(".agents", "skills");
@@ -27787,6 +28298,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
27787
28298
  supportsGlobal: true
27788
28299
  }
27789
28300
  }],
28301
+ ["reasonix", {
28302
+ class: ReasonixSkill,
28303
+ meta: {
28304
+ supportsProject: true,
28305
+ supportsSimulated: false,
28306
+ supportsGlobal: true
28307
+ }
28308
+ }],
27790
28309
  ["replit", {
27791
28310
  class: ReplitSkill,
27792
28311
  meta: {
@@ -30070,7 +30589,7 @@ var GooseSubagent = class GooseSubagent extends ToolSubagent {
30070
30589
  const fileContent = await readFileContent(filePath);
30071
30590
  let parsed;
30072
30591
  try {
30073
- parsed = (0, js_yaml.load)(fileContent);
30592
+ parsed = loadYaml(fileContent);
30074
30593
  } catch (error) {
30075
30594
  throw new Error(`Failed to parse Goose recipe (${filePath}): ${formatError(error)}`, { cause: error });
30076
30595
  }
@@ -31458,7 +31977,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
31458
31977
  const fileContent = await readFileContent(filePath);
31459
31978
  let parsed;
31460
31979
  try {
31461
- parsed = (0, js_yaml.load)(fileContent);
31980
+ parsed = loadYaml(fileContent);
31462
31981
  } catch (error) {
31463
31982
  throw new Error(`Failed to parse .roomodes (${filePath}): ${formatError(error)}`, { cause: error });
31464
31983
  }
@@ -35651,6 +36170,72 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
35651
36170
  }
35652
36171
  };
35653
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
35654
36239
  //#region src/features/rules/replit-rule.ts
35655
36240
  /**
35656
36241
  * Rule generator for Replit Agent
@@ -36563,6 +37148,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
36563
37148
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
36564
37149
  }
36565
37150
  }],
37151
+ ["reasonix", {
37152
+ class: ReasonixRule,
37153
+ meta: {
37154
+ extension: "md",
37155
+ supportsGlobal: true,
37156
+ ruleDiscoveryMode: "auto",
37157
+ foldsNonRootIntoRoot: true
37158
+ }
37159
+ }],
36566
37160
  ["replit", {
36567
37161
  class: ReplitRule,
36568
37162
  meta: {
@@ -37448,7 +38042,6 @@ const SHARED_WRITE_FEATURE_ORDER = [
37448
38042
  "permissions",
37449
38043
  "rules"
37450
38044
  ];
37451
- const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
37452
38045
  const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
37453
38046
  const sharedFileKey = (path) => {
37454
38047
  const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
@@ -37471,7 +38064,13 @@ const settablePathsForScope = (cls, global) => {
37471
38064
  paths.push(settable.root);
37472
38065
  for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
37473
38066
  }
37474
- 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);
37475
38074
  return paths;
37476
38075
  };
37477
38076
  const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
@@ -37484,7 +38083,6 @@ const deriveSharedFileWriters = () => {
37484
38083
  const byKey = /* @__PURE__ */ new Map();
37485
38084
  const pathByKey = /* @__PURE__ */ new Map();
37486
38085
  for (const entry of PROCESSOR_REGISTRY) {
37487
- if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
37488
38086
  const factories = entry.factory;
37489
38087
  for (const [tool, factory] of factories) {
37490
38088
  if (TARGETS_NOT_DERIVED.has(tool)) continue;
@@ -38906,6 +39504,12 @@ Object.defineProperty(exports, "listDirectoryFiles", {
38906
39504
  return listDirectoryFiles;
38907
39505
  }
38908
39506
  });
39507
+ Object.defineProperty(exports, "loadYaml", {
39508
+ enumerable: true,
39509
+ get: function() {
39510
+ return loadYaml;
39511
+ }
39512
+ });
38909
39513
  Object.defineProperty(exports, "readFileContent", {
38910
39514
  enumerable: true,
38911
39515
  get: function() {