rulesync 16.24.0 → 16.25.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.
@@ -713,7 +713,8 @@ const hooksProcessorToolTargetTuple = [
713
713
  "vibe",
714
714
  "qwencode",
715
715
  "reasonix",
716
- "grokcli"
716
+ "grokcli",
717
+ "zcode"
717
718
  ];
718
719
  const permissionsProcessorToolTargetTuple = [
719
720
  "amp",
@@ -2393,6 +2394,7 @@ const ConfigParamsSchema = z.object({
2393
2394
  simulateCommands: optional(z.boolean()),
2394
2395
  simulateSubagents: optional(z.boolean()),
2395
2396
  simulateSkills: optional(z.boolean()),
2397
+ preserveUnownedHooks: optional(z.boolean()),
2396
2398
  deriveSubprojectPathFromGlobs: optional(z.boolean()),
2397
2399
  flattenedCommandNaming: optional(FlattenedCommandNamingSchema),
2398
2400
  /**
@@ -2541,6 +2543,7 @@ var Config = class Config {
2541
2543
  simulateCommands;
2542
2544
  simulateSubagents;
2543
2545
  simulateSkills;
2546
+ preserveUnownedHooks;
2544
2547
  deriveSubprojectPathFromGlobs;
2545
2548
  flattenedCommandNaming;
2546
2549
  language;
@@ -2567,7 +2570,7 @@ var Config = class Config {
2567
2570
  inputRoots;
2568
2571
  configFilePath;
2569
2572
  sources;
2570
- constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
2573
+ constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, preserveUnownedHooks, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
2571
2574
  assertTargetsFeaturesExclusive({
2572
2575
  targets,
2573
2576
  features
@@ -2599,6 +2602,7 @@ var Config = class Config {
2599
2602
  this.simulateCommands = simulateCommands ?? false;
2600
2603
  this.simulateSubagents = simulateSubagents ?? false;
2601
2604
  this.simulateSkills = simulateSkills ?? false;
2605
+ this.preserveUnownedHooks = preserveUnownedHooks ?? false;
2602
2606
  this.deriveSubprojectPathFromGlobs = deriveSubprojectPathFromGlobs ?? false;
2603
2607
  this.flattenedCommandNaming = flattenedCommandNaming ?? "basename";
2604
2608
  this.language = language;
@@ -2777,6 +2781,9 @@ var Config = class Config {
2777
2781
  getSimulateCommands() {
2778
2782
  return this.simulateCommands;
2779
2783
  }
2784
+ getPreserveUnownedHooks() {
2785
+ return this.preserveUnownedHooks;
2786
+ }
2780
2787
  getFlattenedCommandNaming() {
2781
2788
  return this.flattenedCommandNaming;
2782
2789
  }
@@ -2856,6 +2863,7 @@ const getDefaults = () => ({
2856
2863
  simulateCommands: false,
2857
2864
  simulateSubagents: false,
2858
2865
  simulateSkills: false,
2866
+ preserveUnownedHooks: false,
2859
2867
  deriveSubprojectPathFromGlobs: false,
2860
2868
  flattenedCommandNaming: "basename",
2861
2869
  gitignoreTargetsOnly: true,
@@ -2904,6 +2912,7 @@ const mergeConfigs = (baseConfig, localConfig) => {
2904
2912
  simulateCommands: localConfig.simulateCommands ?? baseConfig.simulateCommands,
2905
2913
  simulateSubagents: localConfig.simulateSubagents ?? baseConfig.simulateSubagents,
2906
2914
  simulateSkills: localConfig.simulateSkills ?? baseConfig.simulateSkills,
2915
+ preserveUnownedHooks: localConfig.preserveUnownedHooks ?? baseConfig.preserveUnownedHooks,
2907
2916
  deriveSubprojectPathFromGlobs: localConfig.deriveSubprojectPathFromGlobs ?? baseConfig.deriveSubprojectPathFromGlobs,
2908
2917
  flattenedCommandNaming: localConfig.flattenedCommandNaming ?? baseConfig.flattenedCommandNaming,
2909
2918
  language: localConfig.language ?? baseConfig.language,
@@ -3127,6 +3136,11 @@ var ConfigResolver = class {
3127
3136
  file: configByFile.simulateSkills,
3128
3137
  fallback: getDefaults().simulateSkills
3129
3138
  }),
3139
+ preserveUnownedHooks: pick({
3140
+ cli: void 0,
3141
+ file: configByFile.preserveUnownedHooks,
3142
+ fallback: getDefaults().preserveUnownedHooks
3143
+ }),
3130
3144
  deriveSubprojectPathFromGlobs: pick({
3131
3145
  cli: deriveSubprojectPathFromGlobs,
3132
3146
  file: configByFile.deriveSubprojectPathFromGlobs,
@@ -4686,6 +4700,43 @@ const KIMI_CODE_NATIVE_HOOK_EVENTS = [
4686
4700
  ];
4687
4701
  const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIMI_CODE_EVENT_NAMES).map(([canonical, kimiCode]) => [kimiCode, canonical]));
4688
4702
  /**
4703
+ * Hook events supported by ZCode.
4704
+ *
4705
+ * ZCode's configuration-file hooks expose exactly seven PascalCase events, all
4706
+ * of which have a clean canonical equivalent: `SessionStart`, `PreToolUse`,
4707
+ * `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `Stop`, and
4708
+ * `UserPromptSubmit` ← `beforeSubmitPrompt`. An optional matcher (a
4709
+ * case-sensitive regular expression) is honored on all of them except
4710
+ * `UserPromptSubmit` and `Stop`, which expose no value to match against.
4711
+ * Configuration hooks are read only from the user config
4712
+ * `~/.zcode/cli/config.json` (workspace config hooks are never executed) and
4713
+ * additionally require `hooks.enabled: true` to run.
4714
+ *
4715
+ * ZCode also accepts a native `process` hook type (an argv run without a
4716
+ * shell) which has no canonical equivalent; see ZcodeHooks.
4717
+ *
4718
+ * @see https://zcode.z.ai/en/docs
4719
+ */
4720
+ const ZCODE_HOOK_EVENTS = [
4721
+ "sessionStart",
4722
+ "beforeSubmitPrompt",
4723
+ "preToolUse",
4724
+ "postToolUse",
4725
+ "postToolUseFailure",
4726
+ "permissionRequest",
4727
+ "stop"
4728
+ ];
4729
+ const CANONICAL_TO_ZCODE_EVENT_NAMES = {
4730
+ sessionStart: "SessionStart",
4731
+ beforeSubmitPrompt: "UserPromptSubmit",
4732
+ preToolUse: "PreToolUse",
4733
+ postToolUse: "PostToolUse",
4734
+ postToolUseFailure: "PostToolUseFailure",
4735
+ permissionRequest: "PermissionRequest",
4736
+ stop: "Stop"
4737
+ };
4738
+ const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
4739
+ /**
4689
4740
  * Hook events supported by Hermes Agent's native Shell Hooks system.
4690
4741
  *
4691
4742
  * Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
@@ -4837,6 +4888,7 @@ const HooksConfigSchema = z.looseObject({
4837
4888
  reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4838
4889
  grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4839
4890
  "kimi-code": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4891
+ zcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
4840
4892
  qwencode: z.optional(z.looseObject({
4841
4893
  hooks: z.optional(hooksRecordSchema),
4842
4894
  disableAllHooks: z.optional(z.boolean())
@@ -12387,14 +12439,15 @@ const KIMI_CODE_CONFIG_DECLARATION = {
12387
12439
  };
12388
12440
  /**
12389
12441
  * ZCode's settings file, which also carries model/theme/permission keys
12390
- * rulesync does not own. The workspace copy (`<project>/.zcode/config.json`)
12391
- * and the user copy (`~/.zcode/cli/config.json`) are the same file with the
12392
- * same owners, so the declaration is written once and shared a policy edit
12393
- * cannot land on one scope only. `mcp` is owned as a whole key because the
12394
- * writer recomputes it from the existing file (non-`servers` siblings carried
12395
- * over) before applying the patch.
12396
- */
12397
- const ZCODE_CONFIG_DECLARATION = {
12442
+ * rulesync does not own. Both copies are the user's primary ZCode config, so
12443
+ * every writer refuses to read-modify-write a file it could not parse rather
12444
+ * than replacing it with generated output. `mcp` and `hooks` are owned as
12445
+ * whole keys because their writers recompute each from the existing file
12446
+ * (non-owned siblings carried over) before applying the patch. ZCode never
12447
+ * executes workspace config hooks, so the workspace copy is declared with
12448
+ * `mcp` alone and the user copy adds `hooks`.
12449
+ */
12450
+ const ZCODE_WORKSPACE_CONFIG_DECLARATION = {
12398
12451
  format: "json",
12399
12452
  invalidRootPolicy: "error",
12400
12453
  features: { mcp: {
@@ -12402,6 +12455,16 @@ const ZCODE_CONFIG_DECLARATION = {
12402
12455
  ownedKeys: ["mcp"]
12403
12456
  } }
12404
12457
  };
12458
+ const ZCODE_USER_CONFIG_DECLARATION = {
12459
+ ...ZCODE_WORKSPACE_CONFIG_DECLARATION,
12460
+ features: {
12461
+ ...ZCODE_WORKSPACE_CONFIG_DECLARATION.features,
12462
+ hooks: {
12463
+ kind: "replace-owned-keys",
12464
+ ownedKeys: ["hooks"]
12465
+ }
12466
+ }
12467
+ };
12405
12468
  /**
12406
12469
  * What the two Claude Code settings files have in common: both are plain JSON,
12407
12470
  * and both validate against the one published schema, which the gateway
@@ -12659,8 +12722,8 @@ const SHARED_CONFIG_OWNERSHIP = {
12659
12722
  ownedKeys: ["mcp_servers", "schema_version"]
12660
12723
  } }
12661
12724
  },
12662
- ".zcode/config.json": ZCODE_CONFIG_DECLARATION,
12663
- ".zcode/cli/config.json": ZCODE_CONFIG_DECLARATION,
12725
+ ".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
12726
+ ".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
12664
12727
  ".kiro/agents/default.json": {
12665
12728
  format: "json",
12666
12729
  features: {
@@ -18324,6 +18387,8 @@ const ZCODE_SKILLS_DIR_PATH = join(ZCODE_DIR, "skills");
18324
18387
  const ZCODE_CONFIG_FILE_NAME = "config.json";
18325
18388
  const ZCODE_GLOBAL_CONFIG_DIR_PATH = join(ZCODE_DIR, "cli");
18326
18389
  const ZCODE_MCP_SERVERS_KEY = "servers";
18390
+ const ZCODE_HOOKS_CONFIG_KEY = "hooks";
18391
+ const ZCODE_HOOKS_EVENTS_KEY = "events";
18327
18392
  const ZCODE_AGENTS_DIR_PATH = join(ZCODE_DIR, "agents");
18328
18393
  //#endregion
18329
18394
  //#region src/features/commands/zcode-command.ts
@@ -19196,19 +19261,124 @@ function generateAmpPluginCode({ config, supportedEvents, eventMap }) {
19196
19261
  lines.push("}", "");
19197
19262
  return lines.join("\n");
19198
19263
  }
19264
+ /**
19265
+ * Name of the ownership record written next to the hooks destination it
19266
+ * describes (e.g. `.claude/.rulesync-hooks-lock.json`). Only written when hook
19267
+ * preservation is enabled, so a project that never opts in never sees it.
19268
+ */
19269
+ const HOOKS_OWNERSHIP_LOCK_FILE_NAME = ".rulesync-hooks-lock.json";
19270
+ const OwnedHookRefSchema = z.object({
19271
+ event: z.string(),
19272
+ matcher: z.optional(z.string()),
19273
+ identity: z.string()
19274
+ });
19275
+ const HooksOwnershipLockSchema = z.object({
19276
+ lockfileVersion: z.number(),
19277
+ owned: z.array(OwnedHookRefSchema)
19278
+ });
19279
+ /**
19280
+ * The comparable form of a hook entry: identical keys mean "the same hook, in
19281
+ * the same place". Encoded as JSON so no separator can collide with a matcher
19282
+ * or an identity that happens to contain one.
19283
+ */
19284
+ function ownedHookKey({ event, matcher, identity }) {
19285
+ return JSON.stringify([
19286
+ event,
19287
+ matcher ?? null,
19288
+ identity
19289
+ ]);
19290
+ }
19291
+ /**
19292
+ * Read the previous run's owned set. Anything unreadable — missing, empty,
19293
+ * malformed, or written by a different lock version — yields an empty set,
19294
+ * which makes every existing handler look third-party and therefore preserved.
19295
+ */
19296
+ function parseHooksOwnershipLock(content) {
19297
+ if (content === null || content.trim() === "") return /* @__PURE__ */ new Set();
19298
+ let parsed;
19299
+ try {
19300
+ parsed = JSON.parse(content);
19301
+ } catch {
19302
+ return /* @__PURE__ */ new Set();
19303
+ }
19304
+ const result = HooksOwnershipLockSchema.safeParse(parsed);
19305
+ if (!result.success || result.data.lockfileVersion !== 1) return /* @__PURE__ */ new Set();
19306
+ return new Set(result.data.owned.map((ref) => ownedHookKey(ref)));
19307
+ }
19308
+ function compareOwnedHookRefs(a, b) {
19309
+ const left = ownedHookKey(a);
19310
+ const right = ownedHookKey(b);
19311
+ if (left === right) return 0;
19312
+ return left < right ? -1 : 1;
19313
+ }
19314
+ /** Serialize the owned set in a stable order so regenerating produces no diff. */
19315
+ function serializeHooksOwnershipLock(owned) {
19316
+ return `${JSON.stringify({
19317
+ lockfileVersion: 1,
19318
+ owned: [...owned].toSorted(compareOwnedHookRefs)
19319
+ }, null, 2)}\n`;
19320
+ }
19321
+ /**
19322
+ * The ownership record itself. It is a rulesync by-product rather than tool
19323
+ * configuration, so it is deletable: dropping the target should take it along.
19324
+ */
19325
+ var HooksOwnershipLockFile = class extends ToolFile {
19326
+ isDeletable() {
19327
+ return true;
19328
+ }
19329
+ validate() {
19330
+ return {
19331
+ success: true,
19332
+ error: null
19333
+ };
19334
+ }
19335
+ };
19336
+ /** Build the lock file that records what this run generated. */
19337
+ function buildHooksOwnershipLockFile({ outputRoot, relativeDirPath, owned }) {
19338
+ return new HooksOwnershipLockFile({
19339
+ outputRoot,
19340
+ relativeDirPath,
19341
+ relativeFilePath: HOOKS_OWNERSHIP_LOCK_FILE_NAME,
19342
+ fileContent: serializeHooksOwnershipLock(owned),
19343
+ validate: false
19344
+ });
19345
+ }
19199
19346
  //#endregion
19200
19347
  //#region src/features/hooks/tool-hooks.ts
19201
19348
  var ToolHooks = class extends ToolFile {
19349
+ ownedHookRefs;
19202
19350
  constructor(params) {
19203
19351
  super({
19204
19352
  ...params,
19205
19353
  validate: true
19206
19354
  });
19355
+ this.ownedHookRefs = params.ownedHookRefs;
19207
19356
  if (params.validate) {
19208
19357
  const result = this.validate();
19209
19358
  if (!result.success) throw result.error;
19210
19359
  }
19211
19360
  }
19361
+ /**
19362
+ * The ownership record for this destination, or nothing when preservation is
19363
+ * off — in which case rulesync owns the whole list and needs no record.
19364
+ */
19365
+ getOwnershipLockFiles() {
19366
+ if (this.ownedHookRefs === void 0) return [];
19367
+ return [buildHooksOwnershipLockFile({
19368
+ outputRoot: this.getOutputRoot(),
19369
+ relativeDirPath: this.getRelativeDirPath(),
19370
+ owned: this.ownedHookRefs
19371
+ })];
19372
+ }
19373
+ /**
19374
+ * Whether the adapter can keep handlers it did not generate. Destinations
19375
+ * rulesync owns outright (plugin bundles) must answer `false`: there is no
19376
+ * third party writing into them, and preserving there would only make
19377
+ * removals impossible.
19378
+ */
19379
+ static supportsPreserveUnowned() {
19380
+ return false;
19381
+ }
19212
19382
  static getSettablePaths(_options) {
19213
19383
  throw new Error("Please implement this method in the subclass.");
19214
19384
  }
@@ -20645,6 +20815,292 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
20645
20815
  }
20646
20816
  };
20647
20817
  //#endregion
20818
+ //#region src/features/hooks/preserve-unowned-hook-commands.ts
20819
+ /**
20820
+ * Read the `hooks` value from a destination JSON file.
20821
+ *
20822
+ * `malformed` separates "the file has no hooks" from "the file could not be
20823
+ * read", so a caller that is about to preserve content can say out loud that
20824
+ * it is replacing a file it failed to parse.
20825
+ */
20826
+ function parseExistingHooksValue(existingContent) {
20827
+ if (existingContent.trim() === "") return {
20828
+ hooks: void 0,
20829
+ malformed: false
20830
+ };
20831
+ try {
20832
+ const parsed = JSON.parse(existingContent);
20833
+ return {
20834
+ hooks: isPlainObject$1(parsed) ? parsed.hooks : void 0,
20835
+ malformed: false
20836
+ };
20837
+ } catch {
20838
+ return {
20839
+ hooks: void 0,
20840
+ malformed: true
20841
+ };
20842
+ }
20843
+ }
20844
+ /**
20845
+ * Produce the destination hooks list, optionally keeping handlers rulesync does
20846
+ * not own.
20847
+ *
20848
+ * Ownership is decided by `previouslyOwned` — the identities recorded in the
20849
+ * destination's ownership lock on the previous run — and never by guessing from
20850
+ * the command text. That is what makes removal work: a handler rulesync wrote
20851
+ * before and no longer generates is retracted, while a handler rulesync has
20852
+ * never written is left alone. Without a lock (the first run after opting in)
20853
+ * nothing is retracted, which is the non-destructive direction.
20854
+ *
20855
+ * Callers must pass the destination's shape. Plugin destinations are fully
20856
+ * owned by rulesync and must not call this.
20857
+ */
20858
+ function mergeGeneratedHookLists({ existingContent, generatedHooks, shape, preserveUnowned, previouslyOwned, logger }) {
20859
+ const owned = collectOwnedRefs({
20860
+ generatedHooks,
20861
+ shape
20862
+ });
20863
+ if (!preserveUnowned) return {
20864
+ hooks: generatedHooks,
20865
+ owned
20866
+ };
20867
+ const { hooks: existingHooks, malformed } = parseExistingHooksValue(existingContent);
20868
+ if (malformed) logger?.warn("Replacing hooks wholesale: the existing file is not valid JSON, so no third-party hook in it can be preserved.");
20869
+ return {
20870
+ hooks: preserveUnownedHookCommands({
20871
+ existingHooks,
20872
+ generatedHooks,
20873
+ shape,
20874
+ owned,
20875
+ previouslyOwned: previouslyOwned ?? /* @__PURE__ */ new Set(),
20876
+ logger
20877
+ }),
20878
+ owned
20879
+ };
20880
+ }
20881
+ /**
20882
+ * Merge the destination's existing handlers into the generated set.
20883
+ *
20884
+ * Each existing handler falls into exactly one of three cases:
20885
+ * - this run generates it → the generated copy stands, the existing one is dropped;
20886
+ * - a previous run generated it and this one does not → it is retracted, with a warning;
20887
+ * - neither → it is unowned, and kept.
20888
+ */
20889
+ function preserveUnownedHookCommands({ existingHooks, generatedHooks, shape, owned, previouslyOwned, logger }) {
20890
+ const result = cloneGeneratedHooks({
20891
+ generatedHooks,
20892
+ shape
20893
+ });
20894
+ if (!isPlainObject$1(existingHooks)) return result;
20895
+ const seen = new Set(owned.map((ref) => ownedHookKey(ref)));
20896
+ for (const [event, existingValue] of Object.entries(existingHooks)) {
20897
+ if (isPrototypePollutionKey(event)) continue;
20898
+ if (!Array.isArray(existingValue)) {
20899
+ warnSkip({
20900
+ logger,
20901
+ event,
20902
+ expected: "an array of hook entries"
20903
+ });
20904
+ continue;
20905
+ }
20906
+ const merged = shape === "matcher-groups" ? mergeMatcherGroups({
20907
+ existing: existingValue,
20908
+ generated: result[event] ?? [],
20909
+ event,
20910
+ seen,
20911
+ previouslyOwned,
20912
+ logger
20913
+ }) : mergeFlatHandlers({
20914
+ existing: existingValue,
20915
+ generated: result[event] ?? [],
20916
+ event,
20917
+ seen,
20918
+ previouslyOwned,
20919
+ logger
20920
+ });
20921
+ if (merged.length === 0) delete result[event];
20922
+ else result[event] = merged;
20923
+ }
20924
+ return result;
20925
+ }
20926
+ function cloneGeneratedHooks({ generatedHooks, shape }) {
20927
+ const result = Object.create(null);
20928
+ for (const [event, value] of Object.entries(generatedHooks)) {
20929
+ if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
20930
+ result[event] = shape === "matcher-groups" ? value.map((group) => cloneMatcherGroup(group)) : value.map((handler) => cloneHandler(handler));
20931
+ }
20932
+ return result;
20933
+ }
20934
+ /** The identities of every handler in the generated set, in destination order. */
20935
+ function collectOwnedRefs({ generatedHooks, shape }) {
20936
+ const owned = [];
20937
+ for (const [event, value] of Object.entries(generatedHooks)) {
20938
+ if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
20939
+ if (shape === "flat") {
20940
+ for (const handler of value) if (isPlainObject$1(handler)) owned.push({
20941
+ event,
20942
+ identity: handlerIdentity(handler)
20943
+ });
20944
+ continue;
20945
+ }
20946
+ for (const group of value) {
20947
+ if (!isMatcherGroup(group)) continue;
20948
+ const matcher = matcherKey(group.matcher);
20949
+ for (const handler of group.hooks) if (isPlainObject$1(handler)) owned.push({
20950
+ event,
20951
+ matcher,
20952
+ identity: handlerIdentity(handler)
20953
+ });
20954
+ }
20955
+ }
20956
+ return owned;
20957
+ }
20958
+ function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
20959
+ const merged = [...generated];
20960
+ for (const group of existing) {
20961
+ if (!isMatcherGroup(group)) {
20962
+ warnSkip({
20963
+ logger,
20964
+ event,
20965
+ expected: "a matcher group"
20966
+ });
20967
+ continue;
20968
+ }
20969
+ const matcher = matcherKey(group.matcher);
20970
+ const preserved = group.hooks.filter((handler) => shouldPreserve({
20971
+ handler,
20972
+ event,
20973
+ matcher,
20974
+ seen,
20975
+ previouslyOwned,
20976
+ logger
20977
+ })).map((handler) => cloneHandler(handler));
20978
+ if (preserved.length === 0) continue;
20979
+ const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher);
20980
+ if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
20981
+ else merged.push({
20982
+ ...omitPrototypePollutionKeys(group),
20983
+ hooks: preserved
20984
+ });
20985
+ }
20986
+ return merged;
20987
+ }
20988
+ function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
20989
+ const preserved = [];
20990
+ for (const handler of existing) {
20991
+ if (isMatcherGroup(handler)) {
20992
+ warnSkip({
20993
+ logger,
20994
+ event,
20995
+ expected: "a flat handler"
20996
+ });
20997
+ continue;
20998
+ }
20999
+ if (!shouldPreserve({
21000
+ handler,
21001
+ event,
21002
+ matcher: void 0,
21003
+ seen,
21004
+ previouslyOwned,
21005
+ logger
21006
+ })) continue;
21007
+ preserved.push(cloneHandler(handler));
21008
+ }
21009
+ return [...generated, ...preserved];
21010
+ }
21011
+ function isMatcherGroup(value) {
21012
+ return isPlainObject$1(value) && Array.isArray(value.hooks);
21013
+ }
21014
+ function cloneMatcherGroup(group) {
21015
+ if (!isMatcherGroup(group)) return cloneHandler(group);
21016
+ return {
21017
+ ...group,
21018
+ hooks: group.hooks.map((handler) => cloneHandler(handler))
21019
+ };
21020
+ }
21021
+ function cloneHandler(handler) {
21022
+ return isPlainObject$1(handler) ? omitPrototypePollutionKeys(handler) : handler;
21023
+ }
21024
+ /**
21025
+ * Decide one existing handler, and remember it so an exact duplicate later in
21026
+ * the same event is not appended twice.
21027
+ */
21028
+ function shouldPreserve({ handler, event, matcher, seen, previouslyOwned, logger }) {
21029
+ if (!isPlainObject$1(handler)) {
21030
+ warnSkip({
21031
+ logger,
21032
+ event,
21033
+ expected: "a hook object"
21034
+ });
21035
+ return false;
21036
+ }
21037
+ const identity = handlerIdentity(handler);
21038
+ const key = ownedHookKey({
21039
+ event,
21040
+ matcher,
21041
+ identity
21042
+ });
21043
+ if (seen.has(key)) return false;
21044
+ if (previouslyOwned.has(key)) {
21045
+ logger?.warn(`Removing hook rulesync no longer generates on ${event}: ${identity}`);
21046
+ return false;
21047
+ }
21048
+ seen.add(key);
21049
+ logger?.warn(`Preserving unowned hook on ${event}: ${identity}`);
21050
+ return true;
21051
+ }
21052
+ /**
21053
+ * A handler's identity is its action, not its whole shape: two entries running
21054
+ * the same command are the same hook even if their timeouts differ, so the
21055
+ * generated one replaces the existing one instead of doubling it. Shapes with
21056
+ * no recognizable action fall back to their full structure, which keeps them
21057
+ * comparable across runs — the alternative, no identity at all, made them
21058
+ * accumulate on every generate.
21059
+ */
21060
+ function handlerIdentity(handler) {
21061
+ const type = typeof handler.type === "string" ? handler.type : inferredType(handler);
21062
+ switch (type) {
21063
+ case "http": return taggedIdentity("http", handler.url) ?? structuralIdentity(handler);
21064
+ case "mcp_tool": return mcpToolIdentity(handler) ?? structuralIdentity(handler);
21065
+ case "prompt":
21066
+ case "agent": return taggedIdentity(type, handler.prompt) ?? structuralIdentity(handler);
21067
+ case "function": return taggedIdentity("function", handler.name) ?? structuralIdentity(handler);
21068
+ default: return taggedIdentity("command", handler.command) ?? taggedIdentity("prompt", handler.prompt) ?? structuralIdentity(handler);
21069
+ }
21070
+ }
21071
+ function inferredType(handler) {
21072
+ return typeof handler.url === "string" ? "http" : "command";
21073
+ }
21074
+ function taggedIdentity(kind, value) {
21075
+ return typeof value === "string" && value !== "" ? `${kind}:${value}` : void 0;
21076
+ }
21077
+ function mcpToolIdentity(handler) {
21078
+ const server = typeof handler.server === "string" ? handler.server : "";
21079
+ const tool = typeof handler.tool === "string" ? handler.tool : "";
21080
+ if (server === "" && tool === "") return;
21081
+ return `mcp_tool:${server}:${tool}:${stableStringify$1(handler.input)}`;
21082
+ }
21083
+ function structuralIdentity(handler) {
21084
+ return `json:${stableStringify$1(handler)}`;
21085
+ }
21086
+ /** Key-order-independent JSON, so an identity survives a rewritten file. */
21087
+ function stableStringify$1(value) {
21088
+ if (Array.isArray(value)) return `[${value.map((entry) => stableStringify$1(entry)).join(",")}]`;
21089
+ if (isPlainObject$1(value)) return `{${Object.entries(value).filter(([key]) => !isPrototypePollutionKey(key)).toSorted(([left], [right]) => left === right ? 0 : left < right ? -1 : 1).map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify$1(entry)}`).join(",")}}`;
21090
+ return JSON.stringify(value) ?? "null";
21091
+ }
21092
+ /**
21093
+ * Matcher groups are matched by the whole matcher value, not just strings: a
21094
+ * non-string matcher is a shape rulesync does not emit, and collapsing every
21095
+ * such group onto one key would merge unrelated third-party groups together.
21096
+ */
21097
+ function matcherKey(matcher) {
21098
+ return matcher === void 0 ? "" : stableStringify$1(matcher);
21099
+ }
21100
+ function warnSkip({ logger, event, expected }) {
21101
+ logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
21102
+ }
21103
+ //#endregion
20648
21104
  //#region src/features/hooks/claudecode-hooks.ts
20649
21105
  const CLAUDE_CONVERTER_CONFIG = {
20650
21106
  supportedEvents: CLAUDE_HOOK_EVENTS,
@@ -20713,7 +21169,7 @@ const CLAUDE_CONVERTER_CONFIG = {
20713
21169
  commandOnly: true
20714
21170
  }]
20715
21171
  };
20716
- var ClaudecodeHooks = class extends ToolHooks {
21172
+ var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
20717
21173
  constructor(params) {
20718
21174
  super({
20719
21175
  ...params,
@@ -20748,7 +21204,13 @@ var ClaudecodeHooks = class extends ToolHooks {
20748
21204
  validate
20749
21205
  });
20750
21206
  }
20751
- static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
21207
+ static supportsPreserveUnowned() {
21208
+ return true;
21209
+ }
21210
+ static async getAuxiliaryFiles({ toolHooks } = {}) {
21211
+ return toolHooks instanceof ClaudecodeHooks ? toolHooks.getOwnershipLockFiles() : [];
21212
+ }
21213
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
20752
21214
  const paths = this.getSettablePaths({ global });
20753
21215
  const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
20754
21216
  const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
@@ -20759,11 +21221,20 @@ var ClaudecodeHooks = class extends ToolHooks {
20759
21221
  converterConfig: this.getConverterConfig(),
20760
21222
  logger
20761
21223
  });
21224
+ const preserving = preserveUnowned && this.supportsPreserveUnowned();
21225
+ const merged = mergeGeneratedHookLists({
21226
+ existingContent,
21227
+ generatedHooks: claudeHooks,
21228
+ shape: "matcher-groups",
21229
+ preserveUnowned: preserving,
21230
+ previouslyOwned: preserving ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
21231
+ logger
21232
+ });
20762
21233
  const fileContent = applySharedConfigPatch({
20763
21234
  fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
20764
21235
  feature: "hooks",
20765
21236
  existingContent,
20766
- patch: { hooks: claudeHooks },
21237
+ patch: { hooks: merged.hooks },
20767
21238
  filePath
20768
21239
  });
20769
21240
  return new this({
@@ -20771,6 +21242,7 @@ var ClaudecodeHooks = class extends ToolHooks {
20771
21242
  relativeDirPath: paths.relativeDirPath,
20772
21243
  relativeFilePath: paths.relativeFilePath,
20773
21244
  fileContent,
21245
+ ownedHookRefs: preserving ? merged.owned : void 0,
20774
21246
  validate
20775
21247
  });
20776
21248
  }
@@ -20828,6 +21300,14 @@ var ClaudecodePluginHooks = class extends ClaudecodeHooks {
20828
21300
  projectDirVar: "$CLAUDE_PLUGIN_ROOT"
20829
21301
  };
20830
21302
  }
21303
+ /**
21304
+ * The plugin bundle is generated in full by rulesync — nothing else writes
21305
+ * into it — so preserving there would buy nothing and cost the ability to
21306
+ * remove a hook.
21307
+ */
21308
+ static supportsPreserveUnowned() {
21309
+ return false;
21310
+ }
20831
21311
  static getSettablePaths() {
20832
21312
  return {
20833
21313
  relativeDirPath: CLAUDECODE_PLUGIN_HOOKS_DIR,
@@ -21258,21 +21738,30 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
21258
21738
  validate
21259
21739
  });
21260
21740
  }
21261
- static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
21741
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
21262
21742
  const paths = CodexcliHooks.getSettablePaths({ global });
21743
+ const existingContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
21263
21744
  const config = rulesyncHooks.getJson();
21264
- const codexHooks = canonicalToToolHooks({
21265
- config,
21266
- toolOverrideHooks: config.codexcli?.hooks,
21267
- converterConfig: CODEXCLI_CONVERTER_CONFIG,
21745
+ const merged = mergeGeneratedHookLists({
21746
+ existingContent,
21747
+ generatedHooks: canonicalToToolHooks({
21748
+ config,
21749
+ toolOverrideHooks: config.codexcli?.hooks,
21750
+ converterConfig: CODEXCLI_CONVERTER_CONFIG,
21751
+ logger
21752
+ }),
21753
+ shape: "matcher-groups",
21754
+ preserveUnowned,
21755
+ previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
21268
21756
  logger
21269
21757
  });
21270
- const fileContent = JSON.stringify({ hooks: codexHooks }, null, 2);
21758
+ const fileContent = JSON.stringify({ hooks: merged.hooks }, null, 2);
21271
21759
  return new CodexcliHooks({
21272
21760
  outputRoot,
21273
21761
  relativeDirPath: paths.relativeDirPath,
21274
21762
  relativeFilePath: paths.relativeFilePath,
21275
21763
  fileContent,
21764
+ ownedHookRefs: preserveUnowned ? merged.owned : void 0,
21276
21765
  validate
21277
21766
  });
21278
21767
  }
@@ -21308,8 +21797,11 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
21308
21797
  validate: false
21309
21798
  });
21310
21799
  }
21311
- static async getAuxiliaryFiles({ outputRoot = process.cwd() } = {}) {
21312
- return [await CodexcliConfigToml.fromOutputRoot({ outputRoot })];
21800
+ static supportsPreserveUnowned() {
21801
+ return true;
21802
+ }
21803
+ static async getAuxiliaryFiles({ outputRoot = process.cwd(), toolHooks } = {}) {
21804
+ return [await CodexcliConfigToml.fromOutputRoot({ outputRoot }), ...toolHooks instanceof CodexcliHooks ? toolHooks.getOwnershipLockFiles() : []];
21313
21805
  }
21314
21806
  };
21315
21807
  //#endregion
@@ -21914,7 +22406,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
21914
22406
  validate
21915
22407
  });
21916
22408
  }
21917
- static fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
22409
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
21918
22410
  const config = rulesyncHooks.getJson();
21919
22411
  const cursorSupported = new Set(CURSOR_HOOK_EVENTS);
21920
22412
  const sharedHooks = {};
@@ -21941,21 +22433,36 @@ var CursorHooks = class CursorHooks extends ToolHooks {
21941
22433
  }));
21942
22434
  if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
21943
22435
  }
22436
+ const paths = CursorHooks.getSettablePaths({ global });
22437
+ const merged = mergeGeneratedHookLists({
22438
+ existingContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "",
22439
+ generatedHooks: mappedHooks,
22440
+ shape: "flat",
22441
+ preserveUnowned,
22442
+ previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
22443
+ logger
22444
+ });
21944
22445
  const cursorConfig = {
21945
22446
  version: config.version ?? 1,
21946
- hooks: mappedHooks
22447
+ hooks: merged.hooks
21947
22448
  };
21948
22449
  const fileContent = JSON.stringify(cursorConfig, null, 2);
21949
- const paths = CursorHooks.getSettablePaths({ global });
21950
22450
  return new CursorHooks({
21951
22451
  outputRoot,
21952
22452
  relativeDirPath: paths.relativeDirPath,
21953
22453
  relativeFilePath: paths.relativeFilePath,
21954
22454
  fileContent,
22455
+ ownedHookRefs: preserveUnowned ? merged.owned : void 0,
21955
22456
  validate,
21956
22457
  rulesyncHooks
21957
22458
  });
21958
22459
  }
22460
+ static supportsPreserveUnowned() {
22461
+ return true;
22462
+ }
22463
+ static async getAuxiliaryFiles({ toolHooks } = {}) {
22464
+ return toolHooks instanceof CursorHooks ? toolHooks.getOwnershipLockFiles() : [];
22465
+ }
21959
22466
  toRulesyncHooks() {
21960
22467
  const content = this.getFileContent();
21961
22468
  const parsed = JSON.parse(content);
@@ -22802,8 +23309,10 @@ function definitionsToHermesEntries({ event, sourceEvent = event, definitions, l
22802
23309
  for (const definition of definitions) {
22803
23310
  if ((definition.type ?? "command") !== "command" || typeof definition.command !== "string" || definition.command === "") continue;
22804
23311
  const entry = { command: definition.command };
22805
- if (typeof definition.matcher === "string" && definition.matcher !== "") if (supportsMatcher) entry.matcher = definition.matcher;
22806
- else logger?.warn(`matcher "${definition.matcher}" on "${sourceEvent}" hook will be ignored — Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
23312
+ if (typeof definition.matcher === "string" && definition.matcher !== "") {
23313
+ if (supportsMatcher) entry.matcher = definition.matcher;
23314
+ else logger?.warn(`matcher "${definition.matcher}" on "${sourceEvent}" hook will be ignored — Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
23315
+ }
22807
23316
  if (typeof definition.timeout === "number") entry.timeout = definition.timeout;
22808
23317
  if (typeof definition.failClosed === "boolean") {
22809
23318
  if (event === HERMESAGENT_FAIL_CLOSED_EVENT) entry.fail_closed = definition.failClosed;
@@ -24031,8 +24540,10 @@ function canonicalToKiroHooks({ config, logger }) {
24031
24540
  key: eventName
24032
24541
  }) ?? eventName;
24033
24542
  const entries = buildKiroEntriesForEvent(definitions);
24034
- if (entries.length > 0) if (kiro[kiroEventName]) kiro[kiroEventName].push(...entries);
24035
- else kiro[kiroEventName] = entries;
24543
+ if (entries.length > 0) {
24544
+ if (kiro[kiroEventName]) kiro[kiroEventName].push(...entries);
24545
+ else kiro[kiroEventName] = entries;
24546
+ }
24036
24547
  }
24037
24548
  return kiro;
24038
24549
  }
@@ -24858,8 +25369,10 @@ function canonicalToReasonixHooks({ config, toolOverrideHooks, logger }) {
24858
25369
  if ((def.type ?? "command") !== "command") continue;
24859
25370
  if (typeof def.command !== "string") continue;
24860
25371
  const entry = { command: def.command };
24861
- if (typeof def.matcher === "string" && def.matcher !== "") if (isMatcherEvent) entry.match = def.matcher;
24862
- else logger?.warn(`matcher "${def.matcher}" on "${event}" hook will be ignored — Reasonix's "${reasonixEvent}" event does not support matchers`);
25372
+ if (typeof def.matcher === "string" && def.matcher !== "") {
25373
+ if (isMatcherEvent) entry.match = def.matcher;
25374
+ else logger?.warn(`matcher "${def.matcher}" on "${event}" hook will be ignored — Reasonix's "${reasonixEvent}" event does not support matchers`);
25375
+ }
24863
25376
  if (typeof def.description === "string" && def.description !== "") entry.description = def.description;
24864
25377
  if (typeof def.timeout === "number") entry.timeout = Math.round(def.timeout * 1e3);
24865
25378
  entries.push(entry);
@@ -25189,6 +25702,186 @@ var VibeHooks = class VibeHooks extends ToolHooks {
25189
25702
  }
25190
25703
  };
25191
25704
  //#endregion
25705
+ //#region src/features/hooks/zcode-hooks.ts
25706
+ /**
25707
+ * Single spelling of the config.json codec/policy, matching the one in
25708
+ * ZcodeMcp: fail closed on an unparseable root rather than replacing the
25709
+ * user's primary ZCode config with generated output.
25710
+ */
25711
+ function parseZcodeConfig$1(fileContent, filePath) {
25712
+ return parseSharedConfig({
25713
+ format: "json",
25714
+ fileContent,
25715
+ filePath,
25716
+ invalidRootPolicy: "error"
25717
+ });
25718
+ }
25719
+ /**
25720
+ * Drop ZCode's native `process` hooks from an `events` map before the shared
25721
+ * import converter runs. The converter would coerce the unknown type to
25722
+ * `command` and import the executable alone — losing the `args` vector that
25723
+ * *is* the process hook's command line — so the imported hook would run
25724
+ * something else than the file states. Matcher groups left without hooks are
25725
+ * dropped with them.
25726
+ */
25727
+ function stripProcessHooks({ events, logger }) {
25728
+ if (!isRecord$1(events)) return events;
25729
+ const result = Object.create(null);
25730
+ for (const [eventName, entries] of Object.entries(events)) {
25731
+ if (!Array.isArray(entries)) {
25732
+ result[eventName] = entries;
25733
+ continue;
25734
+ }
25735
+ const kept = [];
25736
+ for (const entry of entries) {
25737
+ if (!isRecord$1(entry) || !Array.isArray(entry.hooks)) {
25738
+ kept.push(entry);
25739
+ continue;
25740
+ }
25741
+ const hooks = entry.hooks.filter((hook) => {
25742
+ if (isRecord$1(hook) && hook.type === "process") {
25743
+ logger?.warn(`Skipping a ZCode "process" hook on "${eventName}" while importing: it has no canonical equivalent, and importing its executable alone would change what it runs.`);
25744
+ return false;
25745
+ }
25746
+ return true;
25747
+ });
25748
+ if (hooks.length > 0) kept.push({
25749
+ ...entry,
25750
+ hooks
25751
+ });
25752
+ }
25753
+ result[eventName] = kept;
25754
+ }
25755
+ return result;
25756
+ }
25757
+ const ZCODE_CONVERTER_CONFIG = {
25758
+ supportedEvents: ZCODE_HOOK_EVENTS,
25759
+ canonicalToToolEventNames: CANONICAL_TO_ZCODE_EVENT_NAMES,
25760
+ toolToCanonicalEventNames: ZCODE_TO_CANONICAL_EVENT_NAMES,
25761
+ projectDirVar: "",
25762
+ noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
25763
+ supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
25764
+ booleanPassthroughFields: [{
25765
+ canonical: "async",
25766
+ tool: "async"
25767
+ }, {
25768
+ canonical: "enabled",
25769
+ tool: "enabled"
25770
+ }],
25771
+ stringPassthroughFields: [{
25772
+ canonical: "statusMessage",
25773
+ tool: "statusMessage"
25774
+ }, {
25775
+ canonical: "shell",
25776
+ tool: "shell",
25777
+ commandOnly: true
25778
+ }],
25779
+ wildcardMatcherMeansAll: true
25780
+ };
25781
+ /**
25782
+ * ZCode hooks.
25783
+ *
25784
+ * ZCode reads configuration-file hooks from the `hooks` block of its user
25785
+ * config file, `~/.zcode/cli/config.json`. Workspace config hooks are never
25786
+ * executed — the workspace file is ignored regardless of `hooks.enabled` — so
25787
+ * rulesync treats ZCode hooks as global-only. The event map is nested under
25788
+ * `hooks.events` beside the user-tunable `enabled` and `timeoutMs` siblings,
25789
+ * which are carried over while `events` is replaced.
25790
+ *
25791
+ * @see https://zcode.z.ai/en/docs
25792
+ */
25793
+ var ZcodeHooks = class ZcodeHooks extends ToolHooks {
25794
+ json;
25795
+ constructor(params) {
25796
+ super(params);
25797
+ this.json = parseZcodeConfig$1(this.fileContent ?? "{}", join(this.relativeDirPath, this.relativeFilePath));
25798
+ }
25799
+ isDeletable() {
25800
+ return false;
25801
+ }
25802
+ static getSettablePaths(_options = {}) {
25803
+ return {
25804
+ relativeDirPath: ZCODE_GLOBAL_CONFIG_DIR_PATH,
25805
+ relativeFilePath: ZCODE_CONFIG_FILE_NAME
25806
+ };
25807
+ }
25808
+ static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
25809
+ const paths = this.getSettablePaths({ global });
25810
+ const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
25811
+ return new ZcodeHooks({
25812
+ outputRoot,
25813
+ relativeDirPath: paths.relativeDirPath,
25814
+ relativeFilePath: paths.relativeFilePath,
25815
+ fileContent,
25816
+ validate,
25817
+ global
25818
+ });
25819
+ }
25820
+ static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
25821
+ const paths = this.getSettablePaths({ global });
25822
+ const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
25823
+ const existingContent = await readFileContentOrNull(filePath) ?? "{}";
25824
+ const existing = parseZcodeConfig$1(existingContent, filePath);
25825
+ const config = rulesyncHooks.getJson();
25826
+ const events = canonicalToToolHooks({
25827
+ config,
25828
+ toolOverrideHooks: config.zcode?.hooks,
25829
+ converterConfig: ZCODE_CONVERTER_CONFIG,
25830
+ logger
25831
+ });
25832
+ const existingHooks = isRecord$1(existing["hooks"]) ? existing[ZCODE_HOOKS_CONFIG_KEY] : {};
25833
+ const shouldStateEnabled = Object.keys(events).length > 0 && existingHooks.enabled === void 0;
25834
+ return new ZcodeHooks({
25835
+ outputRoot,
25836
+ relativeDirPath: paths.relativeDirPath,
25837
+ relativeFilePath: paths.relativeFilePath,
25838
+ fileContent: applySharedConfigPatch({
25839
+ fileKey: sharedConfigFileKey(paths),
25840
+ feature: "hooks",
25841
+ existingContent,
25842
+ patch: { [ZCODE_HOOKS_CONFIG_KEY]: {
25843
+ ...existingHooks,
25844
+ ...shouldStateEnabled ? { enabled: true } : {},
25845
+ [ZCODE_HOOKS_EVENTS_KEY]: events
25846
+ } },
25847
+ filePath
25848
+ }),
25849
+ validate,
25850
+ global
25851
+ });
25852
+ }
25853
+ toRulesyncHooks({ logger } = {}) {
25854
+ const hooks = toolHooksToCanonical({
25855
+ hooks: stripProcessHooks({
25856
+ events: (isRecord$1(this.json["hooks"]) ? this.json[ZCODE_HOOKS_CONFIG_KEY] : {})[ZCODE_HOOKS_EVENTS_KEY],
25857
+ logger
25858
+ }),
25859
+ converterConfig: ZCODE_CONVERTER_CONFIG,
25860
+ logger
25861
+ });
25862
+ return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
25863
+ hooks,
25864
+ overrideKey: "zcode"
25865
+ }), null, 2) });
25866
+ }
25867
+ validate() {
25868
+ return {
25869
+ success: true,
25870
+ error: null
25871
+ };
25872
+ }
25873
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
25874
+ return new ZcodeHooks({
25875
+ outputRoot,
25876
+ relativeDirPath,
25877
+ relativeFilePath,
25878
+ fileContent: JSON.stringify({ [ZCODE_HOOKS_CONFIG_KEY]: { [ZCODE_HOOKS_EVENTS_KEY]: {} } }, null, 2),
25879
+ validate: false,
25880
+ global
25881
+ });
25882
+ }
25883
+ };
25884
+ //#endregion
25192
25885
  //#region src/features/hooks/hooks-processor.ts
25193
25886
  const HooksProcessorToolTargetSchema = z.enum(hooksProcessorToolTargetTuple);
25194
25887
  /**
@@ -25260,8 +25953,12 @@ const HOOKS_OVERRIDE_KEY_ALIASES = {
25260
25953
  "kiro-cli": KIRO_HOOKS_OVERRIDE_KEY,
25261
25954
  "kiro-ide": KIRO_HOOKS_OVERRIDE_KEY
25262
25955
  };
25263
- /** The targets writing the standalone `.kiro/hooks/*.json` v1 format. */
25264
- const KIRO_STANDALONE_HOOKS_TARGETS = /* @__PURE__ */ new Set(["kiro-cli", "kiro-ide"]);
25956
+ /** The targets whose hooks format carries a per-hook on-disk enable flag. */
25957
+ const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
25958
+ "kiro-cli",
25959
+ "kiro-ide",
25960
+ "zcode"
25961
+ ]);
25265
25962
  const toolHooksFactories = /* @__PURE__ */ new Map([
25266
25963
  ["amp", {
25267
25964
  class: AmpHooks,
@@ -25610,6 +26307,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
25610
26307
  supportedEvents: GROKCLI_HOOK_EVENTS,
25611
26308
  supportedHookTypes: ["command", "http"],
25612
26309
  supportsMatcher: true
26310
+ }],
26311
+ ["zcode", {
26312
+ class: ZcodeHooks,
26313
+ meta: {
26314
+ supportsProject: false,
26315
+ supportsGlobal: true,
26316
+ supportsImport: true
26317
+ },
26318
+ supportedEvents: ZCODE_HOOK_EVENTS,
26319
+ supportedHookTypes: ["command"],
26320
+ supportsMatcher: true
25613
26321
  }]
25614
26322
  ]);
25615
26323
  const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
@@ -25619,7 +26327,8 @@ const hooksProcessorToolTargetsGlobalImportable = [...toolHooksFactories.entries
25619
26327
  var HooksProcessor = class extends FeatureProcessor {
25620
26328
  toolTarget;
25621
26329
  global;
25622
- constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, dryRun = false, logger }) {
26330
+ preserveUnownedHooks;
26331
+ constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, dryRun = false, preserveUnownedHooks = false, logger }) {
25623
26332
  super({
25624
26333
  outputRoot,
25625
26334
  inputRoots,
@@ -25630,6 +26339,7 @@ var HooksProcessor = class extends FeatureProcessor {
25630
26339
  if (!result.success) throw new Error(`Invalid tool target for HooksProcessor: ${toolTarget}. ${formatError(result.error)}`);
25631
26340
  this.toolTarget = result.data;
25632
26341
  this.global = global;
26342
+ this.preserveUnownedHooks = preserveUnownedHooks;
25633
26343
  }
25634
26344
  async loadRulesyncFiles() {
25635
26345
  const relativePaths = getRulesyncSourceCandidates({ paths: RulesyncHooks.getSettablePaths() }).map((candidate) => candidate.relativeFilePath);
@@ -25721,14 +26431,14 @@ var HooksProcessor = class extends FeatureProcessor {
25721
26431
  }
25722
26432
  for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
25723
26433
  }
25724
- if (!KIRO_STANDALONE_HOOKS_TARGETS.has(this.toolTarget)) {
26434
+ if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
25725
26435
  const skippedEvents = new Set(unsupportedEventNames({
25726
26436
  factory,
25727
26437
  sharedHooks,
25728
26438
  effectiveHooks
25729
26439
  }));
25730
26440
  const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
25731
- if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide standalone hooks format supports the flag): ${eventsWithDisabledHooks.join(", ")}`);
26441
+ 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(", ")}`);
25732
26442
  }
25733
26443
  const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
25734
26444
  factory,
@@ -25740,6 +26450,7 @@ var HooksProcessor = class extends FeatureProcessor {
25740
26450
  rulesyncHooks,
25741
26451
  validate: true,
25742
26452
  global: this.global,
26453
+ preserveUnowned: this.preserveUnownedHooks,
25743
26454
  logger: withToolTargetPrefix({
25744
26455
  logger: this.logger,
25745
26456
  toolTarget: this.toolTarget
@@ -28427,12 +29138,16 @@ function convertFromCodexFormat(codexMcp) {
28427
29138
  } else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthFromCodex(value);
28428
29139
  else if (Object.hasOwn(CODEX_TO_RULESYNC_FIELD_MAP, key)) {
28429
29140
  const mappedKey = CODEX_TO_RULESYNC_FIELD_MAP[key];
28430
- if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
28431
- else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
29141
+ if (mappedKey) {
29142
+ if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
29143
+ else warnWithFallback(void 0, `Ignored malformed array for ${key} in MCP server ${name}`);
29144
+ }
28432
29145
  } else if (Object.hasOwn(CODEX_TO_RULESYNC_SCALAR_FIELD_MAP, key)) {
28433
29146
  const mappedKey = CODEX_TO_RULESYNC_SCALAR_FIELD_MAP[key];
28434
- if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
28435
- else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
29147
+ if (mappedKey) {
29148
+ if (typeof value === "string") converted[mappedKey] = value;
29149
+ else warnWithFallback(void 0, `Ignored malformed value for ${key} in MCP server ${name}: expected a string`);
29150
+ }
28436
29151
  } else converted[key] = value;
28437
29152
  }
28438
29153
  restateCanonicalTransport(converted);
@@ -28464,12 +29179,16 @@ function convertToCodexFormat(mcpServers) {
28464
29179
  } else if (key === "oauth" && isRecord$1(value)) converted[key] = mapOauthToCodex(value);
28465
29180
  else if (Object.hasOwn(RULESYNC_TO_CODEX_FIELD_MAP, key)) {
28466
29181
  const mappedKey = RULESYNC_TO_CODEX_FIELD_MAP[key];
28467
- if (mappedKey) if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
28468
- else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
29182
+ if (mappedKey) {
29183
+ if (isValidRenamedArray(key, value)) converted[mappedKey] = value;
29184
+ else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string array, got ${typeof value}`);
29185
+ }
28469
29186
  } else if (Object.hasOwn(RULESYNC_TO_CODEX_SCALAR_FIELD_MAP, key)) {
28470
29187
  const mappedKey = RULESYNC_TO_CODEX_SCALAR_FIELD_MAP[key];
28471
- if (mappedKey) if (typeof value === "string") converted[mappedKey] = value;
28472
- else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
29188
+ if (mappedKey) {
29189
+ if (typeof value === "string") converted[mappedKey] = value;
29190
+ else warnWithFallback(void 0, `[CodexCliMcp] Skipping invalid value type for mapped key '${key}': expected string, got ${typeof value}`);
29191
+ }
28473
29192
  } else converted[key] = value;
28474
29193
  }
28475
29194
  const previousName = originalNames.get(codexName);
@@ -28713,8 +29432,10 @@ function resolveRemoteMcpUrl(serverConfig) {
28713
29432
  /** The `command` array a `local` server is spawned with, `args` merged in. */
28714
29433
  function resolveLocalMcpCommand(serverConfig) {
28715
29434
  const commandArray = [];
28716
- if (serverConfig.command) if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
28717
- else commandArray.push(serverConfig.command);
29435
+ if (serverConfig.command) {
29436
+ if (Array.isArray(serverConfig.command)) commandArray.push(...serverConfig.command);
29437
+ else commandArray.push(serverConfig.command);
29438
+ }
28718
29439
  if (serverConfig.args) commandArray.push(...serverConfig.args);
28719
29440
  return commandArray;
28720
29441
  }
@@ -29251,8 +29972,10 @@ function toDeepagentsServer({ name, server, logger }) {
29251
29972
  const { enabledTools, disabledTools, type: _type, transport, ...rest } = server;
29252
29973
  const converted = { ...rest };
29253
29974
  const normalized = normalizeDeepagentsTransport(rawTransport);
29254
- if (normalized !== void 0) if (transport !== void 0) converted.transport = normalized;
29255
- else converted.type = normalized;
29975
+ if (normalized !== void 0) {
29976
+ if (transport !== void 0) converted.transport = normalized;
29977
+ else converted.type = normalized;
29978
+ }
29256
29979
  if (enabledTools !== void 0 && disabledTools !== void 0) return warnAndSkipMcpServer({
29257
29980
  toolName: TOOL_NAME,
29258
29981
  serverName: name,
@@ -29267,8 +29990,10 @@ function toDeepagentsServer({ name, server, logger }) {
29267
29990
  logger
29268
29991
  });
29269
29992
  converted.allowedTools = enabledTools;
29270
- } else if (disabledTools !== void 0) if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
29271
- else converted.disabledTools = disabledTools;
29993
+ } else if (disabledTools !== void 0) {
29994
+ if (disabledTools.length === 0) logger?.warn(`${TOOL_NAME} MCP: dropping the empty disabledTools list on "${name}"; it denies nothing, and deepagents rejects the empty form.`);
29995
+ else converted.disabledTools = disabledTools;
29996
+ }
29272
29997
  return converted;
29273
29998
  }
29274
29999
  /** Lift dcode's spellings back into the canonical model. */
@@ -32205,14 +32930,16 @@ function rulesyncMcpServerToReasonix(name, server, logger) {
32205
32930
  name,
32206
32931
  ...type !== void 0 && { type }
32207
32932
  };
32208
- if (server.command !== void 0) if (Array.isArray(server.command)) {
32209
- const [command, ...commandArgs] = server.command;
32210
- if (command !== void 0) plugin.command = command;
32211
- const args = [...commandArgs, ...server.args ?? []];
32212
- if (args.length > 0) plugin.args = args;
32213
- } else {
32214
- plugin.command = server.command;
32215
- if (server.args !== void 0) plugin.args = server.args;
32933
+ if (server.command !== void 0) {
32934
+ if (Array.isArray(server.command)) {
32935
+ const [command, ...commandArgs] = server.command;
32936
+ if (command !== void 0) plugin.command = command;
32937
+ const args = [...commandArgs, ...server.args ?? []];
32938
+ if (args.length > 0) plugin.args = args;
32939
+ } else {
32940
+ plugin.command = server.command;
32941
+ if (server.args !== void 0) plugin.args = server.args;
32942
+ }
32216
32943
  }
32217
32944
  for (const field of REASONIX_PLUGIN_FIELDS) {
32218
32945
  if (field === "type" || field === "command" || field === "args") continue;
@@ -33280,14 +34007,16 @@ function rulesyncMcpServerToVibe(name, server, existing) {
33280
34007
  name,
33281
34008
  ...transport !== void 0 && { transport }
33282
34009
  };
33283
- if (server.command !== void 0) if (Array.isArray(server.command)) {
33284
- const [command, ...commandArgs] = server.command;
33285
- if (command !== void 0) vibeServer.command = command;
33286
- const args = [...commandArgs, ...server.args ?? []];
33287
- if (args.length > 0) vibeServer.args = args;
33288
- } else {
33289
- vibeServer.command = server.command;
33290
- if (server.args !== void 0) vibeServer.args = server.args;
34010
+ if (server.command !== void 0) {
34011
+ if (Array.isArray(server.command)) {
34012
+ const [command, ...commandArgs] = server.command;
34013
+ if (command !== void 0) vibeServer.command = command;
34014
+ const args = [...commandArgs, ...server.args ?? []];
34015
+ if (args.length > 0) vibeServer.args = args;
34016
+ } else {
34017
+ vibeServer.command = server.command;
34018
+ if (server.args !== void 0) vibeServer.args = server.args;
34019
+ }
33291
34020
  }
33292
34021
  const hasStructuredAuth = serverRecord.auth !== void 0;
33293
34022
  for (const field of VIBE_MCP_SERVER_FIELDS) {
@@ -65185,8 +65914,10 @@ var RooRule = class RooRule extends ToolRule {
65185
65914
  nonRootPath: this.getSettablePaths().nonRoot
65186
65915
  });
65187
65916
  const mode = rulesyncRule.getFrontmatter().roo?.mode;
65188
- if (!params.root && mode !== void 0 && mode !== "") if (!ROO_MODE_SLUG_PATTERN.test(mode)) warnWithFallback(void 0, `Ignoring roo.mode "${mode}" on ${rulesyncRule.getRelativeFilePath()}: a mode slug may contain only letters, digits and hyphens. Writing the rule to ${params.relativeDirPath} instead.`);
65189
- else params.relativeDirPath = join(dirname(params.relativeDirPath), rooModeRulesDirName(mode));
65917
+ if (!params.root && mode !== void 0 && mode !== "") {
65918
+ if (!ROO_MODE_SLUG_PATTERN.test(mode)) warnWithFallback(void 0, `Ignoring roo.mode "${mode}" on ${rulesyncRule.getRelativeFilePath()}: a mode slug may contain only letters, digits and hyphens. Writing the rule to ${params.relativeDirPath} instead.`);
65919
+ else params.relativeDirPath = join(dirname(params.relativeDirPath), rooModeRulesDirName(mode));
65920
+ }
65190
65921
  return new RooRule(params);
65191
65922
  }
65192
65923
  /**
@@ -68951,6 +69682,7 @@ async function generateHooksCore(params) {
68951
69682
  toolTarget,
68952
69683
  global: config.getGlobal(),
68953
69684
  dryRun: config.isPreviewMode(),
69685
+ preserveUnownedHooks: config.getPreserveUnownedHooks(),
68954
69686
  logger
68955
69687
  });
68956
69688
  const result = await processFeatureWithRulesyncFiles({
@@ -69422,4 +70154,4 @@ async function importChecksCore(params) {
69422
70154
  //#endregion
69423
70155
  export { RulesyncCheckFrontmatterSchema as $, ALL_TOOL_TARGETS_WITH_WILDCARD as $t, FACTORYDROID_DIR as A, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as An, fileExists as At, RulesyncSkillFrontmatterSchema as B, stripControlCharacters as Bn, readFileContent as Bt, CODEXCLI_BASH_RULES_FILE_NAME as C, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as Cn, assertDirectoryIfExists as Ct, CLAUDECODE_MEMORIES_DIR_NAME as D, RULESYNC_RULES_RELATIVE_DIR_PATH as Dn, createTempDirectory as Dt, CLAUDECODE_LOCAL_RULE_FILE_NAME as E, RULESYNC_RELATIVE_DIR_PATH as En, checkPathTraversal as Et, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as F, formatError as Fn, isSymlink as Ft, RulesyncIgnore as G, removeFileStrict as Gt, RulesyncRuleFrontmatterSchema as H, stripHiddenCharacters as Hn, removeDirectory as Ht, getLocalSkillDirNames as I, truncateText as In, listDirectoryEntryNames as It, resolveRulesyncSourceWritePath as J, runWithDirectoryRollback as Jt, RulesyncHooks as K, removeTempDirectory as Kt, RulesyncSubagent as L, hasDeceptiveHiddenCharacters as Ln, listFilePathsRecursively as Lt, caseFoldIdentity as M, ALL_FEATURES as Mn, getHomeDirectory as Mt, groupSpellingsByCaseFoldedIdentity as N, ALL_FEATURES_WITH_WILDCARD as Nn, isFileNotFoundError as Nt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as O, RULESYNC_SKILLS_RELATIVE_DIR_PATH as On, directoryExists as Ot, AUGMENTCODE_DIR as P, DEPRECATED_FEATURE_REPLACEMENTS as Pn, isFileSystemError as Pt, RulesyncCheck as Q, ALL_TOOL_TARGETS as Qt, RulesyncSubagentFrontmatterSchema as R, hasEnclosingMarkOutsideKeycap as Rn, listSubdirectoryNames as Rt, ChecksProcessor as S, RULESYNC_PERMISSIONS_FILE_NAME as Sn, applyFileMode as St, CLAUDECODE_DIR as T, RULESYNC_PERMISSIONS_SCHEMA_URL as Tn, assertWritablePathInsideRoot as Tt, RulesyncPermissions as U, removeDirectoryStrict as Ut, RulesyncRule as V, stripControlCharactersKeepingLineFeeds as Vn, readFileContentOrNull as Vt, RulesyncMcp as W, removeFile as Wt, RulesyncCommand as X, writeFileBuffer as Xt, parseJsonc as Y, toPosixPath as Yt, RulesyncCommandFrontmatterSchema as Z, writeFileContent as Zt, IgnoreProcessor as _, RULESYNC_MCP_FILE_NAME as _n, withFallbackLoggerTarget as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as an, mergeInputRootConfigs as at, QWENCODE_DIR as b, RULESYNC_MCP_SCHEMA_URL as bn, CLIError as bt, RulesProcessor as c, RULESYNC_CONFIG_RELATIVE_FILE_PATH as cn, ConfigFileSchema as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as dn, findControlCharacter as dt, PACKAGING_TOOL_TARGETS as en, stringifyFrontmatter as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_HOOKS_FILE_NAME as fn, ConsoleLogger as ft, McpProcessor as g, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as gn, warnOnConflictingFlags as gt, shortenToWidth as h, RULESYNC_IGNORE_RELATIVE_FILE_PATH as hn, fallbackLogger as ht, inspectInputRoots as i, RULESYNC_AIIGNORE_FILE_NAME as in, ConfigResolver as it, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as j, parseCommaSeparatedList as jn, getFileSize as jt, CLAUDECODE_SKILLS_DIR_PATH as k, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as kn, ensureDir as kt, SubagentsProcessor as l, RULESYNC_CONFIG_SCHEMA_URL as ln, GITIGNORE_DESTINATION_KEY as lt, displayWidthOf as m, RULESYNC_HOOKS_RELATIVE_FILE_PATH as mn, WarningCollectingLogger as mt, formatSourceLoadFailure as n, CURATED_RULES_FEATURE_SUBDIR as nn, SHARED_USER_MANAGED_CONFIG_PATHS as nt, convertFromTool as o, RULESYNC_CHECKS_RELATIVE_DIR_PATH as on, resolveEffectiveInputRoots as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_LEGACY_FILE_NAME as pn, JsonLogger as pt, getRulesyncSourceCandidates as q, resolvePath as qt, generate as r, MAX_FILE_SIZE as rn, SKILL_FILE_NAME as rt, isPackagingToolTarget as s, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as sn, CONFLICTING_TARGET_PAIRS as st, importFromTool as t, ToolTargetSchema as tn, loadYaml as tt, SkillsProcessor as u, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as un, SourceEntrySchema as ut, HooksProcessor as v, RULESYNC_MCP_LEGACY_FILE_NAME as vn, resetRunWarningState as vt, CODEXCLI_DIR as w, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as wn, assertTreeContainsNoSymlinks as wt, QWENCODE_LOCAL_RULE_FILE_NAME as x, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as xn, ErrorCodes as xt, CommandsProcessor as y, RULESYNC_MCP_RELATIVE_FILE_PATH as yn, withWarnOnceScope as yt, RulesyncSkill as z, quoteForLog as zn, pathEscapesRoot as zt };
69424
70156
 
69425
- //# sourceMappingURL=import-DyWRanf6.js.map
70157
+ //# sourceMappingURL=import-B6p6c3L0.js.map