rulesync 43.6.1 → 43.6.3

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.
@@ -2269,6 +2269,190 @@ async function removeTempDirectory(tempDir) {
2269
2269
  });
2270
2270
  } catch {}
2271
2271
  }
2272
+ // src/utils/prototype-pollution.ts
2273
+ /**
2274
+ * Keys that, if walked into when constructing or merging objects from
2275
+ * untrusted input, can mutate `Object.prototype` (or otherwise the prototype
2276
+ * chain) and propagate state to every other object in the runtime. Any code
2277
+ * that copies arbitrary user-supplied keys into a fresh object — frontmatter
2278
+ * parsing, MCP config conversion, settings round-trip — should skip these.
2279
+ */
2280
+ const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
2281
+ "__proto__",
2282
+ "constructor",
2283
+ "prototype"
2284
+ ]);
2285
+ function isPrototypePollutionKey(key) {
2286
+ return PROTOTYPE_POLLUTION_KEYS.has(key);
2287
+ }
2288
+ /**
2289
+ * Returns a shallow copy of a record's own entries with every
2290
+ * prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
2291
+ *
2292
+ * Use when copying a nested, user-supplied string map — an MCP server's `env`
2293
+ * or `headers` table — into freshly generated config. Carrying such a map by
2294
+ * reference, or re-assigning its keys via bracket notation, would let a literal
2295
+ * `__proto__` key ride along (and re-assigning it would mutate the target's
2296
+ * prototype). Walking the entries through this helper severs that path while
2297
+ * preserving every legitimate key.
2298
+ */
2299
+ function omitPrototypePollutionKeys(record) {
2300
+ const sanitized = {};
2301
+ for (const [key, value] of Object.entries(record)) {
2302
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
2303
+ sanitized[key] = value;
2304
+ }
2305
+ return sanitized;
2306
+ }
2307
+ /**
2308
+ * Like {@link omitPrototypePollutionKeys}, but recursive: every plain object
2309
+ * reachable through the value (directly, or through an array) is copied with
2310
+ * its prototype-pollution keys dropped. Scalars and arrays of scalars come back
2311
+ * as they are.
2312
+ *
2313
+ * Use when passing an unknown, user-supplied value through unchanged — an MCP
2314
+ * server field a tool documents that rulesync does not model — so a
2315
+ * `__proto__` key nested anywhere inside it cannot ride into the generated
2316
+ * config, where the consuming tool may merge the object without the same care.
2317
+ */
2318
+ function omitPrototypePollutionKeysDeep(value) {
2319
+ if (Array.isArray(value)) return value.map((entry) => omitPrototypePollutionKeysDeep(entry));
2320
+ if (value === null || typeof value !== "object") return value;
2321
+ const sanitized = {};
2322
+ for (const [key, entry] of Object.entries(value)) {
2323
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
2324
+ sanitized[key] = omitPrototypePollutionKeysDeep(entry);
2325
+ }
2326
+ return sanitized;
2327
+ }
2328
+ // src/utils/jsonc.ts
2329
+ /**
2330
+ * Rebuild the parsed value from its own enumerable entries, dropping
2331
+ * prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
2332
+ * `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
2333
+ * literal `__proto__` key would replace the containing object's prototype
2334
+ * instead of becoming an own property; rebuilding gives every object a clean
2335
+ * `Object.prototype` again and severs that path.
2336
+ */
2337
+ function deepSanitize(value) {
2338
+ if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
2339
+ if (value !== null && typeof value === "object") {
2340
+ const sanitized = {};
2341
+ for (const [key, entry] of Object.entries(value)) {
2342
+ if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
2343
+ sanitized[key] = deepSanitize(entry);
2344
+ }
2345
+ return sanitized;
2346
+ }
2347
+ return value;
2348
+ }
2349
+ /**
2350
+ * Collect the paths of every prototype-pollution key in the document, walking
2351
+ * the syntax tree rather than the parsed value.
2352
+ *
2353
+ * The parsed value cannot answer this question for `__proto__`:
2354
+ * `obj["__proto__"] = "deny"` is discarded by the engine outright, and
2355
+ * `obj["__proto__"] = {...}` replaces the prototype instead of adding an own
2356
+ * property — either way the key is already gone by the time
2357
+ * {@link deepSanitize} could look for it, and only the source text still knows
2358
+ * the user wrote it. `constructor` and `prototype` do become own properties and
2359
+ * could be found in the parsed value, but they are collected here too so all
2360
+ * three keys are answered the same way.
2361
+ *
2362
+ * A pollution key's subtree is not descended into, matching
2363
+ * {@link deepSanitize}, which drops the whole value along with the key.
2364
+ */
2365
+ function collectPollutionKeyPaths({ node, path, found }) {
2366
+ if (node.type === "array") {
2367
+ for (const [index, child] of (node.children ?? []).entries()) collectPollutionKeyPaths({
2368
+ node: child,
2369
+ path: `${path}[${index}]`,
2370
+ found
2371
+ });
2372
+ return;
2373
+ }
2374
+ if (node.type !== "object") return;
2375
+ for (const property of node.children ?? []) {
2376
+ const [keyNode, valueNode] = property.children ?? [];
2377
+ if (typeof keyNode?.value !== "string") continue;
2378
+ const keyPath = path === "" ? keyNode.value : `${path}.${keyNode.value}`;
2379
+ if (PROTOTYPE_POLLUTION_KEYS.has(keyNode.value)) {
2380
+ found.push(keyPath);
2381
+ continue;
2382
+ }
2383
+ if (valueNode !== void 0) collectPollutionKeyPaths({
2384
+ node: valueNode,
2385
+ path: keyPath,
2386
+ found
2387
+ });
2388
+ }
2389
+ }
2390
+ /**
2391
+ * Parse a JSONC (JSON with Comments) document strictly.
2392
+ *
2393
+ * Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
2394
+ * errors and returns a best-effort value), this throws on any parse error so
2395
+ * a malformed source file fails loudly instead of generating half-empty tool
2396
+ * configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
2397
+ * `JSON.parse` on files that may contain comments or trailing commas.
2398
+ */
2399
+ function parseJsonc(content) {
2400
+ return deepSanitize(parseStrict(content));
2401
+ }
2402
+ /**
2403
+ * The strict parse shared by both entry points. Kept separate from the syntax
2404
+ * tree walk so the many callers that never look at `droppedKeys` do not pay
2405
+ * for building a second representation of the document.
2406
+ */
2407
+ function parseStrict(content) {
2408
+ const errors = [];
2409
+ const result = parse$1(content, errors, {
2410
+ allowTrailingComma: true,
2411
+ disallowComments: false
2412
+ });
2413
+ if (errors.length > 0) {
2414
+ const details = errors.map((e) => `${printParseErrorCode(e.error)} at offset ${e.offset}`).join(", ");
2415
+ throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
2416
+ }
2417
+ return result;
2418
+ }
2419
+ /**
2420
+ * The error a source document should fail with when
2421
+ * {@link parseJsoncReportingDroppedKeys} reports keys the parser removed.
2422
+ *
2423
+ * Shared by every source that reports them, so the three files a user can
2424
+ * author explain the same removal the same way rather than each inventing its
2425
+ * own wording.
2426
+ */
2427
+ function droppedPollutionKeysError({ sourcePath, droppedKeys }) {
2428
+ return /* @__PURE__ */ new Error(`${JSON.stringify(sourcePath)} uses ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")} as ${droppedKeys.length === 1 ? "a key" : "keys"}. Rulesync removes __proto__, constructor and prototype from every source document it parses, because assigning them would reach the prototype chain instead of the object. They are therefore never written to any tool's config — rename them rather than leaving entries that silently do nothing.`);
2429
+ }
2430
+ /**
2431
+ * The same parse as {@link parseJsonc}, additionally reporting which
2432
+ * prototype-pollution keys were removed, as dotted paths
2433
+ * (`permission.bash.__proto__`).
2434
+ *
2435
+ * The parse itself is identical — the paths only let a caller tell the user
2436
+ * about entries that vanished, which they would otherwise have to notice by
2437
+ * comparing their source file against the generated output.
2438
+ */
2439
+ function parseJsoncReportingDroppedKeys({ content }) {
2440
+ const result = parseStrict(content);
2441
+ const found = [];
2442
+ const tree = parseTree(content, [], {
2443
+ allowTrailingComma: true,
2444
+ disallowComments: false
2445
+ });
2446
+ if (tree !== void 0) collectPollutionKeyPaths({
2447
+ node: tree,
2448
+ path: "",
2449
+ found
2450
+ });
2451
+ return {
2452
+ value: deepSanitize(result),
2453
+ droppedKeys: [...new Set(found)]
2454
+ };
2455
+ }
2272
2456
  // src/types/json-output.ts
2273
2457
  /**
2274
2458
  * Error codes for CLI errors
@@ -3558,10 +3742,37 @@ const getDefaults = () => ({
3558
3742
  inputRoots: void 0,
3559
3743
  sources: []
3560
3744
  });
3745
+ /**
3746
+ * Parse the content of a config file (`rulesync.jsonc` / `rulesync.local.jsonc`)
3747
+ * through the same strict, sanitizing JSONC parser as every other rulesync
3748
+ * source.
3749
+ *
3750
+ * `jsonc-parser`'s bare `parse` recovers from syntax errors without reporting
3751
+ * them, which let a typo (a missing comma, unquoted keys, trailing garbage)
3752
+ * silently drop keys and run rulesync with a different config than the user
3753
+ * wrote. Prototype-pollution keys are rejected rather than silently removed,
3754
+ * matching the hooks, MCP and permissions sources.
3755
+ */
3756
+ function parseConfigFileContent({ content, filePath }) {
3757
+ let result;
3758
+ try {
3759
+ result = parseJsoncReportingDroppedKeys({ content });
3760
+ } catch (error) {
3761
+ const detail = error instanceof Error ? error.message : String(error);
3762
+ throw new Error(`Failed to parse ${JSON.stringify(filePath)}: ${detail}`, { cause: error });
3763
+ }
3764
+ if (result.droppedKeys.length > 0) throw droppedPollutionKeysError({
3765
+ sourcePath: filePath,
3766
+ droppedKeys: result.droppedKeys
3767
+ });
3768
+ return result.value;
3769
+ }
3561
3770
  const loadConfigFromFile = async (filePath) => {
3562
3771
  if (!await fileExists(filePath)) return {};
3563
- const fileContent = await readFileContent(filePath);
3564
- const jsonData = parse$1(fileContent);
3772
+ const jsonData = parseConfigFileContent({
3773
+ content: await readFileContent(filePath),
3774
+ filePath
3775
+ });
3565
3776
  const { $schema: _schema, ...configParams } = ConfigFileSchema.parse(jsonData);
3566
3777
  assertTargetsFeaturesExclusive({
3567
3778
  targets: configParams.targets,
@@ -4174,62 +4385,6 @@ function createBoundedWalk({ subject, limits, root }) {
4174
4385
  leave
4175
4386
  };
4176
4387
  }
4177
- // src/utils/prototype-pollution.ts
4178
- /**
4179
- * Keys that, if walked into when constructing or merging objects from
4180
- * untrusted input, can mutate `Object.prototype` (or otherwise the prototype
4181
- * chain) and propagate state to every other object in the runtime. Any code
4182
- * that copies arbitrary user-supplied keys into a fresh object — frontmatter
4183
- * parsing, MCP config conversion, settings round-trip — should skip these.
4184
- */
4185
- const PROTOTYPE_POLLUTION_KEYS = /* @__PURE__ */ new Set([
4186
- "__proto__",
4187
- "constructor",
4188
- "prototype"
4189
- ]);
4190
- function isPrototypePollutionKey(key) {
4191
- return PROTOTYPE_POLLUTION_KEYS.has(key);
4192
- }
4193
- /**
4194
- * Returns a shallow copy of a record's own entries with every
4195
- * prototype-pollution key (`__proto__`, `constructor`, `prototype`) dropped.
4196
- *
4197
- * Use when copying a nested, user-supplied string map — an MCP server's `env`
4198
- * or `headers` table — into freshly generated config. Carrying such a map by
4199
- * reference, or re-assigning its keys via bracket notation, would let a literal
4200
- * `__proto__` key ride along (and re-assigning it would mutate the target's
4201
- * prototype). Walking the entries through this helper severs that path while
4202
- * preserving every legitimate key.
4203
- */
4204
- function omitPrototypePollutionKeys(record) {
4205
- const sanitized = {};
4206
- for (const [key, value] of Object.entries(record)) {
4207
- if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4208
- sanitized[key] = value;
4209
- }
4210
- return sanitized;
4211
- }
4212
- /**
4213
- * Like {@link omitPrototypePollutionKeys}, but recursive: every plain object
4214
- * reachable through the value (directly, or through an array) is copied with
4215
- * its prototype-pollution keys dropped. Scalars and arrays of scalars come back
4216
- * as they are.
4217
- *
4218
- * Use when passing an unknown, user-supplied value through unchanged — an MCP
4219
- * server field a tool documents that rulesync does not model — so a
4220
- * `__proto__` key nested anywhere inside it cannot ride into the generated
4221
- * config, where the consuming tool may merge the object without the same care.
4222
- */
4223
- function omitPrototypePollutionKeysDeep(value) {
4224
- if (Array.isArray(value)) return value.map((entry) => omitPrototypePollutionKeysDeep(entry));
4225
- if (value === null || typeof value !== "object") return value;
4226
- const sanitized = {};
4227
- for (const [key, entry] of Object.entries(value)) {
4228
- if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
4229
- sanitized[key] = omitPrototypePollutionKeysDeep(entry);
4230
- }
4231
- return sanitized;
4232
- }
4233
4388
  // src/utils/type-guards.ts
4234
4389
  /**
4235
4390
  * Type guard to check if a value is a plain object (Record<string, unknown>).
@@ -4857,6 +5012,24 @@ function lookupOwn({ record, key }) {
4857
5012
  return Object.hasOwn(record, key) ? record[key] : void 0;
4858
5013
  }
4859
5014
  /**
5015
+ * Return the value a map holds under its own `key`, first storing `create()`
5016
+ * there when it has none. A bracket read would find an inherited member for a
5017
+ * key such as `toString`, so an importer bucketing rules by a user-supplied
5018
+ * name would write into an `Object.prototype` function instead of a fresh
5019
+ * bucket.
5020
+ */
5021
+ function getOrCreateOwn({ record, key, create }) {
5022
+ if (Object.hasOwn(record, key)) return record[key];
5023
+ const created = create();
5024
+ Object.defineProperty(record, key, {
5025
+ value: created,
5026
+ writable: true,
5027
+ enumerable: true,
5028
+ configurable: true
5029
+ });
5030
+ return created;
5031
+ }
5032
+ /**
4860
5033
  * Swap the keys and values of a name map, e.g. a canonical-to-tool map into
4861
5034
  * the tool-to-canonical map an importer reads. When two keys share a value,
4862
5035
  * the later key wins.
@@ -6391,14 +6564,16 @@ const CANONICAL_TO_GOOSE_EVENT_NAMES = {
6391
6564
  * Hook events supported by Goose.
6392
6565
  *
6393
6566
  * Goose adopts the Open Plugins hooks spec: each plugin's `hooks/hooks.json`
6394
- * maps PascalCase event names to matcher/handler arrays. Every Goose event has a
6395
- * 1:1 canonical equivalent, so no new canonical events are required.
6396
- *
6397
- * Goose's `HookEvent` enum defines exactly these 11 events (v1.41.0). Notably it
6398
- * has NO `SubagentStart`/`SubagentStop` arms — emitting them would write keys
6399
- * Goose silently ignores, so `subagentStart`/`subagentStop` are intentionally
6567
+ * maps PascalCase event names to matcher/handler arrays.
6568
+ *
6569
+ * Goose's `HookEvent` enum defines these 11 events plus `PreToolUseResult`
6570
+ * (added in v1.48.0), which has no canonical equivalent: it is authored under
6571
+ * the `goose.hooks` override block by its native name, emitted verbatim, and
6572
+ * imported back into that block. Notably the enum has NO
6573
+ * `SubagentStart`/`SubagentStop` arms — emitting them would write keys Goose
6574
+ * silently ignores, so `subagentStart`/`subagentStop` are intentionally
6400
6575
  * excluded here and from `CANONICAL_TO_GOOSE_EVENT_NAMES`.
6401
- * @see https://github.com/aaif-goose/goose/blob/v1.41.0/crates/goose/src/hooks/mod.rs
6576
+ * @see https://github.com/aaif-goose/goose/blob/v1.48.0/crates/goose/src/hooks/mod.rs
6402
6577
  * @see https://goose-docs.ai/docs/guides/context-engineering/hooks/
6403
6578
  */
6404
6579
  const GOOSE_HOOK_EVENTS = eventsOf(CANONICAL_TO_GOOSE_EVENT_NAMES);
@@ -6876,134 +7051,6 @@ const GROKCLI_HOOK_EVENTS = eventsOf(CANONICAL_TO_GROKCLI_EVENT_NAMES);
6876
7051
  * Map Grok CLI PascalCase event names to canonical camelCase.
6877
7052
  */
6878
7053
  const GROKCLI_TO_CANONICAL_EVENT_NAMES = invertRecord(CANONICAL_TO_GROKCLI_EVENT_NAMES);
6879
- // src/utils/jsonc.ts
6880
- /**
6881
- * Rebuild the parsed value from its own enumerable entries, dropping
6882
- * prototype-pollution keys (`__proto__`, `constructor`, `prototype`).
6883
- * `jsonc-parser` assigns keys with plain `obj[key] = value` semantics, so a
6884
- * literal `__proto__` key would replace the containing object's prototype
6885
- * instead of becoming an own property; rebuilding gives every object a clean
6886
- * `Object.prototype` again and severs that path.
6887
- */
6888
- function deepSanitize(value) {
6889
- if (Array.isArray(value)) return value.map((item) => deepSanitize(item));
6890
- if (value !== null && typeof value === "object") {
6891
- const sanitized = {};
6892
- for (const [key, entry] of Object.entries(value)) {
6893
- if (PROTOTYPE_POLLUTION_KEYS.has(key)) continue;
6894
- sanitized[key] = deepSanitize(entry);
6895
- }
6896
- return sanitized;
6897
- }
6898
- return value;
6899
- }
6900
- /**
6901
- * Collect the paths of every prototype-pollution key in the document, walking
6902
- * the syntax tree rather than the parsed value.
6903
- *
6904
- * The parsed value cannot answer this question for `__proto__`:
6905
- * `obj["__proto__"] = "deny"` is discarded by the engine outright, and
6906
- * `obj["__proto__"] = {...}` replaces the prototype instead of adding an own
6907
- * property — either way the key is already gone by the time
6908
- * {@link deepSanitize} could look for it, and only the source text still knows
6909
- * the user wrote it. `constructor` and `prototype` do become own properties and
6910
- * could be found in the parsed value, but they are collected here too so all
6911
- * three keys are answered the same way.
6912
- *
6913
- * A pollution key's subtree is not descended into, matching
6914
- * {@link deepSanitize}, which drops the whole value along with the key.
6915
- */
6916
- function collectPollutionKeyPaths({ node, path, found }) {
6917
- if (node.type === "array") {
6918
- for (const [index, child] of (node.children ?? []).entries()) collectPollutionKeyPaths({
6919
- node: child,
6920
- path: `${path}[${index}]`,
6921
- found
6922
- });
6923
- return;
6924
- }
6925
- if (node.type !== "object") return;
6926
- for (const property of node.children ?? []) {
6927
- const [keyNode, valueNode] = property.children ?? [];
6928
- if (typeof keyNode?.value !== "string") continue;
6929
- const keyPath = path === "" ? keyNode.value : `${path}.${keyNode.value}`;
6930
- if (PROTOTYPE_POLLUTION_KEYS.has(keyNode.value)) {
6931
- found.push(keyPath);
6932
- continue;
6933
- }
6934
- if (valueNode !== void 0) collectPollutionKeyPaths({
6935
- node: valueNode,
6936
- path: keyPath,
6937
- found
6938
- });
6939
- }
6940
- }
6941
- /**
6942
- * Parse a JSONC (JSON with Comments) document strictly.
6943
- *
6944
- * Unlike `jsonc-parser`'s bare `parse` (which silently tolerates syntax
6945
- * errors and returns a best-effort value), this throws on any parse error so
6946
- * a malformed source file fails loudly instead of generating half-empty tool
6947
- * configs. Plain JSON is valid JSONC, so this is a drop-in replacement for
6948
- * `JSON.parse` on files that may contain comments or trailing commas.
6949
- */
6950
- function parseJsonc(content) {
6951
- return deepSanitize(parseStrict(content));
6952
- }
6953
- /**
6954
- * The strict parse shared by both entry points. Kept separate from the syntax
6955
- * tree walk so the many callers that never look at `droppedKeys` do not pay
6956
- * for building a second representation of the document.
6957
- */
6958
- function parseStrict(content) {
6959
- const errors = [];
6960
- const result = parse$1(content, errors, {
6961
- allowTrailingComma: true,
6962
- disallowComments: false
6963
- });
6964
- if (errors.length > 0) {
6965
- const details = errors.map((e) => `${printParseErrorCode(e.error)} at offset ${e.offset}`).join(", ");
6966
- throw new SyntaxError(`Failed to parse JSONC content: ${details}`);
6967
- }
6968
- return result;
6969
- }
6970
- /**
6971
- * The error a source document should fail with when
6972
- * {@link parseJsoncReportingDroppedKeys} reports keys the parser removed.
6973
- *
6974
- * Shared by every source that reports them, so the three files a user can
6975
- * author explain the same removal the same way rather than each inventing its
6976
- * own wording.
6977
- */
6978
- function droppedPollutionKeysError({ sourcePath, droppedKeys }) {
6979
- return /* @__PURE__ */ new Error(`${JSON.stringify(sourcePath)} uses ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")} as ${droppedKeys.length === 1 ? "a key" : "keys"}. Rulesync removes __proto__, constructor and prototype from every source document it parses, because assigning them would reach the prototype chain instead of the object. They are therefore never written to any tool's config — rename them rather than leaving entries that silently do nothing.`);
6980
- }
6981
- /**
6982
- * The same parse as {@link parseJsonc}, additionally reporting which
6983
- * prototype-pollution keys were removed, as dotted paths
6984
- * (`permission.bash.__proto__`).
6985
- *
6986
- * The parse itself is identical — the paths only let a caller tell the user
6987
- * about entries that vanished, which they would otherwise have to notice by
6988
- * comparing their source file against the generated output.
6989
- */
6990
- function parseJsoncReportingDroppedKeys({ content }) {
6991
- const result = parseStrict(content);
6992
- const found = [];
6993
- const tree = parseTree(content, [], {
6994
- allowTrailingComma: true,
6995
- disallowComments: false
6996
- });
6997
- if (tree !== void 0) collectPollutionKeyPaths({
6998
- node: tree,
6999
- path: "",
7000
- found
7001
- });
7002
- return {
7003
- value: deepSanitize(result),
7004
- droppedKeys: [...new Set(found)]
7005
- };
7006
- }
7007
7054
  // src/utils/rulesync-source-path.ts
7008
7055
  /**
7009
7056
  * No `.rulesync/` source file exists for a feature at any of its candidate
@@ -9676,11 +9723,9 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
9676
9723
  * `PERMISSION_OVERRIDE_KEY_ALIASES`) is replaced wholesale, and removed when
9677
9724
  * the import does not produce one: only that tool's import ever writes it, so
9678
9725
  * a setting removed from the tool's own config has to disappear here too.
9679
- * Any other tool's override block the import carries (Hermes restores them
9680
- * from its provenance) merges one level down: an imported field replaces the
9681
- * existing field, fields the import does not name are kept, and a
9682
- * `permission` record inside the block merges per category. Every other
9683
- * top-level key — `$schema` included — is kept unless the import names it.
9726
+ * Every other top-level key — `$schema` included — is kept unless the import
9727
+ * names it, in which case the imported value replaces it. No importer emits
9728
+ * another tool's override block, so those blocks are never touched here.
9684
9729
  */
9685
9730
  static mergeImportedJson({ existing, imported, toolTarget }) {
9686
9731
  const ownOverrideKey = PERMISSION_OVERRIDE_KEY_ALIASES[toolTarget] ?? toolTarget;
@@ -9692,14 +9737,7 @@ var RulesyncPermissions = class RulesyncPermissions extends RulesyncFile {
9692
9737
  existing: current,
9693
9738
  imported: value
9694
9739
  });
9695
- else if (isRecord(current) && isRecord(value)) {
9696
- const block = { ...current };
9697
- for (const [field, fieldValue] of Object.entries(value)) block[field] = field === "permission" ? mergePermissionCategories({
9698
- existing: block[field],
9699
- imported: fieldValue
9700
- }) : fieldValue;
9701
- merged[key] = block;
9702
- } else merged[key] = value;
9740
+ else merged[key] = value;
9703
9741
  }
9704
9742
  return merged;
9705
9743
  }
@@ -19558,9 +19596,6 @@ var KiloCommand = class extends FrontmatterMarkdownToolCommand {
19558
19596
  static getFrontmatterSchema() {
19559
19597
  return KiloCommandFrontmatterSchema;
19560
19598
  }
19561
- getRulesyncOutputRoot() {
19562
- return process.cwd();
19563
- }
19564
19599
  /**
19565
19600
  * Refuse to write a command Kilo reserves (see
19566
19601
  * {@link KILO_RESERVED_COMMAND_NAMES}). The name Kilo loads is the file
@@ -20849,9 +20884,6 @@ var OpenCodeCommand = class extends FrontmatterMarkdownToolCommand {
20849
20884
  ...Object.keys(restFields).length > 0 && { [toolTarget]: restFields }
20850
20885
  };
20851
20886
  }
20852
- getRulesyncOutputRoot() {
20853
- return process.cwd();
20854
- }
20855
20887
  /**
20856
20888
  * Imports commands defined inline in `opencode.json` / `opencode.jsonc` (and,
20857
20889
  * at project scope, `.opencode/opencode.json(c)`, which OpenCode merges over
@@ -52366,6 +52398,29 @@ var AmpHooks = class AmpHooks extends ToolHooks {
52366
52398
  });
52367
52399
  }
52368
52400
  };
52401
+ // src/features/hooks/hook-matcher-tool-names.ts
52402
+ const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
52403
+ function isMatcherName(token) {
52404
+ return !token.startsWith("\\") && !token.startsWith("[");
52405
+ }
52406
+ /**
52407
+ * The distinct whole names in a matcher that start with a capital letter, for
52408
+ * adapters whose runtime tool names are all lowercase: after translation, such
52409
+ * a name has no runtime equivalent and can never match.
52410
+ */
52411
+ function capitalizedMatcherNames(matcher) {
52412
+ const names = [...matcher.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
52413
+ return [...new Set(names)];
52414
+ }
52415
+ /**
52416
+ * Rewrites the whole tool names inside a hook `matcher` regex through `names`,
52417
+ * leaving escapes, character classes and unknown names as written. Used by
52418
+ * adapters whose runtime tool names differ from the canonical (Claude Code)
52419
+ * spelling.
52420
+ */
52421
+ function replaceMatcherNames(matcher, names) {
52422
+ return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
52423
+ }
52369
52424
  // src/utils/object.ts
52370
52425
  /**
52371
52426
  * Return a shallow copy of `obj` keeping only the entries whose value is
@@ -54286,6 +54341,48 @@ const ANTIGRAVITY_FLAT_HANDLER_EVENTS = new Set([...ANTIGRAVITY_NO_MATCHER_EVENT
54286
54341
  record: CANONICAL_TO_ANTIGRAVITY_EVENT_NAMES,
54287
54342
  key: event
54288
54343
  }) ?? event));
54344
+ const CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES = {
54345
+ Bash: "run_command",
54346
+ Read: "(?:read_file|view_file)",
54347
+ Write: "(?:write_file|write_to_file)",
54348
+ Edit: "(?:edit_file|replace_file_content)",
54349
+ WebSearch: "search_web"
54350
+ };
54351
+ const ANTIGRAVITY_TO_CANONICAL_TOOL_NAMES = {
54352
+ run_command: "Bash",
54353
+ read_file: "Read",
54354
+ view_file: "Read",
54355
+ write_file: "Write",
54356
+ write_to_file: "Write",
54357
+ edit_file: "Edit",
54358
+ replace_file_content: "Edit",
54359
+ search_web: "WebSearch"
54360
+ };
54361
+ const ANTIGRAVITY_GROUP_TO_CANONICAL_TOOL_NAMES = Object.entries(CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES).filter(([, name]) => name.startsWith("(?:")).map(([canonical, group]) => [group, canonical]);
54362
+ const ANTIGRAVITY_DOCUMENTED_TOOL_CANDIDATES = {
54363
+ Grep: "grep_search",
54364
+ Glob: "find_by_name",
54365
+ WebFetch: "read_url_content",
54366
+ MultiEdit: "multi_replace_file_content"
54367
+ };
54368
+ function canonicalToAntigravityMatcher({ matcher, eventName, warn }) {
54369
+ const translated = replaceMatcherNames(matcher, CANONICAL_TO_ANTIGRAVITY_TOOL_NAMES);
54370
+ const unmatched = capitalizedMatcherNames(translated);
54371
+ if (unmatched.length > 0) {
54372
+ const candidates = unmatched.flatMap((name) => {
54373
+ const candidate = lookupOwn({
54374
+ record: ANTIGRAVITY_DOCUMENTED_TOOL_CANDIDATES,
54375
+ key: name
54376
+ });
54377
+ return candidate ? [`\`${candidate}\` for ${quoteValueForWarning(name)}`] : [];
54378
+ });
54379
+ 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).` : "."));
54380
+ }
54381
+ return translated;
54382
+ }
54383
+ function antigravityToCanonicalMatcher(matcher) {
54384
+ return replaceMatcherNames(ANTIGRAVITY_GROUP_TO_CANONICAL_TOOL_NAMES.reduce((current, [group, canonical]) => current.split(group).join(canonical), matcher), ANTIGRAVITY_TO_CANONICAL_TOOL_NAMES);
54385
+ }
54289
54386
  /**
54290
54387
  * Antigravity uses a Claude-Code-like `hooks.json`. The `PreToolUse` /
54291
54388
  * `PostToolUse` events use the matcher-based group shape, while `SessionStart`,
@@ -54299,7 +54396,11 @@ const ANTIGRAVITY_CONVERTER_CONFIG = {
54299
54396
  toolToCanonicalEventNames: ANTIGRAVITY_TO_CANONICAL_EVENT_NAMES,
54300
54397
  projectDirVar: "",
54301
54398
  noMatcherEvents: ANTIGRAVITY_NO_MATCHER_EVENTS,
54302
- supportedHookTypes: /* @__PURE__ */ new Set(["command"])
54399
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
54400
+ matcherTranslation: {
54401
+ toTool: canonicalToAntigravityMatcher,
54402
+ toCanonical: antigravityToCanonicalMatcher
54403
+ }
54303
54404
  };
54304
54405
  /**
54305
54406
  * Turn the shared converter's matcher-group output into Antigravity's shape:
@@ -57332,20 +57433,6 @@ var CopilotcliHooks = class CopilotcliHooks extends ToolHooks {
57332
57433
  }), null, 2) });
57333
57434
  }
57334
57435
  };
57335
- // src/features/hooks/hook-matcher-tool-names.ts
57336
- const MATCHER_TOKEN_PATTERN = /\\.|\[(?:\\.|[^\]\\])*\]|(?<=^|[^\w\\]|\\\w)[A-Za-z_]\w*(?!\w)/gs;
57337
- function isMatcherName(token) {
57338
- return !token.startsWith("\\") && !token.startsWith("[");
57339
- }
57340
- /**
57341
- * Rewrites the whole tool names inside a hook `matcher` regex through `names`,
57342
- * leaving escapes, character classes and unknown names as written. Used by
57343
- * adapters whose runtime tool names differ from the canonical (Claude Code)
57344
- * spelling.
57345
- */
57346
- function replaceMatcherNames(matcher, names) {
57347
- return matcher.replace(MATCHER_TOKEN_PATTERN, (token) => isMatcherName(token) && Object.hasOwn(names, token) ? names[token] ?? token : token);
57348
- }
57349
57436
  // src/features/hooks/cortexcode-hooks.ts
57350
57437
  const CORTEXCODE_NO_MATCHER_EVENTS = /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]);
57351
57438
  const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
@@ -57359,8 +57446,8 @@ const CANONICAL_TO_CORTEXCODE_TOOL_NAMES = {
57359
57446
  const CORTEXCODE_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
57360
57447
  function canonicalToCortexcodeMatcher({ matcher, eventName, warn }) {
57361
57448
  const translated = replaceMatcherNames(matcher, CANONICAL_TO_CORTEXCODE_TOOL_NAMES);
57362
- const unmatched = [...translated.matchAll(MATCHER_TOKEN_PATTERN)].map(([token]) => token).filter((token) => isMatcherName(token) && /^[A-Z]/.test(token));
57363
- 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.`);
57449
+ const unmatched = capitalizedMatcherNames(translated);
57450
+ 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.`);
57364
57451
  return translated;
57365
57452
  }
57366
57453
  function cortexcodeToCanonicalMatcher(matcher) {
@@ -58608,12 +58695,11 @@ var GooseHooks = class GooseHooks extends ToolHooks {
58608
58695
  });
58609
58696
  }
58610
58697
  toRulesyncHooks({ logger } = {}) {
58611
- const parsed = this.parseFileContentOrThrow({
58612
- displayName: "Goose",
58613
- parse: (fileContent) => JSON.parse(fileContent)
58614
- });
58615
58698
  const hooks = toolHooksToCanonical({
58616
- hooks: parsed.hooks && typeof parsed.hooks === "object" && !Array.isArray(parsed.hooks) ? Object.fromEntries(Object.entries(parsed.hooks).filter(([eventName]) => Object.hasOwn(GOOSE_TO_CANONICAL_EVENT_NAMES, eventName))) : parsed.hooks,
58699
+ hooks: this.parseFileContentOrThrow({
58700
+ displayName: "Goose",
58701
+ parse: (fileContent) => JSON.parse(fileContent)
58702
+ }).hooks,
58617
58703
  converterConfig: GOOSE_CONVERTER_CONFIG,
58618
58704
  logger
58619
58705
  });
@@ -61880,12 +61966,14 @@ var ReasonixHooks = class ReasonixHooks extends ToolHooks {
61880
61966
  const paths = ReasonixHooks.getSettablePaths({ global });
61881
61967
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
61882
61968
  const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
61883
- let settings;
61969
+ let parsed;
61884
61970
  try {
61885
- settings = JSON.parse(existingContent);
61971
+ parsed = JSON.parse(existingContent);
61886
61972
  } catch (error) {
61887
61973
  throw new Error(`Failed to parse existing Reasonix settings at ${filePath}: ${formatError(error)}`, { cause: error });
61888
61974
  }
61975
+ if (!isPlainObject(parsed)) throw new Error(`Failed to parse existing Reasonix settings at ${filePath}: expected a mapping at the root`);
61976
+ const settings = parsed;
61889
61977
  const config = rulesyncHooks.getJson();
61890
61978
  const reasonixHooks = canonicalToReasonixHooks({
61891
61979
  config,
@@ -62844,15 +62932,18 @@ function emittedEventsWithHook(params) {
62844
62932
  }
62845
62933
  /**
62846
62934
  * The capabilities an adapter built on the shared hooks converter already
62847
- * declares in its converter config: the events it emits and the hook types it
62848
- * keeps. Reading them from there keeps the processor's skip warnings in step
62849
- * with what the converter actually writes.
62935
+ * declares in its converter config: the events it emits, the hook types it
62936
+ * keeps, and whether it writes the per-hook `enabled` flag. Reading them from
62937
+ * there keeps the processor's skip and dropped-field warnings in step with
62938
+ * what the converter actually writes.
62850
62939
  */
62851
62940
  function converterCapabilities(config) {
62852
62941
  if (!config.supportedHookTypes) throw new Error("Hooks converter config must declare its supportedHookTypes");
62942
+ const passesThroughEnabled = (config.booleanPassthroughFields ?? []).some((field) => field.canonical === "enabled");
62853
62943
  return {
62854
62944
  supportedEvents: config.supportedEvents,
62855
- supportedHookTypes: [...config.supportedHookTypes]
62945
+ supportedHookTypes: [...config.supportedHookTypes],
62946
+ ...passesThroughEnabled ? { supportsPerHookEnabled: true } : {}
62856
62947
  };
62857
62948
  }
62858
62949
  const toolHooksFactories = /* @__PURE__ */ new Map([
@@ -63474,8 +63565,7 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
63474
63565
  supportsImport: true
63475
63566
  },
63476
63567
  ...converterCapabilities(ZCODE_CONVERTER_CONFIG),
63477
- supportsMatcher: true,
63478
- supportsPerHookEnabled: true
63568
+ supportsMatcher: true
63479
63569
  }],
63480
63570
  ["zcode-plugin", {
63481
63571
  class: ZcodePluginHooks,
@@ -63485,10 +63575,12 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
63485
63575
  supportsImport: true
63486
63576
  },
63487
63577
  ...converterCapabilities(ZCODE_PLUGIN_CONVERTER_CONFIG),
63488
- supportsMatcher: true,
63489
- supportsPerHookEnabled: true
63578
+ supportsMatcher: true
63490
63579
  }]
63491
63580
  ]);
63581
+ function targetsSupporting(capability) {
63582
+ return [...toolHooksFactories.entries()].filter(([, f]) => f[capability]).map(([t]) => t).join(" / ");
63583
+ }
63492
63584
  const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
63493
63585
  const hooksProcessorToolTargetsGlobal = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsGlobal).map(([t]) => t);
63494
63586
  const hooksProcessorToolTargetsImportable = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject && f.meta.supportsImport).map(([t]) => t);
@@ -63649,7 +63741,7 @@ var HooksProcessor = class extends FeatureProcessor {
63649
63741
  effectiveHooks,
63650
63742
  predicate: (def) => def.enabled === false
63651
63743
  });
63652
- if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide / zcode hooks formats support the flag): ${eventsWithDisabledHooks.join(", ")}`);
63744
+ if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the ${targetsSupporting("supportsPerHookEnabled")} hooks formats support the flag): ${eventsWithDisabledHooks.join(", ")}`);
63653
63745
  }
63654
63746
  if (!factory.supportsPerHookConfirm) {
63655
63747
  const eventsWithConfirmHooks = emittedEventsWithHook({
@@ -63658,7 +63750,7 @@ var HooksProcessor = class extends FeatureProcessor {
63658
63750
  effectiveHooks,
63659
63751
  predicate: (def) => def.confirm !== void 0
63660
63752
  });
63661
- if (eventsWithConfirmHooks.length > 0) this.logger.warn(`Emitting hook(s) without their "confirm" prompt for ${this.toolTarget} (only the kiro-cli / kiro-ide hooks formats support it): ${eventsWithConfirmHooks.join(", ")}`);
63753
+ if (eventsWithConfirmHooks.length > 0) this.logger.warn(`Emitting hook(s) without their "confirm" prompt for ${this.toolTarget} (only the ${targetsSupporting("supportsPerHookConfirm")} hooks formats support it): ${eventsWithConfirmHooks.join(", ")}`);
63662
63754
  }
63663
63755
  }
63664
63756
  async convertToolFilesToRulesyncFiles(toolFiles) {
@@ -64921,12 +65013,7 @@ var ReasonixIgnore = class ReasonixIgnore extends ToolIgnore {
64921
65013
  }
64922
65014
  toRulesyncIgnore() {
64923
65015
  const rulesyncPatterns = uniq(this.patterns.map((entry) => readDenyPatternOf(entry)).filter((pattern) => pattern !== null && pattern.length > 0));
64924
- return new RulesyncIgnore({
64925
- outputRoot: this.outputRoot,
64926
- relativeDirPath: RulesyncIgnore.getSettablePaths().recommended.relativeDirPath,
64927
- relativeFilePath: RulesyncIgnore.getSettablePaths().recommended.relativeFilePath,
64928
- fileContent: rulesyncPatterns.join("\n")
64929
- });
65016
+ return this.toRulesyncIgnoreDefault({ fileContent: rulesyncPatterns.join("\n") });
64930
65017
  }
64931
65018
  static async fromRulesyncIgnore({ outputRoot = process.cwd(), rulesyncIgnore, global = false, logger }) {
64932
65019
  await warnIfReasonixNestedProjectConfigShadowed({
@@ -78633,11 +78720,17 @@ function convertAmpToRulesync({ disable, permissions }) {
78633
78720
  const assign = (tool, pattern, action) => {
78634
78721
  if (isPrototypePollutionKey(tool)) return;
78635
78722
  if (isPrototypePollutionKey(pattern)) return;
78636
- const bucket = permission[tool] ??= {};
78637
- const existing = bucket[pattern];
78723
+ const bucket = getOrCreateOwn({
78724
+ record: permission,
78725
+ key: tool,
78726
+ create: () => ({})
78727
+ });
78638
78728
  if (isStricterAction({
78639
78729
  action,
78640
- existing
78730
+ existing: lookupOwn({
78731
+ record: bucket,
78732
+ key: pattern
78733
+ })
78641
78734
  })) bucket[pattern] = action;
78642
78735
  };
78643
78736
  for (const tool of disable) assign(tool, "*", "deny");
@@ -80040,8 +80133,12 @@ function convertAntigravityToRulesyncPermissions({ allow, ask, deny, toCategory,
80040
80133
  logger,
80041
80134
  toolLabel
80042
80135
  }) : target;
80043
- if (!permission[canonical]) permission[canonical] = {};
80044
- permission[canonical][pattern] = action;
80136
+ if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
80137
+ getOrCreateOwn({
80138
+ record: permission,
80139
+ key: canonical,
80140
+ create: () => ({})
80141
+ })[pattern] = action;
80045
80142
  }
80046
80143
  };
80047
80144
  processEntries(allow, "allow");
@@ -80109,10 +80206,16 @@ const ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES = {
80109
80206
  };
80110
80207
  function toAntigravityCliToolName(canonical) {
80111
80208
  if (canonical.startsWith("mcp__")) return "mcp";
80112
- return CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES[canonical] ?? canonical;
80209
+ return lookupOwn({
80210
+ record: CANONICAL_TO_ANTIGRAVITY_CLI_TOOL_NAMES,
80211
+ key: canonical
80212
+ }) ?? canonical;
80113
80213
  }
80114
80214
  function toCanonicalToolName$9(cliName) {
80115
- return ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES[cliName] ?? cliName;
80215
+ return lookupOwn({
80216
+ record: ANTIGRAVITY_CLI_TO_CANONICAL_TOOL_NAMES,
80217
+ key: cliName
80218
+ }) ?? cliName;
80116
80219
  }
80117
80220
  /**
80118
80221
  * Permissions generator for the Google Antigravity CLI (`agy`, Antigravity 2.0).
@@ -80275,10 +80378,16 @@ const IDE_ACTION_TO_CANONICAL = {
80275
80378
  };
80276
80379
  function toIdeAction(canonical) {
80277
80380
  if (canonical.startsWith("mcp__")) return "mcp";
80278
- return CANONICAL_TO_IDE_ACTION[canonical] ?? canonical;
80381
+ return lookupOwn({
80382
+ record: CANONICAL_TO_IDE_ACTION,
80383
+ key: canonical
80384
+ }) ?? canonical;
80279
80385
  }
80280
80386
  function toCanonicalCategory$2(ideAction) {
80281
- return IDE_ACTION_TO_CANONICAL[ideAction] ?? ideAction;
80387
+ return lookupOwn({
80388
+ record: IDE_ACTION_TO_CANONICAL,
80389
+ key: ideAction
80390
+ }) ?? ideAction;
80282
80391
  }
80283
80392
  /**
80284
80393
  * Permissions generator for the Google Antigravity IDE (Antigravity 2.0).
@@ -81817,10 +81926,16 @@ const CANONICAL_TO_CLAUDE_TOOL_NAMES = {
81817
81926
  */
81818
81927
  const CLAUDE_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_CLAUDE_TOOL_NAMES);
81819
81928
  function toClaudeToolName(canonical) {
81820
- return CANONICAL_TO_CLAUDE_TOOL_NAMES[canonical] ?? canonical;
81929
+ return lookupOwn({
81930
+ record: CANONICAL_TO_CLAUDE_TOOL_NAMES,
81931
+ key: canonical
81932
+ }) ?? canonical;
81821
81933
  }
81822
81934
  function toCanonicalToolName$7(claudeName) {
81823
- return CLAUDE_TO_CANONICAL_TOOL_NAMES[claudeName] ?? claudeName;
81935
+ return lookupOwn({
81936
+ record: CLAUDE_TO_CANONICAL_TOOL_NAMES,
81937
+ key: claudeName
81938
+ }) ?? claudeName;
81824
81939
  }
81825
81940
  /**
81826
81941
  * Merge `patch` into `base`, recursing into plain objects so a sibling key at
@@ -82792,7 +82907,10 @@ const CLAUDE_PATH_RULE_ALIASES = {
82792
82907
  */
82793
82908
  function buildClaudePermissionEntry(toolName, pattern) {
82794
82909
  if (pattern === "*") return toolName;
82795
- return `${CLAUDE_PATH_RULE_ALIASES[toolName] ?? toolName}(${pattern})`;
82910
+ return `${lookupOwn({
82911
+ record: CLAUDE_PATH_RULE_ALIASES,
82912
+ key: toolName
82913
+ }) ?? toolName}(${pattern})`;
82796
82914
  }
82797
82915
  /**
82798
82916
  * The Claude tool names the canonical config manages. Deliberately the tool
@@ -83030,8 +83148,12 @@ function convertClaudeToRulesyncPermissions(params) {
83030
83148
  for (const entry of entries) {
83031
83149
  const { toolName, pattern } = parseToolPatternEntry(entry);
83032
83150
  const canonical = toCanonicalToolName$7(toolName);
83033
- if (!permission[canonical]) permission[canonical] = {};
83034
- permission[canonical][pattern] = action;
83151
+ if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
83152
+ getOrCreateOwn({
83153
+ record: permission,
83154
+ key: canonical,
83155
+ create: () => ({})
83156
+ })[pattern] = action;
83035
83157
  }
83036
83158
  };
83037
83159
  processEntries(params.allow, "allow");
@@ -83613,11 +83735,17 @@ var CodebuddyPermissions = class CodebuddyPermissions extends ToolPermissions {
83613
83735
  for (const action of CODEBUDDY_RULE_LISTS) for (const rule of toStringArray$1(permissions[action])) {
83614
83736
  const parsed = parseCodebuddyRule(rule);
83615
83737
  if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
83616
- const rules = permission[parsed.category] ??= {};
83617
- const existing = rules[parsed.pattern];
83738
+ const rules = getOrCreateOwn({
83739
+ record: permission,
83740
+ key: parsed.category,
83741
+ create: () => ({})
83742
+ });
83618
83743
  if (isStricterAction({
83619
83744
  action,
83620
- existing
83745
+ existing: lookupOwn({
83746
+ record: rules,
83747
+ key: parsed.pattern
83748
+ })
83621
83749
  })) rules[parsed.pattern] = action;
83622
83750
  }
83623
83751
  const codebuddy = liftCodebuddyOverride({
@@ -84127,7 +84255,10 @@ const CODEWHALE_WRITE_RESTRICTION_TOOLS = ["write_file", "apply_patch"];
84127
84255
  /** The Codewhale tools a canonical path rule is written for. */
84128
84256
  function pathCategoryTools(category, action) {
84129
84257
  if (category === "write" && action !== "allow") return CODEWHALE_WRITE_RESTRICTION_TOOLS;
84130
- return CODEWHALE_PATH_CATEGORY_TOOLS[category] ?? [];
84258
+ return lookupOwn({
84259
+ record: CODEWHALE_PATH_CATEGORY_TOOLS,
84260
+ key: category
84261
+ }) ?? [];
84131
84262
  }
84132
84263
  /** The canonical path categories a Codewhale file tool's rule can come from. */
84133
84264
  const CODEWHALE_TOOL_TO_PATH_CATEGORIES = {
@@ -84446,7 +84577,10 @@ function canonicalToCodewhaleRules({ config, preservedRules, logger }) {
84446
84577
  });
84447
84578
  for (const [category, rules] of Object.entries(config.permission)) {
84448
84579
  if (category === "bash" || category === "*") continue;
84449
- if (CODEWHALE_PATH_CATEGORY_TOOLS[category] === void 0) {
84580
+ if (lookupOwn({
84581
+ record: CODEWHALE_PATH_CATEGORY_TOOLS,
84582
+ key: category
84583
+ }) === void 0) {
84450
84584
  if (Object.keys(rules).length > 0) logger?.warn(`Codewhale permissions: skipping unsupported category "${category}"; Codewhale evaluates permission rules only for shell commands and file tools.`);
84451
84585
  continue;
84452
84586
  }
@@ -84556,7 +84690,10 @@ function toImportCandidates(rule) {
84556
84690
  }
84557
84691
  const pattern = pathRuleToPattern(rule);
84558
84692
  if (pattern === null) return [];
84559
- return (CODEWHALE_TOOL_TO_PATH_CATEGORIES[rule.tool] ?? []).map((category) => ({
84693
+ return (lookupOwn({
84694
+ record: CODEWHALE_TOOL_TO_PATH_CATEGORIES,
84695
+ key: rule.tool
84696
+ }) ?? []).map((category) => ({
84560
84697
  category,
84561
84698
  pattern,
84562
84699
  rule
@@ -86164,10 +86301,17 @@ function parseCommandcodeRuleLists(permissions) {
86164
86301
  rule,
86165
86302
  category
86166
86303
  })) fallbackLogger.warn(`Command Code matches '${rule}' in "${action}" against MCP tool names case-insensitively, but the '${category}' rule generated from it matches the server and tool names exactly as written; spell the rule the way the tool is registered if they differ.`);
86167
- const rules = permission[category] ??= {};
86304
+ const rules = getOrCreateOwn({
86305
+ record: permission,
86306
+ key: category,
86307
+ create: () => ({})
86308
+ });
86168
86309
  if (isStricterAction({
86169
86310
  action,
86170
- existing: rules[pattern]
86311
+ existing: lookupOwn({
86312
+ record: rules,
86313
+ key: pattern
86314
+ })
86171
86315
  })) rules[pattern] = action;
86172
86316
  }
86173
86317
  }
@@ -86528,9 +86672,12 @@ function convertContinuePermissionsToRulesync(lists) {
86528
86672
  const parsedEntry = parseContinuePermissionEntry(entry);
86529
86673
  if (parsedEntry === void 0) continue;
86530
86674
  if (isPrototypePollutionKey(parsedEntry.toolName) || isPrototypePollutionKey(parsedEntry.pattern)) continue;
86531
- const category = toCanonicalToolName$6(parsedEntry.toolName);
86532
- permission[category] ??= {};
86533
- permission[category][parsedEntry.pattern] ??= action;
86675
+ const rules = getOrCreateOwn({
86676
+ record: permission,
86677
+ key: toCanonicalToolName$6(parsedEntry.toolName),
86678
+ create: () => ({})
86679
+ });
86680
+ if (!Object.hasOwn(rules, parsedEntry.pattern)) rules[parsedEntry.pattern] = action;
86534
86681
  }
86535
86682
  }
86536
86683
  return { permission };
@@ -87159,7 +87306,9 @@ var CopilotcliPermissions = class CopilotcliPermissions extends ToolPermissions
87159
87306
  const settings = this.parseSettings();
87160
87307
  const rules = {};
87161
87308
  for (const pattern of toUrlList(settings[DENIED_URLS_KEY])) rules[pattern] = "deny";
87162
- if (this.global) for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) rules[pattern] ??= "allow";
87309
+ if (this.global) {
87310
+ for (const pattern of toUrlList(settings[ALLOWED_URLS_KEY])) if (!Object.hasOwn(rules, pattern)) rules[pattern] = "allow";
87311
+ }
87163
87312
  const permission = Object.keys(rules).length > 0 ? { [WEBFETCH_CATEGORY$1]: rules } : {};
87164
87313
  const defaultPermissionMode = this.global ? settings[DEFAULT_PERMISSION_MODE_KEY] : void 0;
87165
87314
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({
@@ -87506,7 +87655,10 @@ function isMcpScopedCategory(canonical) {
87506
87655
  }
87507
87656
  function toCursorType(canonical) {
87508
87657
  if (isMcpScopedCategory(canonical)) return "Mcp";
87509
- return CANONICAL_TO_CURSOR_TYPE[canonical] ?? canonical;
87658
+ return lookupOwn({
87659
+ record: CANONICAL_TO_CURSOR_TYPE,
87660
+ key: canonical
87661
+ }) ?? canonical;
87510
87662
  }
87511
87663
  /**
87512
87664
  * For per-tool MCP canonical categories like `mcp__puppeteer__navigate`, the
@@ -87538,9 +87690,15 @@ function toCanonicalCategory$1(cursorType, pattern) {
87538
87690
  const tool = match[2] ?? "*";
87539
87691
  return `${MCP_CATEGORY_PREFIX}${server}__${tool}`;
87540
87692
  }
87541
- return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
87693
+ return lookupOwn({
87694
+ record: CURSOR_TYPE_TO_CANONICAL,
87695
+ key: cursorType
87696
+ }) ?? cursorType.toLowerCase();
87542
87697
  }
87543
- return CURSOR_TYPE_TO_CANONICAL[cursorType] ?? cursorType.toLowerCase();
87698
+ return lookupOwn({
87699
+ record: CURSOR_TYPE_TO_CANONICAL,
87700
+ key: cursorType
87701
+ }) ?? cursorType.toLowerCase();
87544
87702
  }
87545
87703
  /**
87546
87704
  * Narrow `JSON.parse` output to a plain object before treating it as a Cursor
@@ -87785,9 +87943,13 @@ function convertCursorToRulesyncPermissions(params) {
87785
87943
  for (const entry of entries) {
87786
87944
  const { toolName: type, pattern } = parseToolPatternEntry(entry);
87787
87945
  const canonical = toCanonicalCategory$1(type, pattern);
87788
- if (!permission[canonical]) permission[canonical] = {};
87789
87946
  const canonicalPattern = type === "Mcp" && canonical.startsWith("mcp__") ? "*" : pattern;
87790
- permission[canonical][canonicalPattern] = action;
87947
+ if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(canonicalPattern)) continue;
87948
+ getOrCreateOwn({
87949
+ record: permission,
87950
+ key: canonical,
87951
+ create: () => ({})
87952
+ })[canonicalPattern] = action;
87791
87953
  }
87792
87954
  };
87793
87955
  processEntries(params.allow, "allow");
@@ -88714,10 +88876,16 @@ const DEVIN_SCOPE_TO_CANONICAL = {
88714
88876
  */
88715
88877
  const DEVIN_BARE_ONLY_SCOPES = /* @__PURE__ */ new Set(["web_search"]);
88716
88878
  function toDevinScope(canonical) {
88717
- return CANONICAL_TO_DEVIN_SCOPE[canonical] ?? canonical;
88879
+ return lookupOwn({
88880
+ record: CANONICAL_TO_DEVIN_SCOPE,
88881
+ key: canonical
88882
+ }) ?? canonical;
88718
88883
  }
88719
88884
  function toCanonicalCategory(devinScope) {
88720
- return DEVIN_SCOPE_TO_CANONICAL[devinScope] ?? devinScope;
88885
+ return lookupOwn({
88886
+ record: DEVIN_SCOPE_TO_CANONICAL,
88887
+ key: devinScope
88888
+ }) ?? devinScope;
88721
88889
  }
88722
88890
  function asDevinRecord(value) {
88723
88891
  return isRecord(value) ? { ...value } : {};
@@ -89069,7 +89237,11 @@ function convertDevinToRulesyncPermissions(params) {
89069
89237
  const { toolName: scope, pattern } = parseToolPatternEntry(entry);
89070
89238
  if (isPrototypePollutionKey(scope) || isPrototypePollutionKey(pattern)) continue;
89071
89239
  const canonical = toCanonicalCategory(scope);
89072
- (permission[canonical] ??= {})[pattern] = action;
89240
+ getOrCreateOwn({
89241
+ record: permission,
89242
+ key: canonical,
89243
+ create: () => ({})
89244
+ })[pattern] = action;
89073
89245
  }
89074
89246
  };
89075
89247
  processEntries(params.allow, "allow");
@@ -89375,7 +89547,10 @@ function convertRulesyncToGoosePermissionConfig({ config, logger }) {
89375
89547
  };
89376
89548
  const assigned = /* @__PURE__ */ new Map();
89377
89549
  for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
89378
- const toolName = RULESYNC_TO_GOOSE_TOOL_NAME[category] ?? category;
89550
+ const toolName = lookupOwn({
89551
+ record: RULESYNC_TO_GOOSE_TOOL_NAME,
89552
+ key: category
89553
+ }) ?? category;
89379
89554
  for (const [pattern, action] of Object.entries(rules)) if (pattern !== "*") logger?.warn(`Goose permission.yaml lists whole tool names, so the per-pattern "${action}" rule for "${category}" (pattern "${pattern}") cannot be expressed and was skipped.`);
89380
89555
  const { action, downgraded } = failSafeCatchAllAction({
89381
89556
  rules,
@@ -89407,9 +89582,16 @@ function convertGoosePermissionConfigToRulesync(userPermission) {
89407
89582
  for (const key of GOOSE_PERMISSION_LIST_KEYS) {
89408
89583
  const toolNames = isStringArray(userPermission[key]) ? userPermission[key] : [];
89409
89584
  const action = GOOSE_LIST_TO_ACTION[key];
89410
- for (const toolName of toolNames) for (const category of GOOSE_TO_RULESYNC_TOOL_NAMES[toolName] ?? [toolName]) {
89411
- permission[category] ??= {};
89412
- permission[category]["*"] = action;
89585
+ for (const toolName of toolNames) for (const category of lookupOwn({
89586
+ record: GOOSE_TO_RULESYNC_TOOL_NAMES,
89587
+ key: toolName
89588
+ }) ?? [toolName]) {
89589
+ if (isPrototypePollutionKey(category)) continue;
89590
+ getOrCreateOwn({
89591
+ record: permission,
89592
+ key: category,
89593
+ create: () => ({})
89594
+ })["*"] = action;
89413
89595
  }
89414
89596
  }
89415
89597
  return { permission };
@@ -89490,7 +89672,10 @@ function buildGrokEntry(category, pattern) {
89490
89672
  return remainder.length > 0 ? `${GROK_MCP_TOOL}(${remainder})` : GROK_MCP_TOOL;
89491
89673
  }
89492
89674
  if (category === "mcp") return pattern === "*" || pattern === "" ? GROK_MCP_TOOL : `${GROK_MCP_TOOL}(${pattern})`;
89493
- const tool = CATEGORY_TO_GROK_TOOL[category];
89675
+ const tool = lookupOwn({
89676
+ record: CATEGORY_TO_GROK_TOOL,
89677
+ key: category
89678
+ });
89494
89679
  if (tool === void 0) return null;
89495
89680
  return pattern === "*" || pattern === "" ? tool : `${tool}(${toGrokPathPattern(category, pattern)})`;
89496
89681
  }
@@ -89524,7 +89709,10 @@ function parseGrokEntry(entry) {
89524
89709
  category: "mcp",
89525
89710
  pattern: "*"
89526
89711
  };
89527
- const category = GROK_TOOL_TO_CATEGORY[tool];
89712
+ const category = lookupOwn({
89713
+ record: GROK_TOOL_TO_CATEGORY,
89714
+ key: tool
89715
+ });
89528
89716
  if (category === void 0) return null;
89529
89717
  return {
89530
89718
  category,
@@ -89845,11 +90033,17 @@ function parseGrokPermissionArrays(permission) {
89845
90033
  if ((allow?.length ?? 0) + (deny?.length ?? 0) + (ask?.length ?? 0) + (rules?.length ?? 0) === 0) return null;
89846
90034
  const result = {};
89847
90035
  const record = ({ category, pattern, action }) => {
89848
- const bucket = result[category] ??= {};
89849
- const existing = bucket[pattern];
90036
+ const bucket = getOrCreateOwn({
90037
+ record: result,
90038
+ key: category,
90039
+ create: () => ({})
90040
+ });
89850
90041
  if (isStricterAction({
89851
90042
  action,
89852
- existing
90043
+ existing: lookupOwn({
90044
+ record: bucket,
90045
+ key: pattern
90046
+ })
89853
90047
  })) bucket[pattern] = action;
89854
90048
  };
89855
90049
  const applyEntries = (entries, action) => {
@@ -89906,7 +90100,11 @@ function deleteRulesByAction(rules, action) {
89906
90100
  for (const [pattern, currentAction] of Object.entries(rules)) if (currentAction === action) delete rules[pattern];
89907
90101
  }
89908
90102
  function ensureCategory(permission, category) {
89909
- return permission[category] ??= {};
90103
+ return getOrCreateOwn({
90104
+ record: permission,
90105
+ key: category,
90106
+ create: () => ({})
90107
+ });
89910
90108
  }
89911
90109
  function removeEmptyCategories(permission) {
89912
90110
  for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
@@ -90087,6 +90285,22 @@ function unmanagedMcpAllowlistEntries({ existing, previousPermission, nextPermis
90087
90285
  return category === null || !Object.hasOwn(previousPermission, category) && !Object.hasOwn(nextPermission, category);
90088
90286
  });
90089
90287
  }
90288
+ /** Hermes's override key in the canonical document (`hermes`). */
90289
+ const HERMES_OVERRIDE_KEY = PERMISSION_OVERRIDE_KEY_ALIASES.hermesagent ?? "hermesagent";
90290
+ /**
90291
+ * The part of a canonical permissions document Hermes keeps as provenance: the
90292
+ * `$schema`, the canonical `permission` block, and Hermes's own override.
90293
+ */
90294
+ function hermesProvenanceOf(permissions) {
90295
+ const source = permissions;
90296
+ const provenance = {};
90297
+ for (const key of [
90298
+ "$schema",
90299
+ "permission",
90300
+ HERMES_OVERRIDE_KEY
90301
+ ]) if (source[key] !== void 0) provenance[key] = source[key];
90302
+ return provenance;
90303
+ }
90090
90304
  /** The validated canonical `permission` block of a config's `permissions.rulesync` blob. */
90091
90305
  function canonicalPermissionOf(config) {
90092
90306
  const parsed = RulesyncPermissionsFileSchema.safeParse({ permission: provenancePermissionOf(config) });
@@ -90254,10 +90468,10 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
90254
90468
  patterns: websiteBlocklist.enabled === true && isStringArray(websiteBlocklist.domains) ? websiteBlocklist.domains : []
90255
90469
  });
90256
90470
  removeEmptyCategories(permission);
90257
- const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
90258
90471
  const hermes = buildHermesOverride(config, provenance);
90472
+ const { $schema } = provenance;
90259
90473
  const imported = {
90260
- ...otherProvenance,
90474
+ ...$schema !== void 0 && { $schema },
90261
90475
  permission,
90262
90476
  ...Object.keys(hermes).length > 0 && { hermes }
90263
90477
  };
@@ -90315,7 +90529,7 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
90315
90529
  base: config,
90316
90530
  patch: permissions.hermes
90317
90531
  });
90318
- config.permissions = { rulesync: permissions };
90532
+ config.permissions = { rulesync: hermesProvenanceOf(permissions) };
90319
90533
  return new HermesagentPermissions({
90320
90534
  outputRoot,
90321
90535
  fileContent: stringifySharedConfig({
@@ -90549,7 +90763,10 @@ var JuniePermissions = class JuniePermissions extends ToolPermissions {
90549
90763
  function convertRulesyncToJunieRules({ config, logger, existingRules, overrideSecretFile, overrideRuleDefaults }) {
90550
90764
  const ruleLists = {};
90551
90765
  for (const [category, patterns] of Object.entries(honorAllToolsOnBash(config.permission))) {
90552
- const group = CANONICAL_TO_JUNIE_GROUP[category];
90766
+ const group = lookupOwn({
90767
+ record: CANONICAL_TO_JUNIE_GROUP,
90768
+ key: category
90769
+ });
90553
90770
  if (!group) {
90554
90771
  if (Object.keys(patterns).length > 0) logger?.warn(`Junie allowlist only models executables/fileEditing/mcpTools/readOutsideProject (canonical bash/edit/write/read/mcp); '${category}' rules cannot be represented and were skipped.`);
90555
90772
  continue;
@@ -90947,7 +91164,10 @@ const CATEGORY_TO_KIMI_CODE_TOOL = {
90947
91164
  function buildKimiCodePattern(category, pattern) {
90948
91165
  if (category.startsWith("mcp__")) return pattern === "*" || pattern === "" ? category : null;
90949
91166
  if (category === "mcp") return pattern === "*" || pattern === "" ? "mcp__*" : `mcp__${pattern}`;
90950
- const tool = CATEGORY_TO_KIMI_CODE_TOOL[category];
91167
+ const tool = lookupOwn({
91168
+ record: CATEGORY_TO_KIMI_CODE_TOOL,
91169
+ key: category
91170
+ });
90951
91171
  if (!tool) return null;
90952
91172
  return pattern === "*" || pattern === "" ? tool : `${tool}(${pattern})`;
90953
91173
  }
@@ -91043,7 +91263,10 @@ function canonicalToKimiCodeRules({ config, logger }) {
91043
91263
  const canonicalRules = [];
91044
91264
  const overrideRules = config["kimi-code"]?.rules ?? [];
91045
91265
  for (const [category, rules] of Object.entries(config.permission)) {
91046
- if (!(category === "mcp" || category.startsWith("mcp__") || CATEGORY_TO_KIMI_CODE_TOOL[category] !== void 0)) {
91266
+ if (!(category === "mcp" || category.startsWith("mcp__") || lookupOwn({
91267
+ record: CATEGORY_TO_KIMI_CODE_TOOL,
91268
+ key: category
91269
+ }) !== void 0)) {
91047
91270
  if (Object.keys(rules).length > 0) logger?.warn(`Kimi Code permissions: skipping unsupported category "${category}".`);
91048
91271
  continue;
91049
91272
  }
@@ -91860,9 +92083,16 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
91860
92083
  if (!isImportableRule(rule) || !isRecord(rule) || !isPermissionAction(rule.effect)) continue;
91861
92084
  const category = KIRO_CAPABILITY_TO_CANONICAL[rule.capability];
91862
92085
  const patterns = rule.match === void 0 ? ["*"] : isStringArray(rule.match) ? rule.match : [];
91863
- const categoryRules = permission[category] ??= {};
92086
+ const categoryRules = getOrCreateOwn({
92087
+ record: permission,
92088
+ key: category,
92089
+ create: () => ({})
92090
+ });
91864
92091
  for (const pattern of patterns) {
91865
- const previous = categoryRules[pattern];
92092
+ const previous = lookupOwn({
92093
+ record: categoryRules,
92094
+ key: pattern
92095
+ });
91866
92096
  if (isStricterAction({
91867
92097
  action: rule.effect,
91868
92098
  existing: previous
@@ -91880,7 +92110,10 @@ var KiroCapabilityPermissions = class KiroCapabilityPermissions extends ToolPerm
91880
92110
  function buildKiroCapabilityRules({ config, logger }) {
91881
92111
  const buckets = /* @__PURE__ */ new Map();
91882
92112
  for (const [category, rules] of Object.entries(honorAllToolsOnBash(config.permission))) {
91883
- const capability = CANONICAL_TO_KIRO_CAPABILITY[category];
92113
+ const capability = lookupOwn({
92114
+ record: CANONICAL_TO_KIRO_CAPABILITY,
92115
+ key: category
92116
+ });
91884
92117
  if (capability === void 0) {
91885
92118
  logger?.warn(`Kiro permissions.yaml has no capability for category: ${category}. Skipping.`);
91886
92119
  continue;
@@ -92040,7 +92273,10 @@ function toLettacodeBashPayload({ pattern, action, logger }) {
92040
92273
  return prefix === "" ? "" : `${prefix}${PREFIX_SUFFIX}`;
92041
92274
  }
92042
92275
  function toLettacodeRule({ category, pattern, action, logger }) {
92043
- const tool = CATEGORY_TO_LETTACODE_TOOL[category];
92276
+ const tool = lookupOwn({
92277
+ record: CATEGORY_TO_LETTACODE_TOOL,
92278
+ key: category
92279
+ });
92044
92280
  if (tool === void 0) return null;
92045
92281
  const payload = category === "bash" ? toLettacodeBashPayload({
92046
92282
  pattern,
@@ -92195,11 +92431,17 @@ var LettacodePermissions = class LettacodePermissions extends ToolPermissions {
92195
92431
  for (const [key, action] of Object.entries(LIST_TO_ACTION)) for (const rule of toStringArray$1(permissions[key])) {
92196
92432
  const parsed = fromLettacodeRule(rule);
92197
92433
  if (parsed === null || isPrototypePollutionKey(parsed.pattern)) continue;
92198
- const rules = permission[parsed.category] ??= {};
92199
- const existing = rules[parsed.pattern];
92434
+ const rules = getOrCreateOwn({
92435
+ record: permission,
92436
+ key: parsed.category,
92437
+ create: () => ({})
92438
+ });
92200
92439
  if (isStricterAction({
92201
92440
  action,
92202
- existing
92441
+ existing: lookupOwn({
92442
+ record: rules,
92443
+ key: parsed.pattern
92444
+ })
92203
92445
  })) rules[parsed.pattern] = action;
92204
92446
  }
92205
92447
  return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify({ permission }, null, 2) });
@@ -92386,7 +92628,13 @@ function getOpencodeMcpServerNames(mcp) {
92386
92628
  return isRecord(lowered) ? Object.keys(lowered) : [];
92387
92629
  }
92388
92630
  function toOpencodePermissionKey(canonical) {
92389
- return CANONICAL_TO_OPENCODE_PERMISSION_KEYS[canonical] ?? CANONICAL_TO_OPENCODE_FOLDED_PERMISSION_KEYS[canonical] ?? canonical;
92631
+ return lookupOwn({
92632
+ record: CANONICAL_TO_OPENCODE_PERMISSION_KEYS,
92633
+ key: canonical
92634
+ }) ?? lookupOwn({
92635
+ record: CANONICAL_TO_OPENCODE_FOLDED_PERMISSION_KEYS,
92636
+ key: canonical
92637
+ }) ?? canonical;
92390
92638
  }
92391
92639
  /**
92392
92640
  * Merge two canonical pattern maps that land on the same OpenCode key.
@@ -92410,7 +92658,10 @@ function mergePatternRules(base, incoming) {
92410
92658
  return Object.fromEntries(Object.entries(merged).toSorted(([, a], [, b]) => PERMISSION_ACTION_PRIORITY[a] - PERMISSION_ACTION_PRIORITY[b]));
92411
92659
  }
92412
92660
  function toCanonicalPermissionKey(opencodeKey) {
92413
- return OPENCODE_TO_CANONICAL_PERMISSION_KEYS[opencodeKey] ?? opencodeKey;
92661
+ return lookupOwn({
92662
+ record: OPENCODE_TO_CANONICAL_PERMISSION_KEYS,
92663
+ key: opencodeKey
92664
+ }) ?? opencodeKey;
92414
92665
  }
92415
92666
  function isSharedPermissionCategory(category) {
92416
92667
  return category === "*" || CANONICAL_PERMISSION_CATEGORIES$1.has(category) || category.startsWith("mcp__");
@@ -93692,7 +93943,14 @@ function importPoolTool({ toolConfig, bucket }) {
93692
93943
  const denied = isStringArray(toolConfig[POOL_DENY_KEY]) ? toolConfig[POOL_DENY_KEY] : [];
93693
93944
  const allowed = isStringArray(toolConfig[POOL_ALLOW_KEY]) ? toolConfig[POOL_ALLOW_KEY] : [];
93694
93945
  for (const pattern of denied.filter(isUsablePattern)) bucket[pattern] = "deny";
93695
- for (const pattern of allowed.filter(isUsablePattern)) bucket[pattern] ??= "allow";
93946
+ for (const pattern of allowed.filter(isUsablePattern)) if (!Object.hasOwn(bucket, pattern)) bucket[pattern] = "allow";
93947
+ }
93948
+ /**
93949
+ * Record `allow` for a path no deny has claimed. The check is on own
93950
+ * properties, so a path such as `toString` is not mistaken for a stored rule.
93951
+ */
93952
+ function allowUnlessSet(bucket, path) {
93953
+ if (!Object.hasOwn(bucket, path)) bucket[path] = "allow";
93696
93954
  }
93697
93955
  /**
93698
93956
  * Convert Pool's `tools` and `paths` blocks back into a rulesync config.
@@ -93727,8 +93985,8 @@ function convertPoolToRulesync({ tools, paths }) {
93727
93985
  for (const entry of allowEntries) {
93728
93986
  const path = pathOfEntry(entry);
93729
93987
  if (path === void 0) continue;
93730
- bucketFor(READ_CATEGORY)[path] ??= "allow";
93731
- if (isRecord(entry) && entry[POOL_WRITE_KEY] === true) for (const category of WRITE_CATEGORIES) bucketFor(category)[path] ??= "allow";
93988
+ allowUnlessSet(bucketFor(READ_CATEGORY), path);
93989
+ if (isRecord(entry) && entry[POOL_WRITE_KEY] === true) for (const category of WRITE_CATEGORIES) allowUnlessSet(bucketFor(category), path);
93732
93990
  }
93733
93991
  return { permission };
93734
93992
  }
@@ -93857,7 +94115,10 @@ function toQoderRule({ category, pattern, logger }) {
93857
94115
  logger?.warn(`Qoder permissions: skipping the '${category}' rule '${pattern}' — Qoder matches MCP tools by name only.`);
93858
94116
  return null;
93859
94117
  }
93860
- const tool = CATEGORY_TO_QODER_TOOL[category];
94118
+ const tool = lookupOwn({
94119
+ record: CATEGORY_TO_QODER_TOOL,
94120
+ key: category
94121
+ });
93861
94122
  if (tool === void 0) return null;
93862
94123
  if (pattern === "*") return tool;
93863
94124
  return `${QODER_PATH_RULE_ALIASES[tool] ?? tool}(${pattern})`;
@@ -94143,11 +94404,17 @@ var QoderPermissions = class QoderPermissions extends ToolPermissions {
94143
94404
  for (const action of ACTIONS) for (const rule of toStringArray$1(permissions[action])) {
94144
94405
  const parsed = fromQoderRule(rule);
94145
94406
  if (parsed === null || isPrototypePollutionKey(parsed.category) || isPrototypePollutionKey(parsed.pattern)) continue;
94146
- const rules = permission[parsed.category] ??= {};
94147
- const existing = rules[parsed.pattern];
94407
+ const rules = getOrCreateOwn({
94408
+ record: permission,
94409
+ key: parsed.category,
94410
+ create: () => ({})
94411
+ });
94148
94412
  if (isStricterAction({
94149
94413
  action,
94150
- existing
94414
+ existing: lookupOwn({
94415
+ record: rules,
94416
+ key: parsed.pattern
94417
+ })
94151
94418
  })) rules[parsed.pattern] = action;
94152
94419
  }
94153
94420
  const outputStyle = settings[QODER_OUTPUT_STYLE_KEY];
@@ -94211,10 +94478,16 @@ const CANONICAL_TO_QWEN_TOOL_NAMES = {
94211
94478
  };
94212
94479
  const QWEN_TO_CANONICAL_TOOL_NAMES = invertRecord(CANONICAL_TO_QWEN_TOOL_NAMES);
94213
94480
  function toQwenToolName(canonical) {
94214
- return CANONICAL_TO_QWEN_TOOL_NAMES[canonical] ?? canonical;
94481
+ return lookupOwn({
94482
+ record: CANONICAL_TO_QWEN_TOOL_NAMES,
94483
+ key: canonical
94484
+ }) ?? canonical;
94215
94485
  }
94216
94486
  function toCanonicalToolName$3(qwenName) {
94217
- return QWEN_TO_CANONICAL_TOOL_NAMES[qwenName] ?? qwenName;
94487
+ return lookupOwn({
94488
+ record: QWEN_TO_CANONICAL_TOOL_NAMES,
94489
+ key: qwenName
94490
+ }) ?? qwenName;
94218
94491
  }
94219
94492
  function parseQwenPermissionEntry(entry, options = {}) {
94220
94493
  const parenIndex = entry.indexOf("(");
@@ -94830,15 +95103,23 @@ function convertQwenToRulesyncPermissions(params) {
94830
95103
  if (!parsed.ok) {
94831
95104
  if (action === "deny") {
94832
95105
  const canonical = toCanonicalToolName$3(parsed.toolName);
94833
- if (!permission[canonical]) permission[canonical] = {};
94834
- permission[canonical]["*"] = action;
95106
+ if (isPrototypePollutionKey(canonical)) continue;
95107
+ getOrCreateOwn({
95108
+ record: permission,
95109
+ key: canonical,
95110
+ create: () => ({})
95111
+ })["*"] = action;
94835
95112
  }
94836
95113
  continue;
94837
95114
  }
94838
95115
  const { toolName, pattern } = parsed;
94839
95116
  const canonical = toCanonicalToolName$3(toolName);
94840
- if (!permission[canonical]) permission[canonical] = {};
94841
- permission[canonical][pattern] = action;
95117
+ if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
95118
+ getOrCreateOwn({
95119
+ record: permission,
95120
+ key: canonical,
95121
+ create: () => ({})
95122
+ })[pattern] = action;
94842
95123
  }
94843
95124
  };
94844
95125
  processEntries(params.allow, "allow");
@@ -94902,15 +95183,24 @@ const REASONIX_TO_CANONICAL_TOOL_NAMES = {
94902
95183
  };
94903
95184
  /** Every Reasonix tool name a canonical category owns on generate. */
94904
95185
  function reasonixToolNamesOf(canonical) {
94905
- const legacy = CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES[canonical];
95186
+ const legacy = lookupOwn({
95187
+ record: CANONICAL_TO_LEGACY_REASONIX_TOOL_NAMES,
95188
+ key: canonical
95189
+ });
94906
95190
  const current = toReasonixToolName(canonical);
94907
95191
  return legacy === void 0 ? [current] : [current, legacy];
94908
95192
  }
94909
95193
  function toReasonixToolName(canonical) {
94910
- return CANONICAL_TO_REASONIX_TOOL_NAMES[canonical] ?? canonical;
95194
+ return lookupOwn({
95195
+ record: CANONICAL_TO_REASONIX_TOOL_NAMES,
95196
+ key: canonical
95197
+ }) ?? canonical;
94911
95198
  }
94912
95199
  function toCanonicalToolName$2(reasonixName) {
94913
- return REASONIX_TO_CANONICAL_TOOL_NAMES[reasonixName] ?? reasonixName;
95200
+ return lookupOwn({
95201
+ record: REASONIX_TO_CANONICAL_TOOL_NAMES,
95202
+ key: reasonixName
95203
+ }) ?? reasonixName;
94914
95204
  }
94915
95205
  /**
94916
95206
  * Whether an entry uses the first-class `Tool=<literal>` exact-command form
@@ -95533,8 +95823,12 @@ function convertReasonixToRulesyncPermissions(params) {
95533
95823
  for (const entry of entries) {
95534
95824
  const { toolName, pattern } = parseToolPatternEntry(entry);
95535
95825
  const canonical = toCanonicalToolName$2(toolName);
95536
- if (!permission[canonical]) permission[canonical] = {};
95537
- permission[canonical][pattern] = action;
95826
+ if (isPrototypePollutionKey(canonical) || isPrototypePollutionKey(pattern)) continue;
95827
+ getOrCreateOwn({
95828
+ record: permission,
95829
+ key: canonical,
95830
+ create: () => ({})
95831
+ })[pattern] = action;
95538
95832
  }
95539
95833
  };
95540
95834
  processEntries(params.allow, "allow");
@@ -96086,7 +96380,10 @@ function convertRulesyncToRovodevToolPermissions({ config, logger }) {
96086
96380
  if (bash) toolPermissions.bash = bash;
96087
96381
  continue;
96088
96382
  }
96089
- const toolKeys = CATEGORY_TO_TOOL_KEYS[category];
96383
+ const toolKeys = lookupOwn({
96384
+ record: CATEGORY_TO_TOOL_KEYS,
96385
+ key: category
96386
+ });
96090
96387
  if (!toolKeys) {
96091
96388
  logger?.warn(`Rovo Dev permissions have no target for the "${category}" category. Skipping it.`);
96092
96389
  markSharedToolKeyRestriction({
@@ -96234,7 +96531,7 @@ function convertRovodevToolPermissionsToRulesync(toolPermissions) {
96234
96531
  }
96235
96532
  if (isStringArray(toolPermissions.allowedExternalPaths)) for (const path of toolPermissions.allowedExternalPaths) {
96236
96533
  permission.read ??= {};
96237
- permission.read[path] ??= "allow";
96534
+ if (!Object.hasOwn(permission.read, path)) permission.read[path] = "allow";
96238
96535
  }
96239
96536
  return { permission };
96240
96537
  }
@@ -97971,11 +98268,17 @@ var TraePermissions = class TraePermissions extends ToolPermissions {
97971
98268
  const permission = {};
97972
98269
  const put = (category, pattern, action) => {
97973
98270
  if (isPrototypePollutionKey(category) || isPrototypePollutionKey(pattern)) return;
97974
- const rules = permission[category] ??= {};
97975
- const existing = rules[pattern];
98271
+ const rules = getOrCreateOwn({
98272
+ record: permission,
98273
+ key: category,
98274
+ create: () => ({})
98275
+ });
97976
98276
  if (isStricterAction({
97977
98277
  action,
97978
- existing
98278
+ existing: lookupOwn({
98279
+ record: rules,
98280
+ key: pattern
98281
+ })
97979
98282
  })) rules[pattern] = action;
97980
98283
  };
97981
98284
  for (const [key, value] of Object.entries(readRecord(customProfiles, COMMAND_RULES_PATH))) {
@@ -100318,7 +100621,10 @@ function toZedToolName(canonical) {
100318
100621
  const address = toolParts.length > 0 ? `${server}:${toolParts.join("__")}` : server ?? "";
100319
100622
  return `${MCP_ZED_PREFIX}${address}`;
100320
100623
  }
100321
- return CANONICAL_TO_ZED_TOOL_NAMES[canonical] ?? canonical;
100624
+ return lookupOwn({
100625
+ record: CANONICAL_TO_ZED_TOOL_NAMES,
100626
+ key: canonical
100627
+ }) ?? canonical;
100322
100628
  }
100323
100629
  function toCanonicalToolName(zedName) {
100324
100630
  if (zedName.startsWith(MCP_ZED_PREFIX)) {
@@ -100326,7 +100632,10 @@ function toCanonicalToolName(zedName) {
100326
100632
  const address = toolParts.length > 0 ? `${server}__${toolParts.join(":")}` : server ?? "";
100327
100633
  return `${MCP_CATEGORY_PREFIX}${address}`;
100328
100634
  }
100329
- return ZED_TO_CANONICAL_TOOL_NAMES[zedName] ?? zedName;
100635
+ return lookupOwn({
100636
+ record: ZED_TO_CANONICAL_TOOL_NAMES,
100637
+ key: zedName
100638
+ }) ?? zedName;
100330
100639
  }
100331
100640
  /**
100332
100641
  * Zed matches `always_allow`/`always_deny`/`always_confirm` regexes against the
@@ -100638,7 +100947,10 @@ var ZedPermissions = class ZedPermissions extends ToolPermissions {
100638
100947
  const globalDefault = parsed.success ? parsed.data.default : void 0;
100639
100948
  const permission = {};
100640
100949
  const ensure = (category) => {
100641
- const existing = permission[category];
100950
+ const existing = lookupOwn({
100951
+ record: permission,
100952
+ key: category
100953
+ });
100642
100954
  if (existing) return existing;
100643
100955
  const created = {};
100644
100956
  permission[category] = created;
@@ -101265,7 +101577,7 @@ function resolveToolOutputRoot({ outputRoot, toolTarget, global }) {
101265
101577
  async function convertFromTool(params) {
101266
101578
  resetRunWarningState();
101267
101579
  const packagingTarget = [params.fromTool, ...params.toTools].find(isPackagingToolTarget);
101268
- if (packagingTarget) throw new Error(`Plugin packaging target '${packagingTarget}' is not supported by convert. Use import and generate with explicit plugin directories.`);
101580
+ if (packagingTarget) throw new Error(`Plugin packaging target '${packagingTarget}' is not supported by convert. Use import with the plugin directory as outputRoot (--output-root) and generate with it in outputRoots (--output-roots).`);
101269
101581
  const ctx = params;
101270
101582
  const [rulesCount, ignoreCount, mcpCount, commandsCount, subagentsCount, skillsCount, hooksCount, permissionsCount, checksCount] = [
101271
101583
  await runFeatureConvert(ctx, buildRulesStrategy(ctx)),
@@ -103745,6 +104057,6 @@ const CHECKS_IMPORT_STRATEGY = {
103745
104057
  missingLabel: "check files",
103746
104058
  createdLabel: "check files"
103747
104059
  };
103748
- export { RulesyncPermissions as $, RULESYNC_USER_CONFIG_DIR_NAME as $n, isRootDirPath as $t, CODEBUDDY_DIR as A, CURATED_RULES_FEATURE_SUBDIR as An, withFallbackLoggerTarget as At, CLAUDECODE_LOCAL_RULE_FILE_NAME as B, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, QWENCODE_LOCAL_RULE_FILE_NAME as C, ALL_TOOL_TARGETS as Cn, getSourceTransport as Ct, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as D, ALL_FEATURES as Dn, WarningCollectingLogger as Dt, FACTORYDROID_DIR as E, ToolTargetSchema as En, JsonLogger as Et, groupSpellingsByCaseFoldedIdentity as F, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Fn, PathOutsideRootError as Ft, toInstructionBudgetFile as G, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Gn, ensureDir as Gt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as H, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Hn, checkPathTraversal as Ht, CODEXCLI_BASH_RULES_FILE_NAME as I, RULESYNC_CONFIG_RELATIVE_FILE_PATH as In, applyFileMode as It, RulesyncSubagentFrontmatterSchema as J, RULESYNC_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, getLocalSkillDirNames as K, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Kn, fileExists as Kt, CODEXCLI_DIR as L, RULESYNC_CONFIG_SCHEMA_URL as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_DIR as M, MAX_FILE_SIZE as Mn, withWarnOnceScope as Mt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as N, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Nn, CLIError as Nt, COMMANDCODE_DIR as O, ALL_FEATURES_WITH_WILDCARD as On, fallbackLogger as Ot, caseFoldIdentity as P, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Pn, ErrorCodes as Pt, RulesyncRuleFrontmatterSchema as Q, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Qn, isFileSystemError as Qt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as R, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_DIR as S, mapWithConcurrency as Sn, assertTargetsFeaturesExclusive as St, LETTACODE_SETTINGS_LOCAL_FILE_NAME as T, PACKAGING_TOOL_TARGETS as Tn, ConsoleLogger as Tt, CLAUDECODE_SKILLS_DIR_PATH as U, RULESYNC_MCP_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_MEMORIES_DIR_NAME as V, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, findProjectInstructionBudgetFindings as W, RULESYNC_MCP_SCHEMA_URL as Wn, directoryExists as Wt, RulesyncSkillFrontmatterSchema as X, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkill as Y, RULESYNC_RULES_RELATIVE_DIR_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRule as Z, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Zn, isFileNotFoundError as Zt, SubagentsProcessor as _, runWithDirectoryRollback as _n, resolveEffectiveInputRoots as _t, convertFromTool as a, pathEscapesRoot as an, hasEnclosingMarkOutsideKeycap as ar, parseJsonc as at, CommandsProcessor as b, writeFileBuffer as bn, GITIGNORE_DESTINATION_KEY as bt, parseCommaSeparatedList as c, readFileContentOrNull as cn, stripControlCharactersKeepingLineFeeds as cr, RulesyncCheck as ct, shortenToWidth as d, removeFile as dn, loadYaml as dt, isSymlink as en, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as er, RulesyncMcp as et, McpProcessor as f, removeFileStrict as fn, isRecord as ft, RulesProcessor as g, resolvedRelativePath as gn, mergeInputRootConfigs as gt, ChecksProcessor as h, resolvePath as hn, ConfigResolver as ht, inspectInputRoots as i, normalizePosixDirPath as in, hasDeceptiveHiddenCharacters as ir, resolveRulesyncSourceWritePath as it, CODEBUDDY_LOCAL_RULE_FILE_NAME as j, FEATURE_SOURCE_TREE_ENTRIES as jn, resetRunWarningState as jt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as k, DEPRECATED_FEATURE_REPLACEMENTS as kn, warnOnConflictingFlags as kt, ELLIPSIS_WIDTH as l, removeDirectory as ln, stripControlCharactersKeepingLineFeedsAndTabs as lr, RulesyncCheckFrontmatterSchema as lt, HooksProcessor as m, removeTempDirectory as mn, SKILL_FILE_NAME as mt, formatSourceLoadFailure as n, listFilePathsRecursively as nn, formatError as nr, RulesyncHooks as nt, isPackagingToolTarget as o, posixRelativePathEscapesRoot as on, quoteForLog as or, RulesyncCommand as ot, IgnoreProcessor as p, removeFileWithoutFollowingSymlinks as pn, SHARED_USER_MANAGED_CONFIG_PATHS as pt, RulesyncSubagent as q, RULESYNC_PERMISSIONS_SCHEMA_URL as qn, fileExistsStrict as qt, generate as r, listSubdirectoryNames as rn, truncateText as rr, getRulesyncSourceCandidates as rt, getProcessorRegistryEntry as s, readFileContent as sn, stripControlCharacters as sr, RulesyncCommandFrontmatterSchema as st, importFromTool as t, listDirectoryEntryNames as tn, formatMegabytes as tr, RulesyncIgnore as tt, displayWidthOf as u, removeDirectoryStrict as un, stripHiddenCharacters as ur, stringifyFrontmatter as ut, SkillsProcessor as v, splitPathSegments as vn, CONFLICTING_TARGET_PAIRS as vt, LETTACODE_DIR as w, ALL_TOOL_TARGETS_WITH_WILDCARD as wn, findControlCharacter as wt, REASONIX_LOCAL_RULE_FILE_NAME as x, writeFileContent as xn, SourceEntrySchema as xt, CRUSH_LOCAL_RULE_FILE_NAME as y, toPosixPath as yn, ConfigFileSchema as yt, CLAUDECODE_DIR as z, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as zn, assertPathResolvesInsideRoot as zt };
104060
+ export { RulesyncMcp as $, RULESYNC_USER_CONFIG_DIR_NAME as $n, isRootDirPath as $t, CODEBUDDY_LOCAL_RULE_FILE_NAME as A, CURATED_RULES_FEATURE_SUBDIR as An, resetRunWarningState as At, CLAUDECODE_MEMORIES_DIR_NAME as B, RULESYNC_HOOKS_RELATIVE_FILE_PATH as Bn, assertTreeContainsNoSymlinks as Bt, LETTACODE_DIR as C, ALL_TOOL_TARGETS as Cn, findControlCharacter as Ct, COMMANDCODE_DIR as D, ALL_FEATURES as Dn, fallbackLogger as Dt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as E, ToolTargetSchema as En, WarningCollectingLogger as Et, CODEXCLI_BASH_RULES_FILE_NAME as F, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as Fn, PathOutsideRootError as Ft, getLocalSkillDirNames as G, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Gn, ensureDir as Gt, CLAUDECODE_SKILLS_DIR_PATH as H, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as Hn, checkPathTraversal as Ht, CODEXCLI_DIR as I, RULESYNC_CONFIG_RELATIVE_FILE_PATH as In, applyFileMode as It, RulesyncSkill as J, RULESYNC_RELATIVE_DIR_PATH as Jn, findFilesByGlobs as Jt, RulesyncSubagent as K, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Kn, fileExists as Kt, CLAUDECODE_AGENT_MEMORY_LOCAL_DIR_NAME as L, RULESYNC_CONFIG_SCHEMA_URL as Ln, assertDirectoryIfExists as Lt, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as M, MAX_FILE_SIZE as Mn, CLIError as Mt, caseFoldIdentity as N, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as Nn, ErrorCodes as Nt, COMMANDCODE_SETTINGS_LOCAL_FILE_NAME as O, ALL_FEATURES_WITH_WILDCARD as On, warnOnConflictingFlags as Ot, groupSpellingsByCaseFoldedIdentity as P, RULESYNC_CHECKS_RELATIVE_DIR_PATH as Pn, parseJsonc as Pt, RulesyncPermissions as Q, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as Qn, isFileSystemError as Qt, CLAUDECODE_DIR as R, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as Rn, assertNoSymlinkBelowRoot as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, mapWithConcurrency as Sn, getSourceTransport as St, FACTORYDROID_DIR as T, PACKAGING_TOOL_TARGETS as Tn, JsonLogger as Tt, findProjectInstructionBudgetFindings as U, RULESYNC_MCP_RELATIVE_FILE_PATH as Un, createTempDirectory as Ut, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as V, RULESYNC_IGNORE_RELATIVE_FILE_PATH as Vn, assertWritablePathInsideRoot as Vt, toInstructionBudgetFile as W, RULESYNC_MCP_SCHEMA_URL as Wn, directoryExists as Wt, RulesyncRule as X, RULESYNC_SKILLS_RELATIVE_DIR_PATH as Xn, getHomeDirectory as Xt, RulesyncSkillFrontmatterSchema as Y, RULESYNC_RULES_RELATIVE_DIR_PATH as Yn, getFileSizeAndExecutableBit as Yt, RulesyncRuleFrontmatterSchema as Z, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as Zn, isFileNotFoundError as Zt, SkillsProcessor as _, runWithDirectoryRollback as _n, CONFLICTING_TARGET_PAIRS as _t, convertFromTool as a, pathEscapesRoot as an, hasEnclosingMarkOutsideKeycap as ar, RulesyncCommandFrontmatterSchema as at, REASONIX_LOCAL_RULE_FILE_NAME as b, writeFileBuffer as bn, SourceEntrySchema as bt, ELLIPSIS_WIDTH as c, readFileContentOrNull as cn, stripControlCharactersKeepingLineFeeds as cr, stringifyFrontmatter as ct, McpProcessor as d, removeFile as dn, SHARED_USER_MANAGED_CONFIG_PATHS as dt, isSymlink as en, RULESYNC_XDG_CONFIG_HOME_DEFAULT_DIR_NAME as er, RulesyncIgnore as et, IgnoreProcessor as f, removeFileStrict as fn, SKILL_FILE_NAME as ft, SubagentsProcessor as g, resolvedRelativePath as gn, resolveEffectiveInputRoots as gt, RulesProcessor as h, resolvePath as hn, parseConfigFileContent as ht, inspectInputRoots as i, normalizePosixDirPath as in, hasDeceptiveHiddenCharacters as ir, RulesyncCommand as it, AUGMENTCODE_DIR as j, FEATURE_SOURCE_TREE_ENTRIES as jn, withWarnOnceScope as jt, CODEBUDDY_DIR as k, DEPRECATED_FEATURE_REPLACEMENTS as kn, withFallbackLoggerTarget as kt, displayWidthOf as l, removeDirectory as ln, stripControlCharactersKeepingLineFeedsAndTabs as lr, loadYaml as lt, ChecksProcessor as m, removeTempDirectory as mn, mergeInputRootConfigs as mt, formatSourceLoadFailure as n, listFilePathsRecursively as nn, formatError as nr, getRulesyncSourceCandidates as nt, getProcessorRegistryEntry as o, posixRelativePathEscapesRoot as on, quoteForLog as or, RulesyncCheck as ot, HooksProcessor as p, removeFileWithoutFollowingSymlinks as pn, ConfigResolver as pt, RulesyncSubagentFrontmatterSchema as q, RULESYNC_PERMISSIONS_SCHEMA_URL as qn, fileExistsStrict as qt, generate as r, listSubdirectoryNames as rn, truncateText as rr, resolveRulesyncSourceWritePath as rt, parseCommaSeparatedList as s, readFileContent as sn, stripControlCharacters as sr, RulesyncCheckFrontmatterSchema as st, importFromTool as t, listDirectoryEntryNames as tn, formatMegabytes as tr, RulesyncHooks as tt, shortenToWidth as u, removeDirectoryStrict as un, stripHiddenCharacters as ur, isRecord as ut, CRUSH_LOCAL_RULE_FILE_NAME as v, splitPathSegments as vn, ConfigFileSchema as vt, LETTACODE_SETTINGS_LOCAL_FILE_NAME as w, ALL_TOOL_TARGETS_WITH_WILDCARD as wn, ConsoleLogger as wt, QWENCODE_DIR as x, writeFileContent as xn, assertTargetsFeaturesExclusive as xt, CommandsProcessor as y, toPosixPath as yn, GITIGNORE_DESTINATION_KEY as yt, CLAUDECODE_LOCAL_RULE_FILE_NAME as z, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as zn, assertPathResolvesInsideRoot as zt };
103749
104061
 
103750
- //# sourceMappingURL=import-Safgeb1C.js.map
104062
+ //# sourceMappingURL=import-w0JlEoNl.js.map