rulesync 9.4.0 → 9.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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",
@@ -852,7 +854,7 @@ const SourceEntrySchema = z.object({
852
854
  scope: optional(z.enum(["project", "user"]))
853
855
  });
854
856
  const ConfigParamsSchema = z.object({
855
- outputRoots: z.array(z.string()),
857
+ outputRoots: z.union([z.array(z.string()), z.record(z.string(), z.union([z.string(), z.array(z.string())]))]),
856
858
  targets: RulesyncConfigTargetsSchema,
857
859
  features: RulesyncFeaturesSchema,
858
860
  verbose: z.boolean(),
@@ -957,6 +959,7 @@ var Config = class Config {
957
959
  const resolvedTargets = targets ?? [];
958
960
  const resolvedFeatures = features ?? [];
959
961
  this.validateObjectFormTargetKeys(resolvedTargets);
962
+ this.validateObjectFormOutputRootKeys(outputRoots);
960
963
  this.validateConflictingTargets(resolvedTargets);
961
964
  if (dryRun && check) throw new Error("--dry-run and --check cannot be used together");
962
965
  this.outputRoots = outputRoots;
@@ -994,6 +997,11 @@ var Config = class Config {
994
997
  if (!validTargets.has(key)) throw new Error(`Unknown target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
995
998
  }
996
999
  }
1000
+ validateObjectFormOutputRootKeys(outputRoots) {
1001
+ if (Array.isArray(outputRoots)) return;
1002
+ const validTargets = new Set(ALL_TOOL_TARGETS);
1003
+ for (const key of Object.keys(outputRoots)) if (!validTargets.has(key)) throw new Error(`Unknown outputRoots target '${key}'. Valid targets: ${ALL_TOOL_TARGETS.join(", ")}.`);
1004
+ }
997
1005
  validateConflictingTargets(targets) {
998
1006
  const has = (target) => {
999
1007
  if (Array.isArray(targets)) return targets.includes(target);
@@ -1001,8 +1009,19 @@ var Config = class Config {
1001
1009
  };
1002
1010
  for (const [target1, target2] of CONFLICTING_TARGET_PAIRS) if (has(target1) && has(target2)) throw new Error(`Conflicting targets: '${target1}' and '${target2}' cannot be used together. Please choose one.`);
1003
1011
  }
1004
- getOutputRoots() {
1005
- return this.outputRoots;
1012
+ getOutputRoots(target) {
1013
+ if (Array.isArray(this.outputRoots)) return this.outputRoots;
1014
+ if (target) {
1015
+ const targetOutputRoots = this.outputRoots[target];
1016
+ if (targetOutputRoots === void 0) return [];
1017
+ return Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
1018
+ }
1019
+ const allRoots = [];
1020
+ for (const value of Object.values(this.outputRoots)) {
1021
+ if (value === void 0) continue;
1022
+ allRoots.push(...Array.isArray(value) ? value : [value]);
1023
+ }
1024
+ return [...new Set(allRoots)];
1006
1025
  }
1007
1026
  /**
1008
1027
  * Filter an arbitrary string-key list down to the known `ToolTarget` set,
@@ -1364,10 +1383,21 @@ var ConfigResolver = class {
1364
1383
  };
1365
1384
  function getOutputRootsInLightOfGlobal({ outputRoots, global }) {
1366
1385
  if (global) return [getHomeDirectory()];
1367
- outputRoots.forEach((outputRoot) => {
1368
- validateOutputRoot(outputRoot);
1369
- });
1370
- return outputRoots.map((outputRoot) => resolve(outputRoot));
1386
+ if (Array.isArray(outputRoots)) {
1387
+ outputRoots.forEach((outputRoot) => {
1388
+ validateOutputRoot(outputRoot);
1389
+ });
1390
+ return outputRoots.map((outputRoot) => resolve(outputRoot));
1391
+ }
1392
+ const resolvedOutputRoots = {};
1393
+ for (const [target, targetOutputRoots] of Object.entries(outputRoots)) {
1394
+ const roots = Array.isArray(targetOutputRoots) ? targetOutputRoots : [targetOutputRoots];
1395
+ roots.forEach((outputRoot) => {
1396
+ validateOutputRoot(outputRoot);
1397
+ });
1398
+ resolvedOutputRoots[target] = Array.isArray(targetOutputRoots) ? roots.map((outputRoot) => resolve(outputRoot)) : resolve(targetOutputRoots);
1399
+ }
1400
+ return resolvedOutputRoots;
1371
1401
  }
1372
1402
  function extractConfigFileTargets(targets) {
1373
1403
  if (targets === void 0) return void 0;
@@ -1396,7 +1426,7 @@ function isRecord(value) {
1396
1426
  * conversion, etc.) should reject inputs whose prototype could carry
1397
1427
  * malicious accessor descriptors.
1398
1428
  */
1399
- function isPlainObject(value) {
1429
+ function isPlainObject$1(value) {
1400
1430
  if (!isRecord(value)) return false;
1401
1431
  const proto = Object.getPrototypeOf(value);
1402
1432
  return proto === null || proto === Object.prototype;
@@ -1408,11 +1438,43 @@ function isStringArray(value) {
1408
1438
  return Array.isArray(value) && value.every((item) => typeof item === "string");
1409
1439
  }
1410
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
1411
1473
  //#region src/utils/frontmatter.ts
1412
1474
  function deepRemoveNullishValue(value) {
1413
1475
  if (value === null || value === void 0) return;
1414
1476
  if (Array.isArray(value)) return value.map((item) => deepRemoveNullishValue(item)).filter((item) => item !== void 0);
1415
- if (isPlainObject(value)) {
1477
+ if (isPlainObject$1(value)) {
1416
1478
  const result = {};
1417
1479
  for (const [key, val] of Object.entries(value)) {
1418
1480
  const cleaned = deepRemoveNullishValue(val);
@@ -1435,7 +1497,7 @@ function deepFlattenStringsValue(value) {
1435
1497
  if (value === null || value === void 0) return;
1436
1498
  if (typeof value === "string") return value.replace(/\n+/g, " ").trim();
1437
1499
  if (Array.isArray(value)) return value.map((item) => deepFlattenStringsValue(item)).filter((item) => item !== void 0);
1438
- if (isPlainObject(value)) {
1500
+ if (isPlainObject$1(value)) {
1439
1501
  const result = {};
1440
1502
  for (const [key, val] of Object.entries(value)) {
1441
1503
  const cleaned = deepFlattenStringsValue(val);
@@ -1458,7 +1520,7 @@ function stringifyFrontmatter(body, frontmatter, options) {
1458
1520
  const { avoidBlockScalars = false } = options ?? {};
1459
1521
  const cleanFrontmatter = avoidBlockScalars ? deepFlattenStringsObject(frontmatter) : deepRemoveNullishObject(frontmatter);
1460
1522
  if (avoidBlockScalars) return matter.stringify(body, cleanFrontmatter, { engines: { yaml: {
1461
- parse: (input) => load(input) ?? {},
1523
+ parse: (input) => loadYaml(input) ?? {},
1462
1524
  stringify: (data) => dump(data, { lineWidth: -1 })
1463
1525
  } } });
1464
1526
  return matter.stringify(body, cleanFrontmatter);
@@ -1497,7 +1559,7 @@ function tryJsonEquivalent(a, b) {
1497
1559
  }
1498
1560
  function tryYamlEquivalent(a, b) {
1499
1561
  try {
1500
- return isDeepStrictEqual(load(a), load(b));
1562
+ return isDeepStrictEqual(loadYaml(a), loadYaml(b));
1501
1563
  } catch {
1502
1564
  return;
1503
1565
  }
@@ -3304,7 +3366,7 @@ var GooseCommand = class GooseCommand extends ToolCommand {
3304
3366
  const where = join(this.relativeDirPath, this.relativeFilePath);
3305
3367
  let parsed;
3306
3368
  try {
3307
- parsed = load(content);
3369
+ parsed = loadYaml(content);
3308
3370
  } catch (error) {
3309
3371
  throw new Error(`Failed to parse Goose recipe (${where}): ${formatError(error)}`, { cause: error });
3310
3372
  }
@@ -3957,7 +4019,7 @@ function omitPrototypePollutionKeys(record) {
3957
4019
  //#region src/features/shared/shared-config-gateway.ts
3958
4020
  function sanitizeSharedConfigValue(value) {
3959
4021
  if (Array.isArray(value)) return value.map(sanitizeSharedConfigValue);
3960
- if (!isPlainObject(value)) return value;
4022
+ if (!isPlainObject$1(value)) return value;
3961
4023
  const result = {};
3962
4024
  for (const [key, nested] of Object.entries(omitPrototypePollutionKeys(value))) result[key] = sanitizeSharedConfigValue(nested);
3963
4025
  return result;
@@ -3973,14 +4035,14 @@ function parseSharedConfig({ format, fileContent, filePath, invalidRootPolicy =
3973
4035
  const at = filePath === void 0 ? "" : ` at ${filePath}`;
3974
4036
  let parsed;
3975
4037
  try {
3976
- if (format === "yaml") parsed = load(fileContent);
4038
+ if (format === "yaml") parsed = loadYaml(fileContent);
3977
4039
  else if (format === "json") parsed = JSON.parse(fileContent);
3978
4040
  else parsed = parse(fileContent);
3979
4041
  } catch (error) {
3980
4042
  throw new Error(`Failed to parse shared config${at}: ${formatError(error)}`, { cause: error });
3981
4043
  }
3982
4044
  if (parsed === void 0 || parsed === null) return {};
3983
- if (!isPlainObject(parsed)) {
4045
+ if (!isPlainObject$1(parsed)) {
3984
4046
  if (invalidRootPolicy === "error") throw new Error(`Failed to parse shared config${at}: expected a mapping at the root`);
3985
4047
  return {};
3986
4048
  }
@@ -4023,7 +4085,7 @@ function mergeSharedConfigDeep({ base, patch }) {
4023
4085
  for (const [key, patchValue] of Object.entries(patch)) {
4024
4086
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4025
4087
  const baseValue = result[key];
4026
- if (isPlainObject(baseValue) && isPlainObject(patchValue)) result[key] = mergeSharedConfigDeep({
4088
+ if (isPlainObject$1(baseValue) && isPlainObject$1(patchValue)) result[key] = mergeSharedConfigDeep({
4027
4089
  base: baseValue,
4028
4090
  patch: patchValue
4029
4091
  });
@@ -4657,6 +4719,8 @@ const REASONIX_GLOBAL_PERMISSIONS_FILE_NAME = REASONIX_GLOBAL_MCP_FILE_NAME;
4657
4719
  const REASONIX_DIR = REASONIX_GLOBAL_DIR;
4658
4720
  const REASONIX_SETTINGS_FILE_NAME = "settings.json";
4659
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");
4660
4724
  //#endregion
4661
4725
  //#region src/features/commands/reasonix-command.ts
4662
4726
  /**
@@ -5062,8 +5126,8 @@ var RovodevCommand = class RovodevCommand extends ToolCommand {
5062
5126
  const existingContent = await readFileContentOrNull(join(outputRoot, ROVODEV_DIR, ROVODEV_PROMPTS_FILE_NAME));
5063
5127
  let existing = {};
5064
5128
  if (existingContent) try {
5065
- const parsed = load(existingContent);
5066
- if (isPlainObject(parsed)) existing = parsed;
5129
+ const parsed = loadYaml(existingContent);
5130
+ if (isPlainObject$1(parsed)) existing = parsed;
5067
5131
  } catch {}
5068
5132
  const prompts = rovodevCommands.map((command) => ({
5069
5133
  name: command.getName(),
@@ -5093,11 +5157,11 @@ async function lookupPromptDescription({ outputRoot, relativeFilePath, name }) {
5093
5157
  if (!manifestContent) return "";
5094
5158
  let parsed;
5095
5159
  try {
5096
- parsed = load(manifestContent);
5160
+ parsed = loadYaml(manifestContent);
5097
5161
  } catch {
5098
5162
  return "";
5099
5163
  }
5100
- if (!isPlainObject(parsed) || !Array.isArray(parsed.prompts)) return "";
5164
+ if (!isPlainObject$1(parsed) || !Array.isArray(parsed.prompts)) return "";
5101
5165
  const expectedContentFile = toPosixPath(join("prompts", relativeFilePath));
5102
5166
  const entry = parsed.prompts.find((candidate) => isRecord(candidate) && (candidate.content_file === expectedContentFile || candidate.name === name));
5103
5167
  return isRecord(entry) && typeof entry.description === "string" ? entry.description : "";
@@ -6135,18 +6199,25 @@ const QWENCODE_HOOK_EVENTS = [
6135
6199
  * Reasonix's `.reasonix/settings.json` (project) / `~/.reasonix/settings.json`
6136
6200
  * (global) documents a ten-event surface (`PreToolUse`, `PostToolUse`,
6137
6201
  * `UserPromptSubmit`, `Stop`, `PostLLMCall`, `SessionStart`, `SessionEnd`,
6138
- * `SubagentStop`, `Notification`, `PreCompact`), but only the four events the
6139
- * upstream issue scoped in are mapped here: `PreToolUse`, `PostToolUse`,
6140
- * `UserPromptSubmit` ← `beforeSubmitPrompt`, and `Stop`. `match` (Reasonix's
6141
- * matcher field name) is honored only on `PreToolUse`/`PostToolUse`, matching
6142
- * 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.
6143
6210
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6144
6211
  */
6145
6212
  const REASONIX_HOOK_EVENTS = [
6146
6213
  "preToolUse",
6147
6214
  "postToolUse",
6148
6215
  "beforeSubmitPrompt",
6149
- "stop"
6216
+ "stop",
6217
+ "sessionStart",
6218
+ "sessionEnd",
6219
+ "subagentStop",
6220
+ "postModelInvocation"
6150
6221
  ];
6151
6222
  /**
6152
6223
  * Hook events supported by Hermes Agent's native Shell Hooks system.
@@ -6582,14 +6653,18 @@ const QWENCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
6582
6653
  /**
6583
6654
  * Map canonical camelCase event names to Reasonix PascalCase.
6584
6655
  * Reasonix explicitly mirrors Claude Code's hooks model, so it reuses the same
6585
- * PascalCase names for the four events rulesync maps.
6656
+ * PascalCase names for the events rulesync maps.
6586
6657
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
6587
6658
  */
6588
6659
  const CANONICAL_TO_REASONIX_EVENT_NAMES = {
6589
6660
  preToolUse: "PreToolUse",
6590
6661
  postToolUse: "PostToolUse",
6591
6662
  beforeSubmitPrompt: "UserPromptSubmit",
6592
- stop: "Stop"
6663
+ stop: "Stop",
6664
+ sessionStart: "SessionStart",
6665
+ sessionEnd: "SessionEnd",
6666
+ subagentStop: "SubagentStop",
6667
+ postModelInvocation: "PostLLMCall"
6593
6668
  };
6594
6669
  /**
6595
6670
  * Map Reasonix PascalCase event names to canonical camelCase.
@@ -6638,6 +6713,21 @@ function applyCommandPrefix({ def, converterConfig }) {
6638
6713
  return converterConfig.projectDirVar !== "" && typeof trimmedCommand === "string" && !trimmedCommand.startsWith("$") && (!converterConfig.prefixDotRelativeCommandsOnly || trimmedCommand.startsWith(".")) && typeof trimmedCommand === "string" ? `"${converterConfig.projectDirVar}"/${trimmedCommand.replace(/^\.\//, "")}` : def.command;
6639
6714
  }
6640
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
+ /**
6641
6731
  * Convert the definitions of a single matcher group into tool hook entries,
6642
6732
  * honoring supported hook types and passthrough fields.
6643
6733
  */
@@ -6651,6 +6741,10 @@ function buildToolHooks({ defs, converterConfig }) {
6651
6741
  converterConfig
6652
6742
  });
6653
6743
  hooks.push({
6744
+ ...emitBooleanPassthroughFields({
6745
+ def,
6746
+ converterConfig
6747
+ }),
6654
6748
  type: hookType,
6655
6749
  ...command !== void 0 && command !== null && { command },
6656
6750
  ...def.timeout !== void 0 && def.timeout !== null && { timeout: def.timeout },
@@ -6738,6 +6832,10 @@ function toolHookToCanonical({ h, rawEntry, converterConfig }) {
6738
6832
  ...prompt !== void 0 && prompt !== null && { prompt },
6739
6833
  ...converterConfig.passthroughFields?.includes("name") && typeof h.name === "string" && { name: h.name },
6740
6834
  ...converterConfig.passthroughFields?.includes("description") && typeof h.description === "string" && { description: h.description },
6835
+ ...importBooleanPassthroughFields({
6836
+ h,
6837
+ converterConfig
6838
+ }),
6741
6839
  ...rawEntry.matcher !== void 0 && rawEntry.matcher !== null && rawEntry.matcher !== "" && { matcher: rawEntry.matcher }
6742
6840
  };
6743
6841
  }
@@ -7032,7 +7130,7 @@ function combineAugmentSettings(base, local) {
7032
7130
  const baseValue = result[key];
7033
7131
  if (AUGMENTCODE_REPLACE_KEYS.has(key)) result[key] = localValue;
7034
7132
  else if (Array.isArray(localValue) && Array.isArray(baseValue)) result[key] = [...localValue, ...baseValue];
7035
- 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);
7036
7134
  else result[key] = localValue;
7037
7135
  }
7038
7136
  return result;
@@ -7076,14 +7174,14 @@ async function readAugmentcodeSettingsWithLocalOverlay({ outputRoot, relativeDir
7076
7174
  } catch (error) {
7077
7175
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
7078
7176
  }
7079
- 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`);
7080
7178
  let baseParsed;
7081
7179
  try {
7082
7180
  baseParsed = JSON.parse(baseContent);
7083
7181
  } catch {
7084
7182
  return baseContent;
7085
7183
  }
7086
- const merged = combineAugmentSettings(isPlainObject(baseParsed) ? baseParsed : {}, localParsed);
7184
+ const merged = combineAugmentSettings(isPlainObject$1(baseParsed) ? baseParsed : {}, localParsed);
7087
7185
  return JSON.stringify(merged, null, 2);
7088
7186
  }
7089
7187
  //#endregion
@@ -8668,7 +8766,14 @@ const JUNIE_CONVERTER_CONFIG = {
8668
8766
  toolToCanonicalEventNames: JUNIE_TO_CANONICAL_EVENT_NAMES,
8669
8767
  projectDirVar: "",
8670
8768
  supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
8671
- 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
+ }]
8672
8777
  };
8673
8778
  var JunieHooks = class JunieHooks extends ToolHooks {
8674
8779
  constructor(params) {
@@ -9704,8 +9809,9 @@ function reasonixHooksToCanonical(hooks) {
9704
9809
  * Reasonix hooks live in a Claude-Code-style but standalone JSON file —
9705
9810
  * `.reasonix/settings.json` (project) or `~/.reasonix/settings.json`
9706
9811
  * (global) — separate from the `[permissions]`/`[[plugins]]` TOML config.
9707
- * Only the four events documented in the upstream issue are mapped:
9708
- * 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).
9709
9815
  * @see https://github.com/esengine/DeepSeek-Reasonix/blob/main-v2/docs/DESKTOP_HOOKS.zh-CN.md
9710
9816
  */
9711
9817
  var ReasonixHooks = class ReasonixHooks extends ToolHooks {
@@ -11784,7 +11890,7 @@ function parseAmpSettingsJsonc(fileContent) {
11784
11890
  const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
11785
11891
  throw new Error(`Failed to parse Amp settings: ${details}`);
11786
11892
  }
11787
- 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");
11788
11894
  return parsed;
11789
11895
  }
11790
11896
  function filterMcpServers(mcpServers) {
@@ -12106,7 +12212,7 @@ function parseAugmentcodeSettings(fileContent, relativeDirPath, relativeFilePath
12106
12212
  } catch (error) {
12107
12213
  throw new Error(`Failed to parse AugmentCode settings at ${configPath}: ${formatError(error)}`, { cause: error });
12108
12214
  }
12109
- 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`);
12110
12216
  return parsed;
12111
12217
  }
12112
12218
  /**
@@ -12332,7 +12438,7 @@ function parseClineSettings(fileContent, relativeDirPath, relativeFilePath) {
12332
12438
  } catch (error) {
12333
12439
  throw new Error(`Failed to parse Cline MCP settings at ${configPath}: ${formatError(error)}`, { cause: error });
12334
12440
  }
12335
- 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`);
12336
12442
  return parsed;
12337
12443
  }
12338
12444
  /**
@@ -12603,7 +12709,7 @@ var CodexcliMcp = class CodexcliMcp extends ToolMcp {
12603
12709
  for (const [key, value] of Object.entries(obj)) {
12604
12710
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
12605
12711
  if (value === null) continue;
12606
- if (isPlainObject(value)) {
12712
+ if (isPlainObject$1(value)) {
12607
12713
  const cleaned = this.removeEmptyEntries(value, depth + 1);
12608
12714
  if (Object.keys(cleaned).length === 0) continue;
12609
12715
  filtered[key] = cleaned;
@@ -13244,12 +13350,12 @@ function parseGooseConfig(fileContent, relativeDirPath, relativeFilePath) {
13244
13350
  const configPath = join(relativeDirPath, relativeFilePath);
13245
13351
  let parsed;
13246
13352
  try {
13247
- parsed = load(fileContent);
13353
+ parsed = loadYaml(fileContent);
13248
13354
  } catch (error) {
13249
13355
  throw new Error(`Failed to parse Goose config at ${configPath}: ${formatError(error)}`, { cause: error });
13250
13356
  }
13251
13357
  if (parsed === void 0 || parsed === null) return {};
13252
- 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`);
13253
13359
  return parsed;
13254
13360
  }
13255
13361
  /**
@@ -13294,7 +13400,7 @@ function applyGooseStdioFields(ext, config) {
13294
13400
  ext.cmd = command;
13295
13401
  if (isStringArray(config.args)) ext.args = config.args;
13296
13402
  }
13297
- if (isPlainObject(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13403
+ if (isPlainObject$1(config.env)) ext.envs = omitPrototypePollutionKeys(config.env);
13298
13404
  }
13299
13405
  /**
13300
13406
  * Converts a single rulesync canonical MCP server into a Goose `extensions:` entry.
@@ -13309,7 +13415,7 @@ function convertServerToGooseExtension(name, config) {
13309
13415
  if (gooseType === "stdio") applyGooseStdioFields(ext, config);
13310
13416
  else if (gooseType === "sse" || gooseType === "streamable_http") {
13311
13417
  if (url !== void 0) ext.uri = url;
13312
- if (isPlainObject(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13418
+ if (isPlainObject$1(config.headers)) ext.headers = omitPrototypePollutionKeys(config.headers);
13313
13419
  }
13314
13420
  ext.enabled = config.disabled !== true;
13315
13421
  const timeout = resolveGooseTimeout(config);
@@ -13351,9 +13457,9 @@ function convertFromGooseFormat(extensions) {
13351
13457
  else if (type === "stdio") server.type = "stdio";
13352
13458
  if (typeof ext.cmd === "string") server.command = ext.cmd;
13353
13459
  if (isStringArray(ext.args)) server.args = ext.args;
13354
- if (isPlainObject(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13460
+ if (isPlainObject$1(ext.envs)) server.env = omitPrototypePollutionKeys(ext.envs);
13355
13461
  if (typeof ext.uri === "string") server.url = ext.uri;
13356
- if (isPlainObject(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13462
+ if (isPlainObject$1(ext.headers)) server.headers = omitPrototypePollutionKeys(ext.headers);
13357
13463
  if (ext.enabled === false) server.disabled = true;
13358
13464
  if (typeof ext.timeout === "number") server.timeout = ext.timeout;
13359
13465
  result[name] = server;
@@ -13376,7 +13482,7 @@ function buildGoosePluginStdioServer(config) {
13376
13482
  server.command = command;
13377
13483
  if (isStringArray(config.args)) server.args = config.args;
13378
13484
  }
13379
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13485
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13380
13486
  if (typeof config.cwd === "string") server.cwd = config.cwd;
13381
13487
  return server;
13382
13488
  }
@@ -13676,7 +13782,7 @@ var GrokcliMcp = class GrokcliMcp extends ToolMcp {
13676
13782
  for (const [key, value] of Object.entries(obj)) {
13677
13783
  if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
13678
13784
  if (value === null) continue;
13679
- if (isPlainObject(value)) {
13785
+ if (isPlainObject$1(value)) {
13680
13786
  const cleaned = this.removeEmptyEntries(value, depth + 1);
13681
13787
  if (Object.keys(cleaned).length === 0) continue;
13682
13788
  filtered[key] = cleaned;
@@ -13738,10 +13844,10 @@ function convertServerToHermes(config) {
13738
13844
  out.command = command;
13739
13845
  if (isStringArray(config.args)) out.args = config.args;
13740
13846
  }
13741
- if (isPlainObject(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13847
+ if (isPlainObject$1(config.env)) out.env = omitPrototypePollutionKeys(config.env);
13742
13848
  } else if (url !== void 0) {
13743
13849
  out.url = url;
13744
- if (isPlainObject(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13850
+ if (isPlainObject$1(config.headers)) out.headers = omitPrototypePollutionKeys(config.headers);
13745
13851
  }
13746
13852
  if (config.disabled === true) out.enabled = false;
13747
13853
  const timeout = resolveHermesTimeout(config);
@@ -13786,9 +13892,9 @@ function convertFromHermesFormat(mcpServers) {
13786
13892
  const server = {};
13787
13893
  if (typeof config.command === "string") server.command = config.command;
13788
13894
  if (isStringArray(config.args)) server.args = config.args;
13789
- if (isPlainObject(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13895
+ if (isPlainObject$1(config.env)) server.env = omitPrototypePollutionKeys(config.env);
13790
13896
  if (typeof config.url === "string") server.url = config.url;
13791
- if (isPlainObject(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13897
+ if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
13792
13898
  if (config.enabled === false) server.disabled = true;
13793
13899
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
13794
13900
  if (isRecord(config.tools)) {
@@ -14725,7 +14831,9 @@ const REASONIX_PLUGIN_FIELDS = [
14725
14831
  "env",
14726
14832
  "url",
14727
14833
  "headers",
14728
- "trusted_read_only_tools"
14834
+ "trusted_read_only_tools",
14835
+ "call_timeout_seconds",
14836
+ "tool_timeout_seconds"
14729
14837
  ];
14730
14838
  var ReasonixMcp = class ReasonixMcp extends ToolMcp {
14731
14839
  toml;
@@ -14957,7 +15065,7 @@ function parseRovodevMcpJson(fileContent, relativeDirPath, relativeFilePath) {
14957
15065
  } catch (error) {
14958
15066
  throw new Error(`Failed to parse Rovodev MCP config at ${configPath}: ${formatError(error)}`, { cause: error });
14959
15067
  }
14960
- 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`);
14961
15069
  return parsed;
14962
15070
  }
14963
15071
  /**
@@ -16228,15 +16336,123 @@ const AntigravityCliPermissionsOverrideSchema = z.looseObject({
16228
16336
  enableTerminalSandbox: z.optional(z.boolean())
16229
16337
  });
16230
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
+ /**
16231
16446
  * Permissions configuration.
16232
16447
  * Keys are tool category names (e.g., "bash", "edit", "read", "webfetch").
16233
16448
  * Values are pattern-to-action mappings for that tool category.
16234
16449
  *
16235
16450
  * The optional `opencode`/`hermes`/`cline`/`kilo`/`claudecode`/`vibe`/`cursor`/
16236
16451
  * `qwencode`/`reasonix`/`factorydroid`/`warp`/`junie`/`takt`/`amp`/
16237
- * `antigravity-cli` keys are tool-scoped overrides consumed only by their
16238
- * respective translator (see the matching `*PermissionsOverrideSchema`); every
16239
- * 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.
16240
16456
  *
16241
16457
  * @example
16242
16458
  * {
@@ -16260,7 +16476,10 @@ const PermissionsConfigSchema = z.looseObject({
16260
16476
  junie: z.optional(JuniePermissionsOverrideSchema),
16261
16477
  takt: z.optional(TaktPermissionsOverrideSchema),
16262
16478
  amp: z.optional(AmpPermissionsOverrideSchema),
16263
- "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)
16264
16483
  });
16265
16484
  /**
16266
16485
  * Full permissions file schema including optional $schema field.
@@ -16412,7 +16631,7 @@ function parseAmpSettings(fileContent) {
16412
16631
  const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
16413
16632
  throw new Error(`Failed to parse Amp settings: ${details}`);
16414
16633
  }
16415
- 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");
16416
16635
  return parsed;
16417
16636
  }
16418
16637
  function toDisableList(value) {
@@ -16439,7 +16658,7 @@ function toPermissionsList(value) {
16439
16658
  if (!Array.isArray(value)) return [];
16440
16659
  const entries = [];
16441
16660
  for (const raw of value) {
16442
- if (!isPlainObject(raw)) continue;
16661
+ if (!isPlainObject$1(raw)) continue;
16443
16662
  const { tool, action } = raw;
16444
16663
  if (typeof tool !== "string" || typeof action !== "string") continue;
16445
16664
  if (action !== "allow" && action !== "reject" && action !== "ask" && action !== "delegate") continue;
@@ -16447,7 +16666,7 @@ function toPermissionsList(value) {
16447
16666
  ...raw,
16448
16667
  tool,
16449
16668
  action,
16450
- ...isPlainObject(raw.matches) ? { matches: raw.matches } : {}
16669
+ ...isPlainObject$1(raw.matches) ? { matches: raw.matches } : {}
16451
16670
  });
16452
16671
  }
16453
16672
  return entries;
@@ -17307,6 +17526,55 @@ function isSpecialEntry(entry) {
17307
17526
  return false;
17308
17527
  }
17309
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
+ /**
17310
17578
  * Convert a glob-like pattern into a regex string for AugmentCode's `shellInputRegex`.
17311
17579
  * Maps glob `*` to `.*`, `?` to `.`, escapes other regex metacharacters, and anchors at both ends.
17312
17580
  */
@@ -17437,12 +17705,18 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17437
17705
  } catch (error) {
17438
17706
  throw new Error(`Failed to parse existing AugmentCode settings at ${filePath}: ${formatError(error)}`, { cause: error });
17439
17707
  }
17708
+ const config = rulesyncPermissions.getJson();
17440
17709
  const generated = convertRulesyncToAugmentEntries({
17441
- config: rulesyncPermissions.getJson(),
17710
+ config,
17442
17711
  logger
17443
17712
  });
17444
17713
  const existingEntries = settings.toolPermissions ?? [];
17445
- 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]);
17446
17720
  const basicExistingEntries = existingEntries.filter((entry) => !isSpecialEntry(entry));
17447
17721
  const generatedKeys = new Set(generated.map((e) => `${e.toolName}|${e.shellInputRegex ?? ""}|${e.permission.type}`));
17448
17722
  const preservedBasicEntries = basicExistingEntries.filter((entry) => {
@@ -17453,7 +17727,11 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17453
17727
  }
17454
17728
  return false;
17455
17729
  });
17456
- const sortedBasic = sortAugmentEntries([...generated, ...preservedBasicEntries]);
17730
+ const sortedBasic = sortAugmentEntries([
17731
+ ...generated,
17732
+ ...preservedBasicEntries,
17733
+ ...authoredBasics
17734
+ ]);
17457
17735
  const merged = {
17458
17736
  ...settings,
17459
17737
  toolPermissions: [...specialEntries, ...sortedBasic]
@@ -17477,11 +17755,14 @@ var AugmentcodePermissions = class AugmentcodePermissions extends ToolPermission
17477
17755
  } catch (error) {
17478
17756
  throw new Error(`Failed to parse AugmentCode permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
17479
17757
  }
17480
- const config = convertAugmentToRulesyncPermissions({
17481
- 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)),
17482
17762
  logger: moduleLogger$1
17483
- });
17484
- 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) });
17485
17766
  }
17486
17767
  validate() {
17487
17768
  try {
@@ -17615,10 +17896,7 @@ function convertAugmentToRulesyncPermissions({ entries, logger }) {
17615
17896
  ]);
17616
17897
  const permission = {};
17617
17898
  for (const entry of entries) {
17618
- if (isSpecialEntry(entry)) {
17619
- 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.`);
17620
- continue;
17621
- }
17899
+ if (isSpecialEntry(entry)) continue;
17622
17900
  const type = entry.permission.type;
17623
17901
  if (!isBasicAugmentType(type)) continue;
17624
17902
  const canonical = toCanonicalToolName$5(entry.toolName);
@@ -18050,6 +18328,13 @@ const WORKSPACE_WIDE_WRITE_PATTERNS = /* @__PURE__ */ new Set([
18050
18328
  ]);
18051
18329
  const CODEX_MINIMAL_KEY = ":minimal";
18052
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
+ ];
18053
18338
  var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18054
18339
  static getSettablePaths(_options = {}) {
18055
18340
  return {
@@ -18091,6 +18376,11 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18091
18376
  });
18092
18377
  parsed.permissions = permissionsTable;
18093
18378
  parsed.default_permissions = RULESYNC_PROFILE_NAME;
18379
+ applyCodexcliOverride({
18380
+ parsed,
18381
+ override: rulesyncPermissions.getJson().codexcli,
18382
+ logger
18383
+ });
18094
18384
  return new CodexcliPermissions({
18095
18385
  outputRoot,
18096
18386
  relativeDirPath: paths.relativeDirPath,
@@ -18115,7 +18405,12 @@ var CodexcliPermissions = class CodexcliPermissions extends ToolPermissions {
18115
18405
  profile,
18116
18406
  domainsHadUnknown
18117
18407
  });
18118
- 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) });
18119
18414
  }
18120
18415
  validate() {
18121
18416
  return {
@@ -18307,6 +18602,30 @@ function toMutableTable(value) {
18307
18602
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
18308
18603
  return { ...value };
18309
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
+ }
18310
18629
  function toFilesystemRecord(value) {
18311
18630
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
18312
18631
  const result = {};
@@ -19221,7 +19540,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19221
19540
  const existingContent = await readFileContentOrNull(filePath) ?? "";
19222
19541
  let parsed;
19223
19542
  try {
19224
- parsed = existingContent.trim() === "" ? {} : load(existingContent);
19543
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
19225
19544
  } catch (error) {
19226
19545
  throw new Error(`Failed to parse existing Goose permission.yaml at ${filePath}: ${formatError(error)}`, { cause: error });
19227
19546
  }
@@ -19243,7 +19562,7 @@ var GoosePermissions = class GoosePermissions extends ToolPermissions {
19243
19562
  let parsed;
19244
19563
  try {
19245
19564
  const content = this.getFileContent();
19246
- parsed = content.trim() === "" ? {} : load(content);
19565
+ parsed = content.trim() === "" ? {} : loadYaml(content);
19247
19566
  } catch (error) {
19248
19567
  throw new Error(`Failed to parse Goose permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
19249
19568
  }
@@ -20150,6 +20469,13 @@ const KiroAgentSchema = z.looseObject({
20150
20469
  toolsSettings: z.optional(z.record(z.string(), z.unknown()))
20151
20470
  });
20152
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
+ ]);
20153
20479
  var KiroPermissions = class KiroPermissions extends ToolPermissions {
20154
20480
  static getSettablePaths(_options = {}) {
20155
20481
  return {
@@ -20213,7 +20539,10 @@ var KiroPermissions = class KiroPermissions extends ToolPermissions {
20213
20539
  const allowedTools = new Set(parsed.allowedTools ?? []);
20214
20540
  if (allowedTools.has("web_fetch")) permission.webfetch = { "*": "allow" };
20215
20541
  if (allowedTools.has("web_search")) permission.websearch = { "*": "allow" };
20216
- 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) });
20217
20546
  }
20218
20547
  validate() {
20219
20548
  return {
@@ -20263,19 +20592,71 @@ function buildKiroPermissionsFromRulesync({ config, logger, existing }) {
20263
20592
  });
20264
20593
  else logger?.warn(`Kiro permissions do not support category: ${category}. Skipping.`);
20265
20594
  }
20266
- nextToolsSettings.shell = shell;
20595
+ nextToolsSettings.shell = {
20596
+ ...preservedShellFlags(existing),
20597
+ ...shell
20598
+ };
20267
20599
  nextToolsSettings.read = pathTable(pathBuckets.read);
20268
20600
  nextToolsSettings.write = pathTable(pathBuckets.write);
20269
20601
  for (const key of ["grep", "glob"]) {
20270
20602
  const bucket = pathBuckets[key];
20271
20603
  if (bucket && (bucket.allow.length > 0 || bucket.deny.length > 0)) nextToolsSettings[key] = pathTable(bucket);
20272
20604
  }
20605
+ applyKiroOverride({
20606
+ override: config.kiro,
20607
+ nextToolsSettings,
20608
+ logger
20609
+ });
20273
20610
  return {
20274
20611
  ...existing,
20275
20612
  allowedTools: [...nextAllowedTools].toSorted(),
20276
20613
  toolsSettings: nextToolsSettings
20277
20614
  };
20278
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
+ }
20279
20660
  function pathTable(bucket) {
20280
20661
  return {
20281
20662
  allowedPaths: bucket?.allow ?? [],
@@ -20295,6 +20676,31 @@ function asRecord$1(value) {
20295
20676
  const result = UnknownRecordSchema.safeParse(value);
20296
20677
  return result.success ? result.data : {};
20297
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
+ }
20298
20704
  function asStringArray(value) {
20299
20705
  return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
20300
20706
  }
@@ -21107,7 +21513,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21107
21513
  const existingContent = await readFileContentOrNull(filePath) ?? "";
21108
21514
  let parsed;
21109
21515
  try {
21110
- parsed = existingContent.trim() === "" ? {} : load(existingContent);
21516
+ parsed = existingContent.trim() === "" ? {} : loadYaml(existingContent);
21111
21517
  } catch (error) {
21112
21518
  throw new Error(`Failed to parse existing Rovodev config at ${filePath}: ${formatError(error)}`, { cause: error });
21113
21519
  }
@@ -21133,7 +21539,7 @@ var RovodevPermissions = class RovodevPermissions extends ToolPermissions {
21133
21539
  let parsed;
21134
21540
  try {
21135
21541
  const content = this.getFileContent();
21136
- parsed = content.trim() === "" ? {} : load(content);
21542
+ parsed = content.trim() === "" ? {} : loadYaml(content);
21137
21543
  } catch (error) {
21138
21544
  throw new Error(`Failed to parse Rovodev permissions content in ${join(this.getRelativeDirPath(), this.getRelativeFilePath())}: ${formatError(error)}`, { cause: error });
21139
21545
  }
@@ -21328,9 +21734,9 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21328
21734
  const rulesyncJson = rulesyncPermissions.getJson();
21329
21735
  const provider = resolveActiveProvider(config);
21330
21736
  const mode = deriveTaktPermissionMode(rulesyncJson);
21331
- const override = isPlainObject(rulesyncJson.takt) ? rulesyncJson.takt : void 0;
21332
- const stepOverrides = isPlainObject(override?.[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? override[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21333
- 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;
21334
21740
  const patch = {
21335
21741
  [TAKT_PROVIDER_PROFILES_KEY]: { [provider]: {
21336
21742
  [TAKT_DEFAULT_PERMISSION_MODE_KEY]: mode,
@@ -21361,12 +21767,12 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21361
21767
  invalidRootPolicy: "error"
21362
21768
  });
21363
21769
  const provider = resolveActiveProvider(config);
21364
- const profiles = isPlainObject(config[TAKT_PROVIDER_PROFILES_KEY]) ? config[TAKT_PROVIDER_PROFILES_KEY] : {};
21365
- 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] : {};
21366
21772
  const mode = profile[TAKT_DEFAULT_PERMISSION_MODE_KEY];
21367
21773
  const rulesyncConfig = taktModeToRulesyncConfig(mode);
21368
- const stepOverrides = isPlainObject(profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY]) ? profile[TAKT_STEP_PERMISSION_OVERRIDES_KEY] : void 0;
21369
- 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;
21370
21776
  const taktOverride = {};
21371
21777
  if (stepOverrides && Object.keys(stepOverrides).length > 0) taktOverride[TAKT_STEP_PERMISSION_OVERRIDES_KEY] = stepOverrides;
21372
21778
  if (providerOptions && Object.keys(providerOptions).length > 0) taktOverride[TAKT_PROVIDER_OPTIONS_KEY] = providerOptions;
@@ -21398,7 +21804,7 @@ var TaktPermissions = class TaktPermissions extends ToolPermissions {
21398
21804
  function resolveActiveProvider(config) {
21399
21805
  if (typeof config[TAKT_PROVIDER_KEY] === "string" && config[TAKT_PROVIDER_KEY].trim() !== "") return config[TAKT_PROVIDER_KEY];
21400
21806
  const profiles = config[TAKT_PROVIDER_PROFILES_KEY];
21401
- if (isPlainObject(profiles)) {
21807
+ if (isPlainObject$1(profiles)) {
21402
21808
  const keys = Object.keys(profiles);
21403
21809
  if (keys.length === 1) return keys[0];
21404
21810
  }
@@ -24387,7 +24793,7 @@ function extractOpenaiYamlFile(otherFiles) {
24387
24793
  const rest = [];
24388
24794
  for (const file of otherFiles) {
24389
24795
  if (toPosixPath(file.relativeFilePathToDirPath) === target) try {
24390
- const loaded = load(file.fileBuffer.toString("utf-8"));
24796
+ const loaded = loadYaml(file.fileBuffer.toString("utf-8"));
24391
24797
  if (loaded !== null && typeof loaded === "object" && !Array.isArray(loaded)) {
24392
24798
  parsed = loaded;
24393
24799
  continue;
@@ -26638,6 +27044,139 @@ var QwencodeSkill = class QwencodeSkill extends ToolSkill {
26638
27044
  }
26639
27045
  };
26640
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
26641
27180
  //#region src/constants/replit-paths.ts
26642
27181
  const REPLIT_RULE_FILE_NAME = "replit.md";
26643
27182
  const REPLIT_SKILLS_DIR_PATH = join(".agents", "skills");
@@ -27732,6 +28271,14 @@ const toolSkillFactories = /* @__PURE__ */ new Map([
27732
28271
  supportsGlobal: true
27733
28272
  }
27734
28273
  }],
28274
+ ["reasonix", {
28275
+ class: ReasonixSkill,
28276
+ meta: {
28277
+ supportsProject: true,
28278
+ supportsSimulated: false,
28279
+ supportsGlobal: true
28280
+ }
28281
+ }],
27735
28282
  ["replit", {
27736
28283
  class: ReplitSkill,
27737
28284
  meta: {
@@ -30015,7 +30562,7 @@ var GooseSubagent = class GooseSubagent extends ToolSubagent {
30015
30562
  const fileContent = await readFileContent(filePath);
30016
30563
  let parsed;
30017
30564
  try {
30018
- parsed = load(fileContent);
30565
+ parsed = loadYaml(fileContent);
30019
30566
  } catch (error) {
30020
30567
  throw new Error(`Failed to parse Goose recipe (${filePath}): ${formatError(error)}`, { cause: error });
30021
30568
  }
@@ -31403,7 +31950,7 @@ var RooSubagent = class RooSubagent extends ToolSubagent {
31403
31950
  const fileContent = await readFileContent(filePath);
31404
31951
  let parsed;
31405
31952
  try {
31406
- parsed = load(fileContent);
31953
+ parsed = loadYaml(fileContent);
31407
31954
  } catch (error) {
31408
31955
  throw new Error(`Failed to parse .roomodes (${filePath}): ${formatError(error)}`, { cause: error });
31409
31956
  }
@@ -35596,6 +36143,72 @@ var QwencodeRule = class QwencodeRule extends ToolRule {
35596
36143
  }
35597
36144
  };
35598
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
35599
36212
  //#region src/features/rules/replit-rule.ts
35600
36213
  /**
35601
36214
  * Rule generator for Replit Agent
@@ -36508,6 +37121,15 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
36508
37121
  additionalConventions: { subagents: { subagentClass: QwencodeSubagent } }
36509
37122
  }
36510
37123
  }],
37124
+ ["reasonix", {
37125
+ class: ReasonixRule,
37126
+ meta: {
37127
+ extension: "md",
37128
+ supportsGlobal: true,
37129
+ ruleDiscoveryMode: "auto",
37130
+ foldsNonRootIntoRoot: true
37131
+ }
37132
+ }],
36511
37133
  ["replit", {
36512
37134
  class: ReplitRule,
36513
37135
  meta: {
@@ -37393,7 +38015,6 @@ const SHARED_WRITE_FEATURE_ORDER = [
37393
38015
  "permissions",
37394
38016
  "rules"
37395
38017
  ];
37396
- const SHARED_WRITE_FEATURES = new Set(SHARED_WRITE_FEATURE_ORDER);
37397
38018
  const TARGETS_NOT_DERIVED = /* @__PURE__ */ new Set(["augmentcode-legacy", "claudecode-legacy"]);
37398
38019
  const sharedFileKey = (path) => {
37399
38020
  const dir = path.relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "");
@@ -37416,7 +38037,13 @@ const settablePathsForScope = (cls, global) => {
37416
38037
  paths.push(settable.root);
37417
38038
  for (const alt of settable.alternativeRoots ?? []) paths.push(alt);
37418
38039
  }
37419
- 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);
37420
38047
  return paths;
37421
38048
  };
37422
38049
  const collectFactoryPaths = (factory) => [...settablePathsForScope(factory.class, false), ...settablePathsForScope(factory.class, true)];
@@ -37429,7 +38056,6 @@ const deriveSharedFileWriters = () => {
37429
38056
  const byKey = /* @__PURE__ */ new Map();
37430
38057
  const pathByKey = /* @__PURE__ */ new Map();
37431
38058
  for (const entry of PROCESSOR_REGISTRY) {
37432
- if (!SHARED_WRITE_FEATURES.has(entry.feature)) continue;
37433
38059
  const factories = entry.factory;
37434
38060
  for (const [tool, factory] of factories) {
37435
38061
  if (TARGETS_NOT_DERIVED.has(tool)) continue;
@@ -37818,7 +38444,7 @@ async function generateRulesCore(params) {
37818
38444
  targets: config.getConfigFileTargets(),
37819
38445
  global: config.getGlobal()
37820
38446
  }) : /* @__PURE__ */ new Map();
37821
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38447
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37822
38448
  if (!config.getFeatures(toolTarget).includes("rules")) continue;
37823
38449
  const processor = new RulesProcessor({
37824
38450
  outputRoot,
@@ -37873,7 +38499,7 @@ async function generateIgnoreCore(params) {
37873
38499
  let hasDiff = false;
37874
38500
  for (const toolTarget of intersection(config.getTargets(), supportedIgnoreTargets)) {
37875
38501
  if (!config.getFeatures(toolTarget).includes("ignore")) continue;
37876
- for (const outputRoot of config.getOutputRoots()) try {
38502
+ for (const outputRoot of config.getOutputRoots(toolTarget)) try {
37877
38503
  const processor = new IgnoreProcessor({
37878
38504
  outputRoot,
37879
38505
  inputRoot: config.getInputRoot(),
@@ -37914,7 +38540,7 @@ async function generateMcpCore(params) {
37914
38540
  featureName: "mcp",
37915
38541
  logger
37916
38542
  });
37917
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38543
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37918
38544
  if (!config.getFeatures(toolTarget).includes("mcp")) continue;
37919
38545
  const processor = new McpProcessor({
37920
38546
  outputRoot,
@@ -37956,7 +38582,7 @@ async function generateCommandsCore(params) {
37956
38582
  featureName: "commands",
37957
38583
  logger
37958
38584
  });
37959
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38585
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
37960
38586
  if (!config.getFeatures(toolTarget).includes("commands")) continue;
37961
38587
  const processor = new CommandsProcessor({
37962
38588
  outputRoot,
@@ -37998,7 +38624,7 @@ async function generateSubagentsCore(params) {
37998
38624
  featureName: "subagents",
37999
38625
  logger
38000
38626
  });
38001
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38627
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38002
38628
  if (!config.getFeatures(toolTarget).includes("subagents")) continue;
38003
38629
  const processor = new SubagentsProcessor({
38004
38630
  outputRoot,
@@ -38041,7 +38667,7 @@ async function generateSkillsCore(params) {
38041
38667
  featureName: "skills",
38042
38668
  logger
38043
38669
  });
38044
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38670
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38045
38671
  if (!config.getFeatures(toolTarget).includes("skills")) continue;
38046
38672
  const processor = new SkillsProcessor({
38047
38673
  outputRoot,
@@ -38082,7 +38708,7 @@ async function generateHooksCore(params) {
38082
38708
  featureName: "hooks",
38083
38709
  logger
38084
38710
  });
38085
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of toolTargets) {
38711
+ for (const toolTarget of toolTargets) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38086
38712
  if (!config.getFeatures(toolTarget).includes("hooks")) continue;
38087
38713
  const processor = new HooksProcessor({
38088
38714
  outputRoot,
@@ -38119,7 +38745,7 @@ async function generatePermissionsCore(params) {
38119
38745
  let totalCount = 0;
38120
38746
  const allPaths = [];
38121
38747
  let hasDiff = false;
38122
- for (const outputRoot of config.getOutputRoots()) for (const toolTarget of intersection(config.getTargets(), supportedPermissionsTargets)) {
38748
+ for (const toolTarget of intersection(config.getTargets(), supportedPermissionsTargets)) for (const outputRoot of config.getOutputRoots(toolTarget)) {
38123
38749
  if (!config.getFeatures(toolTarget).includes("permissions")) continue;
38124
38750
  try {
38125
38751
  const processor = new PermissionsProcessor({
@@ -38395,6 +39021,6 @@ async function importPermissionsCore(params) {
38395
39021
  return writtenCount;
38396
39022
  }
38397
39023
  //#endregion
38398
- 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 };
38399
39025
 
38400
- //# sourceMappingURL=import-BxqZVTKb.js.map
39026
+ //# sourceMappingURL=import-B6nwZmGl.js.map