rulesync 43.6.2 → 43.7.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.
@@ -9745,11 +9745,9 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
9745
9745
  * `PERMISSION_OVERRIDE_KEY_ALIASES`) is replaced wholesale, and removed when
9746
9746
  * the import does not produce one: only that tool's import ever writes it, so
9747
9747
  * a setting removed from the tool's own config has to disappear here too.
9748
- * Any other tool's override block the import carries (Hermes restores them
9749
- * from its provenance) merges one level down: an imported field replaces the
9750
- * existing field, fields the import does not name are kept, and a
9751
- * `permission` record inside the block merges per category. Every other
9752
- * top-level key — `$schema` included — is kept unless the import names it.
9748
+ * Every other top-level key — `$schema` included — is kept unless the import
9749
+ * names it, in which case the imported value replaces it. No importer emits
9750
+ * another tool's override block, so those blocks are never touched here.
9753
9751
  */
9754
9752
  static mergeImportedJson({ existing, imported, toolTarget }) {
9755
9753
  const ownOverrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
@@ -9761,14 +9759,7 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
9761
9759
  existing: current,
9762
9760
  imported: value
9763
9761
  });
9764
- else if (isRecord(current) && isRecord(value)) {
9765
- const block = { ...current };
9766
- for (const [field, fieldValue] of Object.entries(value)) block[field] = field === "permission" ? mergePermissionCategories({
9767
- existing: block[field],
9768
- imported: fieldValue
9769
- }) : fieldValue;
9770
- merged[key] = block;
9771
- } else merged[key] = value;
9762
+ else merged[key] = value;
9772
9763
  }
9773
9764
  return merged;
9774
9765
  }
@@ -29131,11 +29122,7 @@ var ZoocodeSkill = class extends RooSkill {
29131
29122
  static toolTarget = "zoocode";
29132
29123
  };
29133
29124
  // src/features/skills/skills-processor.ts
29134
- var skills_processor_exports = /* @__PURE__ */ __exportAll({
29135
- SkillsProcessor: () => SkillsProcessor,
29136
- SkillsProcessorToolTargetSchema: () => SkillsProcessorToolTargetSchema,
29137
- toolSkillFactories: () => toolSkillFactories
29138
- });
29125
+ var skills_processor_exports = /* @__PURE__ */ __exportAll({ toolSkillFactories: () => toolSkillFactories });
29139
29126
  const SkillsProcessorToolTargetSchema = zod_mini.z.enum(skillsProcessorToolTargetTuple);
29140
29127
  /**
29141
29128
  * Factory Map mapping tool targets to their skill factories.
@@ -52429,6 +52416,29 @@ var AmpHooks = class AmpHooks extends ToolHooks {
52429
52416
  });
52430
52417
  }
52431
52418
  };
52419
+ // src/features/hooks/hook-matcher-tool-names.ts
52420
+ const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
52421
+ function isMatcherName(token) {
52422
+ return !token.startsWith("\\") && !token.startsWith("[");
52423
+ }
52424
+ /**
52425
+ * The distinct whole names in a matcher that start with a capital letter, for
52426
+ * adapters whose runtime tool names are all lowercase: after translation, such
52427
+ * a name has no runtime equivalent and can never match.
52428
+ */
52429
+ function capitalizedMatcherNames(matcher) {
52430
+ const names = [...matcher.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
52431
+ return [...new Set(names)];
52432
+ }
52433
+ /**
52434
+ * Rewrites the whole tool names inside a hook `matcher` regex through `names`,
52435
+ * leaving escapes, character classes and unknown names as written. Used by
52436
+ * adapters whose runtime tool names differ from the canonical (Claude Code)
52437
+ * spelling.
52438
+ */
52439
+ function replaceMatcherNames(matcher, names) {
52440
+ return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
52441
+ }
52432
52442
  // src/utils/object.ts
52433
52443
  /**
52434
52444
  * Return a shallow copy of `obj` keeping only the entries whose value is
@@ -54349,6 +54359,48 @@ const ANTIGRAVITY_FLAT_HANDLER_EVENTS = new Set([...ANTIGRAVITY_NO_MATCHER_EVENT
54349
54359
  record: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
54350
54360
  key: event
54351
54361
  }) ?? event));
54362
+ const CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES = {
54363
+ Bash: "run_command",
54364
+ Read: "(?:read_file|view_file)",
54365
+ Write: "(?:write_file|write_to_file)",
54366
+ Edit: "(?:edit_file|replace_file_content)",
54367
+ WebSearch: "search_web"
54368
+ };
54369
+ const ANTIGRAVITY_TO_CANONICAL_TOOL_NAMES = {
54370
+ run_command: "Bash",
54371
+ read_file: "Read",
54372
+ view_file: "Read",
54373
+ write_file: "Write",
54374
+ write_to_file: "Write",
54375
+ edit_file: "Edit",
54376
+ replace_file_content: "Edit",
54377
+ search_web: "WebSearch"
54378
+ };
54379
+ const ANTIGRAVITY_GROUP_TO_CANONICAL_TOOL_NAMES = Object.entries(CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES).filter(([, name]) => name.startsWith("(?:")).map(([canonical, group]) => [group, canonical]);
54380
+ const ANTIGRAVITY_DOCUMENTED_TOOL_CANDIDATES = {
54381
+ Grep: "grep_search",
54382
+ Glob: "find_by_name",
54383
+ WebFetch: "read_url_content",
54384
+ MultiEdit: "multi_replace_file_content"
54385
+ };
54386
+ function canonicalToAntigravityMatcher({ matcher, eventName, warn }) {
54387
+ const translated = replaceMatcherNames(matcher, CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES);
54388
+ const unmatched = capitalizedMatcherNames(translated);
54389
+ if (unmatched.length > 0) {
54390
+ const candidates = unmatched.flatMap((name) => {
54391
+ const candidate = lookupOwn({
54392
+ record: ANTIGRAVITY_DOCUMENTED_TOOL_CANDIDATES,
54393
+ key: name
54394
+ });
54395
+ return candidate ? [`\`${candidate}\` for ${quoteValueForWarning(name)}`] : [];
54396
+ });
54397
+ warn(`Antigravity reports lowercase tool names in the hook payload (\`toolCall.name\`), and ${unmatched.map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} has no verified Antigravity equivalent, so it is written as-is and will likely never match. Use the tool name Antigravity reports instead` + (candidates.length > 0 ? ` (the Antigravity hooks docs list ${candidates.join(", ")}, unverified).` : "."));
54398
+ }
54399
+ return translated;
54400
+ }
54401
+ function antigravityToCanonicalMatcher(matcher) {
54402
+ return replaceMatcherNames(ANTIGRAVITY_GROUP_TO_CANONICAL_TOOL_NAMES.reduce((current, [group, canonical]) => current.split(group).join(canonical), matcher), ANTIGRAVITY_TO_CANONICAL_TOOL_NAMES);
54403
+ }
54352
54404
  /**
54353
54405
  * Antigravity uses a Claude-Code-like `hooks.json`. The `PreToolUse` /
54354
54406
  * `PostToolUse` events use the matcher-based group shape, while `SessionStart`,
@@ -54362,7 +54414,11 @@ const ANTIGRAVITY_CONVERTER_CONFIG = {
54362
54414
  toolToCanonicalEventNames: ANTIGRAVITY_TO_CANONICAL_EVENT_NAMES,
54363
54415
  projectDirVar: "",
54364
54416
  noMatcherEvents: ANTIGRAVITY_NO_MATCHER_EVENTS,
54365
- supportedHookTypes: /* @__PURE__ */ new Set(["command"])
54417
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
54418
+ matcherTranslation: {
54419
+ toTool: canonicalToAntigravityMatcher,
54420
+ toCanonical: antigravityToCanonicalMatcher
54421
+ }
54366
54422
  };
54367
54423
  /**
54368
54424
  * Turn the shared converter's matcher-group output into Antigravity's shape:
@@ -57395,20 +57451,6 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
57395
57451
  }), null, 2) });
57396
57452
  }
57397
57453
  };
57398
- // src/features/hooks/hook-matcher-tool-names.ts
57399
- const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
57400
- function isMatcherName(token) {
57401
- return !token.startsWith("\\") && !token.startsWith("[");
57402
- }
57403
- /**
57404
- * Rewrites the whole tool names inside a hook `matcher` regex through `names`,
57405
- * leaving escapes, character classes and unknown names as written. Used by
57406
- * adapters whose runtime tool names differ from the canonical (Claude Code)
57407
- * spelling.
57408
- */
57409
- function replaceMatcherNames(matcher, names) {
57410
- return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
57411
- }
57412
57454
  // src/features/hooks/cortexcode-hooks.ts
57413
57455
  const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
57414
57456
  const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
@@ -57422,8 +57464,8 @@ const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
57422
57464
  const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
57423
57465
  function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
57424
57466
  const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
57425
- const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
57426
- if (unmatched.length > 0) warn(`Cortex Code matches hook matchers case-sensitively against lowercase runtime tool names, and ${[...new Set(unmatched)].map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} is not a tool Cortex Code documents a runtime ID for, so it is written as-is and will likely never match. Replace it with the lowercase name Cortex Code uses for that tool, if it has one.`);
57467
+ const unmatched = capitalizedMatcherNames(translated);
57468
+ if (unmatched.length > 0) warn(`Cortex Code matches hook matchers case-sensitively against lowercase runtime tool names, and ${unmatched.map((name) => quoteValueForWarning(name)).join(", ")} in the "${eventName}" matcher ${quoteValueForWarning(matcher)} is not a tool Cortex Code documents a runtime ID for, so it is written as-is and will likely never match. Replace it with the lowercase name Cortex Code uses for that tool, if it has one.`);
57427
57469
  return translated;
57428
57470
  }
57429
57471
  function cortexcodeToCanonicalMatcher(matcher) {
@@ -79859,6 +79901,39 @@ const MATCH_ALL_PATH_GLOBS = /* @__PURE__ */ new Set([
79859
79901
  ]);
79860
79902
  const DIRECTORY_CONTENTS_SUFFIX = /\/\*\*(?:\/\*)?$/;
79861
79903
  const HOME_OR_VARIABLE_PATH = /^~(?:\/|$)|^\$[A-Za-z_]|\$\{[A-Za-z_]/;
79904
+ const LEADING_HOME = /^~(?:\/|$)/;
79905
+ /**
79906
+ * The home directory as an Antigravity path: forward slashes, no trailing
79907
+ * slash. agy accepts `C:/Users/...` on Windows, so one spelling serves both.
79908
+ */
79909
+ function toAntigravityHomePath(homeDirectory) {
79910
+ return homeDirectory.replaceAll("\\", "/").replace(/\/+$/, "");
79911
+ }
79912
+ /**
79913
+ * Rewrite a leading `~` or `~/` of a path target to the absolute home
79914
+ * directory. agy 1.5.1 matches `~` literally, so a `~/...` rule would never
79915
+ * apply (rulesync#3324). Only a file the user's own home holds, such as the
79916
+ * global Antigravity CLI settings, may carry a machine-specific path, so the
79917
+ * caller passes `homeDirectory` only then. `$HOME`, `${HOME}`, `%VAR%` and
79918
+ * `~user/` are left as written (the variable forms are still warned about).
79919
+ */
79920
+ function expandLeadingHome(target, homeDirectory) {
79921
+ if (homeDirectory === void 0 || !LEADING_HOME.test(target)) return target;
79922
+ const home = toAntigravityHomePath(homeDirectory);
79923
+ return target === "~" ? home || "/" : `${home}${target.slice(1)}`;
79924
+ }
79925
+ /**
79926
+ * The reverse of {@link expandLeadingHome} on import: an absolute path in the
79927
+ * home directory goes back to `~/...`, so `.rulesync/permissions.jsonc` stays
79928
+ * the same on every machine.
79929
+ */
79930
+ function collapseLeadingHome(target, homeDirectory) {
79931
+ if (homeDirectory === void 0) return target;
79932
+ const home = toAntigravityHomePath(homeDirectory);
79933
+ if (home.length === 0) return target;
79934
+ if (target === home) return "~";
79935
+ return target.startsWith(`${home}/`) ? `~${target.slice(home.length)}` : target;
79936
+ }
79862
79937
  const HOSTNAME = /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/;
79863
79938
  const DENY_HINTS = {
79864
79939
  read_file: "deny a directory or file path instead",
@@ -80011,13 +80086,14 @@ function warnHomeOrVariablePath({ action, target, rule, decision, logger, toolLa
80011
80086
  * nothing enforces it, and a widened one is warned about too, because it
80012
80087
  * blocks more than it says.
80013
80088
  */
80014
- function buildAntigravityPermissionEntry({ action: toolAction, category, pattern, decision, logger, toolLabel }) {
80089
+ function buildAntigravityPermissionEntry({ action: toolAction, category, pattern, decision, logger, toolLabel, homeDirectory }) {
80015
80090
  const isMcp = category.startsWith(MCP_CATEGORY_PREFIX);
80016
80091
  const action = isMcp ? "mcp" : toolAction;
80017
80092
  const result = isMcp ? toMcpRuleTarget(category, pattern) : pattern === "*" ? { target: "*" } : toTarget(action, pattern);
80018
80093
  const rule = `${category}: { "${pattern}": "${decision}" }`;
80019
80094
  if ("skipReason" in result) {
80020
- const prefix = decision !== "deny" ? void 0 : isMcp ? result.lead : toWidenedDenyTarget(action, pattern);
80095
+ const lead = decision !== "deny" ? void 0 : isMcp ? result.lead : toWidenedDenyTarget(action, pattern);
80096
+ const prefix = lead !== void 0 && PATH_ACTIONS.has(action) ? expandLeadingHome(lead, homeDirectory) : lead;
80021
80097
  if (prefix !== void 0) {
80022
80098
  const { entry, scope } = describeWidenedDeny({
80023
80099
  action,
@@ -80043,22 +80119,23 @@ function buildAntigravityPermissionEntry({ action: toolAction, category, pattern
80043
80119
  const consequence = decision === "deny" ? ", so the deny also overrides any allow or ask there" : "";
80044
80120
  logger.warn(`${toolLabel} permissions: ${rule}: ${result.note}${consequence}.`);
80045
80121
  }
80122
+ const target = PATH_ACTIONS.has(action) ? expandLeadingHome(result.target, homeDirectory) : result.target;
80046
80123
  warnHomeOrVariablePath({
80047
80124
  action,
80048
- target: result.target,
80125
+ target,
80049
80126
  rule,
80050
80127
  decision,
80051
80128
  logger,
80052
80129
  toolLabel
80053
80130
  });
80054
- return result.target === "*" ? action : `${action}(${result.target})`;
80131
+ return target === "*" ? action : `${action}(${target})`;
80055
80132
  }
80056
80133
  /**
80057
80134
  * Convert rulesync permissions config to Antigravity allow/ask/deny arrays,
80058
80135
  * shared by the CLI and IDE generators; `toAction` maps a canonical category to
80059
80136
  * the target's tool or action name.
80060
80137
  */
80061
- function convertRulesyncToAntigravityPermissions({ config, toAction, toolLabel, logger }) {
80138
+ function convertRulesyncToAntigravityPermissions({ config, toAction, toolLabel, logger, homeDirectory }) {
80062
80139
  const allow = [];
80063
80140
  const ask = [];
80064
80141
  const deny = [];
@@ -80071,7 +80148,8 @@ function convertRulesyncToAntigravityPermissions({ config, toAction, toolLabel,
80071
80148
  pattern,
80072
80149
  decision: permissionAction,
80073
80150
  logger,
80074
- toolLabel
80151
+ toolLabel,
80152
+ homeDirectory
80075
80153
  });
80076
80154
  if (entry === void 0) continue;
80077
80155
  switch (permissionAction) {
@@ -80096,7 +80174,7 @@ function convertRulesyncToAntigravityPermissions({ config, toAction, toolLabel,
80096
80174
  * shared by the CLI and IDE adapters; `toCategory` maps the target's tool or
80097
80175
  * action name back to a canonical category.
80098
80176
  */
80099
- function convertAntigravityToRulesyncPermissions({ allow, ask, deny, toCategory, toolLabel, logger }) {
80177
+ function convertAntigravityToRulesyncPermissions({ allow, ask, deny, toCategory, toolLabel, logger, homeDirectory }) {
80100
80178
  const permission = {};
80101
80179
  const processEntries = (entries, action) => {
80102
80180
  for (const entry of entries) {
@@ -80108,7 +80186,7 @@ function convertAntigravityToRulesyncPermissions({ allow, ask, deny, toCategory,
80108
80186
  decision: action,
80109
80187
  logger,
80110
80188
  toolLabel
80111
- }) : target;
80189
+ }) : PATH_ACTIONS.has(toolName) ? collapseLeadingHome(target, homeDirectory) : target;
80112
80190
  if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
80113
80191
  getOrCreateOwn({
80114
80192
  record: permission,
@@ -80247,7 +80325,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
80247
80325
  config,
80248
80326
  toAction: toAntigravityCliToolName,
80249
80327
  toolLabel: "Antigravity CLI",
80250
- logger
80328
+ logger,
80329
+ homeDirectory: outputRoot
80251
80330
  });
80252
80331
  const managedToolNames = new Set(Object.keys(config.permission).map((category) => toAntigravityCliToolName(category)));
80253
80332
  const isManagedToolName = (toolName) => managedToolNames.has(toolName) || managedToolNames.has("mcp") && toolName.startsWith("mcp__");
@@ -80298,7 +80377,8 @@ var AntigravityCliPermissions = class AntigravityCliPermissions extends ToolPerm
80298
80377
  deny: permissions.deny ?? [],
80299
80378
  toCategory: toCanonicalToolName$9,
80300
80379
  toolLabel: "Antigravity CLI",
80301
- logger: moduleLogger$4
80380
+ logger: moduleLogger$4,
80381
+ homeDirectory: this.getOutputRoot()
80302
80382
  });
80303
80383
  const override = {};
80304
80384
  const toolPermission = pickDocumentedValue({
@@ -90261,6 +90341,22 @@ function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermis
90261
90341
  return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
90262
90342
  });
90263
90343
  }
90344
+ /** Hermes's override key in the canonical document (`hermes`). */
90345
+ const HERMES_OVERRIDE_KEY = PERMISSION_OVERRIDE_KEY_ALIASES.hermesagent ?? "hermesagent";
90346
+ /**
90347
+ * The part of a canonical permissions document Hermes keeps as provenance: the
90348
+ * `$schema`, the canonical `permission` block, and Hermes's own override.
90349
+ */
90350
+ function hermesProvenanceOf(permissions) {
90351
+ const source = permissions;
90352
+ const provenance = {};
90353
+ for (const key of [
90354
+ "$schema",
90355
+ "permission",
90356
+ HERMES_OVERRIDE_KEY
90357
+ ]) if (source[key] !== void 0) provenance[key] = source[key];
90358
+ return provenance;
90359
+ }
90264
90360
  /** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
90265
90361
  function canonicalPermissionOf(config) {
90266
90362
  const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
@@ -90428,10 +90524,10 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
90428
90524
  patterns: websiteBlocklist.enabled === true && isStringArray(websiteBlocklist.domains) ? websiteBlocklist.domains : []
90429
90525
  });
90430
90526
  removeEmptyCategories(permission);
90431
- const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
90432
90527
  const hermes = buildHermesOverride(config, provenance);
90528
+ const { $schema } = provenance;
90433
90529
  const imported = {
90434
- ...otherProvenance,
90530
+ ...$schema !== void 0 && { $schema },
90435
90531
  permission,
90436
90532
  ...Object.keys(hermes).length > 0 && { hermes }
90437
90533
  };
@@ -90489,7 +90585,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
90489
90585
  base: config,
90490
90586
  patch: permissions.hermes
90491
90587
  });
90492
- config.permissions = { rulesync: permissions };
90588
+ config.permissions = { rulesync: hermesProvenanceOf(permissions) };
90493
90589
  return new HermesagentPermissions({
90494
90590
  outputRoot,
90495
90591
  fileContent: stringifySharedConfig({