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.
@@ -7,9 +7,9 @@ import os from "node:os";
7
7
  import { intersection, kebabCase, uniq } from "es-toolkit";
8
8
  import { globbySync } from "globby";
9
9
  import { isDeepStrictEqual } from "node:util";
10
- import { dump, load } from "js-yaml";
11
10
  import * as smolToml from "smol-toml";
12
11
  import matter from "gray-matter";
12
+ import { YAMLException, dump, load } from "js-yaml";
13
13
  import { omit } from "es-toolkit/object";
14
14
  import { encode } from "@toon-format/toon";
15
15
  //#region src/types/features.ts
@@ -141,6 +141,7 @@ const rulesProcessorToolTargetTuple = [
141
141
  "opencode",
142
142
  "pi",
143
143
  "qwencode",
144
+ "reasonix",
144
145
  "replit",
145
146
  "roo",
146
147
  "rovodev",
@@ -288,6 +289,7 @@ const skillsProcessorToolTargetTuple = [
288
289
  "opencode",
289
290
  "pi",
290
291
  "qwencode",
292
+ "reasonix",
291
293
  "replit",
292
294
  "roo",
293
295
  "rovodev",
@@ -1424,7 +1426,7 @@ function isRecord(value) {
1424
1426
  * conversion, etc.) should reject inputs whose prototype could carry
1425
1427
  * malicious accessor descriptors.
1426
1428
  */
1427
- function isPlainObject(value) {
1429
+ function isPlainObject$1(value) {
1428
1430
  if (!isRecord(value)) return false;
1429
1431
  const proto = Object.getPrototypeOf(value);
1430
1432
  return proto === null || proto === Object.prototype;
@@ -1436,11 +1438,43 @@ function isStringArray(value) {
1436
1438
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1437
1439
  }
1438
1440
  //#endregion
1441
+ //#region src/utils/yaml.ts
1442
+ /**
1443
+ * js-yaml v5's `reason` for an empty document. `load("")` — and any
1444
+ * whitespace/comment-only input — throws a `YAMLException` with this reason
1445
+ * instead of returning `undefined` the way v4 did.
1446
+ */
1447
+ const EMPTY_INPUT_REASON = "expected a document, but the input is empty";
1448
+ /**
1449
+ * Load YAML content while preserving js-yaml v4's behavior of returning
1450
+ * `undefined` for empty documents (empty, whitespace-only, or comment-only
1451
+ * input).
1452
+ *
1453
+ * js-yaml v5 changed `load("")` to **throw** ("expected a document, but the
1454
+ * input is empty") instead of returning `undefined`. Many call sites here parse
1455
+ * config/lock/frontmatter files that can legitimately be empty and rely on the
1456
+ * old `undefined` sentinel (`load(input) ?? {}`, `if (loaded === undefined)`,
1457
+ * "an empty file parses to undefined/null"). Routing every load through this
1458
+ * wrapper keeps that contract in one place so a future js-yaml bump cannot
1459
+ * reintroduce the empty-input regression. Genuine parse errors still propagate.
1460
+ *
1461
+ * @see https://github.com/nodeca/js-yaml/blob/master/docs/migrate_v4_to_v5.md#empty-input-throws
1462
+ */
1463
+ function loadYaml(content) {
1464
+ if (content.trim() === "") return;
1465
+ try {
1466
+ return load(content);
1467
+ } catch (error) {
1468
+ if (error instanceof YAMLException && error.reason === EMPTY_INPUT_REASON) return;
1469
+ throw error;
1470
+ }
1471
+ }
1472
+ //#endregion
1439
1473
  //#region src/utils/frontmatter.ts
1440
1474
  function deepRemoveNullishValue(value) {
1441
1475
  if (value === null || value === void 0) return;
1442
1476
  if (Array.isArray(value)) return value.map((item) => deepRemoveNullishValue(item)).filter((item) => item !== void 0);
1443
- if (isPlainObject(value)) {
1477
+ if (isPlainObject$1(value)) {
1444
1478
  const result = {};
1445
1479
  for (const [key, val] of Object.entries(value)) {
1446
1480
  const cleaned = deepRemoveNullishValue(val);
@@ -1463,7 +1497,7 @@ function deepFlattenStringsValue(value) {
1463
1497
  if (value === null || value === void 0) return;
1464
1498
  if (typeof value === "string") return value.replace(/\n+/g, " ").trim();
1465
1499
  if (Array.isArray(value)) return value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
1466
- if (isPlainObject(value)) {
1500
+ if (isPlainObject$1(value)) {
1467
1501
  const result = {};
1468
1502
  for (const [key, val] of Object.entries(value)) {
1469
1503
  const cleaned = deepFlattenStringsValue(val);
@@ -1486,7 +1520,7 @@ function stringifyFrontmatter(body, frontmatter, options) {
1486
1520
  const { avoidBlockScalars = false } = options ?? {};
1487
1521
  const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
1488
1522
  if (avoidBlockScalars) return matter.stringify(body, cleanFrontmatter, { engines: { yaml: {
1489
- parse: (input) => load(input) ?? {},
1523
+ parse: (input) => loadYaml(input) ?? {},
1490
1524
  stringify: (data) => dump(data, { lineWidth: -1 })
1491
1525
  } } });
1492
1526
  return matter.stringify(body, cleanFrontmatter);
@@ -1525,7 +1559,7 @@ function tryJsonEquivalent(a, b) {
1525
1559
  }
1526
1560
  function tryYamlEquivalent(a, b) {
1527
1561
  try {
1528
- return isDeepStrictEqual(load(a), load(b));
1562
+ return isDeepStrictEqual(loadYaml(a), loadYaml(b));
1529
1563
  } catch {
1530
1564
  return;
1531
1565
  }
@@ -3332,7 +3366,7 @@ var GooseCommand = class GooseCommand extends ToolCommand {
3332
3366
  const where = join(this.relativeDirPath, this.relativeFilePath);
3333
3367
  let parsed;
3334
3368
  try {
3335
- parsed = load(content);
3369
+ parsed = loadYaml(content);
3336
3370
  } catch (error) {
3337
3371
  throw new Error(`Failed to parse Goose recipe (${where}): ${formatError(error)}`, { cause: error });
3338
3372
  }
@@ -3985,7 +4019,7 @@ function omitPrototypePollutionKeys(record) {
3985
4019
  //#region src/features/shared/shared-config-gateway.ts
3986
4020
  function sanitizeSharedConfigValue(value) {
3987
4021
  if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
3988
- if (!isPlainObject(value)) return value;
4022
+ if (!isPlainObject$1(value)) return value;
3989
4023
  const result = {};
3990
4024
  for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
3991
4025
  return result;
@@ -4001,14 +4035,14 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
4001
4035
  const at = filePath === void 0 ? "" : ` at ${filePath}`;
4002
4036
  let parsed;
4003
4037
  try {
4004
- if (format === "yaml") parsed = load(fileContent);
4038
+ if (format === "yaml") parsed = loadYaml(fileContent);
4005
4039
  else if (format === "json") parsed = JSON.parse(fileContent);
4006
4040
  else parsed = parse(fileContent);
4007
4041
  } catch (error) {
4008
4042
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
4009
4043
  }
4010
4044
  if (parsed === void 0 || parsed === null) return {};
4011
- if (!isPlainObject(parsed)) {
4045
+ if (!isPlainObject$1(parsed)) {
4012
4046
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
4013
4047
  return {};
4014
4048
  }
@@ -4051,7 +4085,7 @@ function mergeSharedConfigDeep({ base, patch }) {
4051
4085
  for (const [key, patchValue] of Object.entries(patch)) {
4052
4086
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4053
4087
  const baseValue = result[key];
4054
- if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
4088
+ if (isPlainObject$1(baseValue) && isPlainObject$1(patchValue)) result[key] = mergeSharedConfigDeep({
4055
4089
  base: baseValue,
4056
4090
  patch: patchValue
4057
4091
  });
@@ -4685,6 +4719,8 @@ const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
4685
4719
  const REASONIX_DIR = REASONIX_GLOBAL_DIR;
4686
4720
  const REASONIX_SETTINGS_FILE_NAME = "settings.json";
4687
4721
  const REASONIX_COMMANDS_DIR_PATH = join(REASONIX_DIR, "commands");
4722
+ const REASONIX_RULE_FILE_NAME = "REASONIX.md";
4723
+ const REASONIX_SKILLS_DIR_PATH = join(REASONIX_DIR, "skills");
4688
4724
  //#endregion
4689
4725
  //#region src/features/commands/reasonix-command.ts
4690
4726
  /**
@@ -5090,8 +5126,8 @@ var RovodevCommand = class RovodevCommand extends ToolCommand {
5090
5126
  const existingContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
5091
5127
  let existing = {};
5092
5128
  if (existingContent) try {
5093
- const parsed = load(existingContent);
5094
- if (isPlainObject(parsed)) existing = parsed;
5129
+ const parsed = loadYaml(existingContent);
5130
+ if (isPlainObject$1(parsed)) existing = parsed;
5095
5131
  } catch {}
5096
5132
  const prompts = rovodevCommands.map((command) => ({
5097
5133
  name: command.getName(),
@@ -5121,11 +5157,11 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
5121
5157
  if (!manifestContent) return "";
5122
5158
  let parsed;
5123
5159
  try {
5124
- parsed = load(manifestContent);
5160
+ parsed = loadYaml(manifestContent);
5125
5161
  } catch {
5126
5162
  return "";
5127
5163
  }
5128
- if (!isPlainObject(parsed) || !Array.isArray(parsed.prompts)) return "";
5164
+ if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
5129
5165
  const expectedContentFile = toPosixPath(join("prompts", relativeFilePath));
5130
5166
  const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
5131
5167
  return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
@@ -6163,18 +6199,25 @@ const QWENCODE_HOOK_EVENTS = [
6163
6199
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6164
6200
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6165
6201
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6166
- * `SubagentStop`, `Notification`, `PreCompact`), but only the four events the
6167
- * upstream issue scoped in are mapped here: `PreToolUse`, `PostToolUse`,
6168
- * `UserPromptSubmit` ← `beforeSubmitPrompt`, and `Stop`. `match` (Reasonix's
6169
- * matcher field name) is honored only on `PreToolUse`/`PostToolUse`, matching
6170
- * the canonical `matcher` field's tool-event scoping used by other adapters.
6202
+ * `SubagentStop`, `Notification`, `PreCompact`). The eight events with a clean
6203
+ * canonical equivalent are mapped: `PreToolUse`, `PostToolUse`,
6204
+ * `UserPromptSubmit` ← `beforeSubmitPrompt`, `Stop`, `SessionStart`,
6205
+ * `SessionEnd`, `SubagentStop`, and `PostLLMCall` `postModelInvocation`.
6206
+ * (`Notification` and `PreCompact` have no canonical event and are left out.)
6207
+ * `match` (Reasonix's matcher field name) is honored only on
6208
+ * `PreToolUse`/`PostToolUse`, matching the canonical `matcher` field's
6209
+ * tool-event scoping used by other adapters.
6171
6210
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6172
6211
  */
6173
6212
  const REASONIX_HOOK_EVENTS = [
6174
6213
  "preToolUse",
6175
6214
  "postToolUse",
6176
6215
  "beforeSubmitPrompt",
6177
- "stop"
6216
+ "stop",
6217
+ "sessionStart",
6218
+ "sessionEnd",
6219
+ "subagentStop",
6220
+ "postModelInvocation"
6178
6221
  ];
6179
6222
  /**
6180
6223
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6610,14 +6653,18 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6610
6653
  /**
6611
6654
  * Map canonical camelCase event names to Reasonix PascalCase.
6612
6655
  * Reasonix explicitly mirrors Claude Code's hooks model, so it reuses the same
6613
- * PascalCase names for the four events rulesync maps.
6656
+ * PascalCase names for the events rulesync maps.
6614
6657
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6615
6658
  */
6616
6659
  const CANONICAL_TO_REASONIX_EVENT_NAMES = {
6617
6660
  preToolUse: "PreToolUse",
6618
6661
  postToolUse: "PostToolUse",
6619
6662
  beforeSubmitPrompt: "UserPromptSubmit",
6620
- stop: "Stop"
6663
+ stop: "Stop",
6664
+ sessionStart: "SessionStart",
6665
+ sessionEnd: "SessionEnd",
6666
+ subagentStop: "SubagentStop",
6667
+ postModelInvocation: "PostLLMCall"
6621
6668
  };
6622
6669
  /**
6623
6670
  * Map Reasonix PascalCase event names to canonical camelCase.
@@ -6666,6 +6713,21 @@ function applyCommandPrefix({ def, converterConfig }) {
6666
6713
  return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ? `"${converterConfig.projectDirVar}"/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
6667
6714
  }
6668
6715
  /**
6716
+ * Emit the configured boolean passthrough fields on the tool side, mapping each
6717
+ * canonical field name to its (possibly renamed) tool field name. Only boolean
6718
+ * values are carried through.
6719
+ */
6720
+ function emitBooleanPassthroughFields({ def, converterConfig }) {
6721
+ return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ canonical }) => typeof def[canonical] === "boolean").map(({ canonical, tool }) => [tool, def[canonical]]));
6722
+ }
6723
+ /**
6724
+ * Import the configured boolean passthrough fields back into canonical fields,
6725
+ * reversing {@link emitBooleanPassthroughFields}. Only boolean values are read.
6726
+ */
6727
+ function importBooleanPassthroughFields({ h, converterConfig }) {
6728
+ return Object.fromEntries((converterConfig.booleanPassthroughFields ?? []).filter(({ tool }) => typeof h[tool] === "boolean").map(({ canonical, tool }) => [canonical, h[tool]]));
6729
+ }
6730
+ /**
6669
6731
  * Convert the definitions of a single matcher group into tool hook entries,
6670
6732
  * honoring supported hook types and passthrough fields.
6671
6733
  */
@@ -6679,6 +6741,10 @@ function buildToolHooks({ defs, converterConfig }) {
6679
6741
  converterConfig
6680
6742
  });
6681
6743
  hooks.push({
6744
+ ...emitBooleanPassthroughFields({
6745
+ def,
6746
+ converterConfig
6747
+ }),
6682
6748
  type: hookType,
6683
6749
  ...command !== void 0 && command !== null && { command },
6684
6750
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -6766,6 +6832,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
6766
6832
  ...prompt !== void 0 && prompt !== null && { prompt },
6767
6833
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
6768
6834
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
6835
+ ...importBooleanPassthroughFields({
6836
+ h,
6837
+ converterConfig
6838
+ }),
6769
6839
  ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
6770
6840
  };
6771
6841
  }
@@ -7060,7 +7130,7 @@ function combineAugmentSettings(base, local) {
7060
7130
  const baseValue = result[key];
7061
7131
  if (AUGMENTCODE_REPLACE_KEYS.has(key)) result[key] = localValue;
7062
7132
  else if (Array.isArray(localValue) && Array.isArray(baseValue)) result[key] = [...localValue, ...baseValue];
7063
- else if (isPlainObject(localValue) && isPlainObject(baseValue)) result[key] = combineAugmentSettings(baseValue, localValue);
7133
+ else if (isPlainObject$1(localValue) && isPlainObject$1(baseValue)) result[key] = combineAugmentSettings(baseValue, localValue);
7064
7134
  else result[key] = localValue;
7065
7135
  }
7066
7136
  return result;
@@ -7104,14 +7174,14 @@ async function readAugmentcodeSettingsWithLocalOverlay({ outputRoot, relativeDir
7104
7174
  } catch (error) {
7105
7175
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
7106
7176
  }
7107
- if (!isPlainObject(localParsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
7177
+ if (!isPlainObject$1(localParsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
7108
7178
  let baseParsed;
7109
7179
  try {
7110
7180
  baseParsed = JSON.parse(baseContent);
7111
7181
  } catch {
7112
7182
  return baseContent;
7113
7183
  }
7114
- const merged = combineAugmentSettings(isPlainObject(baseParsed) ? baseParsed : {}, localParsed);
7184
+ const merged = combineAugmentSettings(isPlainObject$1(baseParsed) ? baseParsed : {}, localParsed);
7115
7185
  return JSON.stringify(merged, null, 2);
7116
7186
  }
7117
7187
  //#endregion
@@ -8696,7 +8766,14 @@ const JUNIE_CONVERTER_CONFIG = {
8696
8766
  toolToCanonicalEventNames: JUNIE_TO_CANONICAL_EVENT_NAMES,
8697
8767
  projectDirVar: "",
8698
8768
  supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
8699
- noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"])
8769
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
8770
+ booleanPassthroughFields: [{
8771
+ canonical: "failClosed",
8772
+ tool: "blockOnError"
8773
+ }, {
8774
+ canonical: "async",
8775
+ tool: "async"
8776
+ }]
8700
8777
  };
8701
8778
  var JunieHooks = class JunieHooks extends ToolHooks {
8702
8779
  constructor(params) {
@@ -9732,8 +9809,9 @@ function reasonixHooksToCanonical(hooks) {
9732
9809
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
9733
9810
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
9734
9811
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
9735
- * Only the four events documented in the upstream issue are mapped:
9736
- * PreToolUse/PostToolUse/UserPromptSubmit/Stop (see REASONIX_HOOK_EVENTS).
9812
+ * The eight upstream events with a clean canonical equivalent are mapped:
9813
+ * PreToolUse/PostToolUse/UserPromptSubmit/Stop plus SessionStart/SessionEnd/
9814
+ * SubagentStop/PostLLMCall (see REASONIX_HOOK_EVENTS).
9737
9815
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
9738
9816
  */
9739
9817
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -11812,7 +11890,7 @@ function parseAmpSettingsJsonc(fileContent) {
11812
11890
  const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
11813
11891
  throw new Error(`Failed to parse Amp settings: ${details}`);
11814
11892
  }
11815
- if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
11893
+ if (!isPlainObject$1(parsed)) throw new Error("Amp settings must be a JSON object");
11816
11894
  return parsed;
11817
11895
  }
11818
11896
  function filterMcpServers(mcpServers) {
@@ -12134,7 +12212,7 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
12134
12212
  } catch (error) {
12135
12213
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
12136
12214
  }
12137
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
12215
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse AugmentCode settings at ${configPath}: expected a JSON object`);
12138
12216
  return parsed;
12139
12217
  }
12140
12218
  /**
@@ -12360,7 +12438,7 @@ function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
12360
12438
  } catch (error) {
12361
12439
  throw new Error(`Failed to parse Cline MCP settings at ${configPath}: ${formatError(error)}`, { cause: error });
12362
12440
  }
12363
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Cline MCP settings at ${configPath}: expected a JSON object`);
12441
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Cline MCP settings at ${configPath}: expected a JSON object`);
12364
12442
  return parsed;
12365
12443
  }
12366
12444
  /**
@@ -12631,7 +12709,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
12631
12709
  for (const [key, value] of Object.entries(obj)) {
12632
12710
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
12633
12711
  if (value === null) continue;
12634
- if (isPlainObject(value)) {
12712
+ if (isPlainObject$1(value)) {
12635
12713
  const cleaned = this.removeEmptyEntries(value, depth + 1);
12636
12714
  if (Object.keys(cleaned).length === 0) continue;
12637
12715
  filtered[key] = cleaned;
@@ -13272,12 +13350,12 @@ function parseGooseConfig(fileContent, relativeDirPath, relativeFilePath) {
13272
13350
  const configPath = join(relativeDirPath, relativeFilePath);
13273
13351
  let parsed;
13274
13352
  try {
13275
- parsed = load(fileContent);
13353
+ parsed = loadYaml(fileContent);
13276
13354
  } catch (error) {
13277
13355
  throw new Error(`Failed to parse Goose config at ${configPath}: ${formatError(error)}`, { cause: error });
13278
13356
  }
13279
13357
  if (parsed === void 0 || parsed === null) return {};
13280
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Goose config at ${configPath}: expected a YAML mapping`);
13358
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Goose config at ${configPath}: expected a YAML mapping`);
13281
13359
  return parsed;
13282
13360
  }
13283
13361
  /**
@@ -13322,7 +13400,7 @@ function applyGooseStdioFields(ext, config) {
13322
13400
  ext.cmd = command;
13323
13401
  if (isStringArray(config.args)) ext.args = config.args;
13324
13402
  }
13325
- if (isPlainObject(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13403
+ if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13326
13404
  }
13327
13405
  /**
13328
13406
  * Converts a single rulesync canonical MCP server into a Goose `extensions:` entry.
@@ -13337,7 +13415,7 @@ function convertServerToGooseExtension(name, config) {
13337
13415
  if (gooseType === "stdio") applyGooseStdioFields(ext, config);
13338
13416
  else if (gooseType === "sse" || gooseType === "streamable_http") {
13339
13417
  if (url !== void 0) ext.uri = url;
13340
- if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13418
+ if (isPlainObject$1(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13341
13419
  }
13342
13420
  ext.enabled = config.disabled !== true;
13343
13421
  const timeout = resolveGooseTimeout(config);
@@ -13379,9 +13457,9 @@ function convertFromGooseFormat(extensions) {
13379
13457
  else if (type === "stdio") server.type = "stdio";
13380
13458
  if (typeof ext.cmd === "string") server.command = ext.cmd;
13381
13459
  if (isStringArray(ext.args)) server.args = ext.args;
13382
- if (isPlainObject(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13460
+ if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13383
13461
  if (typeof ext.uri === "string") server.url = ext.uri;
13384
- if (isPlainObject(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13462
+ if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13385
13463
  if (ext.enabled === false) server.disabled = true;
13386
13464
  if (typeof ext.timeout === "number") server.timeout = ext.timeout;
13387
13465
  result[name] = server;
@@ -13404,7 +13482,7 @@ function buildGoosePluginStdioServer(config) {
13404
13482
  server.command = command;
13405
13483
  if (isStringArray(config.args)) server.args = config.args;
13406
13484
  }
13407
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13485
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13408
13486
  if (typeof config.cwd === "string") server.cwd = config.cwd;
13409
13487
  return server;
13410
13488
  }
@@ -13704,7 +13782,7 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
13704
13782
  for (const [key, value] of Object.entries(obj)) {
13705
13783
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
13706
13784
  if (value === null) continue;
13707
- if (isPlainObject(value)) {
13785
+ if (isPlainObject$1(value)) {
13708
13786
  const cleaned = this.removeEmptyEntries(value, depth + 1);
13709
13787
  if (Object.keys(cleaned).length === 0) continue;
13710
13788
  filtered[key] = cleaned;
@@ -13766,10 +13844,10 @@ function convertServerToHermes(config) {
13766
13844
  out.command = command;
13767
13845
  if (isStringArray(config.args)) out.args = config.args;
13768
13846
  }
13769
- if (isPlainObject(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13847
+ if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13770
13848
  } else if (url !== void 0) {
13771
13849
  out.url = url;
13772
- if (isPlainObject(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13850
+ if (isPlainObject$1(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13773
13851
  }
13774
13852
  if (config.disabled === true) out.enabled = false;
13775
13853
  const timeout = resolveHermesTimeout(config);
@@ -13814,9 +13892,9 @@ function convertFromHermesFormat(mcpServers) {
13814
13892
  const server = {};
13815
13893
  if (typeof config.command === "string") server.command = config.command;
13816
13894
  if (isStringArray(config.args)) server.args = config.args;
13817
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13895
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13818
13896
  if (typeof config.url === "string") server.url = config.url;
13819
- if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13897
+ if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13820
13898
  if (config.enabled === false) server.disabled = true;
13821
13899
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
13822
13900
  if (isRecord(config.tools)) {
@@ -14753,7 +14831,9 @@ const REASONIX_PLUGIN_FIELDS = [
14753
14831
  "env",
14754
14832
  "url",
14755
14833
  "headers",
14756
- "trusted_read_only_tools"
14834
+ "trusted_read_only_tools",
14835
+ "call_timeout_seconds",
14836
+ "tool_timeout_seconds"
14757
14837
  ];
14758
14838
  var ReasonixMcp = class ReasonixMcp extends ToolMcp {
14759
14839
  toml;
@@ -14985,7 +15065,7 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
14985
15065
  } catch (error) {
14986
15066
  throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: ${formatError(error)}`, { cause: error });
14987
15067
  }
14988
- if (!isPlainObject(parsed)) throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: expected a JSON object`);
15068
+ if (!isPlainObject$1(parsed)) throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: expected a JSON object`);
14989
15069
  return parsed;
14990
15070
  }
14991
15071
  /**
@@ -16256,15 +16336,123 @@ const AntigravityCliPermissionsOverrideSchema = z.looseObject({
16256
16336
  enableTerminalSandbox: z.optional(z.boolean())
16257
16337
  });
16258
16338
  /**
16339
+ * Tool-scoped override block for AugmentCode. AugmentCode's `toolPermissions[]`
16340
+ * array supports "custom policy" entries the canonical allow/ask/deny model
16341
+ * cannot express: `permission.type` of `webhook-policy` / `script-policy`
16342
+ * (delegating the decision to a `webhookUrl` / `script`) and an `eventType` of
16343
+ * `tool-response` (a post-execution check rather than the default pre-execution
16344
+ * `tool-call`). These are authored here as verbatim `toolPermissions` entries
16345
+ * and prepended — ahead of the canonical-generated basic rules — into the shared
16346
+ * `.augment/settings.json`, so a webhook/script gate or tool-response check is
16347
+ * never shadowed by a regenerated allow/deny/ask entry under AugmentCode's
16348
+ * first-match-wins evaluation. The shared `permission` block continues to drive
16349
+ * the basic `allow` / `deny` / `ask-user` entries. Kept `looseObject` (verbatim
16350
+ * passthrough) so `shellInputRegex`, `eventType`, `webhookUrl`, `script`, and
16351
+ * any future policy field survive untouched. Both project and global scope are
16352
+ * supported.
16353
+ *
16354
+ * @example
16355
+ * { "toolPermissions": [
16356
+ * { "toolName": "github-api",
16357
+ * "permission": { "type": "webhook-policy", "webhookUrl": "https://api.example.com/validate" } },
16358
+ * { "toolName": "view", "eventType": "tool-response", "permission": { "type": "allow" } } ] }
16359
+ */
16360
+ const AugmentcodePermissionsOverrideSchema = z.looseObject({ toolPermissions: z.optional(z.array(z.looseObject({
16361
+ toolName: z.string(),
16362
+ permission: z.looseObject({ type: z.string() })
16363
+ }))) });
16364
+ /**
16365
+ * Tool-scoped override block for Kiro. Kiro's agent config (`.kiro/agents/<name>.json`)
16366
+ * exposes per-tool `toolsSettings` knobs with no canonical allow/ask/deny
16367
+ * category: the shell auto-trust flags `shell.autoAllowReadonly` /
16368
+ * `shell.denyByDefault`, the `aws` built-in tool's `allowedServices` /
16369
+ * `deniedServices` (+ `autoAllowReadonly`), and the `web_fetch` domain trust
16370
+ * arrays `trusted` / `blocked` (regex host patterns; documented for `web_fetch`
16371
+ * only — `web_search` has no such surface). Fields placed here are deep-merged
16372
+ * (per `toolsSettings` key, override wins at the leaf) into the shared agent
16373
+ * config, while the canonical `permission` block continues to drive
16374
+ * `shell.{allowed,denied}Commands`, `read`/`write`/`grep`/`glob` paths, and the
16375
+ * `web_fetch`/`web_search` `allowedTools` toggles. Kept `looseObject` at every
16376
+ * level (verbatim passthrough) so future Kiro `toolsSettings` fields survive.
16377
+ *
16378
+ * Kiro's MCP `autoApprove` / `disabledTools` lists are intentionally NOT modeled
16379
+ * here: they live in a SEPARATE file (`.kiro/settings/mcp.json`, under
16380
+ * `mcpServers.<name>`), not the agent config this permissions translator writes,
16381
+ * and reconciling them with the canonical `mcp__*` model is a distinct design
16382
+ * question left out of scope.
16383
+ *
16384
+ * @example
16385
+ * { "toolsSettings": { "shell": { "autoAllowReadonly": true },
16386
+ * "aws": { "allowedServices": ["s3"], "deniedServices": ["eks"] },
16387
+ * "web_fetch": { "trusted": [".*github\\.com.*"] } } }
16388
+ */
16389
+ const KiroPermissionsOverrideSchema = z.looseObject({ toolsSettings: z.optional(z.looseObject({
16390
+ shell: z.optional(z.looseObject({
16391
+ autoAllowReadonly: z.optional(z.boolean()),
16392
+ denyByDefault: z.optional(z.boolean())
16393
+ })),
16394
+ aws: z.optional(z.looseObject({
16395
+ allowedServices: z.optional(z.array(z.string())),
16396
+ deniedServices: z.optional(z.array(z.string())),
16397
+ autoAllowReadonly: z.optional(z.boolean())
16398
+ })),
16399
+ web_fetch: z.optional(z.looseObject({
16400
+ trusted: z.optional(z.array(z.string())),
16401
+ blocked: z.optional(z.array(z.string()))
16402
+ }))
16403
+ })) });
16404
+ /**
16405
+ * Codex CLI-scoped permission override.
16406
+ *
16407
+ * Codex CLI's permission surface is richer than the canonical allow/ask/deny
16408
+ * model: its approval workflow, classic sandbox system, and per-app tool gating
16409
+ * have no canonical category. Author them through a tool-scoped `codexcli`
16410
+ * override whose fields are written verbatim as top-level `.codex/config.toml`
16411
+ * keys (the override wins per key; existing sibling keys the user set directly
16412
+ * are preserved):
16413
+ * - `approval_policy` — `untrusted` | `on-request` | `never`, or a
16414
+ * `{ granular = { … } }` table (kept verbatim; the granular schema has
16415
+ * required fields that are brittle to model as typed keys).
16416
+ * - `sandbox_mode` — `read-only` | `workspace-write` | `danger-full-access`,
16417
+ * with the sibling `sandbox_workspace_write` table (`network_access`,
16418
+ * `writable_roots`, …).
16419
+ * - `apps` — per-app tool gating (`apps.<id>.tools.<tool>.approval_mode` /
16420
+ * `.enabled`, `apps.<id>.default_tools_approval_mode`).
16421
+ * - `approvals_reviewer` — the reviewer-approval surface.
16422
+ *
16423
+ * Two surfaces are deliberately NOT authorable here so the override can never
16424
+ * clobber a feature-owned key: `mcp_servers.*` per-MCP gating is owned by the
16425
+ * MCP feature (`codexcli-mcp.ts` already writes the `mcp_servers` tables in the
16426
+ * same `config.toml`), and `permissions` / `default_permissions` are owned by
16427
+ * the canonical model. Any such key placed in the override is skipped with a
16428
+ * warning. Kept `looseObject` (verbatim passthrough) so future top-level Codex
16429
+ * config keys can be authored without Rulesync modeling each one.
16430
+ *
16431
+ * @see https://developers.openai.com/codex/config-reference
16432
+ * @see https://developers.openai.com/codex/permissions
16433
+ *
16434
+ * @example
16435
+ * { "approval_policy": "on-request", "sandbox_mode": "workspace-write",
16436
+ * "sandbox_workspace_write": { "network_access": true } }
16437
+ */
16438
+ const CodexcliPermissionsOverrideSchema = z.looseObject({
16439
+ approval_policy: z.optional(z.union([z.string(), z.looseObject({})])),
16440
+ sandbox_mode: z.optional(z.string()),
16441
+ sandbox_workspace_write: z.optional(z.looseObject({})),
16442
+ apps: z.optional(z.looseObject({})),
16443
+ approvals_reviewer: z.optional(z.union([z.string(), z.looseObject({})]))
16444
+ });
16445
+ /**
16259
16446
  * Permissions configuration.
16260
16447
  * Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
16261
16448
  * Values are pattern-to-action mappings for that tool category.
16262
16449
  *
16263
16450
  * The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
16264
16451
  * `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
16265
- * `antigravity-cli` keys are tool-scoped overrides consumed only by their
16266
- * respective translator (see the matching `*PermissionsOverrideSchema`); every
16267
- * other tool reads the shared `permission` block and ignores them.
16452
+ * `antigravity-cli`/`augmentcode`/`kiro`/`codexcli` keys are tool-scoped
16453
+ * overrides consumed only by their respective translator (see the matching
16454
+ * `*PermissionsOverrideSchema`); every other tool reads the shared `permission`
16455
+ * block and ignores them.
16268
16456
  *
16269
16457
  * @example
16270
16458
  * {
@@ -16288,7 +16476,10 @@ const PermissionsConfigSchema = z.looseObject({
16288
16476
  junie: z.optional(JuniePermissionsOverrideSchema),
16289
16477
  takt: z.optional(TaktPermissionsOverrideSchema),
16290
16478
  amp: z.optional(AmpPermissionsOverrideSchema),
16291
- "antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema)
16479
+ "antigravity-cli": z.optional(AntigravityCliPermissionsOverrideSchema),
16480
+ augmentcode: z.optional(AugmentcodePermissionsOverrideSchema),
16481
+ kiro: z.optional(KiroPermissionsOverrideSchema),
16482
+ codexcli: z.optional(CodexcliPermissionsOverrideSchema)
16292
16483
  });
16293
16484
  /**
16294
16485
  * Full permissions file schema including optional $schema field.
@@ -16440,7 +16631,7 @@ function parseAmpSettings(fileContent) {
16440
16631
  const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
16441
16632
  throw new Error(`Failed to parse Amp settings: ${details}`);
16442
16633
  }
16443
- if (!isPlainObject(parsed)) throw new Error("Amp settings must be a JSON object");
16634
+ if (!isPlainObject$1(parsed)) throw new Error("Amp settings must be a JSON object");
16444
16635
  return parsed;
16445
16636
  }
16446
16637
  function toDisableList(value) {
@@ -16467,7 +16658,7 @@ function toPermissionsList(value) {
16467
16658
  if (!Array.isArray(value)) return [];
16468
16659
  const entries = [];
16469
16660
  for (const raw of value) {
16470
- if (!isPlainObject(raw)) continue;
16661
+ if (!isPlainObject$1(raw)) continue;
16471
16662
  const { tool, action } = raw;
16472
16663
  if (typeof tool !== "string" || typeof action !== "string") continue;
16473
16664
  if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
@@ -16475,7 +16666,7 @@ function toPermissionsList(value) {
16475
16666
  ...raw,
16476
16667
  tool,
16477
16668
  action,
16478
- ...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
16669
+ ...isPlainObject$1(raw.matches) ? { matches: raw.matches } : {}
16479
16670
  });
16480
16671
  }
16481
16672
  return entries;
@@ -17335,6 +17526,55 @@ function isSpecialEntry(entry) {
17335
17526
  return false;
17336
17527
  }
17337
17528
  /**
17529
+ * A composite identity for a special entry, used to de-duplicate authored
17530
+ * (`augmentcode` override) and preserved (existing-file) special entries so the
17531
+ * same policy is not emitted twice into `toolPermissions`. Every field that can
17532
+ * distinguish two special entries is included.
17533
+ */
17534
+ function specialEntryKey(entry) {
17535
+ return JSON.stringify([
17536
+ entry.toolName,
17537
+ entry.shellInputRegex ?? "",
17538
+ entry.eventType ?? "",
17539
+ entry.permission.type,
17540
+ entry.permission.webhookUrl ?? "",
17541
+ entry.permission.script ?? ""
17542
+ ]);
17543
+ }
17544
+ /**
17545
+ * Stable de-duplication of special entries by {@link specialEntryKey}, keeping
17546
+ * the first occurrence (authored entries lead, so an authored policy wins over
17547
+ * an identical preserved one).
17548
+ */
17549
+ function dedupeSpecialEntries(entries) {
17550
+ const seen = /* @__PURE__ */ new Set();
17551
+ const out = [];
17552
+ for (const entry of entries) {
17553
+ const key = specialEntryKey(entry);
17554
+ if (seen.has(key)) continue;
17555
+ seen.add(key);
17556
+ out.push(entry);
17557
+ }
17558
+ return out;
17559
+ }
17560
+ /**
17561
+ * Validate/normalize the `augmentcode` override's `toolPermissions` array into
17562
+ * typed `AugmentToolPermission` entries, dropping any malformed item (with a
17563
+ * warning so a typo is not lost silently). The override schema is intentionally
17564
+ * loose (verbatim passthrough), so entries are re-parsed through the full entry
17565
+ * schema here to guarantee shape before they are written into the shared
17566
+ * settings file.
17567
+ */
17568
+ function coerceAuthoredEntries(entries, logger) {
17569
+ const out = [];
17570
+ for (const item of entries) {
17571
+ const parsed = AugmentToolPermissionSchema.safeParse(item);
17572
+ if (parsed.success) out.push(parsed.data);
17573
+ else logger?.warn(`AugmentCode permissions: dropping malformed 'augmentcode.toolPermissions' override entry: ${formatError(parsed.error)}.`);
17574
+ }
17575
+ return out;
17576
+ }
17577
+ /**
17338
17578
  * Convert a glob-like pattern into a regex string for AugmentCode's `shellInputRegex`.
17339
17579
  * Maps glob `*` to `.*`, `?` to `.`, escapes other regex metacharacters, and anchors at both ends.
17340
17580
  */
@@ -17465,12 +17705,18 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17465
17705
  } catch (error) {
17466
17706
  throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
17467
17707
  }
17708
+ const config = rulesyncPermissions.getJson();
17468
17709
  const generated = convertRulesyncToAugmentEntries({
17469
- config: rulesyncPermissions.getJson(),
17710
+ config,
17470
17711
  logger
17471
17712
  });
17472
17713
  const existingEntries = settings.toolPermissions ?? [];
17473
- const specialEntries = existingEntries.filter((entry) => isSpecialEntry(entry));
17714
+ const override = config.augmentcode;
17715
+ const authoredEntries = override?.toolPermissions ? coerceAuthoredEntries(override.toolPermissions, logger) : [];
17716
+ const authoredSpecials = authoredEntries.filter((entry) => isSpecialEntry(entry));
17717
+ const authoredBasics = authoredEntries.filter((entry) => !isSpecialEntry(entry));
17718
+ const preservedSpecials = override?.toolPermissions ? [] : existingEntries.filter((entry) => isSpecialEntry(entry));
17719
+ const specialEntries = dedupeSpecialEntries([...authoredSpecials, ...preservedSpecials]);
17474
17720
  const basicExistingEntries = existingEntries.filter((entry) => !isSpecialEntry(entry));
17475
17721
  const generatedKeys = new Set(generated.map((e) => `${e.toolName}|${e.shellInputRegex ?? ""}|${e.permission.type}`));
17476
17722
  const preservedBasicEntries = basicExistingEntries.filter((entry) => {
@@ -17481,7 +17727,11 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17481
17727
  }
17482
17728
  return false;
17483
17729
  });
17484
- const sortedBasic = sortAugmentEntries([...generated, ...preservedBasicEntries]);
17730
+ const sortedBasic = sortAugmentEntries([
17731
+ ...generated,
17732
+ ...preservedBasicEntries,
17733
+ ...authoredBasics
17734
+ ]);
17485
17735
  const merged = {
17486
17736
  ...settings,
17487
17737
  toolPermissions: [...specialEntries, ...sortedBasic]
@@ -17505,11 +17755,14 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17505
17755
  } catch (error) {
17506
17756
  throw new Error(`Failed to parse AugmentCode permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
17507
17757
  }
17508
- const config = convertAugmentToRulesyncPermissions({
17509
- entries: settings.toolPermissions ?? [],
17758
+ const allEntries = settings.toolPermissions ?? [];
17759
+ const specialEntries = allEntries.filter((entry) => isSpecialEntry(entry));
17760
+ const result = { ...convertAugmentToRulesyncPermissions({
17761
+ entries: allEntries.filter((entry) => !isSpecialEntry(entry)),
17510
17762
  logger: moduleLogger$1
17511
- });
17512
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
17763
+ }) };
17764
+ if (specialEntries.length > 0) result.augmentcode = { toolPermissions: specialEntries };
17765
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
17513
17766
  }
17514
17767
  validate() {
17515
17768
  try {
@@ -17643,10 +17896,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
17643
17896
  ]);
17644
17897
  const permission = {};
17645
17898
  for (const entry of entries) {
17646
- if (isSpecialEntry(entry)) {
17647
- 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.`);
17648
- continue;
17649
- }
17899
+ if (isSpecialEntry(entry)) continue;
17650
17900
  const type = entry.permission.type;
17651
17901
  if (!isBasicAugmentType(type)) continue;
17652
17902
  const canonical = toCanonicalToolName$5(entry.toolName);
@@ -18078,6 +18328,13 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
18078
18328
  ]);
18079
18329
  const CODEX_MINIMAL_KEY = ":minimal";
18080
18330
  const GLOBAL_WILDCARD_DOMAIN = "*";
18331
+ const CODEXCLI_OVERRIDE_KEYS = [
18332
+ "approval_policy",
18333
+ "sandbox_mode",
18334
+ "sandbox_workspace_write",
18335
+ "apps",
18336
+ "approvals_reviewer"
18337
+ ];
18081
18338
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18082
18339
  static getSettablePaths(_options = {}) {
18083
18340
  return {
@@ -18119,6 +18376,11 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18119
18376
  });
18120
18377
  parsed.permissions = permissionsTable;
18121
18378
  parsed.default_permissions = RULESYNC_PROFILE_NAME;
18379
+ applyCodexcliOverride({
18380
+ parsed,
18381
+ override: rulesyncPermissions.getJson().codexcli,
18382
+ logger
18383
+ });
18122
18384
  return new CodexcliPermissions({
18123
18385
  outputRoot,
18124
18386
  relativeDirPath: paths.relativeDirPath,
@@ -18143,7 +18405,12 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18143
18405
  profile,
18144
18406
  domainsHadUnknown
18145
18407
  });
18146
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(config, null, 2) });
18408
+ const override = extractCodexcliOverride(table);
18409
+ const result = Object.keys(override).length > 0 ? {
18410
+ ...config,
18411
+ codexcli: override
18412
+ } : config;
18413
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
18147
18414
  }
18148
18415
  validate() {
18149
18416
  return {
@@ -18335,6 +18602,30 @@ function toMutableTable(value) {
18335
18602
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18336
18603
  return { ...value };
18337
18604
  }
18605
+ function isPlainObject(value) {
18606
+ return value !== null && typeof value === "object" && !Array.isArray(value);
18607
+ }
18608
+ function applyCodexcliOverride({ parsed, override, logger }) {
18609
+ if (!override) return;
18610
+ const allowed = new Set(CODEXCLI_OVERRIDE_KEYS);
18611
+ for (const [key, value] of Object.entries(override)) {
18612
+ if (!allowed.has(key)) {
18613
+ 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.`);
18614
+ continue;
18615
+ }
18616
+ if (value === void 0) continue;
18617
+ const existing = parsed[key];
18618
+ parsed[key] = isPlainObject(existing) && isPlainObject(value) ? {
18619
+ ...existing,
18620
+ ...value
18621
+ } : value;
18622
+ }
18623
+ }
18624
+ function extractCodexcliOverride(table) {
18625
+ const override = {};
18626
+ for (const key of CODEXCLI_OVERRIDE_KEYS) if (table[key] !== void 0) override[key] = table[key];
18627
+ return override;
18628
+ }
18338
18629
  function toFilesystemRecord(value) {
18339
18630
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
18340
18631
  const result = {};
@@ -19249,7 +19540,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19249
19540
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19250
19541
  let parsed;
19251
19542
  try {
19252
- parsed = existingContent.trim() === "" ? {} : load(existingContent);
19543
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
19253
19544
  } catch (error) {
19254
19545
  throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
19255
19546
  }
@@ -19271,7 +19562,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19271
19562
  let parsed;
19272
19563
  try {
19273
19564
  const content = this.getFileContent();
19274
- parsed = content.trim() === "" ? {} : load(content);
19565
+ parsed = content.trim() === "" ? {} : loadYaml(content);
19275
19566
  } catch (error) {
19276
19567
  throw new Error(`Failed to parse Goose permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
19277
19568
  }
@@ -20178,6 +20469,13 @@ const KiroAgentSchema = z.looseObject({
20178
20469
  toolsSettings: z.optional(z.record(z.string(), z.unknown()))
20179
20470
  });
20180
20471
  const UnknownRecordSchema = z.record(z.string(), z.unknown());
20472
+ const CANONICAL_SHELL_KEYS = /* @__PURE__ */ new Set(["allowedCommands", "deniedCommands"]);
20473
+ const CANONICAL_TOOL_SETTINGS_KEYS = /* @__PURE__ */ new Set([
20474
+ "read",
20475
+ "write",
20476
+ "grep",
20477
+ "glob"
20478
+ ]);
20181
20479
  var KiroPermissions = class KiroPermissions extends ToolPermissions {
20182
20480
  static getSettablePaths(_options = {}) {
20183
20481
  return {
@@ -20241,7 +20539,10 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
20241
20539
  const allowedTools = new Set(parsed.allowedTools ?? []);
20242
20540
  if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
20243
20541
  if (allowedTools.has("web_search")) permission.websearch = { "*": "allow" };
20244
- return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
20542
+ const kiroOverride = extractKiroOverride(toolsSettings);
20543
+ const result = { permission };
20544
+ if (kiroOverride !== void 0) result.kiro = kiroOverride;
20545
+ return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
20245
20546
  }
20246
20547
  validate() {
20247
20548
  return {
@@ -20291,19 +20592,71 @@ function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
20291
20592
  });
20292
20593
  else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
20293
20594
  }
20294
- nextToolsSettings.shell = shell;
20595
+ nextToolsSettings.shell = {
20596
+ ...preservedShellFlags(existing),
20597
+ ...shell
20598
+ };
20295
20599
  nextToolsSettings.read = pathTable(pathBuckets.read);
20296
20600
  nextToolsSettings.write = pathTable(pathBuckets.write);
20297
20601
  for (const key of ["grep", "glob"]) {
20298
20602
  const bucket = pathBuckets[key];
20299
20603
  if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
20300
20604
  }
20605
+ applyKiroOverride({
20606
+ override: config.kiro,
20607
+ nextToolsSettings,
20608
+ logger
20609
+ });
20301
20610
  return {
20302
20611
  ...existing,
20303
20612
  allowedTools: [...nextAllowedTools].toSorted(),
20304
20613
  toolsSettings: nextToolsSettings
20305
20614
  };
20306
20615
  }
20616
+ /**
20617
+ * Non-canonical `toolsSettings.shell` keys already present in the existing agent
20618
+ * config (everything except the canonical `allowed`/`deniedCommands`), so a
20619
+ * regenerate does not silently drop a hand-set `autoAllowReadonly` /
20620
+ * `denyByDefault` when no `kiro` override re-authors it.
20621
+ */
20622
+ function preservedShellFlags(existing) {
20623
+ const existingShell = asRecord$1(asRecord$1(existing.toolsSettings).shell);
20624
+ const flags = {};
20625
+ for (const [key, value] of Object.entries(existingShell)) if (!CANONICAL_SHELL_KEYS.has(key)) flags[key] = value;
20626
+ return flags;
20627
+ }
20628
+ /**
20629
+ * Deep-merge the `kiro` override's `toolsSettings` block into the generated
20630
+ * settings, one `toolsSettings` key at a time so the override's leaf fields win
20631
+ * without clobbering canonical-generated siblings.
20632
+ *
20633
+ * Guards, so the override can only author the non-canonical surfaces it is meant
20634
+ * for and can never weaken a canonical-generated deny:
20635
+ * - prototype-pollution keys are skipped before being used as object keys;
20636
+ * - fully-canonical `toolsSettings` keys (`read`/`write`/`grep`/`glob`) are
20637
+ * rejected outright with a warning (their paths are owned by the canonical
20638
+ * `permission` block);
20639
+ * - for `shell` (partly canonical), the canonical command-list leaves
20640
+ * (`allowed`/`deniedCommands`) are stripped from the override value so only the
20641
+ * auto-trust flags merge.
20642
+ */
20643
+ function applyKiroOverride({ override, nextToolsSettings, logger }) {
20644
+ const overrideToolsSettings = override?.toolsSettings;
20645
+ if (!isPlainObject$1(overrideToolsSettings)) return;
20646
+ for (const [key, value] of Object.entries(overrideToolsSettings)) {
20647
+ if (isPrototypePollutionKey(key)) continue;
20648
+ if (!isPlainObject$1(value)) continue;
20649
+ if (CANONICAL_TOOL_SETTINGS_KEYS.has(key)) {
20650
+ logger?.warn(`Kiro permissions: ignoring 'kiro.toolsSettings.${key}' override; '${key}' paths are driven by the canonical permission block.`);
20651
+ continue;
20652
+ }
20653
+ const mergeValue = key === "shell" ? Object.fromEntries(Object.entries(value).filter(([leaf]) => !CANONICAL_SHELL_KEYS.has(leaf))) : value;
20654
+ nextToolsSettings[key] = {
20655
+ ...asRecord$1(nextToolsSettings[key]),
20656
+ ...mergeValue
20657
+ };
20658
+ }
20659
+ }
20307
20660
  function pathTable(bucket) {
20308
20661
  return {
20309
20662
  allowedPaths: bucket?.allow ?? [],
@@ -20323,6 +20676,31 @@ function asRecord$1(value) {
20323
20676
  const result = UnknownRecordSchema.safeParse(value);
20324
20677
  return result.success ? result.data : {};
20325
20678
  }
20679
+ /**
20680
+ * Build the `kiro` permissions override from a parsed agent config's
20681
+ * `toolsSettings`, lifting the Kiro-specific knobs with no canonical category:
20682
+ * - `shell.*` flags other than the canonical `allowed`/`deniedCommands`
20683
+ * (e.g. `autoAllowReadonly`, `denyByDefault`), verbatim.
20684
+ * - the whole `aws` object (`allowedServices` / `deniedServices` / …), verbatim.
20685
+ * - the whole `web_fetch` object (`trusted` / `blocked`), verbatim.
20686
+ *
20687
+ * Returns `undefined` when none are present so the override key is omitted.
20688
+ */
20689
+ function extractKiroOverride(toolsSettings) {
20690
+ const overrideToolsSettings = {};
20691
+ const shellFlags = {};
20692
+ for (const [key, value] of Object.entries(asRecord$1(toolsSettings.shell))) {
20693
+ if (isPrototypePollutionKey(key)) continue;
20694
+ if (!CANONICAL_SHELL_KEYS.has(key)) shellFlags[key] = value;
20695
+ }
20696
+ if (Object.keys(shellFlags).length > 0) overrideToolsSettings.shell = shellFlags;
20697
+ const aws = asRecord$1(toolsSettings.aws);
20698
+ if (Object.keys(aws).length > 0) overrideToolsSettings.aws = aws;
20699
+ const webFetch = asRecord$1(toolsSettings.web_fetch);
20700
+ if (Object.keys(webFetch).length > 0) overrideToolsSettings.web_fetch = webFetch;
20701
+ if (Object.keys(overrideToolsSettings).length === 0) return void 0;
20702
+ return { toolsSettings: overrideToolsSettings };
20703
+ }
20326
20704
  function asStringArray(value) {
20327
20705
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
20328
20706
  }
@@ -21135,7 +21513,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21135
21513
  const existingContent = await readFileContentOrNull(filePath) ?? "";
21136
21514
  let parsed;
21137
21515
  try {
21138
- parsed = existingContent.trim() === "" ? {} : load(existingContent);
21516
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
21139
21517
  } catch (error) {
21140
21518
  throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
21141
21519
  }
@@ -21161,7 +21539,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21161
21539
  let parsed;
21162
21540
  try {
21163
21541
  const content = this.getFileContent();
21164
- parsed = content.trim() === "" ? {} : load(content);
21542
+ parsed = content.trim() === "" ? {} : loadYaml(content);
21165
21543
  } catch (error) {
21166
21544
  throw new Error(`Failed to parse Rovodev permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
21167
21545
  }
@@ -21356,9 +21734,9 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21356
21734
  const rulesyncJson = rulesyncPermissions.getJson();
21357
21735
  const provider = resolveActiveProvider(config);
21358
21736
  const mode = deriveTaktPermissionMode(rulesyncJson);
21359
- const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
21360
- const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21361
- const overrideProviderOptions = isPlainObject(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21737
+ const override = isPlainObject$1(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
21738
+ const stepOverrides = isPlainObject$1(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21739
+ const overrideProviderOptions = isPlainObject$1(override?.[TAKT_PROVIDER_OPTIONS_KEY]) ? override[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21362
21740
  const patch = {
21363
21741
  [TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
21364
21742
  [TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
@@ -21389,12 +21767,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21389
21767
  invalidRootPolicy: "error"
21390
21768
  });
21391
21769
  const provider = resolveActiveProvider(config);
21392
- const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21393
- const profile = isPlainObject(profiles[provider]) ? profiles[provider] : {};
21770
+ const profiles = isPlainObject$1(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21771
+ const profile = isPlainObject$1(profiles[provider]) ? profiles[provider] : {};
21394
21772
  const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
21395
21773
  const rulesyncConfig = taktModeToRulesyncConfig(mode);
21396
- const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21397
- const providerOptions = isPlainObject(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21774
+ const stepOverrides = isPlainObject$1(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21775
+ const providerOptions = isPlainObject$1(config[TAKT_PROVIDER_OPTIONS_KEY]) ? config[TAKT_PROVIDER_OPTIONS_KEY] : void 0;
21398
21776
  const taktOverride = {};
21399
21777
  if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
21400
21778
  if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
@@ -21426,7 +21804,7 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21426
21804
  function resolveActiveProvider(config) {
21427
21805
  if (typeof config[TAKT_PROVIDER_KEY] === "string" && config[TAKT_PROVIDER_KEY].trim() !== "") return config[TAKT_PROVIDER_KEY];
21428
21806
  const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
21429
- if (isPlainObject(profiles)) {
21807
+ if (isPlainObject$1(profiles)) {
21430
21808
  const keys = Object.keys(profiles);
21431
21809
  if (keys.length === 1) return keys[0];
21432
21810
  }
@@ -24415,7 +24793,7 @@ function extractOpenaiYamlFile(otherFiles) {
24415
24793
  const rest = [];
24416
24794
  for (const file of otherFiles) {
24417
24795
  if (toPosixPath(file.relativeFilePathToDirPath) === target) try {
24418
- const loaded = load(file.fileBuffer.toString("utf-8"));
24796
+ const loaded = loadYaml(file.fileBuffer.toString("utf-8"));
24419
24797
  if (loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)) {
24420
24798
  parsed = loaded;
24421
24799
  continue;
@@ -26666,6 +27044,139 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
26666
27044
  }
26667
27045
  };
26668
27046
  //#endregion
27047
+ //#region src/features/skills/reasonix-skill.ts
27048
+ const ReasonixSkillFrontmatterSchema = z.looseObject({
27049
+ name: z.string(),
27050
+ description: z.string()
27051
+ });
27052
+ /**
27053
+ * Represents a DeepSeek-Reasonix skill directory.
27054
+ *
27055
+ * Reasonix discovers directory-layout skills (`<name>/SKILL.md`) under
27056
+ * `.reasonix/skills/` (project) and `~/.reasonix/skills/` (global); the global
27057
+ * scope is served by the processor supplying the home directory as outputRoot.
27058
+ * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/GUIDE.md
27059
+ */
27060
+ var ReasonixSkill = class ReasonixSkill extends ToolSkill {
27061
+ constructor({ outputRoot = process.cwd(), relativeDirPath = REASONIX_SKILLS_DIR_PATH, dirName, frontmatter, body, otherFiles = [], validate = true, global = false }) {
27062
+ super({
27063
+ outputRoot,
27064
+ relativeDirPath,
27065
+ dirName,
27066
+ mainFile: {
27067
+ name: SKILL_FILE_NAME,
27068
+ body,
27069
+ frontmatter: { ...frontmatter }
27070
+ },
27071
+ otherFiles,
27072
+ global
27073
+ });
27074
+ if (validate) {
27075
+ const result = this.validate();
27076
+ if (!result.success) throw result.error;
27077
+ }
27078
+ }
27079
+ static getSettablePaths({ global: _global = false } = {}) {
27080
+ return { relativeDirPath: REASONIX_SKILLS_DIR_PATH };
27081
+ }
27082
+ getFrontmatter() {
27083
+ return ReasonixSkillFrontmatterSchema.parse(this.requireMainFileFrontmatter());
27084
+ }
27085
+ getBody() {
27086
+ return this.mainFile?.body ?? "";
27087
+ }
27088
+ validate() {
27089
+ if (this.mainFile === void 0) return {
27090
+ success: false,
27091
+ error: /* @__PURE__ */ new Error(`${this.getDirPath()}: ${SKILL_FILE_NAME} file does not exist`)
27092
+ };
27093
+ const result = ReasonixSkillFrontmatterSchema.safeParse(this.mainFile.frontmatter);
27094
+ if (!result.success) return {
27095
+ success: false,
27096
+ error: /* @__PURE__ */ new Error(`Invalid frontmatter in ${this.getDirPath()}: ${formatError(result.error)}`)
27097
+ };
27098
+ return {
27099
+ success: true,
27100
+ error: null
27101
+ };
27102
+ }
27103
+ toRulesyncSkill() {
27104
+ const frontmatter = this.getFrontmatter();
27105
+ const rulesyncFrontmatter = {
27106
+ name: frontmatter.name,
27107
+ description: frontmatter.description,
27108
+ targets: ["*"]
27109
+ };
27110
+ return new RulesyncSkill({
27111
+ outputRoot: this.outputRoot,
27112
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
27113
+ dirName: this.getDirName(),
27114
+ frontmatter: rulesyncFrontmatter,
27115
+ body: this.getBody(),
27116
+ otherFiles: this.getOtherFiles(),
27117
+ validate: true,
27118
+ global: this.global
27119
+ });
27120
+ }
27121
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
27122
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
27123
+ const reasonixFrontmatter = {
27124
+ name: rulesyncFrontmatter.name,
27125
+ description: rulesyncFrontmatter.description
27126
+ };
27127
+ return new ReasonixSkill({
27128
+ outputRoot,
27129
+ relativeDirPath: ReasonixSkill.getSettablePaths({ global }).relativeDirPath,
27130
+ dirName: rulesyncSkill.getDirName(),
27131
+ frontmatter: reasonixFrontmatter,
27132
+ body: rulesyncSkill.getBody(),
27133
+ otherFiles: rulesyncSkill.getOtherFiles(),
27134
+ validate,
27135
+ global
27136
+ });
27137
+ }
27138
+ static isTargetedByRulesyncSkill(rulesyncSkill) {
27139
+ const targets = rulesyncSkill.getFrontmatter().targets;
27140
+ return targets.includes("*") || targets.includes("reasonix");
27141
+ }
27142
+ static async fromDir(params) {
27143
+ const loaded = await this.loadSkillDirContent({
27144
+ ...params,
27145
+ getSettablePaths: ReasonixSkill.getSettablePaths
27146
+ });
27147
+ const result = ReasonixSkillFrontmatterSchema.safeParse(loaded.frontmatter);
27148
+ if (!result.success) {
27149
+ const skillDirPath = join(loaded.outputRoot, loaded.relativeDirPath, loaded.dirName);
27150
+ throw new Error(`Invalid frontmatter in ${join(skillDirPath, SKILL_FILE_NAME)}: ${formatError(result.error)}`);
27151
+ }
27152
+ return new ReasonixSkill({
27153
+ outputRoot: loaded.outputRoot,
27154
+ relativeDirPath: loaded.relativeDirPath,
27155
+ dirName: loaded.dirName,
27156
+ frontmatter: result.data,
27157
+ body: loaded.body,
27158
+ otherFiles: loaded.otherFiles,
27159
+ validate: true,
27160
+ global: loaded.global
27161
+ });
27162
+ }
27163
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
27164
+ return new ReasonixSkill({
27165
+ outputRoot,
27166
+ relativeDirPath,
27167
+ dirName,
27168
+ frontmatter: {
27169
+ name: "",
27170
+ description: ""
27171
+ },
27172
+ body: "",
27173
+ otherFiles: [],
27174
+ validate: false,
27175
+ global
27176
+ });
27177
+ }
27178
+ };
27179
+ //#endregion
26669
27180
  //#region src/constants/replit-paths.ts
26670
27181
  const REPLIT_RULE_FILE_NAME = "replit.md";
26671
27182
  const REPLIT_SKILLS_DIR_PATH = join(".agents", "skills");
@@ -27760,6 +28271,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
27760
28271
  supportsGlobal: true
27761
28272
  }
27762
28273
  }],
28274
+ ["reasonix", {
28275
+ class: ReasonixSkill,
28276
+ meta: {
28277
+ supportsProject: true,
28278
+ supportsSimulated: false,
28279
+ supportsGlobal: true
28280
+ }
28281
+ }],
27763
28282
  ["replit", {
27764
28283
  class: ReplitSkill,
27765
28284
  meta: {
@@ -30043,7 +30562,7 @@ var GooseSubagent = class GooseSubagent extends ToolSubagent {
30043
30562
  const fileContent = await readFileContent(filePath);
30044
30563
  let parsed;
30045
30564
  try {
30046
- parsed = load(fileContent);
30565
+ parsed = loadYaml(fileContent);
30047
30566
  } catch (error) {
30048
30567
  throw new Error(`Failed to parse Goose recipe (${filePath}): ${formatError(error)}`, { cause: error });
30049
30568
  }
@@ -31431,7 +31950,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
31431
31950
  const fileContent = await readFileContent(filePath);
31432
31951
  let parsed;
31433
31952
  try {
31434
- parsed = load(fileContent);
31953
+ parsed = loadYaml(fileContent);
31435
31954
  } catch (error) {
31436
31955
  throw new Error(`Failed to parse .roomodes (${filePath}): ${formatError(error)}`, { cause: error });
31437
31956
  }
@@ -35624,6 +36143,72 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
35624
36143
  }
35625
36144
  };
35626
36145
  //#endregion
36146
+ //#region src/features/rules/reasonix-rule.ts
36147
+ var ReasonixRule = class ReasonixRule extends ToolRule {
36148
+ constructor({ fileContent, root, ...rest }) {
36149
+ super({
36150
+ ...rest,
36151
+ fileContent,
36152
+ root: root ?? false
36153
+ });
36154
+ }
36155
+ static getSettablePaths({ global = false } = {}) {
36156
+ return { root: {
36157
+ relativeDirPath: global ? REASONIX_GLOBAL_DIR : ".",
36158
+ relativeFilePath: REASONIX_RULE_FILE_NAME
36159
+ } };
36160
+ }
36161
+ static async fromFile({ outputRoot = process.cwd(), relativeFilePath: _relativeFilePath, validate = true, global = false }) {
36162
+ const { root } = this.getSettablePaths({ global });
36163
+ const fileContent = await readFileContent(join(outputRoot, join(root.relativeDirPath, root.relativeFilePath)));
36164
+ return new ReasonixRule({
36165
+ outputRoot,
36166
+ relativeDirPath: root.relativeDirPath,
36167
+ relativeFilePath: root.relativeFilePath,
36168
+ fileContent,
36169
+ validate,
36170
+ root: true
36171
+ });
36172
+ }
36173
+ static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
36174
+ const { root } = this.getSettablePaths({ global });
36175
+ const isRoot = rulesyncRule.getFrontmatter().root ?? false;
36176
+ return new ReasonixRule({
36177
+ outputRoot,
36178
+ relativeDirPath: root.relativeDirPath,
36179
+ relativeFilePath: root.relativeFilePath,
36180
+ fileContent: rulesyncRule.getBody(),
36181
+ validate,
36182
+ root: isRoot
36183
+ });
36184
+ }
36185
+ toRulesyncRule() {
36186
+ return this.toRulesyncRuleDefault();
36187
+ }
36188
+ validate() {
36189
+ return {
36190
+ success: true,
36191
+ error: null
36192
+ };
36193
+ }
36194
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath }) {
36195
+ return new ReasonixRule({
36196
+ outputRoot,
36197
+ relativeDirPath,
36198
+ relativeFilePath,
36199
+ fileContent: "",
36200
+ validate: false,
36201
+ root: relativeFilePath === "REASONIX.md" && (relativeDirPath === "." || relativeDirPath === ".reasonix")
36202
+ });
36203
+ }
36204
+ static isTargetedByRulesyncRule(rulesyncRule) {
36205
+ return this.isTargetedByRulesyncRuleDefault({
36206
+ rulesyncRule,
36207
+ toolTarget: "reasonix"
36208
+ });
36209
+ }
36210
+ };
36211
+ //#endregion
35627
36212
  //#region src/features/rules/replit-rule.ts
35628
36213
  /**
35629
36214
  * Rule generator for Replit Agent
@@ -36536,6 +37121,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
36536
37121
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
36537
37122
  }
36538
37123
  }],
37124
+ ["reasonix", {
37125
+ class: ReasonixRule,
37126
+ meta: {
37127
+ extension: "md",
37128
+ supportsGlobal: true,
37129
+ ruleDiscoveryMode: "auto",
37130
+ foldsNonRootIntoRoot: true
37131
+ }
37132
+ }],
36539
37133
  ["replit", {
36540
37134
  class: ReplitRule,
36541
37135
  meta: {
@@ -37421,7 +38015,6 @@ const SHARED_WRITE_FEATURE_ORDER = [
37421
38015
  "permissions",
37422
38016
  "rules"
37423
38017
  ];
37424
- const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
37425
38018
  const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
37426
38019
  const sharedFileKey = (path) => {
37427
38020
  const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
@@ -37444,7 +38037,13 @@ const settablePathsForScope = (cls, global) => {
37444
38037
  paths.push(settable.root);
37445
38038
  for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
37446
38039
  }
37447
- for (const path of cls.getExtraSharedWritePaths?.({ global }) ?? []) if (path.relativeFilePath) paths.push(path);
38040
+ let extra;
38041
+ try {
38042
+ extra = cls.getExtraSharedWritePaths?.({ global }) ?? [];
38043
+ } catch {
38044
+ return paths;
38045
+ }
38046
+ for (const path of extra) if (path.relativeFilePath) paths.push(path);
37448
38047
  return paths;
37449
38048
  };
37450
38049
  const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
@@ -37457,7 +38056,6 @@ const deriveSharedFileWriters = () => {
37457
38056
  const byKey = /* @__PURE__ */ new Map();
37458
38057
  const pathByKey = /* @__PURE__ */ new Map();
37459
38058
  for (const entry of PROCESSOR_REGISTRY) {
37460
- if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
37461
38059
  const factories = entry.factory;
37462
38060
  for (const [tool, factory] of factories) {
37463
38061
  if (TARGETS_NOT_DERIVED.has(tool)) continue;
@@ -38423,6 +39021,6 @@ async function importPermissionsCore(params) {
38423
39021
  return writtenCount;
38424
39022
  }
38425
39023
  //#endregion
38426
- export { toPosixPath as $, RulesyncCommand as A, createTempDirectory as B, RulesyncHooks as C, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, ALL_FEATURES as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, formatError as Et, ConsoleLogger as F, getFileSize as G, ensureDir as H, JsonLogger as I, listDirectoryFiles as J, getHomeDirectory as K, CLIError as L, stringifyFrontmatter as M, ConfigResolver as N, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES_WITH_WILDCARD as Ot, findControlCharacter as P, removeTempDirectory as Q, ErrorCodes as R, HooksProcessor as S, RULESYNC_RULES_RELATIVE_DIR_PATH as St, CLAUDECODE_DIR as T, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Tt, fileExists as U, directoryExists as V, findFilesByGlobs as W, removeDirectory as X, readFileContent as Y, removeFile as Z, RulesyncPermissions as _, RULESYNC_MCP_SCHEMA_URL as _t, convertFromTool as a, RULESYNC_AIIGNORE_FILE_NAME as at, IgnoreProcessor as b, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_HOOKS_FILE_NAME as dt, writeFileContent as et, SkillsProcessor as f, RULESYNC_HOOKS_RELATIVE_FILE_PATH as ft, SKILL_FILE_NAME as g, RULESYNC_MCP_RELATIVE_FILE_PATH as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_MCP_FILE_NAME as ht, getProcessorRegistryEntry as i, MAX_FILE_SIZE as it, RulesyncCommandFrontmatterSchema as j, CLAUDECODE_SKILLS_DIR_PATH as k, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as lt, RulesyncSkill as m, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as mt, checkRulesyncDirExists as n, ALL_TOOL_TARGETS_WITH_WILDCARD as nt, RulesProcessor as o, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as ot, getLocalSkillDirNames as p, RULESYNC_IGNORE_RELATIVE_FILE_PATH as pt, isSymlink as q, generate as r, ToolTargetSchema as rt, RulesyncRule as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as st, importFromTool as t, ALL_TOOL_TARGETS as tt, RulesyncSubagent as u, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as ut, McpProcessor as v, RULESYNC_OVERVIEW_FILE_NAME as vt, CommandsProcessor as w, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as wt, RulesyncIgnore as x, RULESYNC_RELATIVE_DIR_PATH as xt, RulesyncMcp as y, RULESYNC_PERMISSIONS_FILE_NAME as yt, checkPathTraversal as z };
39024
+ export { removeTempDirectory as $, RulesyncCommand as A, checkPathTraversal as B, RulesyncHooks as C, RULESYNC_RULES_RELATIVE_DIR_PATH as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, formatError as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Et, findControlCharacter as F, findFilesByGlobs as G, directoryExists as H, ConsoleLogger as I, isSymlink as J, getFileSize as K, JsonLogger as L, stringifyFrontmatter as M, loadYaml as N, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES as Ot, ConfigResolver as P, removeFile as Q, CLIError as R, HooksProcessor as S, RULESYNC_RELATIVE_DIR_PATH as St, CLAUDECODE_DIR as T, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Tt, ensureDir as U, createTempDirectory as V, fileExists as W, readFileContent as X, listDirectoryFiles as Y, removeDirectory as Z, RulesyncPermissions as _, RULESYNC_MCP_RELATIVE_FILE_PATH as _t, convertFromTool as a, MAX_FILE_SIZE as at, IgnoreProcessor as b, RULESYNC_PERMISSIONS_FILE_NAME as bt, RulesyncRuleFrontmatterSchema as c, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as ct, RulesyncSubagentFrontmatterSchema as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dt, toPosixPath as et, SkillsProcessor as f, RULESYNC_HOOKS_FILE_NAME as ft, SKILL_FILE_NAME as g, RULESYNC_MCP_FILE_NAME as gt, RulesyncSkillFrontmatterSchema as h, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as ht, getProcessorRegistryEntry as i, ToolTargetSchema as it, RulesyncCommandFrontmatterSchema as j, CLAUDECODE_SKILLS_DIR_PATH as k, ALL_FEATURES_WITH_WILDCARD as kt, SubagentsProcessor as l, RULESYNC_CONFIG_RELATIVE_FILE_PATH as lt, RulesyncSkill as m, RULESYNC_IGNORE_RELATIVE_FILE_PATH as mt, checkRulesyncDirExists as n, ALL_TOOL_TARGETS as nt, RulesProcessor as o, RULESYNC_AIIGNORE_FILE_NAME as ot, getLocalSkillDirNames as p, RULESYNC_HOOKS_RELATIVE_FILE_PATH as pt, getHomeDirectory as q, generate as r, ALL_TOOL_TARGETS_WITH_WILDCARD as rt, RulesyncRule as s, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as st, importFromTool as t, writeFileContent as tt, RulesyncSubagent as u, RULESYNC_CONFIG_SCHEMA_URL as ut, McpProcessor as v, RULESYNC_MCP_SCHEMA_URL as vt, CommandsProcessor as w, RULESYNC_SKILLS_RELATIVE_DIR_PATH as wt, RulesyncIgnore as x, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as xt, RulesyncMcp as y, RULESYNC_OVERVIEW_FILE_NAME as yt, ErrorCodes as z };
38427
39025
 
38428
- //# sourceMappingURL=import-BdJG1fyb.js.map
39026
+ //# sourceMappingURL=import-B6nwZmGl.js.map