rulesync 16.24.1 → 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 +5 -5
- package/dist/cli/index.js +5 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-DL8paEMP.js → import-B6p6c3L0.js} +738 -32
- package/dist/import-B6p6c3L0.js.map +1 -0
- package/dist/{import-CI0pN6s5.cjs → import-DwtzMiDG.cjs} +754 -48
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/import-DL8paEMP.js.map +0 -1
|
@@ -444,34 +444,34 @@ const isFeatureValueEnabled = (value) => {
|
|
|
444
444
|
const parseCommaSeparatedList = (value) => value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
445
445
|
//#endregion
|
|
446
446
|
//#region src/constants/rulesync-paths.ts
|
|
447
|
-
const { join: join$
|
|
447
|
+
const { join: join$305 } = node_path.posix;
|
|
448
448
|
const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
|
|
449
449
|
const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
|
|
450
450
|
const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
|
|
451
451
|
const RULES_FEATURE_SUBDIR = "rules";
|
|
452
|
-
const CURATED_RULES_FEATURE_SUBDIR = join$
|
|
452
|
+
const CURATED_RULES_FEATURE_SUBDIR = join$305(RULES_FEATURE_SUBDIR, ".curated");
|
|
453
453
|
const COMMANDS_FEATURE_SUBDIR = "commands";
|
|
454
454
|
const SUBAGENTS_FEATURE_SUBDIR = "subagents";
|
|
455
455
|
const CHECKS_FEATURE_SUBDIR = "checks";
|
|
456
456
|
const SKILLS_FEATURE_SUBDIR = "skills";
|
|
457
|
-
const CURATED_SKILLS_FEATURE_SUBDIR = join$
|
|
458
|
-
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$
|
|
459
|
-
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$
|
|
460
|
-
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$
|
|
461
|
-
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$
|
|
462
|
-
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$
|
|
463
|
-
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$
|
|
464
|
-
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$
|
|
465
|
-
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$
|
|
466
|
-
join$
|
|
467
|
-
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
468
|
-
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$
|
|
457
|
+
const CURATED_SKILLS_FEATURE_SUBDIR = join$305(SKILLS_FEATURE_SUBDIR, ".curated");
|
|
458
|
+
const RULESYNC_RULES_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, RULES_FEATURE_SUBDIR);
|
|
459
|
+
const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, CURATED_RULES_FEATURE_SUBDIR);
|
|
460
|
+
const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, COMMANDS_FEATURE_SUBDIR);
|
|
461
|
+
const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, SUBAGENTS_FEATURE_SUBDIR);
|
|
462
|
+
const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, CHECKS_FEATURE_SUBDIR);
|
|
463
|
+
const RULESYNC_MCP_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
|
|
464
|
+
const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
|
|
465
|
+
const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
|
|
466
|
+
join$305(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
|
|
467
|
+
const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
|
|
468
|
+
const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
|
|
469
469
|
const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
|
|
470
|
-
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$
|
|
470
|
+
const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
|
|
471
471
|
const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
|
|
472
472
|
const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
|
|
473
|
-
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$
|
|
474
|
-
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$
|
|
473
|
+
const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, SKILLS_FEATURE_SUBDIR);
|
|
474
|
+
const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$305(RULESYNC_RELATIVE_DIR_PATH, CURATED_SKILLS_FEATURE_SUBDIR);
|
|
475
475
|
const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
|
|
476
476
|
const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
|
|
477
477
|
const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
|
|
@@ -738,7 +738,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
738
738
|
"vibe",
|
|
739
739
|
"qwencode",
|
|
740
740
|
"reasonix",
|
|
741
|
-
"grokcli"
|
|
741
|
+
"grokcli",
|
|
742
|
+
"zcode"
|
|
742
743
|
];
|
|
743
744
|
const permissionsProcessorToolTargetTuple = [
|
|
744
745
|
"amp",
|
|
@@ -2418,6 +2419,7 @@ const ConfigParamsSchema = zod_mini.z.object({
|
|
|
2418
2419
|
simulateCommands: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
2419
2420
|
simulateSubagents: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
2420
2421
|
simulateSkills: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
2422
|
+
preserveUnownedHooks: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
2421
2423
|
deriveSubprojectPathFromGlobs: (0, zod_mini.optional)(zod_mini.z.boolean()),
|
|
2422
2424
|
flattenedCommandNaming: (0, zod_mini.optional)(FlattenedCommandNamingSchema),
|
|
2423
2425
|
/**
|
|
@@ -2566,6 +2568,7 @@ var Config = class Config {
|
|
|
2566
2568
|
simulateCommands;
|
|
2567
2569
|
simulateSubagents;
|
|
2568
2570
|
simulateSkills;
|
|
2571
|
+
preserveUnownedHooks;
|
|
2569
2572
|
deriveSubprojectPathFromGlobs;
|
|
2570
2573
|
flattenedCommandNaming;
|
|
2571
2574
|
language;
|
|
@@ -2592,7 +2595,7 @@ var Config = class Config {
|
|
|
2592
2595
|
inputRoots;
|
|
2593
2596
|
configFilePath;
|
|
2594
2597
|
sources;
|
|
2595
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
|
|
2598
|
+
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 }) {
|
|
2596
2599
|
assertTargetsFeaturesExclusive({
|
|
2597
2600
|
targets,
|
|
2598
2601
|
features
|
|
@@ -2624,6 +2627,7 @@ var Config = class Config {
|
|
|
2624
2627
|
this.simulateCommands = simulateCommands ?? false;
|
|
2625
2628
|
this.simulateSubagents = simulateSubagents ?? false;
|
|
2626
2629
|
this.simulateSkills = simulateSkills ?? false;
|
|
2630
|
+
this.preserveUnownedHooks = preserveUnownedHooks ?? false;
|
|
2627
2631
|
this.deriveSubprojectPathFromGlobs = deriveSubprojectPathFromGlobs ?? false;
|
|
2628
2632
|
this.flattenedCommandNaming = flattenedCommandNaming ?? "basename";
|
|
2629
2633
|
this.language = language;
|
|
@@ -2802,6 +2806,9 @@ var Config = class Config {
|
|
|
2802
2806
|
getSimulateCommands() {
|
|
2803
2807
|
return this.simulateCommands;
|
|
2804
2808
|
}
|
|
2809
|
+
getPreserveUnownedHooks() {
|
|
2810
|
+
return this.preserveUnownedHooks;
|
|
2811
|
+
}
|
|
2805
2812
|
getFlattenedCommandNaming() {
|
|
2806
2813
|
return this.flattenedCommandNaming;
|
|
2807
2814
|
}
|
|
@@ -2881,6 +2888,7 @@ const getDefaults = () => ({
|
|
|
2881
2888
|
simulateCommands: false,
|
|
2882
2889
|
simulateSubagents: false,
|
|
2883
2890
|
simulateSkills: false,
|
|
2891
|
+
preserveUnownedHooks: false,
|
|
2884
2892
|
deriveSubprojectPathFromGlobs: false,
|
|
2885
2893
|
flattenedCommandNaming: "basename",
|
|
2886
2894
|
gitignoreTargetsOnly: true,
|
|
@@ -2929,6 +2937,7 @@ const mergeConfigs = (baseConfig, localConfig) => {
|
|
|
2929
2937
|
simulateCommands: localConfig.simulateCommands ?? baseConfig.simulateCommands,
|
|
2930
2938
|
simulateSubagents: localConfig.simulateSubagents ?? baseConfig.simulateSubagents,
|
|
2931
2939
|
simulateSkills: localConfig.simulateSkills ?? baseConfig.simulateSkills,
|
|
2940
|
+
preserveUnownedHooks: localConfig.preserveUnownedHooks ?? baseConfig.preserveUnownedHooks,
|
|
2932
2941
|
deriveSubprojectPathFromGlobs: localConfig.deriveSubprojectPathFromGlobs ?? baseConfig.deriveSubprojectPathFromGlobs,
|
|
2933
2942
|
flattenedCommandNaming: localConfig.flattenedCommandNaming ?? baseConfig.flattenedCommandNaming,
|
|
2934
2943
|
language: localConfig.language ?? baseConfig.language,
|
|
@@ -3152,6 +3161,11 @@ var ConfigResolver = class {
|
|
|
3152
3161
|
file: configByFile.simulateSkills,
|
|
3153
3162
|
fallback: getDefaults().simulateSkills
|
|
3154
3163
|
}),
|
|
3164
|
+
preserveUnownedHooks: pick({
|
|
3165
|
+
cli: void 0,
|
|
3166
|
+
file: configByFile.preserveUnownedHooks,
|
|
3167
|
+
fallback: getDefaults().preserveUnownedHooks
|
|
3168
|
+
}),
|
|
3155
3169
|
deriveSubprojectPathFromGlobs: pick({
|
|
3156
3170
|
cli: deriveSubprojectPathFromGlobs,
|
|
3157
3171
|
file: configByFile.deriveSubprojectPathFromGlobs,
|
|
@@ -4711,6 +4725,43 @@ const KIMI_CODE_NATIVE_HOOK_EVENTS = [
|
|
|
4711
4725
|
];
|
|
4712
4726
|
const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIMI_CODE_EVENT_NAMES).map(([canonical, kimiCode]) => [kimiCode, canonical]));
|
|
4713
4727
|
/**
|
|
4728
|
+
* Hook events supported by ZCode.
|
|
4729
|
+
*
|
|
4730
|
+
* ZCode's configuration-file hooks expose exactly seven PascalCase events, all
|
|
4731
|
+
* of which have a clean canonical equivalent: `SessionStart`, `PreToolUse`,
|
|
4732
|
+
* `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `Stop`, and
|
|
4733
|
+
* `UserPromptSubmit` ← `beforeSubmitPrompt`. An optional matcher (a
|
|
4734
|
+
* case-sensitive regular expression) is honored on all of them except
|
|
4735
|
+
* `UserPromptSubmit` and `Stop`, which expose no value to match against.
|
|
4736
|
+
* Configuration hooks are read only from the user config
|
|
4737
|
+
* `~/.zcode/cli/config.json` (workspace config hooks are never executed) and
|
|
4738
|
+
* additionally require `hooks.enabled: true` to run.
|
|
4739
|
+
*
|
|
4740
|
+
* ZCode also accepts a native `process` hook type (an argv run without a
|
|
4741
|
+
* shell) which has no canonical equivalent; see ZcodeHooks.
|
|
4742
|
+
*
|
|
4743
|
+
* @see https://zcode.z.ai/en/docs
|
|
4744
|
+
*/
|
|
4745
|
+
const ZCODE_HOOK_EVENTS = [
|
|
4746
|
+
"sessionStart",
|
|
4747
|
+
"beforeSubmitPrompt",
|
|
4748
|
+
"preToolUse",
|
|
4749
|
+
"postToolUse",
|
|
4750
|
+
"postToolUseFailure",
|
|
4751
|
+
"permissionRequest",
|
|
4752
|
+
"stop"
|
|
4753
|
+
];
|
|
4754
|
+
const CANONICAL_TO_ZCODE_EVENT_NAMES = {
|
|
4755
|
+
sessionStart: "SessionStart",
|
|
4756
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
4757
|
+
preToolUse: "PreToolUse",
|
|
4758
|
+
postToolUse: "PostToolUse",
|
|
4759
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
4760
|
+
permissionRequest: "PermissionRequest",
|
|
4761
|
+
stop: "Stop"
|
|
4762
|
+
};
|
|
4763
|
+
const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4764
|
+
/**
|
|
4714
4765
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
4715
4766
|
*
|
|
4716
4767
|
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
@@ -4862,6 +4913,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
4862
4913
|
reasonix: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4863
4914
|
grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4864
4915
|
"kimi-code": zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4916
|
+
zcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4865
4917
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4866
4918
|
hooks: zod_mini.z.optional(hooksRecordSchema),
|
|
4867
4919
|
disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -12412,14 +12464,15 @@ const KIMI_CODE_CONFIG_DECLARATION = {
|
|
|
12412
12464
|
};
|
|
12413
12465
|
/**
|
|
12414
12466
|
* ZCode's settings file, which also carries model/theme/permission keys
|
|
12415
|
-
* rulesync does not own.
|
|
12416
|
-
*
|
|
12417
|
-
*
|
|
12418
|
-
*
|
|
12419
|
-
*
|
|
12420
|
-
*
|
|
12421
|
-
|
|
12422
|
-
|
|
12467
|
+
* rulesync does not own. Both copies are the user's primary ZCode config, so
|
|
12468
|
+
* every writer refuses to read-modify-write a file it could not parse rather
|
|
12469
|
+
* than replacing it with generated output. `mcp` and `hooks` are owned as
|
|
12470
|
+
* whole keys because their writers recompute each from the existing file
|
|
12471
|
+
* (non-owned siblings carried over) before applying the patch. ZCode never
|
|
12472
|
+
* executes workspace config hooks, so the workspace copy is declared with
|
|
12473
|
+
* `mcp` alone and the user copy adds `hooks`.
|
|
12474
|
+
*/
|
|
12475
|
+
const ZCODE_WORKSPACE_CONFIG_DECLARATION = {
|
|
12423
12476
|
format: "json",
|
|
12424
12477
|
invalidRootPolicy: "error",
|
|
12425
12478
|
features: { mcp: {
|
|
@@ -12427,6 +12480,16 @@ const ZCODE_CONFIG_DECLARATION = {
|
|
|
12427
12480
|
ownedKeys: ["mcp"]
|
|
12428
12481
|
} }
|
|
12429
12482
|
};
|
|
12483
|
+
const ZCODE_USER_CONFIG_DECLARATION = {
|
|
12484
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12485
|
+
features: {
|
|
12486
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION.features,
|
|
12487
|
+
hooks: {
|
|
12488
|
+
kind: "replace-owned-keys",
|
|
12489
|
+
ownedKeys: ["hooks"]
|
|
12490
|
+
}
|
|
12491
|
+
}
|
|
12492
|
+
};
|
|
12430
12493
|
/**
|
|
12431
12494
|
* What the two Claude Code settings files have in common: both are plain JSON,
|
|
12432
12495
|
* and both validate against the one published schema, which the gateway
|
|
@@ -12684,8 +12747,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12684
12747
|
ownedKeys: ["mcp_servers", "schema_version"]
|
|
12685
12748
|
} }
|
|
12686
12749
|
},
|
|
12687
|
-
".zcode/config.json":
|
|
12688
|
-
".zcode/cli/config.json":
|
|
12750
|
+
".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12751
|
+
".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
|
|
12689
12752
|
".kiro/agents/default.json": {
|
|
12690
12753
|
format: "json",
|
|
12691
12754
|
features: {
|
|
@@ -18349,6 +18412,8 @@ const ZCODE_SKILLS_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "skills");
|
|
|
18349
18412
|
const ZCODE_CONFIG_FILE_NAME = "config.json";
|
|
18350
18413
|
const ZCODE_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "cli");
|
|
18351
18414
|
const ZCODE_MCP_SERVERS_KEY = "servers";
|
|
18415
|
+
const ZCODE_HOOKS_CONFIG_KEY = "hooks";
|
|
18416
|
+
const ZCODE_HOOKS_EVENTS_KEY = "events";
|
|
18352
18417
|
const ZCODE_AGENTS_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "agents");
|
|
18353
18418
|
//#endregion
|
|
18354
18419
|
//#region src/features/commands/zcode-command.ts
|
|
@@ -19221,19 +19286,124 @@ function generateAmpPluginCode({ config, supportedEvents, eventMap }) {
|
|
|
19221
19286
|
lines.push("}", "");
|
|
19222
19287
|
return lines.join("\n");
|
|
19223
19288
|
}
|
|
19289
|
+
/**
|
|
19290
|
+
* Name of the ownership record written next to the hooks destination it
|
|
19291
|
+
* describes (e.g. `.claude/.rulesync-hooks-lock.json`). Only written when hook
|
|
19292
|
+
* preservation is enabled, so a project that never opts in never sees it.
|
|
19293
|
+
*/
|
|
19294
|
+
const HOOKS_OWNERSHIP_LOCK_FILE_NAME = ".rulesync-hooks-lock.json";
|
|
19295
|
+
const OwnedHookRefSchema = zod_mini.z.object({
|
|
19296
|
+
event: zod_mini.z.string(),
|
|
19297
|
+
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
19298
|
+
identity: zod_mini.z.string()
|
|
19299
|
+
});
|
|
19300
|
+
const HooksOwnershipLockSchema = zod_mini.z.object({
|
|
19301
|
+
lockfileVersion: zod_mini.z.number(),
|
|
19302
|
+
owned: zod_mini.z.array(OwnedHookRefSchema)
|
|
19303
|
+
});
|
|
19304
|
+
/**
|
|
19305
|
+
* The comparable form of a hook entry: identical keys mean "the same hook, in
|
|
19306
|
+
* the same place". Encoded as JSON so no separator can collide with a matcher
|
|
19307
|
+
* or an identity that happens to contain one.
|
|
19308
|
+
*/
|
|
19309
|
+
function ownedHookKey({ event, matcher, identity }) {
|
|
19310
|
+
return JSON.stringify([
|
|
19311
|
+
event,
|
|
19312
|
+
matcher ?? null,
|
|
19313
|
+
identity
|
|
19314
|
+
]);
|
|
19315
|
+
}
|
|
19316
|
+
/**
|
|
19317
|
+
* Read the previous run's owned set. Anything unreadable — missing, empty,
|
|
19318
|
+
* malformed, or written by a different lock version — yields an empty set,
|
|
19319
|
+
* which makes every existing handler look third-party and therefore preserved.
|
|
19320
|
+
*/
|
|
19321
|
+
function parseHooksOwnershipLock(content) {
|
|
19322
|
+
if (content === null || content.trim() === "") return /* @__PURE__ */ new Set();
|
|
19323
|
+
let parsed;
|
|
19324
|
+
try {
|
|
19325
|
+
parsed = JSON.parse(content);
|
|
19326
|
+
} catch {
|
|
19327
|
+
return /* @__PURE__ */ new Set();
|
|
19328
|
+
}
|
|
19329
|
+
const result = HooksOwnershipLockSchema.safeParse(parsed);
|
|
19330
|
+
if (!result.success || result.data.lockfileVersion !== 1) return /* @__PURE__ */ new Set();
|
|
19331
|
+
return new Set(result.data.owned.map((ref) => ownedHookKey(ref)));
|
|
19332
|
+
}
|
|
19333
|
+
function compareOwnedHookRefs(a, b) {
|
|
19334
|
+
const left = ownedHookKey(a);
|
|
19335
|
+
const right = ownedHookKey(b);
|
|
19336
|
+
if (left === right) return 0;
|
|
19337
|
+
return left < right ? -1 : 1;
|
|
19338
|
+
}
|
|
19339
|
+
/** Serialize the owned set in a stable order so regenerating produces no diff. */
|
|
19340
|
+
function serializeHooksOwnershipLock(owned) {
|
|
19341
|
+
return `${JSON.stringify({
|
|
19342
|
+
lockfileVersion: 1,
|
|
19343
|
+
owned: [...owned].toSorted(compareOwnedHookRefs)
|
|
19344
|
+
}, null, 2)}\n`;
|
|
19345
|
+
}
|
|
19346
|
+
/**
|
|
19347
|
+
* The ownership record itself. It is a rulesync by-product rather than tool
|
|
19348
|
+
* configuration, so it is deletable: dropping the target should take it along.
|
|
19349
|
+
*/
|
|
19350
|
+
var HooksOwnershipLockFile = class extends ToolFile {
|
|
19351
|
+
isDeletable() {
|
|
19352
|
+
return true;
|
|
19353
|
+
}
|
|
19354
|
+
validate() {
|
|
19355
|
+
return {
|
|
19356
|
+
success: true,
|
|
19357
|
+
error: null
|
|
19358
|
+
};
|
|
19359
|
+
}
|
|
19360
|
+
};
|
|
19361
|
+
/** Build the lock file that records what this run generated. */
|
|
19362
|
+
function buildHooksOwnershipLockFile({ outputRoot, relativeDirPath, owned }) {
|
|
19363
|
+
return new HooksOwnershipLockFile({
|
|
19364
|
+
outputRoot,
|
|
19365
|
+
relativeDirPath,
|
|
19366
|
+
relativeFilePath: HOOKS_OWNERSHIP_LOCK_FILE_NAME,
|
|
19367
|
+
fileContent: serializeHooksOwnershipLock(owned),
|
|
19368
|
+
validate: false
|
|
19369
|
+
});
|
|
19370
|
+
}
|
|
19224
19371
|
//#endregion
|
|
19225
19372
|
//#region src/features/hooks/tool-hooks.ts
|
|
19226
19373
|
var ToolHooks = class extends ToolFile {
|
|
19374
|
+
ownedHookRefs;
|
|
19227
19375
|
constructor(params) {
|
|
19228
19376
|
super({
|
|
19229
19377
|
...params,
|
|
19230
19378
|
validate: true
|
|
19231
19379
|
});
|
|
19380
|
+
this.ownedHookRefs = params.ownedHookRefs;
|
|
19232
19381
|
if (params.validate) {
|
|
19233
19382
|
const result = this.validate();
|
|
19234
19383
|
if (!result.success) throw result.error;
|
|
19235
19384
|
}
|
|
19236
19385
|
}
|
|
19386
|
+
/**
|
|
19387
|
+
* The ownership record for this destination, or nothing when preservation is
|
|
19388
|
+
* off — in which case rulesync owns the whole list and needs no record.
|
|
19389
|
+
*/
|
|
19390
|
+
getOwnershipLockFiles() {
|
|
19391
|
+
if (this.ownedHookRefs === void 0) return [];
|
|
19392
|
+
return [buildHooksOwnershipLockFile({
|
|
19393
|
+
outputRoot: this.getOutputRoot(),
|
|
19394
|
+
relativeDirPath: this.getRelativeDirPath(),
|
|
19395
|
+
owned: this.ownedHookRefs
|
|
19396
|
+
})];
|
|
19397
|
+
}
|
|
19398
|
+
/**
|
|
19399
|
+
* Whether the adapter can keep handlers it did not generate. Destinations
|
|
19400
|
+
* rulesync owns outright (plugin bundles) must answer `false`: there is no
|
|
19401
|
+
* third party writing into them, and preserving there would only make
|
|
19402
|
+
* removals impossible.
|
|
19403
|
+
*/
|
|
19404
|
+
static supportsPreserveUnowned() {
|
|
19405
|
+
return false;
|
|
19406
|
+
}
|
|
19237
19407
|
static getSettablePaths(_options) {
|
|
19238
19408
|
throw new Error("Please implement this method in the subclass.");
|
|
19239
19409
|
}
|
|
@@ -20670,6 +20840,292 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
20670
20840
|
}
|
|
20671
20841
|
};
|
|
20672
20842
|
//#endregion
|
|
20843
|
+
//#region src/features/hooks/preserve-unowned-hook-commands.ts
|
|
20844
|
+
/**
|
|
20845
|
+
* Read the `hooks` value from a destination JSON file.
|
|
20846
|
+
*
|
|
20847
|
+
* `malformed` separates "the file has no hooks" from "the file could not be
|
|
20848
|
+
* read", so a caller that is about to preserve content can say out loud that
|
|
20849
|
+
* it is replacing a file it failed to parse.
|
|
20850
|
+
*/
|
|
20851
|
+
function parseExistingHooksValue(existingContent) {
|
|
20852
|
+
if (existingContent.trim() === "") return {
|
|
20853
|
+
hooks: void 0,
|
|
20854
|
+
malformed: false
|
|
20855
|
+
};
|
|
20856
|
+
try {
|
|
20857
|
+
const parsed = JSON.parse(existingContent);
|
|
20858
|
+
return {
|
|
20859
|
+
hooks: isPlainObject$1(parsed) ? parsed.hooks : void 0,
|
|
20860
|
+
malformed: false
|
|
20861
|
+
};
|
|
20862
|
+
} catch {
|
|
20863
|
+
return {
|
|
20864
|
+
hooks: void 0,
|
|
20865
|
+
malformed: true
|
|
20866
|
+
};
|
|
20867
|
+
}
|
|
20868
|
+
}
|
|
20869
|
+
/**
|
|
20870
|
+
* Produce the destination hooks list, optionally keeping handlers rulesync does
|
|
20871
|
+
* not own.
|
|
20872
|
+
*
|
|
20873
|
+
* Ownership is decided by `previouslyOwned` — the identities recorded in the
|
|
20874
|
+
* destination's ownership lock on the previous run — and never by guessing from
|
|
20875
|
+
* the command text. That is what makes removal work: a handler rulesync wrote
|
|
20876
|
+
* before and no longer generates is retracted, while a handler rulesync has
|
|
20877
|
+
* never written is left alone. Without a lock (the first run after opting in)
|
|
20878
|
+
* nothing is retracted, which is the non-destructive direction.
|
|
20879
|
+
*
|
|
20880
|
+
* Callers must pass the destination's shape. Plugin destinations are fully
|
|
20881
|
+
* owned by rulesync and must not call this.
|
|
20882
|
+
*/
|
|
20883
|
+
function mergeGeneratedHookLists({ existingContent, generatedHooks, shape, preserveUnowned, previouslyOwned, logger }) {
|
|
20884
|
+
const owned = collectOwnedRefs({
|
|
20885
|
+
generatedHooks,
|
|
20886
|
+
shape
|
|
20887
|
+
});
|
|
20888
|
+
if (!preserveUnowned) return {
|
|
20889
|
+
hooks: generatedHooks,
|
|
20890
|
+
owned
|
|
20891
|
+
};
|
|
20892
|
+
const { hooks: existingHooks, malformed } = parseExistingHooksValue(existingContent);
|
|
20893
|
+
if (malformed) logger?.warn("Replacing hooks wholesale: the existing file is not valid JSON, so no third-party hook in it can be preserved.");
|
|
20894
|
+
return {
|
|
20895
|
+
hooks: preserveUnownedHookCommands({
|
|
20896
|
+
existingHooks,
|
|
20897
|
+
generatedHooks,
|
|
20898
|
+
shape,
|
|
20899
|
+
owned,
|
|
20900
|
+
previouslyOwned: previouslyOwned ?? /* @__PURE__ */ new Set(),
|
|
20901
|
+
logger
|
|
20902
|
+
}),
|
|
20903
|
+
owned
|
|
20904
|
+
};
|
|
20905
|
+
}
|
|
20906
|
+
/**
|
|
20907
|
+
* Merge the destination's existing handlers into the generated set.
|
|
20908
|
+
*
|
|
20909
|
+
* Each existing handler falls into exactly one of three cases:
|
|
20910
|
+
* - this run generates it → the generated copy stands, the existing one is dropped;
|
|
20911
|
+
* - a previous run generated it and this one does not → it is retracted, with a warning;
|
|
20912
|
+
* - neither → it is unowned, and kept.
|
|
20913
|
+
*/
|
|
20914
|
+
function preserveUnownedHookCommands({ existingHooks, generatedHooks, shape, owned, previouslyOwned, logger }) {
|
|
20915
|
+
const result = cloneGeneratedHooks({
|
|
20916
|
+
generatedHooks,
|
|
20917
|
+
shape
|
|
20918
|
+
});
|
|
20919
|
+
if (!isPlainObject$1(existingHooks)) return result;
|
|
20920
|
+
const seen = new Set(owned.map((ref) => ownedHookKey(ref)));
|
|
20921
|
+
for (const [event, existingValue] of Object.entries(existingHooks)) {
|
|
20922
|
+
if (isPrototypePollutionKey(event)) continue;
|
|
20923
|
+
if (!Array.isArray(existingValue)) {
|
|
20924
|
+
warnSkip({
|
|
20925
|
+
logger,
|
|
20926
|
+
event,
|
|
20927
|
+
expected: "an array of hook entries"
|
|
20928
|
+
});
|
|
20929
|
+
continue;
|
|
20930
|
+
}
|
|
20931
|
+
const merged = shape === "matcher-groups" ? mergeMatcherGroups({
|
|
20932
|
+
existing: existingValue,
|
|
20933
|
+
generated: result[event] ?? [],
|
|
20934
|
+
event,
|
|
20935
|
+
seen,
|
|
20936
|
+
previouslyOwned,
|
|
20937
|
+
logger
|
|
20938
|
+
}) : mergeFlatHandlers({
|
|
20939
|
+
existing: existingValue,
|
|
20940
|
+
generated: result[event] ?? [],
|
|
20941
|
+
event,
|
|
20942
|
+
seen,
|
|
20943
|
+
previouslyOwned,
|
|
20944
|
+
logger
|
|
20945
|
+
});
|
|
20946
|
+
if (merged.length === 0) delete result[event];
|
|
20947
|
+
else result[event] = merged;
|
|
20948
|
+
}
|
|
20949
|
+
return result;
|
|
20950
|
+
}
|
|
20951
|
+
function cloneGeneratedHooks({ generatedHooks, shape }) {
|
|
20952
|
+
const result = Object.create(null);
|
|
20953
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
20954
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
20955
|
+
result[event] = shape === "matcher-groups" ? value.map((group) => cloneMatcherGroup(group)) : value.map((handler) => cloneHandler(handler));
|
|
20956
|
+
}
|
|
20957
|
+
return result;
|
|
20958
|
+
}
|
|
20959
|
+
/** The identities of every handler in the generated set, in destination order. */
|
|
20960
|
+
function collectOwnedRefs({ generatedHooks, shape }) {
|
|
20961
|
+
const owned = [];
|
|
20962
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
20963
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
20964
|
+
if (shape === "flat") {
|
|
20965
|
+
for (const handler of value) if (isPlainObject$1(handler)) owned.push({
|
|
20966
|
+
event,
|
|
20967
|
+
identity: handlerIdentity(handler)
|
|
20968
|
+
});
|
|
20969
|
+
continue;
|
|
20970
|
+
}
|
|
20971
|
+
for (const group of value) {
|
|
20972
|
+
if (!isMatcherGroup(group)) continue;
|
|
20973
|
+
const matcher = matcherKey(group.matcher);
|
|
20974
|
+
for (const handler of group.hooks) if (isPlainObject$1(handler)) owned.push({
|
|
20975
|
+
event,
|
|
20976
|
+
matcher,
|
|
20977
|
+
identity: handlerIdentity(handler)
|
|
20978
|
+
});
|
|
20979
|
+
}
|
|
20980
|
+
}
|
|
20981
|
+
return owned;
|
|
20982
|
+
}
|
|
20983
|
+
function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
20984
|
+
const merged = [...generated];
|
|
20985
|
+
for (const group of existing) {
|
|
20986
|
+
if (!isMatcherGroup(group)) {
|
|
20987
|
+
warnSkip({
|
|
20988
|
+
logger,
|
|
20989
|
+
event,
|
|
20990
|
+
expected: "a matcher group"
|
|
20991
|
+
});
|
|
20992
|
+
continue;
|
|
20993
|
+
}
|
|
20994
|
+
const matcher = matcherKey(group.matcher);
|
|
20995
|
+
const preserved = group.hooks.filter((handler) => shouldPreserve({
|
|
20996
|
+
handler,
|
|
20997
|
+
event,
|
|
20998
|
+
matcher,
|
|
20999
|
+
seen,
|
|
21000
|
+
previouslyOwned,
|
|
21001
|
+
logger
|
|
21002
|
+
})).map((handler) => cloneHandler(handler));
|
|
21003
|
+
if (preserved.length === 0) continue;
|
|
21004
|
+
const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher);
|
|
21005
|
+
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
21006
|
+
else merged.push({
|
|
21007
|
+
...omitPrototypePollutionKeys(group),
|
|
21008
|
+
hooks: preserved
|
|
21009
|
+
});
|
|
21010
|
+
}
|
|
21011
|
+
return merged;
|
|
21012
|
+
}
|
|
21013
|
+
function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
21014
|
+
const preserved = [];
|
|
21015
|
+
for (const handler of existing) {
|
|
21016
|
+
if (isMatcherGroup(handler)) {
|
|
21017
|
+
warnSkip({
|
|
21018
|
+
logger,
|
|
21019
|
+
event,
|
|
21020
|
+
expected: "a flat handler"
|
|
21021
|
+
});
|
|
21022
|
+
continue;
|
|
21023
|
+
}
|
|
21024
|
+
if (!shouldPreserve({
|
|
21025
|
+
handler,
|
|
21026
|
+
event,
|
|
21027
|
+
matcher: void 0,
|
|
21028
|
+
seen,
|
|
21029
|
+
previouslyOwned,
|
|
21030
|
+
logger
|
|
21031
|
+
})) continue;
|
|
21032
|
+
preserved.push(cloneHandler(handler));
|
|
21033
|
+
}
|
|
21034
|
+
return [...generated, ...preserved];
|
|
21035
|
+
}
|
|
21036
|
+
function isMatcherGroup(value) {
|
|
21037
|
+
return isPlainObject$1(value) && Array.isArray(value.hooks);
|
|
21038
|
+
}
|
|
21039
|
+
function cloneMatcherGroup(group) {
|
|
21040
|
+
if (!isMatcherGroup(group)) return cloneHandler(group);
|
|
21041
|
+
return {
|
|
21042
|
+
...group,
|
|
21043
|
+
hooks: group.hooks.map((handler) => cloneHandler(handler))
|
|
21044
|
+
};
|
|
21045
|
+
}
|
|
21046
|
+
function cloneHandler(handler) {
|
|
21047
|
+
return isPlainObject$1(handler) ? omitPrototypePollutionKeys(handler) : handler;
|
|
21048
|
+
}
|
|
21049
|
+
/**
|
|
21050
|
+
* Decide one existing handler, and remember it so an exact duplicate later in
|
|
21051
|
+
* the same event is not appended twice.
|
|
21052
|
+
*/
|
|
21053
|
+
function shouldPreserve({ handler, event, matcher, seen, previouslyOwned, logger }) {
|
|
21054
|
+
if (!isPlainObject$1(handler)) {
|
|
21055
|
+
warnSkip({
|
|
21056
|
+
logger,
|
|
21057
|
+
event,
|
|
21058
|
+
expected: "a hook object"
|
|
21059
|
+
});
|
|
21060
|
+
return false;
|
|
21061
|
+
}
|
|
21062
|
+
const identity = handlerIdentity(handler);
|
|
21063
|
+
const key = ownedHookKey({
|
|
21064
|
+
event,
|
|
21065
|
+
matcher,
|
|
21066
|
+
identity
|
|
21067
|
+
});
|
|
21068
|
+
if (seen.has(key)) return false;
|
|
21069
|
+
if (previouslyOwned.has(key)) {
|
|
21070
|
+
logger?.warn(`Removing hook rulesync no longer generates on ${event}: ${identity}`);
|
|
21071
|
+
return false;
|
|
21072
|
+
}
|
|
21073
|
+
seen.add(key);
|
|
21074
|
+
logger?.warn(`Preserving unowned hook on ${event}: ${identity}`);
|
|
21075
|
+
return true;
|
|
21076
|
+
}
|
|
21077
|
+
/**
|
|
21078
|
+
* A handler's identity is its action, not its whole shape: two entries running
|
|
21079
|
+
* the same command are the same hook even if their timeouts differ, so the
|
|
21080
|
+
* generated one replaces the existing one instead of doubling it. Shapes with
|
|
21081
|
+
* no recognizable action fall back to their full structure, which keeps them
|
|
21082
|
+
* comparable across runs — the alternative, no identity at all, made them
|
|
21083
|
+
* accumulate on every generate.
|
|
21084
|
+
*/
|
|
21085
|
+
function handlerIdentity(handler) {
|
|
21086
|
+
const type = typeof handler.type === "string" ? handler.type : inferredType(handler);
|
|
21087
|
+
switch (type) {
|
|
21088
|
+
case "http": return taggedIdentity("http", handler.url) ?? structuralIdentity(handler);
|
|
21089
|
+
case "mcp_tool": return mcpToolIdentity(handler) ?? structuralIdentity(handler);
|
|
21090
|
+
case "prompt":
|
|
21091
|
+
case "agent": return taggedIdentity(type, handler.prompt) ?? structuralIdentity(handler);
|
|
21092
|
+
case "function": return taggedIdentity("function", handler.name) ?? structuralIdentity(handler);
|
|
21093
|
+
default: return taggedIdentity("command", handler.command) ?? taggedIdentity("prompt", handler.prompt) ?? structuralIdentity(handler);
|
|
21094
|
+
}
|
|
21095
|
+
}
|
|
21096
|
+
function inferredType(handler) {
|
|
21097
|
+
return typeof handler.url === "string" ? "http" : "command";
|
|
21098
|
+
}
|
|
21099
|
+
function taggedIdentity(kind, value) {
|
|
21100
|
+
return typeof value === "string" && value !== "" ? `${kind}:${value}` : void 0;
|
|
21101
|
+
}
|
|
21102
|
+
function mcpToolIdentity(handler) {
|
|
21103
|
+
const server = typeof handler.server === "string" ? handler.server : "";
|
|
21104
|
+
const tool = typeof handler.tool === "string" ? handler.tool : "";
|
|
21105
|
+
if (server === "" && tool === "") return;
|
|
21106
|
+
return `mcp_tool:${server}:${tool}:${stableStringify$1(handler.input)}`;
|
|
21107
|
+
}
|
|
21108
|
+
function structuralIdentity(handler) {
|
|
21109
|
+
return `json:${stableStringify$1(handler)}`;
|
|
21110
|
+
}
|
|
21111
|
+
/** Key-order-independent JSON, so an identity survives a rewritten file. */
|
|
21112
|
+
function stableStringify$1(value) {
|
|
21113
|
+
if (Array.isArray(value)) return `[${value.map((entry) => stableStringify$1(entry)).join(",")}]`;
|
|
21114
|
+
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(",")}}`;
|
|
21115
|
+
return JSON.stringify(value) ?? "null";
|
|
21116
|
+
}
|
|
21117
|
+
/**
|
|
21118
|
+
* Matcher groups are matched by the whole matcher value, not just strings: a
|
|
21119
|
+
* non-string matcher is a shape rulesync does not emit, and collapsing every
|
|
21120
|
+
* such group onto one key would merge unrelated third-party groups together.
|
|
21121
|
+
*/
|
|
21122
|
+
function matcherKey(matcher) {
|
|
21123
|
+
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
21124
|
+
}
|
|
21125
|
+
function warnSkip({ logger, event, expected }) {
|
|
21126
|
+
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
21127
|
+
}
|
|
21128
|
+
//#endregion
|
|
20673
21129
|
//#region src/features/hooks/claudecode-hooks.ts
|
|
20674
21130
|
const CLAUDE_CONVERTER_CONFIG = {
|
|
20675
21131
|
supportedEvents: CLAUDE_HOOK_EVENTS,
|
|
@@ -20738,7 +21194,7 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
20738
21194
|
commandOnly: true
|
|
20739
21195
|
}]
|
|
20740
21196
|
};
|
|
20741
|
-
var ClaudecodeHooks = class extends ToolHooks {
|
|
21197
|
+
var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
20742
21198
|
constructor(params) {
|
|
20743
21199
|
super({
|
|
20744
21200
|
...params,
|
|
@@ -20773,7 +21229,13 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20773
21229
|
validate
|
|
20774
21230
|
});
|
|
20775
21231
|
}
|
|
20776
|
-
static
|
|
21232
|
+
static supportsPreserveUnowned() {
|
|
21233
|
+
return true;
|
|
21234
|
+
}
|
|
21235
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
21236
|
+
return toolHooks instanceof ClaudecodeHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
21237
|
+
}
|
|
21238
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
20777
21239
|
const paths = this.getSettablePaths({ global });
|
|
20778
21240
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20779
21241
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -20784,11 +21246,20 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20784
21246
|
converterConfig: this.getConverterConfig(),
|
|
20785
21247
|
logger
|
|
20786
21248
|
});
|
|
21249
|
+
const preserving = preserveUnowned && this.supportsPreserveUnowned();
|
|
21250
|
+
const merged = mergeGeneratedHookLists({
|
|
21251
|
+
existingContent,
|
|
21252
|
+
generatedHooks: claudeHooks,
|
|
21253
|
+
shape: "matcher-groups",
|
|
21254
|
+
preserveUnowned: preserving,
|
|
21255
|
+
previouslyOwned: preserving ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21256
|
+
logger
|
|
21257
|
+
});
|
|
20787
21258
|
const fileContent = applySharedConfigPatch({
|
|
20788
21259
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
20789
21260
|
feature: "hooks",
|
|
20790
21261
|
existingContent,
|
|
20791
|
-
patch: { hooks:
|
|
21262
|
+
patch: { hooks: merged.hooks },
|
|
20792
21263
|
filePath
|
|
20793
21264
|
});
|
|
20794
21265
|
return new this({
|
|
@@ -20796,6 +21267,7 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20796
21267
|
relativeDirPath: paths.relativeDirPath,
|
|
20797
21268
|
relativeFilePath: paths.relativeFilePath,
|
|
20798
21269
|
fileContent,
|
|
21270
|
+
ownedHookRefs: preserving ? merged.owned : void 0,
|
|
20799
21271
|
validate
|
|
20800
21272
|
});
|
|
20801
21273
|
}
|
|
@@ -20853,6 +21325,14 @@ var ClaudecodePluginHooks = class extends ClaudecodeHooks {
|
|
|
20853
21325
|
projectDirVar: "$CLAUDE_PLUGIN_ROOT"
|
|
20854
21326
|
};
|
|
20855
21327
|
}
|
|
21328
|
+
/**
|
|
21329
|
+
* The plugin bundle is generated in full by rulesync — nothing else writes
|
|
21330
|
+
* into it — so preserving there would buy nothing and cost the ability to
|
|
21331
|
+
* remove a hook.
|
|
21332
|
+
*/
|
|
21333
|
+
static supportsPreserveUnowned() {
|
|
21334
|
+
return false;
|
|
21335
|
+
}
|
|
20856
21336
|
static getSettablePaths() {
|
|
20857
21337
|
return {
|
|
20858
21338
|
relativeDirPath: CLAUDECODE_PLUGIN_HOOKS_DIR,
|
|
@@ -21283,21 +21763,30 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21283
21763
|
validate
|
|
21284
21764
|
});
|
|
21285
21765
|
}
|
|
21286
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
21766
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21287
21767
|
const paths = CodexcliHooks.getSettablePaths({ global });
|
|
21768
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
21288
21769
|
const config = rulesyncHooks.getJson();
|
|
21289
|
-
const
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21770
|
+
const merged = mergeGeneratedHookLists({
|
|
21771
|
+
existingContent,
|
|
21772
|
+
generatedHooks: canonicalToToolHooks({
|
|
21773
|
+
config,
|
|
21774
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
21775
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
21776
|
+
logger
|
|
21777
|
+
}),
|
|
21778
|
+
shape: "matcher-groups",
|
|
21779
|
+
preserveUnowned,
|
|
21780
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21293
21781
|
logger
|
|
21294
21782
|
});
|
|
21295
|
-
const fileContent = JSON.stringify({ hooks:
|
|
21783
|
+
const fileContent = JSON.stringify({ hooks: merged.hooks }, null, 2);
|
|
21296
21784
|
return new CodexcliHooks({
|
|
21297
21785
|
outputRoot,
|
|
21298
21786
|
relativeDirPath: paths.relativeDirPath,
|
|
21299
21787
|
relativeFilePath: paths.relativeFilePath,
|
|
21300
21788
|
fileContent,
|
|
21789
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21301
21790
|
validate
|
|
21302
21791
|
});
|
|
21303
21792
|
}
|
|
@@ -21333,8 +21822,11 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21333
21822
|
validate: false
|
|
21334
21823
|
});
|
|
21335
21824
|
}
|
|
21336
|
-
static
|
|
21337
|
-
return
|
|
21825
|
+
static supportsPreserveUnowned() {
|
|
21826
|
+
return true;
|
|
21827
|
+
}
|
|
21828
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), toolHooks } = {}) {
|
|
21829
|
+
return [await CodexcliConfigToml.fromOutputRoot({ outputRoot }), ...toolHooks instanceof CodexcliHooks ? toolHooks.getOwnershipLockFiles() : []];
|
|
21338
21830
|
}
|
|
21339
21831
|
};
|
|
21340
21832
|
//#endregion
|
|
@@ -21939,7 +22431,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21939
22431
|
validate
|
|
21940
22432
|
});
|
|
21941
22433
|
}
|
|
21942
|
-
static fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
22434
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21943
22435
|
const config = rulesyncHooks.getJson();
|
|
21944
22436
|
const cursorSupported = new Set(CURSOR_HOOK_EVENTS);
|
|
21945
22437
|
const sharedHooks = {};
|
|
@@ -21966,21 +22458,36 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21966
22458
|
}));
|
|
21967
22459
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
21968
22460
|
}
|
|
22461
|
+
const paths = CursorHooks.getSettablePaths({ global });
|
|
22462
|
+
const merged = mergeGeneratedHookLists({
|
|
22463
|
+
existingContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "",
|
|
22464
|
+
generatedHooks: mappedHooks,
|
|
22465
|
+
shape: "flat",
|
|
22466
|
+
preserveUnowned,
|
|
22467
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
22468
|
+
logger
|
|
22469
|
+
});
|
|
21969
22470
|
const cursorConfig = {
|
|
21970
22471
|
version: config.version ?? 1,
|
|
21971
|
-
hooks:
|
|
22472
|
+
hooks: merged.hooks
|
|
21972
22473
|
};
|
|
21973
22474
|
const fileContent = JSON.stringify(cursorConfig, null, 2);
|
|
21974
|
-
const paths = CursorHooks.getSettablePaths({ global });
|
|
21975
22475
|
return new CursorHooks({
|
|
21976
22476
|
outputRoot,
|
|
21977
22477
|
relativeDirPath: paths.relativeDirPath,
|
|
21978
22478
|
relativeFilePath: paths.relativeFilePath,
|
|
21979
22479
|
fileContent,
|
|
22480
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21980
22481
|
validate,
|
|
21981
22482
|
rulesyncHooks
|
|
21982
22483
|
});
|
|
21983
22484
|
}
|
|
22485
|
+
static supportsPreserveUnowned() {
|
|
22486
|
+
return true;
|
|
22487
|
+
}
|
|
22488
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
22489
|
+
return toolHooks instanceof CursorHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
22490
|
+
}
|
|
21984
22491
|
toRulesyncHooks() {
|
|
21985
22492
|
const content = this.getFileContent();
|
|
21986
22493
|
const parsed = JSON.parse(content);
|
|
@@ -25220,6 +25727,186 @@ var VibeHooks = class VibeHooks extends ToolHooks {
|
|
|
25220
25727
|
}
|
|
25221
25728
|
};
|
|
25222
25729
|
//#endregion
|
|
25730
|
+
//#region src/features/hooks/zcode-hooks.ts
|
|
25731
|
+
/**
|
|
25732
|
+
* Single spelling of the config.json codec/policy, matching the one in
|
|
25733
|
+
* ZcodeMcp: fail closed on an unparseable root rather than replacing the
|
|
25734
|
+
* user's primary ZCode config with generated output.
|
|
25735
|
+
*/
|
|
25736
|
+
function parseZcodeConfig$1(fileContent, filePath) {
|
|
25737
|
+
return parseSharedConfig({
|
|
25738
|
+
format: "json",
|
|
25739
|
+
fileContent,
|
|
25740
|
+
filePath,
|
|
25741
|
+
invalidRootPolicy: "error"
|
|
25742
|
+
});
|
|
25743
|
+
}
|
|
25744
|
+
/**
|
|
25745
|
+
* Drop ZCode's native `process` hooks from an `events` map before the shared
|
|
25746
|
+
* import converter runs. The converter would coerce the unknown type to
|
|
25747
|
+
* `command` and import the executable alone — losing the `args` vector that
|
|
25748
|
+
* *is* the process hook's command line — so the imported hook would run
|
|
25749
|
+
* something else than the file states. Matcher groups left without hooks are
|
|
25750
|
+
* dropped with them.
|
|
25751
|
+
*/
|
|
25752
|
+
function stripProcessHooks({ events, logger }) {
|
|
25753
|
+
if (!isRecord$1(events)) return events;
|
|
25754
|
+
const result = Object.create(null);
|
|
25755
|
+
for (const [eventName, entries] of Object.entries(events)) {
|
|
25756
|
+
if (!Array.isArray(entries)) {
|
|
25757
|
+
result[eventName] = entries;
|
|
25758
|
+
continue;
|
|
25759
|
+
}
|
|
25760
|
+
const kept = [];
|
|
25761
|
+
for (const entry of entries) {
|
|
25762
|
+
if (!isRecord$1(entry) || !Array.isArray(entry.hooks)) {
|
|
25763
|
+
kept.push(entry);
|
|
25764
|
+
continue;
|
|
25765
|
+
}
|
|
25766
|
+
const hooks = entry.hooks.filter((hook) => {
|
|
25767
|
+
if (isRecord$1(hook) && hook.type === "process") {
|
|
25768
|
+
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.`);
|
|
25769
|
+
return false;
|
|
25770
|
+
}
|
|
25771
|
+
return true;
|
|
25772
|
+
});
|
|
25773
|
+
if (hooks.length > 0) kept.push({
|
|
25774
|
+
...entry,
|
|
25775
|
+
hooks
|
|
25776
|
+
});
|
|
25777
|
+
}
|
|
25778
|
+
result[eventName] = kept;
|
|
25779
|
+
}
|
|
25780
|
+
return result;
|
|
25781
|
+
}
|
|
25782
|
+
const ZCODE_CONVERTER_CONFIG = {
|
|
25783
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
25784
|
+
canonicalToToolEventNames: CANONICAL_TO_ZCODE_EVENT_NAMES,
|
|
25785
|
+
toolToCanonicalEventNames: ZCODE_TO_CANONICAL_EVENT_NAMES,
|
|
25786
|
+
projectDirVar: "",
|
|
25787
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
25788
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
25789
|
+
booleanPassthroughFields: [{
|
|
25790
|
+
canonical: "async",
|
|
25791
|
+
tool: "async"
|
|
25792
|
+
}, {
|
|
25793
|
+
canonical: "enabled",
|
|
25794
|
+
tool: "enabled"
|
|
25795
|
+
}],
|
|
25796
|
+
stringPassthroughFields: [{
|
|
25797
|
+
canonical: "statusMessage",
|
|
25798
|
+
tool: "statusMessage"
|
|
25799
|
+
}, {
|
|
25800
|
+
canonical: "shell",
|
|
25801
|
+
tool: "shell",
|
|
25802
|
+
commandOnly: true
|
|
25803
|
+
}],
|
|
25804
|
+
wildcardMatcherMeansAll: true
|
|
25805
|
+
};
|
|
25806
|
+
/**
|
|
25807
|
+
* ZCode hooks.
|
|
25808
|
+
*
|
|
25809
|
+
* ZCode reads configuration-file hooks from the `hooks` block of its user
|
|
25810
|
+
* config file, `~/.zcode/cli/config.json`. Workspace config hooks are never
|
|
25811
|
+
* executed — the workspace file is ignored regardless of `hooks.enabled` — so
|
|
25812
|
+
* rulesync treats ZCode hooks as global-only. The event map is nested under
|
|
25813
|
+
* `hooks.events` beside the user-tunable `enabled` and `timeoutMs` siblings,
|
|
25814
|
+
* which are carried over while `events` is replaced.
|
|
25815
|
+
*
|
|
25816
|
+
* @see https://zcode.z.ai/en/docs
|
|
25817
|
+
*/
|
|
25818
|
+
var ZcodeHooks = class ZcodeHooks extends ToolHooks {
|
|
25819
|
+
json;
|
|
25820
|
+
constructor(params) {
|
|
25821
|
+
super(params);
|
|
25822
|
+
this.json = parseZcodeConfig$1(this.fileContent ?? "{}", (0, node_path.join)(this.relativeDirPath, this.relativeFilePath));
|
|
25823
|
+
}
|
|
25824
|
+
isDeletable() {
|
|
25825
|
+
return false;
|
|
25826
|
+
}
|
|
25827
|
+
static getSettablePaths(_options = {}) {
|
|
25828
|
+
return {
|
|
25829
|
+
relativeDirPath: ZCODE_GLOBAL_CONFIG_DIR_PATH,
|
|
25830
|
+
relativeFilePath: ZCODE_CONFIG_FILE_NAME
|
|
25831
|
+
};
|
|
25832
|
+
}
|
|
25833
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
25834
|
+
const paths = this.getSettablePaths({ global });
|
|
25835
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
25836
|
+
return new ZcodeHooks({
|
|
25837
|
+
outputRoot,
|
|
25838
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25839
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25840
|
+
fileContent,
|
|
25841
|
+
validate,
|
|
25842
|
+
global
|
|
25843
|
+
});
|
|
25844
|
+
}
|
|
25845
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
25846
|
+
const paths = this.getSettablePaths({ global });
|
|
25847
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25848
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25849
|
+
const existing = parseZcodeConfig$1(existingContent, filePath);
|
|
25850
|
+
const config = rulesyncHooks.getJson();
|
|
25851
|
+
const events = canonicalToToolHooks({
|
|
25852
|
+
config,
|
|
25853
|
+
toolOverrideHooks: config.zcode?.hooks,
|
|
25854
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25855
|
+
logger
|
|
25856
|
+
});
|
|
25857
|
+
const existingHooks = isRecord$1(existing["hooks"]) ? existing[ZCODE_HOOKS_CONFIG_KEY] : {};
|
|
25858
|
+
const shouldStateEnabled = Object.keys(events).length > 0 && existingHooks.enabled === void 0;
|
|
25859
|
+
return new ZcodeHooks({
|
|
25860
|
+
outputRoot,
|
|
25861
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25862
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25863
|
+
fileContent: applySharedConfigPatch({
|
|
25864
|
+
fileKey: sharedConfigFileKey(paths),
|
|
25865
|
+
feature: "hooks",
|
|
25866
|
+
existingContent,
|
|
25867
|
+
patch: { [ZCODE_HOOKS_CONFIG_KEY]: {
|
|
25868
|
+
...existingHooks,
|
|
25869
|
+
...shouldStateEnabled ? { enabled: true } : {},
|
|
25870
|
+
[ZCODE_HOOKS_EVENTS_KEY]: events
|
|
25871
|
+
} },
|
|
25872
|
+
filePath
|
|
25873
|
+
}),
|
|
25874
|
+
validate,
|
|
25875
|
+
global
|
|
25876
|
+
});
|
|
25877
|
+
}
|
|
25878
|
+
toRulesyncHooks({ logger } = {}) {
|
|
25879
|
+
const hooks = toolHooksToCanonical({
|
|
25880
|
+
hooks: stripProcessHooks({
|
|
25881
|
+
events: (isRecord$1(this.json["hooks"]) ? this.json[ZCODE_HOOKS_CONFIG_KEY] : {})[ZCODE_HOOKS_EVENTS_KEY],
|
|
25882
|
+
logger
|
|
25883
|
+
}),
|
|
25884
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25885
|
+
logger
|
|
25886
|
+
});
|
|
25887
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
25888
|
+
hooks,
|
|
25889
|
+
overrideKey: "zcode"
|
|
25890
|
+
}), null, 2) });
|
|
25891
|
+
}
|
|
25892
|
+
validate() {
|
|
25893
|
+
return {
|
|
25894
|
+
success: true,
|
|
25895
|
+
error: null
|
|
25896
|
+
};
|
|
25897
|
+
}
|
|
25898
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
25899
|
+
return new ZcodeHooks({
|
|
25900
|
+
outputRoot,
|
|
25901
|
+
relativeDirPath,
|
|
25902
|
+
relativeFilePath,
|
|
25903
|
+
fileContent: JSON.stringify({ [ZCODE_HOOKS_CONFIG_KEY]: { [ZCODE_HOOKS_EVENTS_KEY]: {} } }, null, 2),
|
|
25904
|
+
validate: false,
|
|
25905
|
+
global
|
|
25906
|
+
});
|
|
25907
|
+
}
|
|
25908
|
+
};
|
|
25909
|
+
//#endregion
|
|
25223
25910
|
//#region src/features/hooks/hooks-processor.ts
|
|
25224
25911
|
const HooksProcessorToolTargetSchema = zod_mini.z.enum(hooksProcessorToolTargetTuple);
|
|
25225
25912
|
/**
|
|
@@ -25291,8 +25978,12 @@ const HOOKS_OVERRIDE_KEY_ALIASES = {
|
|
|
25291
25978
|
"kiro-cli": KIRO_HOOKS_OVERRIDE_KEY,
|
|
25292
25979
|
"kiro-ide": KIRO_HOOKS_OVERRIDE_KEY
|
|
25293
25980
|
};
|
|
25294
|
-
/** The targets
|
|
25295
|
-
const
|
|
25981
|
+
/** The targets whose hooks format carries a per-hook on-disk enable flag. */
|
|
25982
|
+
const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
|
|
25983
|
+
"kiro-cli",
|
|
25984
|
+
"kiro-ide",
|
|
25985
|
+
"zcode"
|
|
25986
|
+
]);
|
|
25296
25987
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
25297
25988
|
["amp", {
|
|
25298
25989
|
class: AmpHooks,
|
|
@@ -25641,6 +26332,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
25641
26332
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
25642
26333
|
supportedHookTypes: ["command", "http"],
|
|
25643
26334
|
supportsMatcher: true
|
|
26335
|
+
}],
|
|
26336
|
+
["zcode", {
|
|
26337
|
+
class: ZcodeHooks,
|
|
26338
|
+
meta: {
|
|
26339
|
+
supportsProject: false,
|
|
26340
|
+
supportsGlobal: true,
|
|
26341
|
+
supportsImport: true
|
|
26342
|
+
},
|
|
26343
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
26344
|
+
supportedHookTypes: ["command"],
|
|
26345
|
+
supportsMatcher: true
|
|
25644
26346
|
}]
|
|
25645
26347
|
]);
|
|
25646
26348
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -25650,7 +26352,8 @@ const hooksProcessorToolTargetsGlobalImportable = [...toolHooksFactories.entries
|
|
|
25650
26352
|
var HooksProcessor = class extends FeatureProcessor {
|
|
25651
26353
|
toolTarget;
|
|
25652
26354
|
global;
|
|
25653
|
-
|
|
26355
|
+
preserveUnownedHooks;
|
|
26356
|
+
constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, dryRun = false, preserveUnownedHooks = false, logger }) {
|
|
25654
26357
|
super({
|
|
25655
26358
|
outputRoot,
|
|
25656
26359
|
inputRoots,
|
|
@@ -25661,6 +26364,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25661
26364
|
if (!result.success) throw new Error(`Invalid tool target for HooksProcessor: ${toolTarget}. ${formatError(result.error)}`);
|
|
25662
26365
|
this.toolTarget = result.data;
|
|
25663
26366
|
this.global = global;
|
|
26367
|
+
this.preserveUnownedHooks = preserveUnownedHooks;
|
|
25664
26368
|
}
|
|
25665
26369
|
async loadRulesyncFiles() {
|
|
25666
26370
|
const relativePaths = getRulesyncSourceCandidates({ paths: RulesyncHooks.getSettablePaths() }).map((candidate) => candidate.relativeFilePath);
|
|
@@ -25752,14 +26456,14 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25752
26456
|
}
|
|
25753
26457
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
25754
26458
|
}
|
|
25755
|
-
if (!
|
|
26459
|
+
if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
|
|
25756
26460
|
const skippedEvents = new Set(unsupportedEventNames({
|
|
25757
26461
|
factory,
|
|
25758
26462
|
sharedHooks,
|
|
25759
26463
|
effectiveHooks
|
|
25760
26464
|
}));
|
|
25761
26465
|
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
25762
|
-
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide
|
|
26466
|
+
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(", ")}`);
|
|
25763
26467
|
}
|
|
25764
26468
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
25765
26469
|
factory,
|
|
@@ -25771,6 +26475,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25771
26475
|
rulesyncHooks,
|
|
25772
26476
|
validate: true,
|
|
25773
26477
|
global: this.global,
|
|
26478
|
+
preserveUnowned: this.preserveUnownedHooks,
|
|
25774
26479
|
logger: withToolTargetPrefix({
|
|
25775
26480
|
logger: this.logger,
|
|
25776
26481
|
toolTarget: this.toolTarget
|
|
@@ -69002,6 +69707,7 @@ async function generateHooksCore(params) {
|
|
|
69002
69707
|
toolTarget,
|
|
69003
69708
|
global: config.getGlobal(),
|
|
69004
69709
|
dryRun: config.isPreviewMode(),
|
|
69710
|
+
preserveUnownedHooks: config.getPreserveUnownedHooks(),
|
|
69005
69711
|
logger
|
|
69006
69712
|
});
|
|
69007
69713
|
const result = await processFeatureWithRulesyncFiles({
|