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.
- package/README.md +1 -1
- package/dist/cli/index.cjs +27 -21
- package/dist/cli/index.js +27 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DyWRanf6.js → import-B6p6c3L0.js} +802 -70
- package/dist/import-B6p6c3L0.js.map +1 -0
- package/dist/{import-C0WSCbU9.cjs → import-DwtzMiDG.cjs} +818 -86
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +10 -10
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +20 -20
- package/dist/import-DyWRanf6.js.map +0 -1
|
@@ -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.
|
|
12391
|
-
*
|
|
12392
|
-
*
|
|
12393
|
-
*
|
|
12394
|
-
*
|
|
12395
|
-
*
|
|
12396
|
-
|
|
12397
|
-
|
|
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":
|
|
12663
|
-
".zcode/cli/config.json":
|
|
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
|
|
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:
|
|
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
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
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:
|
|
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
|
|
21312
|
-
return
|
|
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:
|
|
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 !== "")
|
|
22806
|
-
|
|
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)
|
|
24035
|
-
|
|
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 !== "")
|
|
24862
|
-
|
|
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
|
|
25264
|
-
const
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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)
|
|
28431
|
-
|
|
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)
|
|
28435
|
-
|
|
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)
|
|
28468
|
-
|
|
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)
|
|
28472
|
-
|
|
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)
|
|
28717
|
-
|
|
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)
|
|
29255
|
-
|
|
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)
|
|
29271
|
-
|
|
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)
|
|
32209
|
-
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32213
|
-
|
|
32214
|
-
|
|
32215
|
-
|
|
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)
|
|
33284
|
-
|
|
33285
|
-
|
|
33286
|
-
|
|
33287
|
-
|
|
33288
|
-
|
|
33289
|
-
|
|
33290
|
-
|
|
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 !== "")
|
|
65189
|
-
|
|
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-
|
|
70157
|
+
//# sourceMappingURL=import-B6p6c3L0.js.map
|