rulesync 18.0.0 → 20.0.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.
@@ -5254,8 +5254,8 @@ const CONTINUE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANO
5254
5254
  /**
5255
5255
  * Hook events supported by Hermes Agent's native Shell Hooks system.
5256
5256
  *
5257
- * Hermes validates hook events against a fixed `VALID_HOOKS` set — 39 entries as
5258
- * of v0.21.3 (`v2026.9.14`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
5257
+ * Hermes validates hook events against a fixed `VALID_HOOKS` set — 41 entries as
5258
+ * of v0.21.5 (`v2026.9.24`); see {@link HERMESAGENT_NATIVE_HOOK_EVENTS} for the
5259
5259
  * full list. Only the events with a clean 1:1 canonical equivalent are mapped
5260
5260
  * here. All other native events round-trip through `hermesagent.hooks`.
5261
5261
  * @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
@@ -5279,13 +5279,14 @@ const HERMESAGENT_HOOK_EVENTS = [
5279
5279
  * observers, `gateway_platform_event` and `transform_api_error_classification`.
5280
5280
  * Shell hooks are gated on the same set.
5281
5281
  *
5282
- * Verified against the tag `v2026.9.14` (v0.21.3): `VALID_HOOKS` holds 39
5282
+ * Verified against the tag `v2026.9.24` (v0.21.5): `VALID_HOOKS` holds 41
5283
5283
  * entries, grown from 23 at `v2026.8.3` (v0.20.0) and 37 at `v2026.8.16`
5284
- * (v0.20.2) — v0.21 added `agent_loop_stopped` and `on_room_member_activity`.
5285
- * This list holds 38 of them — see
5284
+ * (v0.20.2) — v0.21 added `agent_loop_stopped` and `on_room_member_activity`,
5285
+ * and v0.21.5 added `pre_auxiliary_call` and `post_auxiliary_call`.
5286
+ * This list holds 40 of them — see
5286
5287
  * {@link HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS} for the exclusion.
5287
5288
  *
5288
- * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py
5289
+ * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.24/hermes_cli/plugins.py
5289
5290
  */
5290
5291
  const HERMESAGENT_NATIVE_HOOK_EVENTS = [
5291
5292
  "pre_tool_call",
@@ -5303,6 +5304,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
5303
5304
  "pre_api_request",
5304
5305
  "post_api_request",
5305
5306
  "api_request_error",
5307
+ "pre_auxiliary_call",
5308
+ "post_auxiliary_call",
5306
5309
  "on_session_start",
5307
5310
  "on_session_end",
5308
5311
  "on_session_finalize",
@@ -5337,8 +5340,8 @@ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
5337
5340
  * rulesync only ever writes shell hooks, so these are excluded from
5338
5341
  * {@link HERMESAGENT_NATIVE_HOOK_EVENTS} and warned about with their own message.
5339
5342
  *
5340
- * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
5341
- * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.14/agent/shell_hooks.py — `_parse_hooks_block`
5343
+ * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.24/hermes_cli/plugins.py — `SHELL_UNSUPPORTED_HOOKS`
5344
+ * @see https://github.com/NousResearch/hermes-agent/blob/v2026.9.24/agent/shell_hooks.py — `_parse_hooks_block`
5342
5345
  */
5343
5346
  const HERMESAGENT_SHELL_UNSUPPORTED_HOOK_EVENTS = ["transform_api_error_classification"];
5344
5347
  /**
@@ -26053,7 +26056,7 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
26053
26056
  for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
26054
26057
  if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
26055
26058
  if (HERMESAGENT_SHELL_UNSUPPORTED_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is Python-plugin-only; Hermes Agent refuses it for shell hooks, so the generated entry will never run.`);
26056
- else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.21.3; preserving it for forward compatibility.`);
26059
+ else if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.21.5; preserving it for forward compatibility.`);
26057
26060
  setHermesHookEntries({
26058
26061
  result,
26059
26062
  event: nativeEvent,
@@ -44111,6 +44114,7 @@ var ClinePermissions = class ClinePermissions extends ToolPermissions {
44111
44114
  //#region src/features/permissions/codexcli-permissions.ts
44112
44115
  const RULESYNC_PROFILE_NAME = "rulesync";
44113
44116
  const CODEX_WORKSPACE_ROOTS_KEY = ":workspace_roots";
44117
+ const CODEX_WORKSPACE_ROOT_SUBPATH = ".";
44114
44118
  const CODEX_WORKSPACE_BASELINE = ":workspace";
44115
44119
  const CODEX_READ_ONLY_BASELINE = ":read-only";
44116
44120
  const CODEX_DANGER_FULL_ACCESS_BASELINE = ":danger-full-access";
@@ -44328,10 +44332,19 @@ function convertRulesyncToCodexProfile({ config, logger }) {
44328
44332
  applyDefaultGitWriteRules({
44329
44333
  config,
44330
44334
  filesystem,
44331
- workspaceRootFilesystem
44335
+ workspaceRootFilesystem,
44336
+ logger
44332
44337
  });
44333
44338
  if (Object.keys(workspaceRootFilesystem).length > 0) {
44334
- if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") logger?.warn(`"${CODEX_WORKSPACE_ROOTS_KEY}" is set as a direct filesystem access rule in the permissions, but it will be overwritten by workspace-root rules. Consider removing the direct "${CODEX_WORKSPACE_ROOTS_KEY}" entry.`);
44339
+ const directWorkspaceRootsAccess = filesystem[CODEX_WORKSPACE_ROOTS_KEY];
44340
+ if (isCodexFilesystemAccess(directWorkspaceRootsAccess)) {
44341
+ mergeIntoWorkspaceRootTable({
44342
+ workspaceRootFilesystem,
44343
+ pattern: CODEX_WORKSPACE_ROOT_SUBPATH,
44344
+ access: directWorkspaceRootsAccess
44345
+ });
44346
+ logger?.warn(`"${CODEX_WORKSPACE_ROOTS_KEY}" is set as a direct filesystem access rule in the permissions alongside workspace-relative rules; it is emitted as the "${CODEX_WORKSPACE_ROOT_SUBPATH}" entry of the "${CODEX_WORKSPACE_ROOTS_KEY}" table.`);
44347
+ }
44335
44348
  if (Object.keys(workspaceRootFilesystem).some((pattern) => pattern.includes("**"))) filesystem.glob_scan_max_depth = CODEX_GLOB_SCAN_MAX_DEPTH;
44336
44349
  filesystem[CODEX_WORKSPACE_ROOTS_KEY] = workspaceRootFilesystem;
44337
44350
  }
@@ -44348,9 +44361,13 @@ function convertRulesyncToCodexProfile({ config, logger }) {
44348
44361
  };
44349
44362
  }
44350
44363
  function addCodexFilesystemRules({ categoryRules, filesystem, workspaceRootFilesystem, logger }) {
44351
- for (const rule of mergeFilesystemCategoryRules({
44364
+ const normalizedCategoryRules = normalizeWorkspaceRelativeCategoryRules({
44352
44365
  categoryRules,
44353
44366
  logger
44367
+ });
44368
+ for (const rule of mergeFilesystemCategoryRules({
44369
+ categoryRules: normalizedCategoryRules,
44370
+ logger
44354
44371
  })) {
44355
44372
  const access = normalizeCodexFilesystemAccess({
44356
44373
  pattern: rule.pattern,
@@ -44370,11 +44387,32 @@ function addCodexFilesystemRules({ categoryRules, filesystem, workspaceRootFiles
44370
44387
  });
44371
44388
  }
44372
44389
  }
44373
- function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem }) {
44390
+ function applyDefaultGitWriteRules({ config, filesystem, workspaceRootFilesystem, logger }) {
44374
44391
  if (config.codexcli?.git_write_rules === false) return;
44375
44392
  if (config.codexcli?.base_permission_profile === CODEX_READ_ONLY_BASELINE) return;
44376
44393
  if (typeof filesystem[CODEX_WORKSPACE_ROOTS_KEY] === "string") return;
44377
- for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) workspaceRootFilesystem[pattern] ??= access;
44394
+ for (const [pattern, access] of Object.entries(CODEX_GIT_WRITE_RULES)) {
44395
+ if (workspaceRootFilesystem[pattern] !== void 0) continue;
44396
+ if (isRestrictedInWorkspaceRootTable({
44397
+ pattern,
44398
+ table: workspaceRootFilesystem
44399
+ })) {
44400
+ logger?.warn(`Skipping the default Codex CLI "${pattern}" = "${access}" carve-out: ".git" is covered by a broader workspace-relative deny or read-only rule, which the carve-out would reopen. To keep it, author read and edit allows for "${pattern}"; to silence this warning, set codexcli.git_write_rules to false.`);
44401
+ continue;
44402
+ }
44403
+ workspaceRootFilesystem[pattern] = access;
44404
+ }
44405
+ }
44406
+ function isRestrictedInWorkspaceRootTable({ pattern, table }) {
44407
+ const readRules = {};
44408
+ for (const [tablePattern, access] of Object.entries(table)) {
44409
+ if (tablePattern === pattern) continue;
44410
+ readRules[tablePattern] = access === "write" ? "allow" : "deny";
44411
+ }
44412
+ return isCoveredByReadRestriction({
44413
+ pattern,
44414
+ readRules
44415
+ });
44378
44416
  }
44379
44417
  function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
44380
44418
  const permission = {};
@@ -44388,7 +44426,10 @@ function convertCodexProfileToRulesync({ profile, domainsHadUnknown }) {
44388
44426
  continue;
44389
44427
  }
44390
44428
  if (isCodexFilesystemRuleTable(access)) for (const [nestedPattern, nestedAccess] of Object.entries(access)) {
44391
- if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess) continue;
44429
+ if (pattern === CODEX_WORKSPACE_ROOTS_KEY && CODEX_GIT_WRITE_RULES[nestedPattern] === nestedAccess && !isRestrictedInWorkspaceRootTable({
44430
+ pattern: nestedPattern,
44431
+ table: access
44432
+ })) continue;
44392
44433
  addRulesyncFilesystemRule(permission, nestedPattern, nestedAccess);
44393
44434
  }
44394
44435
  }
@@ -44517,9 +44558,59 @@ function addFilesystemRule({ filesystem, workspaceRootFilesystem, pattern, acces
44517
44558
  filesystem[pattern] = access;
44518
44559
  return;
44519
44560
  }
44520
- workspaceRootFilesystem[pattern] = access;
44561
+ mergeIntoWorkspaceRootTable({
44562
+ workspaceRootFilesystem,
44563
+ pattern,
44564
+ access
44565
+ });
44566
+ }
44567
+ const PERMISSION_ACTION_RESTRICTIVENESS = {
44568
+ deny: 2,
44569
+ ask: 1,
44570
+ allow: 0
44571
+ };
44572
+ function normalizeWorkspaceRelativeCategoryRules({ categoryRules, logger }) {
44573
+ const result = {};
44574
+ for (const category of [
44575
+ "read",
44576
+ "edit",
44577
+ "write"
44578
+ ]) {
44579
+ const rules = categoryRules[category];
44580
+ if (rules === void 0) continue;
44581
+ const normalized = {};
44582
+ for (const [pattern, action] of Object.entries(rules)) {
44583
+ const key = pattern.trim() === "" || canBeCodexFilesystemRoot(pattern) ? pattern : toWorkspaceRootSubpath(pattern);
44584
+ if (key === void 0) {
44585
+ logger?.warn(`Skipping Codex CLI ${category} filesystem entry for workspace-relative pattern "${pattern}": Codex rejects a ":workspace_roots" subpath containing a ".." segment or an empty leading segment and would refuse the whole config, so the rule is dropped entirely — even a deny — and the path is NOT protected by the generated profile. Use a normalized relative path instead.`);
44586
+ continue;
44587
+ }
44588
+ const existing = normalized[key];
44589
+ normalized[key] = existing === void 0 || PERMISSION_ACTION_RESTRICTIVENESS[action] > PERMISSION_ACTION_RESTRICTIVENESS[existing] ? action : existing;
44590
+ }
44591
+ result[category] = normalized;
44592
+ }
44593
+ return result;
44594
+ }
44595
+ function toWorkspaceRootSubpath(pattern) {
44596
+ let subpath = pattern;
44597
+ while (subpath.startsWith("./")) subpath = subpath.slice(2);
44598
+ if (subpath === "" || subpath === "**" && subpath !== pattern) return CODEX_WORKSPACE_ROOT_SUBPATH;
44599
+ if (subpath.startsWith("/") || subpath.split(/[\\/]/).some((segment) => segment === "..")) return;
44600
+ return subpath;
44601
+ }
44602
+ function mergeIntoWorkspaceRootTable({ workspaceRootFilesystem, pattern, access }) {
44603
+ const existing = workspaceRootFilesystem[pattern];
44604
+ workspaceRootFilesystem[pattern] = existing === void 0 ? access : moreRestrictiveCodexAccess({
44605
+ a: existing,
44606
+ b: access
44607
+ });
44521
44608
  }
44522
44609
  function normalizeCodexFilesystemAccess({ pattern, access, writeRestriction, logger }) {
44610
+ if (isWindowsDevicePath(pattern) && !canBeCodexFilesystemRoot(pattern)) {
44611
+ logger?.warn(`Skipping Codex CLI ${access} filesystem entry for Windows device path "${pattern}": Codex cannot express it as a workspace-root entry (it is rejected on Windows and matches nothing elsewhere), so the rule is dropped entirely — even a deny — and the path is NOT protected by the generated profile. Use a plain drive path such as "C:\\..." instead.`);
44612
+ return;
44613
+ }
44523
44614
  if ((access === "read" || access === "write") && isWindowsDevicePath(pattern)) {
44524
44615
  if (writeRestriction !== void 0) {
44525
44616
  logger?.warn(`Codex CLI does not support Windows device paths for read/write access: pattern "${pattern}" will be emitted as "deny" because its write-side ${writeRestriction} restriction cannot be represented as read-only access. Use a plain drive path such as "C:\\..." instead.`);
@@ -44560,7 +44651,9 @@ function addRulesyncFilesystemRule(permission, pattern, access) {
44560
44651
  permission.read ??= {};
44561
44652
  permission.read[pattern] = "allow";
44562
44653
  } else {
44654
+ permission.read ??= {};
44563
44655
  permission.edit ??= {};
44656
+ permission.read[pattern] = "allow";
44564
44657
  permission.edit[pattern] = "allow";
44565
44658
  }
44566
44659
  }
@@ -44696,10 +44789,13 @@ function mapWriteAction(action) {
44696
44789
  * - A write-side non-allow → `"read"` (readable but not writable — exactly
44697
44790
  * what Codex's `"read"` level expresses). An `ask` action is approximated
44698
44791
  * this way because Codex has no path-level write approval.
44699
- * - A write-side-only non-allow (no `read` rule for the same pattern) whose
44700
- * path is covered by a broader `read` deny/ask rule (e.g. `X/**` covering
44701
- * `X/id_rsa`, or `:root`) → `"deny"`. Codex resolves the most specific
44702
- * path, so emitting `"read"` there would re-open reads the user denied.
44792
+ * - A write-side-only rule (no `read` rule for the same pattern) whose path
44793
+ * is covered by a broader `read` deny/ask rule (e.g. `X/**` covering
44794
+ * `X/id_rsa`, or `:root`) → `"deny"`, for a write-side `allow` too (with a
44795
+ * warning). Codex resolves the most specific path, so emitting `"read"` or
44796
+ * `"write"` there would re-open reads the user denied. Only the most
44797
+ * specific covering read rule counts: a narrower `read: allow` between the
44798
+ * broad deny and the path keeps the path readable.
44703
44799
  * - A `read` non-allow on the same pattern → `"deny"` regardless of the
44704
44800
  * write side; a contradictory write-side `allow` (unreadable but writable
44705
44801
  * is not expressible in Codex) is warned about.
@@ -44747,10 +44843,11 @@ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44747
44843
  }
44748
44844
  const writeRestriction = writeAction === "allow" ? void 0 : writeAction;
44749
44845
  const readUnspecified = readAction === void 0;
44750
- if (readUnspecified && writeRestriction !== void 0 && isCoveredByReadRestriction({
44846
+ if (readUnspecified && isCoveredByReadRestriction({
44751
44847
  pattern,
44752
44848
  readRules
44753
44849
  })) {
44850
+ if (writeRestriction === void 0) logger?.warn(`Codex CLI cannot express "writable but not readable": pattern "${pattern}" has a write-side allow but is covered by a broader read deny/ask rule. Emitting "deny".`);
44754
44851
  merged.push({
44755
44852
  pattern,
44756
44853
  access: "deny"
@@ -44767,33 +44864,89 @@ function mergeFilesystemCategoryRules({ categoryRules, logger }) {
44767
44864
  return merged;
44768
44865
  }
44769
44866
  function collapseWriteSideRules(categoryRules) {
44770
- const writeSideRestrictiveness = {
44771
- deny: 2,
44772
- ask: 1,
44773
- allow: 0
44774
- };
44775
44867
  const writeSideRules = {};
44776
44868
  for (const category of ["edit", "write"]) for (const [pattern, action] of Object.entries(categoryRules[category] ?? {})) {
44777
44869
  const existing = writeSideRules[pattern];
44778
- if (existing === void 0 || writeSideRestrictiveness[action] > writeSideRestrictiveness[existing]) writeSideRules[pattern] = action;
44870
+ if (existing === void 0 || PERMISSION_ACTION_RESTRICTIVENESS[action] > PERMISSION_ACTION_RESTRICTIVENESS[existing]) writeSideRules[pattern] = action;
44779
44871
  }
44780
44872
  return writeSideRules;
44781
44873
  }
44782
44874
  function isCoveredByReadRestriction({ pattern, readRules }) {
44783
- const key = stripLeadingDotSlash(pattern);
44784
- return Object.entries(readRules).some(([readPattern, action]) => {
44785
- if (action === "allow" || readPattern === pattern) return false;
44786
- if (readPattern === ":root" || readPattern === "/**") return true;
44787
- if (readPattern === "**") return !canBeCodexFilesystemRoot(pattern);
44788
- const withoutTrailingGlob = readPattern.endsWith("/**") ? readPattern.slice(0, -3) : readPattern;
44789
- if (withoutTrailingGlob.startsWith(":") || hasCodexFilesystemGlob(withoutTrailingGlob)) return false;
44790
- const base = stripTrailingSlash(stripLeadingDotSlash(withoutTrailingGlob));
44791
- if (base === "") return false;
44792
- return key === base || key.startsWith(base.endsWith("/") ? base : `${base}/`);
44793
- });
44875
+ const key = normalizeCoverageKey(pattern);
44876
+ const isWorkspaceRelative = !canBeCodexFilesystemRoot(pattern);
44877
+ const keyHasDotSegment = hasDotPathSegment(pattern);
44878
+ let best;
44879
+ for (const [readPattern, action] of Object.entries(readRules)) {
44880
+ if (readPattern === pattern) continue;
44881
+ const specificity = coverageSpecificity({
44882
+ readPattern,
44883
+ key,
44884
+ isWorkspaceRelative
44885
+ });
44886
+ if (specificity === void 0) continue;
44887
+ const restrictive = action !== "allow";
44888
+ if (!restrictive && isSkippedCodexGrant(readPattern)) continue;
44889
+ if (!restrictive && (keyHasDotSegment || hasDotPathSegment(readPattern))) continue;
44890
+ if (best === void 0 || specificity > best.specificity || specificity === best.specificity && restrictive) best = {
44891
+ specificity,
44892
+ restrictive
44893
+ };
44894
+ }
44895
+ return best?.restrictive ?? false;
44896
+ }
44897
+ function coverageSpecificity({ readPattern, key, isWorkspaceRelative }) {
44898
+ if (readPattern === ":root" || readPattern === "/**") return 0;
44899
+ if (readPattern === CODEX_WORKSPACE_ROOTS_KEY) return isWorkspaceRelative ? 1 : void 0;
44900
+ if (canBeCodexFilesystemRoot(readPattern) === isWorkspaceRelative) return;
44901
+ const normalized = normalizeCoverageKey(readPattern);
44902
+ if (normalized === "**" || normalized === ".") return isWorkspaceRelative ? 1 : void 0;
44903
+ const withoutTrailingGlob = normalized.endsWith("/**") ? normalized.slice(0, -3) : normalized;
44904
+ if (withoutTrailingGlob.startsWith(":") || hasCodexFilesystemGlob(withoutTrailingGlob)) return;
44905
+ const base = stripTrailingSlash(withoutTrailingGlob);
44906
+ if (base === "") return;
44907
+ return key === base || key.startsWith(base.endsWith("/") ? base : `${base}/`) ? 2 + base.length : void 0;
44908
+ }
44909
+ function normalizeCoverageKey(pattern) {
44910
+ const form = toCoverageForm(pattern);
44911
+ return canBeCodexFilesystemRoot(pattern) && !pattern.startsWith(":") ? resolveDotSegments(form) : form;
44912
+ }
44913
+ function resolveDotSegments(path) {
44914
+ const [anchor = "", ...rest] = path.split("/");
44915
+ const stack = [];
44916
+ for (const segment of rest) {
44917
+ if (segment === ".") continue;
44918
+ if (segment === "..") {
44919
+ if (stack.length > 0 && stack[stack.length - 1] !== "..") stack.pop();
44920
+ else if (anchor === "~") stack.push(segment);
44921
+ continue;
44922
+ }
44923
+ stack.push(segment);
44924
+ }
44925
+ const resolved = [anchor, ...stack].join("/");
44926
+ return resolved === "" ? "/" : stripTrailingSlash(resolved);
44927
+ }
44928
+ function toCoverageForm(pattern) {
44929
+ let normalized = /^[A-Za-z]:[\\/]/.test(pattern) ? `${pattern.charAt(0).toLowerCase()}${pattern.slice(1).replaceAll("\\", "/")}` : pattern;
44930
+ while (normalized.startsWith("./")) normalized = normalized.slice(2);
44931
+ normalized = stripTrailingSlash(normalized);
44932
+ return normalized === "" && pattern !== "" ? "." : normalized;
44933
+ }
44934
+ function hasDotPathSegment(pattern) {
44935
+ const form = toCoverageForm(pattern);
44936
+ if (form === ".") return false;
44937
+ return form.split(/[\\/]/).some((segment) => segment === "." || segment === "..");
44794
44938
  }
44795
- function stripLeadingDotSlash(pattern) {
44796
- return pattern.startsWith("./") ? pattern.slice(2) : pattern;
44939
+ function isSkippedCodexGrant(pattern) {
44940
+ return isWindowsDevicePath(pattern) || hasUnsupportedCodexFilesystemGlob(pattern);
44941
+ }
44942
+ function moreRestrictiveCodexAccess({ a, b }) {
44943
+ const rank = {
44944
+ deny: 0,
44945
+ none: 0,
44946
+ read: 1,
44947
+ write: 2
44948
+ };
44949
+ return rank[a] <= rank[b] ? a : b;
44797
44950
  }
44798
44951
  function stripTrailingSlash(pattern) {
44799
44952
  return pattern.length > 1 && pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;