rulesync 16.24.1 → 16.26.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 +10 -10
- package/dist/cli/index.cjs +62 -21
- package/dist/cli/index.js +62 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/{import-CI0pN6s5.cjs → import-BAnifHej.cjs} +1202 -146
- package/dist/{import-DL8paEMP.js → import-BlHibyDS.js} +1181 -131
- package/dist/import-BlHibyDS.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +11 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- 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,48 @@ 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 is honored on
|
|
4734
|
+
* all of them except `UserPromptSubmit` and `Stop`, which expose no value to
|
|
4735
|
+
* match against. ZCode reads the matcher in one of two ways: a value made up
|
|
4736
|
+
* solely of letters, digits, underscores and `|` is an exact name list
|
|
4737
|
+
* (`Write|Edit` matches those two tool names, not the regex alternation), and
|
|
4738
|
+
* any other character makes the whole value a case-sensitive JavaScript regex.
|
|
4739
|
+
* Rulesync passes matchers through verbatim, so an authored value keeps
|
|
4740
|
+
* whichever reading ZCode gives it — worth knowing before escaping one.
|
|
4741
|
+
* Configuration hooks are read only from the user config
|
|
4742
|
+
* `~/.zcode/cli/config.json` (workspace config hooks are never executed) and
|
|
4743
|
+
* additionally require `hooks.enabled: true` to run.
|
|
4744
|
+
*
|
|
4745
|
+
* ZCode also accepts a native `process` hook type (an argv run without a
|
|
4746
|
+
* shell) which has no canonical equivalent; see ZcodeHooks.
|
|
4747
|
+
*
|
|
4748
|
+
* @see https://zcode.z.ai/en/docs
|
|
4749
|
+
*/
|
|
4750
|
+
const ZCODE_HOOK_EVENTS = [
|
|
4751
|
+
"sessionStart",
|
|
4752
|
+
"beforeSubmitPrompt",
|
|
4753
|
+
"preToolUse",
|
|
4754
|
+
"postToolUse",
|
|
4755
|
+
"postToolUseFailure",
|
|
4756
|
+
"permissionRequest",
|
|
4757
|
+
"stop"
|
|
4758
|
+
];
|
|
4759
|
+
const CANONICAL_TO_ZCODE_EVENT_NAMES = {
|
|
4760
|
+
sessionStart: "SessionStart",
|
|
4761
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
4762
|
+
preToolUse: "PreToolUse",
|
|
4763
|
+
postToolUse: "PostToolUse",
|
|
4764
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
4765
|
+
permissionRequest: "PermissionRequest",
|
|
4766
|
+
stop: "Stop"
|
|
4767
|
+
};
|
|
4768
|
+
const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4769
|
+
/**
|
|
4714
4770
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
4715
4771
|
*
|
|
4716
4772
|
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
@@ -4862,6 +4918,7 @@ const HooksConfigSchema = zod_mini.z.looseObject({
|
|
|
4862
4918
|
reasonix: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4863
4919
|
grokcli: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4864
4920
|
"kimi-code": zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4921
|
+
zcode: zod_mini.z.optional(zod_mini.z.looseObject({ hooks: zod_mini.z.optional(hooksRecordSchema) })),
|
|
4865
4922
|
qwencode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
4866
4923
|
hooks: zod_mini.z.optional(hooksRecordSchema),
|
|
4867
4924
|
disableAllHooks: zod_mini.z.optional(zod_mini.z.boolean())
|
|
@@ -6852,12 +6909,37 @@ const DeepagentsStartupOverrideSchema = zod_mini.z.looseObject({
|
|
|
6852
6909
|
read_project_dotenv: zod_mini.z.optional(zod_mini.z.boolean())
|
|
6853
6910
|
});
|
|
6854
6911
|
/**
|
|
6912
|
+
* deepagents-cli's Python extension gate under `[extensions]` in
|
|
6913
|
+
* `~/.deepagents/config.toml`. `discover_extensions` auto-loads `*.py` from the
|
|
6914
|
+
* user's `~/.deepagents/extensions/` and from the checked-out project's
|
|
6915
|
+
* `<root>/.deepagents/extensions/`, so these two keys decide whether
|
|
6916
|
+
* repository-authored Python is imported into the agent process at all —
|
|
6917
|
+
* the same class of knob as `[startup].mode`, and one that ships defaulting to
|
|
6918
|
+
* a prompt a user can accept. Loose so a key added upstream passes through
|
|
6919
|
+
* verbatim.
|
|
6920
|
+
*
|
|
6921
|
+
* `extensions.extra_paths` is deliberately absent: it names machine-local files
|
|
6922
|
+
* and directories, which do not belong in a committed
|
|
6923
|
+
* `.rulesync/permissions.jsonc`, and it only ever widens what loads.
|
|
6924
|
+
*
|
|
6925
|
+
* @see https://docs.langchain.com/oss/deepagents/code/configuration
|
|
6926
|
+
*/
|
|
6927
|
+
const DeepagentsExtensionsOverrideSchema = zod_mini.z.looseObject({
|
|
6928
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
6929
|
+
trust: zod_mini.z.optional(zod_mini.z.enum([
|
|
6930
|
+
"ask",
|
|
6931
|
+
"always",
|
|
6932
|
+
"never"
|
|
6933
|
+
]))
|
|
6934
|
+
});
|
|
6935
|
+
/**
|
|
6855
6936
|
* The `[shell].allow_list` array itself is rulesync-owned, driven by the
|
|
6856
6937
|
* shared `permission.bash` block, so it has no key here.
|
|
6857
6938
|
*/
|
|
6858
6939
|
const DeepagentsPermissionsOverrideSchema = zod_mini.z.looseObject({
|
|
6859
6940
|
permission: zod_mini.z.optional(ToolScopedPermissionSchema),
|
|
6860
|
-
startup: zod_mini.z.optional(DeepagentsStartupOverrideSchema)
|
|
6941
|
+
startup: zod_mini.z.optional(DeepagentsStartupOverrideSchema),
|
|
6942
|
+
extensions: zod_mini.z.optional(DeepagentsExtensionsOverrideSchema)
|
|
6861
6943
|
});
|
|
6862
6944
|
/**
|
|
6863
6945
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -7868,8 +7950,9 @@ const RulesyncRuleFrontmatterSchema = zod_mini.z.object({
|
|
|
7868
7950
|
agentsmd: zod_mini.z.optional(zod_mini.z.looseObject({ subprojectPath: zod_mini.z.optional(zod_mini.z.string()) })),
|
|
7869
7951
|
claudecode: zod_mini.z.optional(zod_mini.z.looseObject({ paths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())) })),
|
|
7870
7952
|
codebuddy: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
7871
|
-
paths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
7953
|
+
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
7872
7954
|
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7955
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
7873
7956
|
description: zod_mini.z.optional(zod_mini.z.string())
|
|
7874
7957
|
})),
|
|
7875
7958
|
cursor: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
@@ -9162,6 +9245,11 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
|
9162
9245
|
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
9163
9246
|
compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
|
|
9164
9247
|
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
9248
|
+
})),
|
|
9249
|
+
zcode: zod_mini.z.optional(zod_mini.z.looseObject({
|
|
9250
|
+
when_to_use: zod_mini.z.optional(zod_mini.z.string()),
|
|
9251
|
+
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
9252
|
+
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
9165
9253
|
}))
|
|
9166
9254
|
});
|
|
9167
9255
|
/**
|
|
@@ -12412,14 +12500,15 @@ const KIMI_CODE_CONFIG_DECLARATION = {
|
|
|
12412
12500
|
};
|
|
12413
12501
|
/**
|
|
12414
12502
|
* ZCode's settings file, which also carries model/theme/permission keys
|
|
12415
|
-
* rulesync does not own.
|
|
12416
|
-
*
|
|
12417
|
-
*
|
|
12418
|
-
*
|
|
12419
|
-
*
|
|
12420
|
-
*
|
|
12421
|
-
|
|
12422
|
-
|
|
12503
|
+
* rulesync does not own. Both copies are the user's primary ZCode config, so
|
|
12504
|
+
* every writer refuses to read-modify-write a file it could not parse rather
|
|
12505
|
+
* than replacing it with generated output. `mcp` and `hooks` are owned as
|
|
12506
|
+
* whole keys because their writers recompute each from the existing file
|
|
12507
|
+
* (non-owned siblings carried over) before applying the patch. ZCode never
|
|
12508
|
+
* executes workspace config hooks, so the workspace copy is declared with
|
|
12509
|
+
* `mcp` alone and the user copy adds `hooks`.
|
|
12510
|
+
*/
|
|
12511
|
+
const ZCODE_WORKSPACE_CONFIG_DECLARATION = {
|
|
12423
12512
|
format: "json",
|
|
12424
12513
|
invalidRootPolicy: "error",
|
|
12425
12514
|
features: { mcp: {
|
|
@@ -12427,6 +12516,16 @@ const ZCODE_CONFIG_DECLARATION = {
|
|
|
12427
12516
|
ownedKeys: ["mcp"]
|
|
12428
12517
|
} }
|
|
12429
12518
|
};
|
|
12519
|
+
const ZCODE_USER_CONFIG_DECLARATION = {
|
|
12520
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12521
|
+
features: {
|
|
12522
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION.features,
|
|
12523
|
+
hooks: {
|
|
12524
|
+
kind: "replace-owned-keys",
|
|
12525
|
+
ownedKeys: ["hooks"]
|
|
12526
|
+
}
|
|
12527
|
+
}
|
|
12528
|
+
};
|
|
12430
12529
|
/**
|
|
12431
12530
|
* What the two Claude Code settings files have in common: both are plain JSON,
|
|
12432
12531
|
* and both validate against the one published schema, which the gateway
|
|
@@ -12684,8 +12783,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12684
12783
|
ownedKeys: ["mcp_servers", "schema_version"]
|
|
12685
12784
|
} }
|
|
12686
12785
|
},
|
|
12687
|
-
".zcode/config.json":
|
|
12688
|
-
".zcode/cli/config.json":
|
|
12786
|
+
".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12787
|
+
".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
|
|
12689
12788
|
".kiro/agents/default.json": {
|
|
12690
12789
|
format: "json",
|
|
12691
12790
|
features: {
|
|
@@ -17733,6 +17832,20 @@ const ROO_MCP_FILE_NAME = "mcp.json";
|
|
|
17733
17832
|
*/
|
|
17734
17833
|
const ROO_MODE_SLUG_PATTERN = /^[a-zA-Z0-9-]+$/;
|
|
17735
17834
|
/**
|
|
17835
|
+
* `~/.roo/rules/AGENTS.md` — where the root rule goes in global scope.
|
|
17836
|
+
*
|
|
17837
|
+
* Roo/Zoo Code discover the agent-rules files (`AGENTS.md`, `AGENT.md`,
|
|
17838
|
+
* `AGENTS.local.md`) in the **workspace only**: `loadAllAgentRulesFiles` reads
|
|
17839
|
+
* `cwd`, plus subdirectories holding a `.roo` folder when `enableSubfolderRules`
|
|
17840
|
+
* is on. There is no home-directory branch, so a `~/AGENTS.md` is never read.
|
|
17841
|
+
* The global rules directory `~/.roo/rules/` *is* loaded, so the root rule is
|
|
17842
|
+
* written there alongside the non-root files rather than to an unread path.
|
|
17843
|
+
* The basename is kept so the file still reads as the root overview.
|
|
17844
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/core/prompts/sections/custom-instructions.ts
|
|
17845
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/services/roo-config/index.ts
|
|
17846
|
+
*/
|
|
17847
|
+
const ROO_GLOBAL_ROOT_RULE_FILE_NAME = "AGENTS.md";
|
|
17848
|
+
/**
|
|
17736
17849
|
* `.roo/rules-{mode}/` — the mode-specific rule directory Roo/Zoo Code load
|
|
17737
17850
|
* INSTEAD of `.roo/rules/` while that mode is active. The relative path is the
|
|
17738
17851
|
* same in global scope, where it resolves under `~/.roo/`.
|
|
@@ -18349,6 +18462,8 @@ const ZCODE_SKILLS_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "skills");
|
|
|
18349
18462
|
const ZCODE_CONFIG_FILE_NAME = "config.json";
|
|
18350
18463
|
const ZCODE_GLOBAL_CONFIG_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "cli");
|
|
18351
18464
|
const ZCODE_MCP_SERVERS_KEY = "servers";
|
|
18465
|
+
const ZCODE_HOOKS_CONFIG_KEY = "hooks";
|
|
18466
|
+
const ZCODE_HOOKS_EVENTS_KEY = "events";
|
|
18352
18467
|
const ZCODE_AGENTS_DIR_PATH = (0, node_path.join)(ZCODE_DIR, "agents");
|
|
18353
18468
|
//#endregion
|
|
18354
18469
|
//#region src/features/commands/zcode-command.ts
|
|
@@ -19221,19 +19336,124 @@ function generateAmpPluginCode({ config, supportedEvents, eventMap }) {
|
|
|
19221
19336
|
lines.push("}", "");
|
|
19222
19337
|
return lines.join("\n");
|
|
19223
19338
|
}
|
|
19339
|
+
/**
|
|
19340
|
+
* Name of the ownership record written next to the hooks destination it
|
|
19341
|
+
* describes (e.g. `.claude/.rulesync-hooks-lock.json`). Only written when hook
|
|
19342
|
+
* preservation is enabled, so a project that never opts in never sees it.
|
|
19343
|
+
*/
|
|
19344
|
+
const HOOKS_OWNERSHIP_LOCK_FILE_NAME = ".rulesync-hooks-lock.json";
|
|
19345
|
+
const OwnedHookRefSchema = zod_mini.z.object({
|
|
19346
|
+
event: zod_mini.z.string(),
|
|
19347
|
+
matcher: zod_mini.z.optional(zod_mini.z.string()),
|
|
19348
|
+
identity: zod_mini.z.string()
|
|
19349
|
+
});
|
|
19350
|
+
const HooksOwnershipLockSchema = zod_mini.z.object({
|
|
19351
|
+
lockfileVersion: zod_mini.z.number(),
|
|
19352
|
+
owned: zod_mini.z.array(OwnedHookRefSchema)
|
|
19353
|
+
});
|
|
19354
|
+
/**
|
|
19355
|
+
* The comparable form of a hook entry: identical keys mean "the same hook, in
|
|
19356
|
+
* the same place". Encoded as JSON so no separator can collide with a matcher
|
|
19357
|
+
* or an identity that happens to contain one.
|
|
19358
|
+
*/
|
|
19359
|
+
function ownedHookKey({ event, matcher, identity }) {
|
|
19360
|
+
return JSON.stringify([
|
|
19361
|
+
event,
|
|
19362
|
+
matcher ?? null,
|
|
19363
|
+
identity
|
|
19364
|
+
]);
|
|
19365
|
+
}
|
|
19366
|
+
/**
|
|
19367
|
+
* Read the previous run's owned set. Anything unreadable — missing, empty,
|
|
19368
|
+
* malformed, or written by a different lock version — yields an empty set,
|
|
19369
|
+
* which makes every existing handler look third-party and therefore preserved.
|
|
19370
|
+
*/
|
|
19371
|
+
function parseHooksOwnershipLock(content) {
|
|
19372
|
+
if (content === null || content.trim() === "") return /* @__PURE__ */ new Set();
|
|
19373
|
+
let parsed;
|
|
19374
|
+
try {
|
|
19375
|
+
parsed = JSON.parse(content);
|
|
19376
|
+
} catch {
|
|
19377
|
+
return /* @__PURE__ */ new Set();
|
|
19378
|
+
}
|
|
19379
|
+
const result = HooksOwnershipLockSchema.safeParse(parsed);
|
|
19380
|
+
if (!result.success || result.data.lockfileVersion !== 1) return /* @__PURE__ */ new Set();
|
|
19381
|
+
return new Set(result.data.owned.map((ref) => ownedHookKey(ref)));
|
|
19382
|
+
}
|
|
19383
|
+
function compareOwnedHookRefs(a, b) {
|
|
19384
|
+
const left = ownedHookKey(a);
|
|
19385
|
+
const right = ownedHookKey(b);
|
|
19386
|
+
if (left === right) return 0;
|
|
19387
|
+
return left < right ? -1 : 1;
|
|
19388
|
+
}
|
|
19389
|
+
/** Serialize the owned set in a stable order so regenerating produces no diff. */
|
|
19390
|
+
function serializeHooksOwnershipLock(owned) {
|
|
19391
|
+
return `${JSON.stringify({
|
|
19392
|
+
lockfileVersion: 1,
|
|
19393
|
+
owned: [...owned].toSorted(compareOwnedHookRefs)
|
|
19394
|
+
}, null, 2)}\n`;
|
|
19395
|
+
}
|
|
19396
|
+
/**
|
|
19397
|
+
* The ownership record itself. It is a rulesync by-product rather than tool
|
|
19398
|
+
* configuration, so it is deletable: dropping the target should take it along.
|
|
19399
|
+
*/
|
|
19400
|
+
var HooksOwnershipLockFile = class extends ToolFile {
|
|
19401
|
+
isDeletable() {
|
|
19402
|
+
return true;
|
|
19403
|
+
}
|
|
19404
|
+
validate() {
|
|
19405
|
+
return {
|
|
19406
|
+
success: true,
|
|
19407
|
+
error: null
|
|
19408
|
+
};
|
|
19409
|
+
}
|
|
19410
|
+
};
|
|
19411
|
+
/** Build the lock file that records what this run generated. */
|
|
19412
|
+
function buildHooksOwnershipLockFile({ outputRoot, relativeDirPath, owned }) {
|
|
19413
|
+
return new HooksOwnershipLockFile({
|
|
19414
|
+
outputRoot,
|
|
19415
|
+
relativeDirPath,
|
|
19416
|
+
relativeFilePath: HOOKS_OWNERSHIP_LOCK_FILE_NAME,
|
|
19417
|
+
fileContent: serializeHooksOwnershipLock(owned),
|
|
19418
|
+
validate: false
|
|
19419
|
+
});
|
|
19420
|
+
}
|
|
19224
19421
|
//#endregion
|
|
19225
19422
|
//#region src/features/hooks/tool-hooks.ts
|
|
19226
19423
|
var ToolHooks = class extends ToolFile {
|
|
19424
|
+
ownedHookRefs;
|
|
19227
19425
|
constructor(params) {
|
|
19228
19426
|
super({
|
|
19229
19427
|
...params,
|
|
19230
19428
|
validate: true
|
|
19231
19429
|
});
|
|
19430
|
+
this.ownedHookRefs = params.ownedHookRefs;
|
|
19232
19431
|
if (params.validate) {
|
|
19233
19432
|
const result = this.validate();
|
|
19234
19433
|
if (!result.success) throw result.error;
|
|
19235
19434
|
}
|
|
19236
19435
|
}
|
|
19436
|
+
/**
|
|
19437
|
+
* The ownership record for this destination, or nothing when preservation is
|
|
19438
|
+
* off — in which case rulesync owns the whole list and needs no record.
|
|
19439
|
+
*/
|
|
19440
|
+
getOwnershipLockFiles() {
|
|
19441
|
+
if (this.ownedHookRefs === void 0) return [];
|
|
19442
|
+
return [buildHooksOwnershipLockFile({
|
|
19443
|
+
outputRoot: this.getOutputRoot(),
|
|
19444
|
+
relativeDirPath: this.getRelativeDirPath(),
|
|
19445
|
+
owned: this.ownedHookRefs
|
|
19446
|
+
})];
|
|
19447
|
+
}
|
|
19448
|
+
/**
|
|
19449
|
+
* Whether the adapter can keep handlers it did not generate. Destinations
|
|
19450
|
+
* rulesync owns outright (plugin bundles) must answer `false`: there is no
|
|
19451
|
+
* third party writing into them, and preserving there would only make
|
|
19452
|
+
* removals impossible.
|
|
19453
|
+
*/
|
|
19454
|
+
static supportsPreserveUnowned() {
|
|
19455
|
+
return false;
|
|
19456
|
+
}
|
|
19237
19457
|
static getSettablePaths(_options) {
|
|
19238
19458
|
throw new Error("Please implement this method in the subclass.");
|
|
19239
19459
|
}
|
|
@@ -20670,6 +20890,292 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
20670
20890
|
}
|
|
20671
20891
|
};
|
|
20672
20892
|
//#endregion
|
|
20893
|
+
//#region src/features/hooks/preserve-unowned-hook-commands.ts
|
|
20894
|
+
/**
|
|
20895
|
+
* Read the `hooks` value from a destination JSON file.
|
|
20896
|
+
*
|
|
20897
|
+
* `malformed` separates "the file has no hooks" from "the file could not be
|
|
20898
|
+
* read", so a caller that is about to preserve content can say out loud that
|
|
20899
|
+
* it is replacing a file it failed to parse.
|
|
20900
|
+
*/
|
|
20901
|
+
function parseExistingHooksValue(existingContent) {
|
|
20902
|
+
if (existingContent.trim() === "") return {
|
|
20903
|
+
hooks: void 0,
|
|
20904
|
+
malformed: false
|
|
20905
|
+
};
|
|
20906
|
+
try {
|
|
20907
|
+
const parsed = JSON.parse(existingContent);
|
|
20908
|
+
return {
|
|
20909
|
+
hooks: isPlainObject$1(parsed) ? parsed.hooks : void 0,
|
|
20910
|
+
malformed: false
|
|
20911
|
+
};
|
|
20912
|
+
} catch {
|
|
20913
|
+
return {
|
|
20914
|
+
hooks: void 0,
|
|
20915
|
+
malformed: true
|
|
20916
|
+
};
|
|
20917
|
+
}
|
|
20918
|
+
}
|
|
20919
|
+
/**
|
|
20920
|
+
* Produce the destination hooks list, optionally keeping handlers rulesync does
|
|
20921
|
+
* not own.
|
|
20922
|
+
*
|
|
20923
|
+
* Ownership is decided by `previouslyOwned` — the identities recorded in the
|
|
20924
|
+
* destination's ownership lock on the previous run — and never by guessing from
|
|
20925
|
+
* the command text. That is what makes removal work: a handler rulesync wrote
|
|
20926
|
+
* before and no longer generates is retracted, while a handler rulesync has
|
|
20927
|
+
* never written is left alone. Without a lock (the first run after opting in)
|
|
20928
|
+
* nothing is retracted, which is the non-destructive direction.
|
|
20929
|
+
*
|
|
20930
|
+
* Callers must pass the destination's shape. Plugin destinations are fully
|
|
20931
|
+
* owned by rulesync and must not call this.
|
|
20932
|
+
*/
|
|
20933
|
+
function mergeGeneratedHookLists({ existingContent, generatedHooks, shape, preserveUnowned, previouslyOwned, logger }) {
|
|
20934
|
+
const owned = collectOwnedRefs({
|
|
20935
|
+
generatedHooks,
|
|
20936
|
+
shape
|
|
20937
|
+
});
|
|
20938
|
+
if (!preserveUnowned) return {
|
|
20939
|
+
hooks: generatedHooks,
|
|
20940
|
+
owned
|
|
20941
|
+
};
|
|
20942
|
+
const { hooks: existingHooks, malformed } = parseExistingHooksValue(existingContent);
|
|
20943
|
+
if (malformed) logger?.warn("Replacing hooks wholesale: the existing file is not valid JSON, so no third-party hook in it can be preserved.");
|
|
20944
|
+
return {
|
|
20945
|
+
hooks: preserveUnownedHookCommands({
|
|
20946
|
+
existingHooks,
|
|
20947
|
+
generatedHooks,
|
|
20948
|
+
shape,
|
|
20949
|
+
owned,
|
|
20950
|
+
previouslyOwned: previouslyOwned ?? /* @__PURE__ */ new Set(),
|
|
20951
|
+
logger
|
|
20952
|
+
}),
|
|
20953
|
+
owned
|
|
20954
|
+
};
|
|
20955
|
+
}
|
|
20956
|
+
/**
|
|
20957
|
+
* Merge the destination's existing handlers into the generated set.
|
|
20958
|
+
*
|
|
20959
|
+
* Each existing handler falls into exactly one of three cases:
|
|
20960
|
+
* - this run generates it → the generated copy stands, the existing one is dropped;
|
|
20961
|
+
* - a previous run generated it and this one does not → it is retracted, with a warning;
|
|
20962
|
+
* - neither → it is unowned, and kept.
|
|
20963
|
+
*/
|
|
20964
|
+
function preserveUnownedHookCommands({ existingHooks, generatedHooks, shape, owned, previouslyOwned, logger }) {
|
|
20965
|
+
const result = cloneGeneratedHooks({
|
|
20966
|
+
generatedHooks,
|
|
20967
|
+
shape
|
|
20968
|
+
});
|
|
20969
|
+
if (!isPlainObject$1(existingHooks)) return result;
|
|
20970
|
+
const seen = new Set(owned.map((ref) => ownedHookKey(ref)));
|
|
20971
|
+
for (const [event, existingValue] of Object.entries(existingHooks)) {
|
|
20972
|
+
if (isPrototypePollutionKey(event)) continue;
|
|
20973
|
+
if (!Array.isArray(existingValue)) {
|
|
20974
|
+
warnSkip({
|
|
20975
|
+
logger,
|
|
20976
|
+
event,
|
|
20977
|
+
expected: "an array of hook entries"
|
|
20978
|
+
});
|
|
20979
|
+
continue;
|
|
20980
|
+
}
|
|
20981
|
+
const merged = shape === "matcher-groups" ? mergeMatcherGroups({
|
|
20982
|
+
existing: existingValue,
|
|
20983
|
+
generated: result[event] ?? [],
|
|
20984
|
+
event,
|
|
20985
|
+
seen,
|
|
20986
|
+
previouslyOwned,
|
|
20987
|
+
logger
|
|
20988
|
+
}) : mergeFlatHandlers({
|
|
20989
|
+
existing: existingValue,
|
|
20990
|
+
generated: result[event] ?? [],
|
|
20991
|
+
event,
|
|
20992
|
+
seen,
|
|
20993
|
+
previouslyOwned,
|
|
20994
|
+
logger
|
|
20995
|
+
});
|
|
20996
|
+
if (merged.length === 0) delete result[event];
|
|
20997
|
+
else result[event] = merged;
|
|
20998
|
+
}
|
|
20999
|
+
return result;
|
|
21000
|
+
}
|
|
21001
|
+
function cloneGeneratedHooks({ generatedHooks, shape }) {
|
|
21002
|
+
const result = Object.create(null);
|
|
21003
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
21004
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
21005
|
+
result[event] = shape === "matcher-groups" ? value.map((group) => cloneMatcherGroup(group)) : value.map((handler) => cloneHandler(handler));
|
|
21006
|
+
}
|
|
21007
|
+
return result;
|
|
21008
|
+
}
|
|
21009
|
+
/** The identities of every handler in the generated set, in destination order. */
|
|
21010
|
+
function collectOwnedRefs({ generatedHooks, shape }) {
|
|
21011
|
+
const owned = [];
|
|
21012
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
21013
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
21014
|
+
if (shape === "flat") {
|
|
21015
|
+
for (const handler of value) if (isPlainObject$1(handler)) owned.push({
|
|
21016
|
+
event,
|
|
21017
|
+
identity: handlerIdentity(handler)
|
|
21018
|
+
});
|
|
21019
|
+
continue;
|
|
21020
|
+
}
|
|
21021
|
+
for (const group of value) {
|
|
21022
|
+
if (!isMatcherGroup(group)) continue;
|
|
21023
|
+
const matcher = matcherKey(group.matcher);
|
|
21024
|
+
for (const handler of group.hooks) if (isPlainObject$1(handler)) owned.push({
|
|
21025
|
+
event,
|
|
21026
|
+
matcher,
|
|
21027
|
+
identity: handlerIdentity(handler)
|
|
21028
|
+
});
|
|
21029
|
+
}
|
|
21030
|
+
}
|
|
21031
|
+
return owned;
|
|
21032
|
+
}
|
|
21033
|
+
function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
21034
|
+
const merged = [...generated];
|
|
21035
|
+
for (const group of existing) {
|
|
21036
|
+
if (!isMatcherGroup(group)) {
|
|
21037
|
+
warnSkip({
|
|
21038
|
+
logger,
|
|
21039
|
+
event,
|
|
21040
|
+
expected: "a matcher group"
|
|
21041
|
+
});
|
|
21042
|
+
continue;
|
|
21043
|
+
}
|
|
21044
|
+
const matcher = matcherKey(group.matcher);
|
|
21045
|
+
const preserved = group.hooks.filter((handler) => shouldPreserve({
|
|
21046
|
+
handler,
|
|
21047
|
+
event,
|
|
21048
|
+
matcher,
|
|
21049
|
+
seen,
|
|
21050
|
+
previouslyOwned,
|
|
21051
|
+
logger
|
|
21052
|
+
})).map((handler) => cloneHandler(handler));
|
|
21053
|
+
if (preserved.length === 0) continue;
|
|
21054
|
+
const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher);
|
|
21055
|
+
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
21056
|
+
else merged.push({
|
|
21057
|
+
...omitPrototypePollutionKeys(group),
|
|
21058
|
+
hooks: preserved
|
|
21059
|
+
});
|
|
21060
|
+
}
|
|
21061
|
+
return merged;
|
|
21062
|
+
}
|
|
21063
|
+
function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
21064
|
+
const preserved = [];
|
|
21065
|
+
for (const handler of existing) {
|
|
21066
|
+
if (isMatcherGroup(handler)) {
|
|
21067
|
+
warnSkip({
|
|
21068
|
+
logger,
|
|
21069
|
+
event,
|
|
21070
|
+
expected: "a flat handler"
|
|
21071
|
+
});
|
|
21072
|
+
continue;
|
|
21073
|
+
}
|
|
21074
|
+
if (!shouldPreserve({
|
|
21075
|
+
handler,
|
|
21076
|
+
event,
|
|
21077
|
+
matcher: void 0,
|
|
21078
|
+
seen,
|
|
21079
|
+
previouslyOwned,
|
|
21080
|
+
logger
|
|
21081
|
+
})) continue;
|
|
21082
|
+
preserved.push(cloneHandler(handler));
|
|
21083
|
+
}
|
|
21084
|
+
return [...generated, ...preserved];
|
|
21085
|
+
}
|
|
21086
|
+
function isMatcherGroup(value) {
|
|
21087
|
+
return isPlainObject$1(value) && Array.isArray(value.hooks);
|
|
21088
|
+
}
|
|
21089
|
+
function cloneMatcherGroup(group) {
|
|
21090
|
+
if (!isMatcherGroup(group)) return cloneHandler(group);
|
|
21091
|
+
return {
|
|
21092
|
+
...group,
|
|
21093
|
+
hooks: group.hooks.map((handler) => cloneHandler(handler))
|
|
21094
|
+
};
|
|
21095
|
+
}
|
|
21096
|
+
function cloneHandler(handler) {
|
|
21097
|
+
return isPlainObject$1(handler) ? omitPrototypePollutionKeys(handler) : handler;
|
|
21098
|
+
}
|
|
21099
|
+
/**
|
|
21100
|
+
* Decide one existing handler, and remember it so an exact duplicate later in
|
|
21101
|
+
* the same event is not appended twice.
|
|
21102
|
+
*/
|
|
21103
|
+
function shouldPreserve({ handler, event, matcher, seen, previouslyOwned, logger }) {
|
|
21104
|
+
if (!isPlainObject$1(handler)) {
|
|
21105
|
+
warnSkip({
|
|
21106
|
+
logger,
|
|
21107
|
+
event,
|
|
21108
|
+
expected: "a hook object"
|
|
21109
|
+
});
|
|
21110
|
+
return false;
|
|
21111
|
+
}
|
|
21112
|
+
const identity = handlerIdentity(handler);
|
|
21113
|
+
const key = ownedHookKey({
|
|
21114
|
+
event,
|
|
21115
|
+
matcher,
|
|
21116
|
+
identity
|
|
21117
|
+
});
|
|
21118
|
+
if (seen.has(key)) return false;
|
|
21119
|
+
if (previouslyOwned.has(key)) {
|
|
21120
|
+
logger?.warn(`Removing hook rulesync no longer generates on ${event}: ${identity}`);
|
|
21121
|
+
return false;
|
|
21122
|
+
}
|
|
21123
|
+
seen.add(key);
|
|
21124
|
+
logger?.warn(`Preserving unowned hook on ${event}: ${identity}`);
|
|
21125
|
+
return true;
|
|
21126
|
+
}
|
|
21127
|
+
/**
|
|
21128
|
+
* A handler's identity is its action, not its whole shape: two entries running
|
|
21129
|
+
* the same command are the same hook even if their timeouts differ, so the
|
|
21130
|
+
* generated one replaces the existing one instead of doubling it. Shapes with
|
|
21131
|
+
* no recognizable action fall back to their full structure, which keeps them
|
|
21132
|
+
* comparable across runs — the alternative, no identity at all, made them
|
|
21133
|
+
* accumulate on every generate.
|
|
21134
|
+
*/
|
|
21135
|
+
function handlerIdentity(handler) {
|
|
21136
|
+
const type = typeof handler.type === "string" ? handler.type : inferredType(handler);
|
|
21137
|
+
switch (type) {
|
|
21138
|
+
case "http": return taggedIdentity("http", handler.url) ?? structuralIdentity(handler);
|
|
21139
|
+
case "mcp_tool": return mcpToolIdentity(handler) ?? structuralIdentity(handler);
|
|
21140
|
+
case "prompt":
|
|
21141
|
+
case "agent": return taggedIdentity(type, handler.prompt) ?? structuralIdentity(handler);
|
|
21142
|
+
case "function": return taggedIdentity("function", handler.name) ?? structuralIdentity(handler);
|
|
21143
|
+
default: return taggedIdentity("command", handler.command) ?? taggedIdentity("prompt", handler.prompt) ?? structuralIdentity(handler);
|
|
21144
|
+
}
|
|
21145
|
+
}
|
|
21146
|
+
function inferredType(handler) {
|
|
21147
|
+
return typeof handler.url === "string" ? "http" : "command";
|
|
21148
|
+
}
|
|
21149
|
+
function taggedIdentity(kind, value) {
|
|
21150
|
+
return typeof value === "string" && value !== "" ? `${kind}:${value}` : void 0;
|
|
21151
|
+
}
|
|
21152
|
+
function mcpToolIdentity(handler) {
|
|
21153
|
+
const server = typeof handler.server === "string" ? handler.server : "";
|
|
21154
|
+
const tool = typeof handler.tool === "string" ? handler.tool : "";
|
|
21155
|
+
if (server === "" && tool === "") return;
|
|
21156
|
+
return `mcp_tool:${server}:${tool}:${stableStringify$1(handler.input)}`;
|
|
21157
|
+
}
|
|
21158
|
+
function structuralIdentity(handler) {
|
|
21159
|
+
return `json:${stableStringify$1(handler)}`;
|
|
21160
|
+
}
|
|
21161
|
+
/** Key-order-independent JSON, so an identity survives a rewritten file. */
|
|
21162
|
+
function stableStringify$1(value) {
|
|
21163
|
+
if (Array.isArray(value)) return `[${value.map((entry) => stableStringify$1(entry)).join(",")}]`;
|
|
21164
|
+
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(",")}}`;
|
|
21165
|
+
return JSON.stringify(value) ?? "null";
|
|
21166
|
+
}
|
|
21167
|
+
/**
|
|
21168
|
+
* Matcher groups are matched by the whole matcher value, not just strings: a
|
|
21169
|
+
* non-string matcher is a shape rulesync does not emit, and collapsing every
|
|
21170
|
+
* such group onto one key would merge unrelated third-party groups together.
|
|
21171
|
+
*/
|
|
21172
|
+
function matcherKey(matcher) {
|
|
21173
|
+
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
21174
|
+
}
|
|
21175
|
+
function warnSkip({ logger, event, expected }) {
|
|
21176
|
+
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
21177
|
+
}
|
|
21178
|
+
//#endregion
|
|
20673
21179
|
//#region src/features/hooks/claudecode-hooks.ts
|
|
20674
21180
|
const CLAUDE_CONVERTER_CONFIG = {
|
|
20675
21181
|
supportedEvents: CLAUDE_HOOK_EVENTS,
|
|
@@ -20738,7 +21244,7 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
20738
21244
|
commandOnly: true
|
|
20739
21245
|
}]
|
|
20740
21246
|
};
|
|
20741
|
-
var ClaudecodeHooks = class extends ToolHooks {
|
|
21247
|
+
var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
20742
21248
|
constructor(params) {
|
|
20743
21249
|
super({
|
|
20744
21250
|
...params,
|
|
@@ -20773,7 +21279,13 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20773
21279
|
validate
|
|
20774
21280
|
});
|
|
20775
21281
|
}
|
|
20776
|
-
static
|
|
21282
|
+
static supportsPreserveUnowned() {
|
|
21283
|
+
return true;
|
|
21284
|
+
}
|
|
21285
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
21286
|
+
return toolHooks instanceof ClaudecodeHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
21287
|
+
}
|
|
21288
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
20777
21289
|
const paths = this.getSettablePaths({ global });
|
|
20778
21290
|
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20779
21291
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -20784,11 +21296,20 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20784
21296
|
converterConfig: this.getConverterConfig(),
|
|
20785
21297
|
logger
|
|
20786
21298
|
});
|
|
21299
|
+
const preserving = preserveUnowned && this.supportsPreserveUnowned();
|
|
21300
|
+
const merged = mergeGeneratedHookLists({
|
|
21301
|
+
existingContent,
|
|
21302
|
+
generatedHooks: claudeHooks,
|
|
21303
|
+
shape: "matcher-groups",
|
|
21304
|
+
preserveUnowned: preserving,
|
|
21305
|
+
previouslyOwned: preserving ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21306
|
+
logger
|
|
21307
|
+
});
|
|
20787
21308
|
const fileContent = applySharedConfigPatch({
|
|
20788
21309
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
20789
21310
|
feature: "hooks",
|
|
20790
21311
|
existingContent,
|
|
20791
|
-
patch: { hooks:
|
|
21312
|
+
patch: { hooks: merged.hooks },
|
|
20792
21313
|
filePath
|
|
20793
21314
|
});
|
|
20794
21315
|
return new this({
|
|
@@ -20796,6 +21317,7 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20796
21317
|
relativeDirPath: paths.relativeDirPath,
|
|
20797
21318
|
relativeFilePath: paths.relativeFilePath,
|
|
20798
21319
|
fileContent,
|
|
21320
|
+
ownedHookRefs: preserving ? merged.owned : void 0,
|
|
20799
21321
|
validate
|
|
20800
21322
|
});
|
|
20801
21323
|
}
|
|
@@ -20853,6 +21375,14 @@ var ClaudecodePluginHooks = class extends ClaudecodeHooks {
|
|
|
20853
21375
|
projectDirVar: "$CLAUDE_PLUGIN_ROOT"
|
|
20854
21376
|
};
|
|
20855
21377
|
}
|
|
21378
|
+
/**
|
|
21379
|
+
* The plugin bundle is generated in full by rulesync — nothing else writes
|
|
21380
|
+
* into it — so preserving there would buy nothing and cost the ability to
|
|
21381
|
+
* remove a hook.
|
|
21382
|
+
*/
|
|
21383
|
+
static supportsPreserveUnowned() {
|
|
21384
|
+
return false;
|
|
21385
|
+
}
|
|
20856
21386
|
static getSettablePaths() {
|
|
20857
21387
|
return {
|
|
20858
21388
|
relativeDirPath: CLAUDECODE_PLUGIN_HOOKS_DIR,
|
|
@@ -21283,21 +21813,30 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21283
21813
|
validate
|
|
21284
21814
|
});
|
|
21285
21815
|
}
|
|
21286
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
21816
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21287
21817
|
const paths = CodexcliHooks.getSettablePaths({ global });
|
|
21818
|
+
const existingContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
21288
21819
|
const config = rulesyncHooks.getJson();
|
|
21289
|
-
const
|
|
21290
|
-
|
|
21291
|
-
|
|
21292
|
-
|
|
21820
|
+
const merged = mergeGeneratedHookLists({
|
|
21821
|
+
existingContent,
|
|
21822
|
+
generatedHooks: canonicalToToolHooks({
|
|
21823
|
+
config,
|
|
21824
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
21825
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
21826
|
+
logger
|
|
21827
|
+
}),
|
|
21828
|
+
shape: "matcher-groups",
|
|
21829
|
+
preserveUnowned,
|
|
21830
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21293
21831
|
logger
|
|
21294
21832
|
});
|
|
21295
|
-
const fileContent = JSON.stringify({ hooks:
|
|
21833
|
+
const fileContent = JSON.stringify({ hooks: merged.hooks }, null, 2);
|
|
21296
21834
|
return new CodexcliHooks({
|
|
21297
21835
|
outputRoot,
|
|
21298
21836
|
relativeDirPath: paths.relativeDirPath,
|
|
21299
21837
|
relativeFilePath: paths.relativeFilePath,
|
|
21300
21838
|
fileContent,
|
|
21839
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21301
21840
|
validate
|
|
21302
21841
|
});
|
|
21303
21842
|
}
|
|
@@ -21333,8 +21872,11 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21333
21872
|
validate: false
|
|
21334
21873
|
});
|
|
21335
21874
|
}
|
|
21336
|
-
static
|
|
21337
|
-
return
|
|
21875
|
+
static supportsPreserveUnowned() {
|
|
21876
|
+
return true;
|
|
21877
|
+
}
|
|
21878
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), toolHooks } = {}) {
|
|
21879
|
+
return [await CodexcliConfigToml.fromOutputRoot({ outputRoot }), ...toolHooks instanceof CodexcliHooks ? toolHooks.getOwnershipLockFiles() : []];
|
|
21338
21880
|
}
|
|
21339
21881
|
};
|
|
21340
21882
|
//#endregion
|
|
@@ -21939,7 +22481,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21939
22481
|
validate
|
|
21940
22482
|
});
|
|
21941
22483
|
}
|
|
21942
|
-
static fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
22484
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21943
22485
|
const config = rulesyncHooks.getJson();
|
|
21944
22486
|
const cursorSupported = new Set(CURSOR_HOOK_EVENTS);
|
|
21945
22487
|
const sharedHooks = {};
|
|
@@ -21966,21 +22508,36 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21966
22508
|
}));
|
|
21967
22509
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
21968
22510
|
}
|
|
22511
|
+
const paths = CursorHooks.getSettablePaths({ global });
|
|
22512
|
+
const merged = mergeGeneratedHookLists({
|
|
22513
|
+
existingContent: await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "",
|
|
22514
|
+
generatedHooks: mappedHooks,
|
|
22515
|
+
shape: "flat",
|
|
22516
|
+
preserveUnowned,
|
|
22517
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
22518
|
+
logger
|
|
22519
|
+
});
|
|
21969
22520
|
const cursorConfig = {
|
|
21970
22521
|
version: config.version ?? 1,
|
|
21971
|
-
hooks:
|
|
22522
|
+
hooks: merged.hooks
|
|
21972
22523
|
};
|
|
21973
22524
|
const fileContent = JSON.stringify(cursorConfig, null, 2);
|
|
21974
|
-
const paths = CursorHooks.getSettablePaths({ global });
|
|
21975
22525
|
return new CursorHooks({
|
|
21976
22526
|
outputRoot,
|
|
21977
22527
|
relativeDirPath: paths.relativeDirPath,
|
|
21978
22528
|
relativeFilePath: paths.relativeFilePath,
|
|
21979
22529
|
fileContent,
|
|
22530
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21980
22531
|
validate,
|
|
21981
22532
|
rulesyncHooks
|
|
21982
22533
|
});
|
|
21983
22534
|
}
|
|
22535
|
+
static supportsPreserveUnowned() {
|
|
22536
|
+
return true;
|
|
22537
|
+
}
|
|
22538
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
22539
|
+
return toolHooks instanceof CursorHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
22540
|
+
}
|
|
21984
22541
|
toRulesyncHooks() {
|
|
21985
22542
|
const content = this.getFileContent();
|
|
21986
22543
|
const parsed = JSON.parse(content);
|
|
@@ -22348,7 +22905,7 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
22348
22905
|
* adapter so the settings reader in `src/utils/` can name the same list without
|
|
22349
22906
|
* importing a feature.
|
|
22350
22907
|
*
|
|
22351
|
-
* @see https://docs.factory.ai/cli/
|
|
22908
|
+
* @see https://docs.factory.ai/droid-cli/settings
|
|
22352
22909
|
*/
|
|
22353
22910
|
const FACTORYDROID_OVERRIDE_KEYS = [
|
|
22354
22911
|
"commandBlocklist",
|
|
@@ -22356,6 +22913,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
22356
22913
|
"sandbox",
|
|
22357
22914
|
"mcpPolicy",
|
|
22358
22915
|
"mcpAutonomyOverrides",
|
|
22916
|
+
"mcpAutonomyUrlOverrides",
|
|
22917
|
+
"builtInToolAutonomyOverrides",
|
|
22359
22918
|
"enableDroidShield",
|
|
22360
22919
|
"sessionDefaultSettings",
|
|
22361
22920
|
"maxAutonomyLevel",
|
|
@@ -22363,7 +22922,10 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
22363
22922
|
"interactionMode",
|
|
22364
22923
|
"extraKnownMarketplaces",
|
|
22365
22924
|
"enabledPlugins",
|
|
22925
|
+
"strictEnabledPlugins",
|
|
22926
|
+
"strictKnownMarketplaces",
|
|
22366
22927
|
"hooksDisabled",
|
|
22928
|
+
"allowManagedHooksOnly",
|
|
22367
22929
|
"disabledSkills",
|
|
22368
22930
|
"modelPolicy",
|
|
22369
22931
|
"missionPolicy"
|
|
@@ -25220,6 +25782,186 @@ var VibeHooks = class VibeHooks extends ToolHooks {
|
|
|
25220
25782
|
}
|
|
25221
25783
|
};
|
|
25222
25784
|
//#endregion
|
|
25785
|
+
//#region src/features/hooks/zcode-hooks.ts
|
|
25786
|
+
/**
|
|
25787
|
+
* Single spelling of the config.json codec/policy, matching the one in
|
|
25788
|
+
* ZcodeMcp: fail closed on an unparseable root rather than replacing the
|
|
25789
|
+
* user's primary ZCode config with generated output.
|
|
25790
|
+
*/
|
|
25791
|
+
function parseZcodeConfig$1(fileContent, filePath) {
|
|
25792
|
+
return parseSharedConfig({
|
|
25793
|
+
format: "json",
|
|
25794
|
+
fileContent,
|
|
25795
|
+
filePath,
|
|
25796
|
+
invalidRootPolicy: "error"
|
|
25797
|
+
});
|
|
25798
|
+
}
|
|
25799
|
+
/**
|
|
25800
|
+
* Drop ZCode's native `process` hooks from an `events` map before the shared
|
|
25801
|
+
* import converter runs. The converter would coerce the unknown type to
|
|
25802
|
+
* `command` and import the executable alone — losing the `args` vector that
|
|
25803
|
+
* *is* the process hook's command line — so the imported hook would run
|
|
25804
|
+
* something else than the file states. Matcher groups left without hooks are
|
|
25805
|
+
* dropped with them.
|
|
25806
|
+
*/
|
|
25807
|
+
function stripProcessHooks({ events, logger }) {
|
|
25808
|
+
if (!isRecord$1(events)) return events;
|
|
25809
|
+
const result = Object.create(null);
|
|
25810
|
+
for (const [eventName, entries] of Object.entries(events)) {
|
|
25811
|
+
if (!Array.isArray(entries)) {
|
|
25812
|
+
result[eventName] = entries;
|
|
25813
|
+
continue;
|
|
25814
|
+
}
|
|
25815
|
+
const kept = [];
|
|
25816
|
+
for (const entry of entries) {
|
|
25817
|
+
if (!isRecord$1(entry) || !Array.isArray(entry.hooks)) {
|
|
25818
|
+
kept.push(entry);
|
|
25819
|
+
continue;
|
|
25820
|
+
}
|
|
25821
|
+
const hooks = entry.hooks.filter((hook) => {
|
|
25822
|
+
if (isRecord$1(hook) && hook.type === "process") {
|
|
25823
|
+
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.`);
|
|
25824
|
+
return false;
|
|
25825
|
+
}
|
|
25826
|
+
return true;
|
|
25827
|
+
});
|
|
25828
|
+
if (hooks.length > 0) kept.push({
|
|
25829
|
+
...entry,
|
|
25830
|
+
hooks
|
|
25831
|
+
});
|
|
25832
|
+
}
|
|
25833
|
+
result[eventName] = kept;
|
|
25834
|
+
}
|
|
25835
|
+
return result;
|
|
25836
|
+
}
|
|
25837
|
+
const ZCODE_CONVERTER_CONFIG = {
|
|
25838
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
25839
|
+
canonicalToToolEventNames: CANONICAL_TO_ZCODE_EVENT_NAMES,
|
|
25840
|
+
toolToCanonicalEventNames: ZCODE_TO_CANONICAL_EVENT_NAMES,
|
|
25841
|
+
projectDirVar: "",
|
|
25842
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
25843
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
25844
|
+
booleanPassthroughFields: [{
|
|
25845
|
+
canonical: "async",
|
|
25846
|
+
tool: "async"
|
|
25847
|
+
}, {
|
|
25848
|
+
canonical: "enabled",
|
|
25849
|
+
tool: "enabled"
|
|
25850
|
+
}],
|
|
25851
|
+
stringPassthroughFields: [{
|
|
25852
|
+
canonical: "statusMessage",
|
|
25853
|
+
tool: "statusMessage"
|
|
25854
|
+
}, {
|
|
25855
|
+
canonical: "shell",
|
|
25856
|
+
tool: "shell",
|
|
25857
|
+
commandOnly: true
|
|
25858
|
+
}],
|
|
25859
|
+
wildcardMatcherMeansAll: true
|
|
25860
|
+
};
|
|
25861
|
+
/**
|
|
25862
|
+
* ZCode hooks.
|
|
25863
|
+
*
|
|
25864
|
+
* ZCode reads configuration-file hooks from the `hooks` block of its user
|
|
25865
|
+
* config file, `~/.zcode/cli/config.json`. Workspace config hooks are never
|
|
25866
|
+
* executed — the workspace file is ignored regardless of `hooks.enabled` — so
|
|
25867
|
+
* rulesync treats ZCode hooks as global-only. The event map is nested under
|
|
25868
|
+
* `hooks.events` beside the user-tunable `enabled` and `timeoutMs` siblings,
|
|
25869
|
+
* which are carried over while `events` is replaced.
|
|
25870
|
+
*
|
|
25871
|
+
* @see https://zcode.z.ai/en/docs
|
|
25872
|
+
*/
|
|
25873
|
+
var ZcodeHooks = class ZcodeHooks extends ToolHooks {
|
|
25874
|
+
json;
|
|
25875
|
+
constructor(params) {
|
|
25876
|
+
super(params);
|
|
25877
|
+
this.json = parseZcodeConfig$1(this.fileContent ?? "{}", (0, node_path.join)(this.relativeDirPath, this.relativeFilePath));
|
|
25878
|
+
}
|
|
25879
|
+
isDeletable() {
|
|
25880
|
+
return false;
|
|
25881
|
+
}
|
|
25882
|
+
static getSettablePaths(_options = {}) {
|
|
25883
|
+
return {
|
|
25884
|
+
relativeDirPath: ZCODE_GLOBAL_CONFIG_DIR_PATH,
|
|
25885
|
+
relativeFilePath: ZCODE_CONFIG_FILE_NAME
|
|
25886
|
+
};
|
|
25887
|
+
}
|
|
25888
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
25889
|
+
const paths = this.getSettablePaths({ global });
|
|
25890
|
+
const fileContent = await readFileContentOrNull((0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
25891
|
+
return new ZcodeHooks({
|
|
25892
|
+
outputRoot,
|
|
25893
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25894
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25895
|
+
fileContent,
|
|
25896
|
+
validate,
|
|
25897
|
+
global
|
|
25898
|
+
});
|
|
25899
|
+
}
|
|
25900
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
25901
|
+
const paths = this.getSettablePaths({ global });
|
|
25902
|
+
const filePath = (0, node_path.join)(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25903
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25904
|
+
const existing = parseZcodeConfig$1(existingContent, filePath);
|
|
25905
|
+
const config = rulesyncHooks.getJson();
|
|
25906
|
+
const events = canonicalToToolHooks({
|
|
25907
|
+
config,
|
|
25908
|
+
toolOverrideHooks: config.zcode?.hooks,
|
|
25909
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25910
|
+
logger
|
|
25911
|
+
});
|
|
25912
|
+
const existingHooks = isRecord$1(existing["hooks"]) ? existing[ZCODE_HOOKS_CONFIG_KEY] : {};
|
|
25913
|
+
const shouldStateEnabled = Object.keys(events).length > 0 && existingHooks.enabled === void 0;
|
|
25914
|
+
return new ZcodeHooks({
|
|
25915
|
+
outputRoot,
|
|
25916
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25917
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25918
|
+
fileContent: applySharedConfigPatch({
|
|
25919
|
+
fileKey: sharedConfigFileKey(paths),
|
|
25920
|
+
feature: "hooks",
|
|
25921
|
+
existingContent,
|
|
25922
|
+
patch: { [ZCODE_HOOKS_CONFIG_KEY]: {
|
|
25923
|
+
...existingHooks,
|
|
25924
|
+
...shouldStateEnabled ? { enabled: true } : {},
|
|
25925
|
+
[ZCODE_HOOKS_EVENTS_KEY]: events
|
|
25926
|
+
} },
|
|
25927
|
+
filePath
|
|
25928
|
+
}),
|
|
25929
|
+
validate,
|
|
25930
|
+
global
|
|
25931
|
+
});
|
|
25932
|
+
}
|
|
25933
|
+
toRulesyncHooks({ logger } = {}) {
|
|
25934
|
+
const hooks = toolHooksToCanonical({
|
|
25935
|
+
hooks: stripProcessHooks({
|
|
25936
|
+
events: (isRecord$1(this.json["hooks"]) ? this.json[ZCODE_HOOKS_CONFIG_KEY] : {})[ZCODE_HOOKS_EVENTS_KEY],
|
|
25937
|
+
logger
|
|
25938
|
+
}),
|
|
25939
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25940
|
+
logger
|
|
25941
|
+
});
|
|
25942
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
25943
|
+
hooks,
|
|
25944
|
+
overrideKey: "zcode"
|
|
25945
|
+
}), null, 2) });
|
|
25946
|
+
}
|
|
25947
|
+
validate() {
|
|
25948
|
+
return {
|
|
25949
|
+
success: true,
|
|
25950
|
+
error: null
|
|
25951
|
+
};
|
|
25952
|
+
}
|
|
25953
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
25954
|
+
return new ZcodeHooks({
|
|
25955
|
+
outputRoot,
|
|
25956
|
+
relativeDirPath,
|
|
25957
|
+
relativeFilePath,
|
|
25958
|
+
fileContent: JSON.stringify({ [ZCODE_HOOKS_CONFIG_KEY]: { [ZCODE_HOOKS_EVENTS_KEY]: {} } }, null, 2),
|
|
25959
|
+
validate: false,
|
|
25960
|
+
global
|
|
25961
|
+
});
|
|
25962
|
+
}
|
|
25963
|
+
};
|
|
25964
|
+
//#endregion
|
|
25223
25965
|
//#region src/features/hooks/hooks-processor.ts
|
|
25224
25966
|
const HooksProcessorToolTargetSchema = zod_mini.z.enum(hooksProcessorToolTargetTuple);
|
|
25225
25967
|
/**
|
|
@@ -25291,8 +26033,12 @@ const HOOKS_OVERRIDE_KEY_ALIASES = {
|
|
|
25291
26033
|
"kiro-cli": KIRO_HOOKS_OVERRIDE_KEY,
|
|
25292
26034
|
"kiro-ide": KIRO_HOOKS_OVERRIDE_KEY
|
|
25293
26035
|
};
|
|
25294
|
-
/** The targets
|
|
25295
|
-
const
|
|
26036
|
+
/** The targets whose hooks format carries a per-hook on-disk enable flag. */
|
|
26037
|
+
const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
|
|
26038
|
+
"kiro-cli",
|
|
26039
|
+
"kiro-ide",
|
|
26040
|
+
"zcode"
|
|
26041
|
+
]);
|
|
25296
26042
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
25297
26043
|
["amp", {
|
|
25298
26044
|
class: AmpHooks,
|
|
@@ -25641,6 +26387,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
25641
26387
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
25642
26388
|
supportedHookTypes: ["command", "http"],
|
|
25643
26389
|
supportsMatcher: true
|
|
26390
|
+
}],
|
|
26391
|
+
["zcode", {
|
|
26392
|
+
class: ZcodeHooks,
|
|
26393
|
+
meta: {
|
|
26394
|
+
supportsProject: false,
|
|
26395
|
+
supportsGlobal: true,
|
|
26396
|
+
supportsImport: true
|
|
26397
|
+
},
|
|
26398
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
26399
|
+
supportedHookTypes: ["command"],
|
|
26400
|
+
supportsMatcher: true
|
|
25644
26401
|
}]
|
|
25645
26402
|
]);
|
|
25646
26403
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -25650,7 +26407,8 @@ const hooksProcessorToolTargetsGlobalImportable = [...toolHooksFactories.entries
|
|
|
25650
26407
|
var HooksProcessor = class extends FeatureProcessor {
|
|
25651
26408
|
toolTarget;
|
|
25652
26409
|
global;
|
|
25653
|
-
|
|
26410
|
+
preserveUnownedHooks;
|
|
26411
|
+
constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, dryRun = false, preserveUnownedHooks = false, logger }) {
|
|
25654
26412
|
super({
|
|
25655
26413
|
outputRoot,
|
|
25656
26414
|
inputRoots,
|
|
@@ -25661,6 +26419,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25661
26419
|
if (!result.success) throw new Error(`Invalid tool target for HooksProcessor: ${toolTarget}. ${formatError(result.error)}`);
|
|
25662
26420
|
this.toolTarget = result.data;
|
|
25663
26421
|
this.global = global;
|
|
26422
|
+
this.preserveUnownedHooks = preserveUnownedHooks;
|
|
25664
26423
|
}
|
|
25665
26424
|
async loadRulesyncFiles() {
|
|
25666
26425
|
const relativePaths = getRulesyncSourceCandidates({ paths: RulesyncHooks.getSettablePaths() }).map((candidate) => candidate.relativeFilePath);
|
|
@@ -25752,14 +26511,14 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25752
26511
|
}
|
|
25753
26512
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
25754
26513
|
}
|
|
25755
|
-
if (!
|
|
26514
|
+
if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
|
|
25756
26515
|
const skippedEvents = new Set(unsupportedEventNames({
|
|
25757
26516
|
factory,
|
|
25758
26517
|
sharedHooks,
|
|
25759
26518
|
effectiveHooks
|
|
25760
26519
|
}));
|
|
25761
26520
|
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
|
|
26521
|
+
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
26522
|
}
|
|
25764
26523
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
25765
26524
|
factory,
|
|
@@ -25771,6 +26530,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25771
26530
|
rulesyncHooks,
|
|
25772
26531
|
validate: true,
|
|
25773
26532
|
global: this.global,
|
|
26533
|
+
preserveUnowned: this.preserveUnownedHooks,
|
|
25774
26534
|
logger: withToolTargetPrefix({
|
|
25775
26535
|
logger: this.logger,
|
|
25776
26536
|
toolTarget: this.toolTarget
|
|
@@ -26206,6 +26966,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
26206
26966
|
//#region src/constants/crush-paths.ts
|
|
26207
26967
|
const CRUSH_RULE_FILE_NAME = "CRUSH.md";
|
|
26208
26968
|
const CRUSH_GLOBAL_DIR = (0, node_path.join)(".config", "crush");
|
|
26969
|
+
const CRUSH_LOCAL_RULE_FILE_NAME = "CRUSH.local.md";
|
|
26209
26970
|
const CRUSH_IGNORE_FILE_NAME = ".crushignore";
|
|
26210
26971
|
const CRUSH_SKILLS_PROJECT_DIR = (0, node_path.join)(".crush", "skills");
|
|
26211
26972
|
const CRUSH_SKILLS_GLOBAL_DIR = (0, node_path.join)(CRUSH_GLOBAL_DIR, "skills");
|
|
@@ -32426,9 +33187,16 @@ function lookupTransport(map, key) {
|
|
|
32426
33187
|
}
|
|
32427
33188
|
/**
|
|
32428
33189
|
* Read the "put this server's own instructions into the agent's prompt" flag
|
|
32429
|
-
* off an unfiltered canonical entry, under either spelling.
|
|
32430
|
-
*
|
|
32431
|
-
*
|
|
33190
|
+
* off an unfiltered canonical entry, under either spelling. `undefined` means
|
|
33191
|
+
* the entry says nothing, which is Rovo Dev's own default.
|
|
33192
|
+
*
|
|
33193
|
+
* The flag is a **tri-state**, not a switch that only exists when on. Atlassian
|
|
33194
|
+
* inverted the default on 2026-09-02: a server's instructions are now surfaced
|
|
33195
|
+
* when the key is absent or `true`, and `false` is what suppresses them — the
|
|
33196
|
+
* opposite of the opt-in wording this adapter was first written against, where
|
|
33197
|
+
* absent and `false` meant the same thing. So `false` has to travel end to end;
|
|
33198
|
+
* collapsing it away is what makes a suppression impossible to author, and
|
|
33199
|
+
* erases one on import.
|
|
32432
33200
|
*
|
|
32433
33201
|
* The canonical key decides whenever it is present, the way codex resolves the
|
|
32434
33202
|
* same two-spelling conflict for `experimental_environment`. OR-ing the two
|
|
@@ -32436,28 +33204,25 @@ function lookupTransport(map, key) {
|
|
|
32436
33204
|
* `enable_instructions: true` copied out of Atlassian's docs — fail-open, on
|
|
32437
33205
|
* the one key whose whole purpose is a trust decision.
|
|
32438
33206
|
*
|
|
33207
|
+
* A non-boolean under either spelling reads as `undefined` rather than as a
|
|
33208
|
+
* suppression: Rovo Dev surfaces the instructions for anything that is not
|
|
33209
|
+
* `false`, so writing `false` there would invent a restriction the tool is not
|
|
33210
|
+
* applying. (The canonical key is a strict boolean in the schema, so only the
|
|
33211
|
+
* raw spelling can carry one.)
|
|
33212
|
+
*
|
|
32439
33213
|
* `isPlainObject` rather than `isRecord`: this walks a user-supplied key set,
|
|
32440
33214
|
* so a `constructor` entry must not resolve up the prototype chain.
|
|
32441
33215
|
*/
|
|
32442
33216
|
function readEnableInstructions(rawServer) {
|
|
32443
|
-
if (!isPlainObject$1(rawServer)) return
|
|
32444
|
-
|
|
32445
|
-
return
|
|
32446
|
-
|
|
32447
|
-
|
|
32448
|
-
* Names that were emitted with `enable_instructions: true` during one
|
|
32449
|
-
* `fromRulesyncMcp` call, so the run can say so once rather than per server.
|
|
32450
|
-
* Atlassian gates this key on trust, and it is the only thing generate writes
|
|
32451
|
-
* that widens what steers the model — the quietest possible write is the wrong
|
|
32452
|
-
* one for it.
|
|
32453
|
-
*/
|
|
32454
|
-
function warnEnabledInstructions(names, logger) {
|
|
32455
|
-
if (names.length === 0) return;
|
|
32456
|
-
logger?.warn(`Rovo Dev MCP: writing enable_instructions: true for ${names.join(", ")}. Rovo Dev pastes ${names.length === 1 ? "that server's" : "those servers'"} own instructions into the agent's system prompt, so enable it only for servers you trust.`);
|
|
33217
|
+
if (!isPlainObject$1(rawServer)) return;
|
|
33218
|
+
const canonical = rawServer.rovodevEnableInstructions;
|
|
33219
|
+
if (canonical !== void 0) return typeof canonical === "boolean" ? canonical : void 0;
|
|
33220
|
+
const raw = rawServer.enable_instructions;
|
|
33221
|
+
return typeof raw === "boolean" ? raw : void 0;
|
|
32457
33222
|
}
|
|
32458
33223
|
function toRovodevServer(name, server, logger) {
|
|
32459
33224
|
const { type, transport, disabled: _disabled, rovodevEnableInstructions, ...rest } = server;
|
|
32460
|
-
if (rovodevEnableInstructions ===
|
|
33225
|
+
if (typeof rovodevEnableInstructions === "boolean") rest.enable_instructions = rovodevEnableInstructions;
|
|
32461
33226
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
32462
33227
|
if (declared === void 0) return rest;
|
|
32463
33228
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -32472,7 +33237,7 @@ function toRovodevServer(name, server, logger) {
|
|
|
32472
33237
|
}
|
|
32473
33238
|
function fromRovodevServer(server) {
|
|
32474
33239
|
const { transport, enable_instructions: enableInstructions, ...rest } = server;
|
|
32475
|
-
if (enableInstructions ===
|
|
33240
|
+
if (typeof enableInstructions === "boolean") rest.rovodevEnableInstructions = enableInstructions;
|
|
32476
33241
|
if (typeof transport !== "string") return rest;
|
|
32477
33242
|
const mapped = lookupTransport(ROVODEV_TO_CANONICAL_TRANSPORT, transport);
|
|
32478
33243
|
return mapped === void 0 ? rest : {
|
|
@@ -32913,10 +33678,10 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
32913
33678
|
canWriteDisableToggle = false;
|
|
32914
33679
|
}
|
|
32915
33680
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
32916
|
-
const
|
|
33681
|
+
const enableInstructions = readEnableInstructions(rulesyncMcp.getRawMcpServer(name));
|
|
32917
33682
|
const record = {
|
|
32918
33683
|
...server,
|
|
32919
|
-
...
|
|
33684
|
+
...enableInstructions !== void 0 && { rovodevEnableInstructions: enableInstructions }
|
|
32920
33685
|
};
|
|
32921
33686
|
if (record.disabled === true && !canWriteDisableToggle) {
|
|
32922
33687
|
logger?.warn(`Rovo Dev MCP: skipping disabled server "${name}" because config.yml cannot be parsed, so mcp.disabledMcpServers cannot be written to switch it off.`);
|
|
@@ -32925,7 +33690,6 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
32925
33690
|
const converted = toRovodevServer(name, record, logger);
|
|
32926
33691
|
return converted === null ? null : [name, converted];
|
|
32927
33692
|
}).filter((entry) => entry !== null));
|
|
32928
|
-
warnEnabledInstructions(Object.entries(mcpServers).filter(([, server]) => server.enable_instructions === true).map(([name]) => name), logger);
|
|
32929
33693
|
const rovodevConfig = {
|
|
32930
33694
|
...json,
|
|
32931
33695
|
mcpServers
|
|
@@ -39069,6 +39833,34 @@ const DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS = {
|
|
|
39069
39833
|
yolo_switcher: true,
|
|
39070
39834
|
read_project_dotenv: true
|
|
39071
39835
|
};
|
|
39836
|
+
/**
|
|
39837
|
+
* `[extensions]` gates dcode's Python extension system. `discover_extensions`
|
|
39838
|
+
* auto-loads `*.py` from the user's `~/.deepagents/extensions/` and from the
|
|
39839
|
+
* checked-out project's `<root>/.deepagents/extensions/`, so this table decides
|
|
39840
|
+
* whether a cloned repository's Python is imported into the agent process.
|
|
39841
|
+
*/
|
|
39842
|
+
const EXTENSIONS_TABLE_KEY = "extensions";
|
|
39843
|
+
/**
|
|
39844
|
+
* Keys lifted back into the override on import. `extensions.extra_paths` is
|
|
39845
|
+
* deliberately absent: it names machine-local files and directories, which do
|
|
39846
|
+
* not belong in a committed `.rulesync/permissions.jsonc`, and it only ever
|
|
39847
|
+
* widens what loads.
|
|
39848
|
+
*/
|
|
39849
|
+
const DEEPAGENTS_EXTENSIONS_KEYS = ["enabled", "trust"];
|
|
39850
|
+
/** `TrustPolicy` in `extensions/settings.py`; dcode reads nothing else. */
|
|
39851
|
+
const DEEPAGENTS_EXTENSION_TRUST_POLICIES = [
|
|
39852
|
+
"ask",
|
|
39853
|
+
"always",
|
|
39854
|
+
"never"
|
|
39855
|
+
];
|
|
39856
|
+
/**
|
|
39857
|
+
* `[extensions].enabled` defaults to `true` upstream, so writing `true` on a
|
|
39858
|
+
* machine that has not set it changes nothing — the same reasoning as
|
|
39859
|
+
* `DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS`.
|
|
39860
|
+
*
|
|
39861
|
+
* @see https://github.com/langchain-ai/deepagents `config_manifest.py`
|
|
39862
|
+
*/
|
|
39863
|
+
const DEEPAGENTS_EXTENSIONS_ENABLED_DEFAULT = true;
|
|
39072
39864
|
const ALLOW_ALL_SENTINEL = "all";
|
|
39073
39865
|
const RECOMMENDED_SENTINEL = "recommended";
|
|
39074
39866
|
const GLOB_CHARACTERS_PATTERN = /[*?[\]]/;
|
|
@@ -39138,7 +39930,10 @@ const TRAILING_ARGUMENT_WILDCARD_PATTERN = /:\*$/;
|
|
|
39138
39930
|
* `DeepagentsPermissionsOverrideSchema`): `[startup].mode`
|
|
39139
39931
|
* (`manual` / `auto` / `yolo`), `[startup].yolo_switcher` and
|
|
39140
39932
|
* `[startup].read_project_dotenv` are merged into the file on generate and
|
|
39141
|
-
* lifted back into the override on import.
|
|
39933
|
+
* lifted back into the override on import. The same override carries
|
|
39934
|
+
* `[extensions].enabled` and `[extensions].trust` (`ask` / `always` / `never`),
|
|
39935
|
+
* which decide whether the Python in a checked-out project's
|
|
39936
|
+
* `.deepagents/extensions/` is imported into the agent process.
|
|
39142
39937
|
*
|
|
39143
39938
|
* On **import** the allowlist comes back as `bash` `allow` rules named by the
|
|
39144
39939
|
* executable — skipping any entry dcode could not match in the first place, so
|
|
@@ -39240,6 +40035,13 @@ var DeepagentsPermissions = class DeepagentsPermissions extends ToolPermissions
|
|
|
39240
40035
|
filePath,
|
|
39241
40036
|
logger
|
|
39242
40037
|
});
|
|
40038
|
+
const extensionsOverride = config.deepagents?.extensions;
|
|
40039
|
+
if (isPlainObject$1(extensionsOverride) && Object.keys(extensionsOverride).length > 0) mergeExtensionsOverride({
|
|
40040
|
+
settings,
|
|
40041
|
+
extensionsOverride,
|
|
40042
|
+
filePath,
|
|
40043
|
+
logger
|
|
40044
|
+
});
|
|
39243
40045
|
return new DeepagentsPermissions({
|
|
39244
40046
|
outputRoot,
|
|
39245
40047
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -39264,8 +40066,15 @@ var DeepagentsPermissions = class DeepagentsPermissions extends ToolPermissions
|
|
|
39264
40066
|
startup: isPlainObject$1(settings[STARTUP_TABLE_KEY]) ? settings[STARTUP_TABLE_KEY] : {},
|
|
39265
40067
|
selfPath
|
|
39266
40068
|
});
|
|
40069
|
+
const extensionsOverride = liftExtensionsOverride({
|
|
40070
|
+
extensions: isPlainObject$1(settings[EXTENSIONS_TABLE_KEY]) ? settings[EXTENSIONS_TABLE_KEY] : {},
|
|
40071
|
+
selfPath
|
|
40072
|
+
});
|
|
39267
40073
|
const result = { ...config };
|
|
39268
|
-
|
|
40074
|
+
const deepagents = {};
|
|
40075
|
+
if (Object.keys(startupOverride).length > 0) deepagents.startup = startupOverride;
|
|
40076
|
+
if (Object.keys(extensionsOverride).length > 0) deepagents.extensions = extensionsOverride;
|
|
40077
|
+
if (Object.keys(deepagents).length > 0) result.deepagents = deepagents;
|
|
39269
40078
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
39270
40079
|
}
|
|
39271
40080
|
validate() {
|
|
@@ -39352,57 +40161,192 @@ function warnAboutUnwrittenBashRules({ allowAll, requestedAllowAll, askPatterns,
|
|
|
39352
40161
|
if (willWrite && allowAll) warnWithFallback(logger, "The bash '*' allow rule became allow_list = [\"all\"] for deepagents-cli, which auto-approves every command and skips its dangerous-pattern check (command substitution, redirects, process substitution). List the executables you want instead if that is more than you meant.");
|
|
39353
40162
|
}
|
|
39354
40163
|
/**
|
|
39355
|
-
* Lift the
|
|
39356
|
-
* override, keeping only values dcode itself accepts.
|
|
40164
|
+
* Lift the keys rulesync models from one `config.toml` table back into the
|
|
40165
|
+
* `deepagents` override, keeping only values dcode itself accepts.
|
|
40166
|
+
*
|
|
40167
|
+
* A value outside what the option's own parser reads is `Invalid` upstream —
|
|
40168
|
+
* dcode ignores it and falls back to its default. Importing it anyway would
|
|
40169
|
+
* record a setting the tool is not applying and, because the canonical schema
|
|
40170
|
+
* is stricter than TOML, would write a `.rulesync/permissions.jsonc` the next
|
|
40171
|
+
* `rulesync generate` cannot even read.
|
|
39357
40172
|
*
|
|
39358
|
-
*
|
|
39359
|
-
*
|
|
39360
|
-
*
|
|
39361
|
-
* and, because the canonical schema is stricter than TOML, would write a
|
|
39362
|
-
* `.rulesync/permissions.jsonc` the next `rulesync generate` cannot even read.
|
|
40173
|
+
* `normalize` returns the value to keep, or `null` for one dcode would not
|
|
40174
|
+
* read — a wrapper rather than the bare value, so a legitimately falsy setting
|
|
40175
|
+
* is not mistaken for a rejection.
|
|
39363
40176
|
*/
|
|
39364
|
-
function
|
|
39365
|
-
const
|
|
40177
|
+
function liftOverrideTable({ table, tableKey, keys, normalize, selfPath }) {
|
|
40178
|
+
const override = {};
|
|
39366
40179
|
const rejected = [];
|
|
39367
|
-
for (const key of
|
|
39368
|
-
const value =
|
|
40180
|
+
for (const key of keys) {
|
|
40181
|
+
const value = table[key];
|
|
39369
40182
|
if (value === void 0) continue;
|
|
39370
|
-
|
|
40183
|
+
const normalized = normalize({
|
|
40184
|
+
key,
|
|
40185
|
+
value
|
|
40186
|
+
});
|
|
40187
|
+
if (normalized) override[key] = normalized.value;
|
|
39371
40188
|
else rejected.push(`${key} = ${JSON.stringify(value)}`);
|
|
39372
40189
|
}
|
|
39373
|
-
if (rejected.length > 0) warnWithFallback(void 0, `deepagents-cli falls back to its own default for a '[${
|
|
39374
|
-
return
|
|
40190
|
+
if (rejected.length > 0) warnWithFallback(void 0, `deepagents-cli falls back to its own default for a '[${tableKey}]' value it cannot read, so ${rejected.join(", ")} in ${selfPath} ${rejected.length === 1 ? "was" : "were"} not imported.`);
|
|
40191
|
+
return override;
|
|
39375
40192
|
}
|
|
39376
40193
|
/**
|
|
39377
|
-
*
|
|
39378
|
-
*
|
|
39379
|
-
*
|
|
39380
|
-
* user has it rather than replaced.
|
|
40194
|
+
* Lift the `[startup]` keys rulesync models back into the `deepagents`
|
|
40195
|
+
* override. A `mode` outside the three approval modes, or a non-boolean where
|
|
40196
|
+
* a switch belongs, is one dcode ignores.
|
|
39381
40197
|
*/
|
|
39382
|
-
function
|
|
39383
|
-
|
|
39384
|
-
|
|
39385
|
-
|
|
40198
|
+
function liftStartupOverride({ startup, selfPath }) {
|
|
40199
|
+
return liftOverrideTable({
|
|
40200
|
+
table: startup,
|
|
40201
|
+
tableKey: STARTUP_TABLE_KEY,
|
|
40202
|
+
keys: DEEPAGENTS_STARTUP_KEYS,
|
|
40203
|
+
normalize: ({ key, value }) => {
|
|
40204
|
+
if (key === "mode") return DEEPAGENTS_STARTUP_MODES.includes(value) ? { value } : null;
|
|
40205
|
+
return typeof value === "boolean" ? { value } : null;
|
|
40206
|
+
},
|
|
40207
|
+
selfPath
|
|
40208
|
+
});
|
|
40209
|
+
}
|
|
40210
|
+
/**
|
|
40211
|
+
* Lift the `[extensions]` keys rulesync models back into the `deepagents`
|
|
40212
|
+
* override.
|
|
40213
|
+
*
|
|
40214
|
+
* `trust` is read the way `parse_trust_policy` reads it — trimmed and
|
|
40215
|
+
* lowercased — so `"Always"` comes back as the policy dcode is actually
|
|
40216
|
+
* applying rather than being dropped, the same courtesy the allowlist
|
|
40217
|
+
* sentinels already get. The canonical enum holds only the lowercase
|
|
40218
|
+
* spellings, so keeping the value verbatim would write a permissions file the
|
|
40219
|
+
* next generate could not parse.
|
|
40220
|
+
*/
|
|
40221
|
+
function liftExtensionsOverride({ extensions, selfPath }) {
|
|
40222
|
+
return liftOverrideTable({
|
|
40223
|
+
table: extensions,
|
|
40224
|
+
tableKey: EXTENSIONS_TABLE_KEY,
|
|
40225
|
+
keys: DEEPAGENTS_EXTENSIONS_KEYS,
|
|
40226
|
+
normalize: ({ key, value }) => {
|
|
40227
|
+
if (key === "trust") {
|
|
40228
|
+
if (typeof value !== "string") return null;
|
|
40229
|
+
const policy = value.trim().toLowerCase();
|
|
40230
|
+
return DEEPAGENTS_EXTENSION_TRUST_POLICIES.includes(policy) ? { value: policy } : null;
|
|
40231
|
+
}
|
|
40232
|
+
return typeof value === "boolean" ? { value } : null;
|
|
40233
|
+
},
|
|
40234
|
+
selfPath
|
|
40235
|
+
});
|
|
40236
|
+
}
|
|
40237
|
+
/**
|
|
40238
|
+
* Merge one block of the `deepagents` override into its `config.toml` table,
|
|
40239
|
+
* preserving every other key of that table. A table that is not a table at all
|
|
40240
|
+
* is something rulesync did not write and cannot merge into, so it is left
|
|
40241
|
+
* exactly as the user has it rather than replaced.
|
|
40242
|
+
*/
|
|
40243
|
+
function mergeOverrideTable({ settings, tableKey, override, knownKeys, isDroppedKey, warnAboutRelaxations, filePath, logger }) {
|
|
40244
|
+
const existing = settings[tableKey];
|
|
40245
|
+
if (existing !== void 0 && !isPlainObject$1(existing)) {
|
|
40246
|
+
warnWithFallback(logger, `deepagents-cli: '${tableKey}' in ${filePath} is not a table, so the deepagents ${tableKey} override was skipped rather than overwriting it.`);
|
|
39386
40247
|
return;
|
|
39387
40248
|
}
|
|
39388
|
-
const
|
|
39389
|
-
const
|
|
40249
|
+
const table = isPlainObject$1(existing) ? { ...existing } : {};
|
|
40250
|
+
const previous = { ...table };
|
|
39390
40251
|
const writtenKeys = [];
|
|
39391
|
-
for (const [key, value] of Object.entries(
|
|
40252
|
+
for (const [key, value] of Object.entries(override)) {
|
|
39392
40253
|
if (isPrototypePollutionKey(key)) continue;
|
|
39393
|
-
if (key
|
|
40254
|
+
if (isDroppedKey?.(key)) continue;
|
|
39394
40255
|
if (value === null || value === void 0) continue;
|
|
39395
|
-
|
|
40256
|
+
table[key] = value;
|
|
39396
40257
|
writtenKeys.push(key);
|
|
39397
40258
|
}
|
|
39398
|
-
|
|
39399
|
-
|
|
39400
|
-
|
|
40259
|
+
warnAboutRelaxations({
|
|
40260
|
+
override,
|
|
40261
|
+
previous,
|
|
40262
|
+
filePath,
|
|
40263
|
+
logger
|
|
40264
|
+
});
|
|
40265
|
+
warnAboutUncheckedKeys({
|
|
40266
|
+
tableKey,
|
|
40267
|
+
knownKeys,
|
|
39401
40268
|
writtenKeys,
|
|
39402
40269
|
filePath,
|
|
39403
40270
|
logger
|
|
39404
40271
|
});
|
|
39405
|
-
if (Object.keys(
|
|
40272
|
+
if (Object.keys(table).length > 0) settings[tableKey] = table;
|
|
40273
|
+
}
|
|
40274
|
+
/**
|
|
40275
|
+
* Name the keys the merge wrote that rulesync does not model. The override is a
|
|
40276
|
+
* loose object so a key added upstream still reaches the config, but this one
|
|
40277
|
+
* writes into the machine's global file: what rulesync cannot judge, it at
|
|
40278
|
+
* least names.
|
|
40279
|
+
*/
|
|
40280
|
+
function warnAboutUncheckedKeys({ tableKey, knownKeys, writtenKeys, filePath, logger }) {
|
|
40281
|
+
const known = new Set(knownKeys);
|
|
40282
|
+
const unknownKeys = writtenKeys.filter((key) => !known.has(key));
|
|
40283
|
+
if (unknownKeys.length === 0) return;
|
|
40284
|
+
warnWithFallback(logger, `The deepagents ${tableKey} override wrote ${unknownKeys.join(", ")} into ${filePath} unchecked — rulesync does not know what those keys grant, and that file is your global deepagents-cli config.`);
|
|
40285
|
+
}
|
|
40286
|
+
/**
|
|
40287
|
+
* Merge the `deepagents.startup` override into `[startup]`.
|
|
40288
|
+
*/
|
|
40289
|
+
function mergeStartupOverride({ settings, startupOverride, filePath, logger }) {
|
|
40290
|
+
mergeOverrideTable({
|
|
40291
|
+
settings,
|
|
40292
|
+
tableKey: STARTUP_TABLE_KEY,
|
|
40293
|
+
override: startupOverride,
|
|
40294
|
+
knownKeys: DEEPAGENTS_STARTUP_KEYS,
|
|
40295
|
+
isDroppedKey: (key) => key === STARTUP_RECENT_KEY,
|
|
40296
|
+
warnAboutRelaxations: warnAboutStartupRelaxations,
|
|
40297
|
+
filePath,
|
|
40298
|
+
logger
|
|
40299
|
+
});
|
|
40300
|
+
}
|
|
40301
|
+
/**
|
|
40302
|
+
* Merge the `deepagents.extensions` override into `[extensions]`. Nothing is
|
|
40303
|
+
* dropped here — `extra_paths` is not modeled, but it is a key the user may
|
|
40304
|
+
* legitimately want carried, so it passes through as any other unknown key
|
|
40305
|
+
* does, named by the unchecked-key warning.
|
|
40306
|
+
*/
|
|
40307
|
+
function mergeExtensionsOverride({ settings, extensionsOverride, filePath, logger }) {
|
|
40308
|
+
mergeOverrideTable({
|
|
40309
|
+
settings,
|
|
40310
|
+
tableKey: EXTENSIONS_TABLE_KEY,
|
|
40311
|
+
override: extensionsOverride,
|
|
40312
|
+
knownKeys: DEEPAGENTS_EXTENSIONS_KEYS,
|
|
40313
|
+
warnAboutRelaxations: warnAboutExtensionsRelaxations,
|
|
40314
|
+
filePath,
|
|
40315
|
+
logger
|
|
40316
|
+
});
|
|
40317
|
+
}
|
|
40318
|
+
/**
|
|
40319
|
+
* Name the value an override key replaces, so a setting the user had turned
|
|
40320
|
+
* down is visible as such rather than reported as a bare new value.
|
|
40321
|
+
*/
|
|
40322
|
+
function describeOverriddenValue({ key, value, previous }) {
|
|
40323
|
+
const previousValue = previous[key];
|
|
40324
|
+
return previousValue === void 0 || previousValue === value ? `${key} = ${JSON.stringify(value)}` : `${key} = ${JSON.stringify(value)} (was ${JSON.stringify(previousValue)})`;
|
|
40325
|
+
}
|
|
40326
|
+
/**
|
|
40327
|
+
* Warn when the `deepagents` extensions override widens what dcode loads on its
|
|
40328
|
+
* own, for the same reason the startup one does: this block is written into the
|
|
40329
|
+
* user's **global** config from a `.rulesync/permissions.jsonc` a repository
|
|
40330
|
+
* can carry.
|
|
40331
|
+
*
|
|
40332
|
+
* `trust` decides what happens to the Python in a checked-out project's
|
|
40333
|
+
* `.deepagents/extensions/` — `always` imports it into the agent process with
|
|
40334
|
+
* no prompt at all — and `enabled` is the switch above it. `never` and `false`
|
|
40335
|
+
* restrict, so neither is reported.
|
|
40336
|
+
*/
|
|
40337
|
+
function warnAboutExtensionsRelaxations({ override, previous, filePath, logger }) {
|
|
40338
|
+
const relaxations = [];
|
|
40339
|
+
if (override.trust === "always") relaxations.push(describeOverriddenValue({
|
|
40340
|
+
key: "trust",
|
|
40341
|
+
value: "always",
|
|
40342
|
+
previous
|
|
40343
|
+
}));
|
|
40344
|
+
if (override.enabled === true && (previous.enabled ?? DEEPAGENTS_EXTENSIONS_ENABLED_DEFAULT) !== true) relaxations.push(describeOverriddenValue({
|
|
40345
|
+
key: "enabled",
|
|
40346
|
+
value: true,
|
|
40347
|
+
previous
|
|
40348
|
+
}));
|
|
40349
|
+
if (relaxations.length > 0) warnWithFallback(logger, `The deepagents extensions override wrote ${relaxations.join(", ")} into ${filePath}, which is your global deepagents-cli config: it decides whether the Python in a checked-out project's .deepagents/extensions/ is imported into dcode, for every project on this machine, not just this one.`);
|
|
39406
40350
|
}
|
|
39407
40351
|
/**
|
|
39408
40352
|
* Warn when the `deepagents` startup override relaxes what dcode may do on its
|
|
@@ -39417,12 +40361,13 @@ function mergeStartupOverride({ settings, startupOverride, filePath, logger }) {
|
|
|
39417
40361
|
* process environment. The value being replaced is named alongside, so a
|
|
39418
40362
|
* setting the user had turned down is visible as such.
|
|
39419
40363
|
*/
|
|
39420
|
-
function warnAboutStartupRelaxations({ startupOverride, previousStartup,
|
|
40364
|
+
function warnAboutStartupRelaxations({ override: startupOverride, previous: previousStartup, filePath, logger }) {
|
|
39421
40365
|
const relaxations = [];
|
|
39422
|
-
const describe = (key, value) => {
|
|
39423
|
-
|
|
39424
|
-
|
|
39425
|
-
|
|
40366
|
+
const describe = (key, value) => describeOverriddenValue({
|
|
40367
|
+
key,
|
|
40368
|
+
value,
|
|
40369
|
+
previous: previousStartup
|
|
40370
|
+
});
|
|
39426
40371
|
const mode = startupOverride.mode;
|
|
39427
40372
|
if (mode === "auto" || mode === "yolo") relaxations.push(describe("mode", mode));
|
|
39428
40373
|
for (const [key, upstreamDefault] of Object.entries(DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS)) {
|
|
@@ -39432,9 +40377,6 @@ function warnAboutStartupRelaxations({ startupOverride, previousStartup, written
|
|
|
39432
40377
|
}
|
|
39433
40378
|
if (relaxations.length > 0) warnWithFallback(logger, `The deepagents startup override wrote ${relaxations.join(", ")} into ${filePath}, which is your global deepagents-cli config: it relaxes how much dcode does without asking, for every project on this machine, not just this one.`);
|
|
39434
40379
|
if (startupOverride[STARTUP_RECENT_KEY] !== void 0) warnWithFallback(logger, `The deepagents startup override's '${STARTUP_RECENT_KEY}' was not written to ${filePath}: dcode manages that key itself, and with no explicit 'mode' beside it, it is what restores auto-approval at launch. Set 'mode' if switching approval modes is the intent.`);
|
|
39435
|
-
const known = new Set(DEEPAGENTS_STARTUP_KEYS);
|
|
39436
|
-
const unknownKeys = writtenKeys.filter((key) => !known.has(key));
|
|
39437
|
-
if (unknownKeys.length > 0) warnWithFallback(logger, `The deepagents startup override wrote ${unknownKeys.join(", ")} into ${filePath} unchecked — rulesync does not know what those keys grant, and that file is your global deepagents-cli config.`);
|
|
39438
40380
|
}
|
|
39439
40381
|
/**
|
|
39440
40382
|
* Read `[shell].allow_list` the way dcode does: a TOML array is taken element
|
|
@@ -54055,7 +54997,10 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
54055
54997
|
//#region src/features/skills/zcode-skill.ts
|
|
54056
54998
|
const ZcodeSkillFrontmatterSchema = zod_mini.z.looseObject({
|
|
54057
54999
|
name: zod_mini.z.string(),
|
|
54058
|
-
description: zod_mini.z.string()
|
|
55000
|
+
description: zod_mini.z.string(),
|
|
55001
|
+
when_to_use: zod_mini.z.optional(zod_mini.z.string()),
|
|
55002
|
+
license: zod_mini.z.optional(zod_mini.z.string()),
|
|
55003
|
+
metadata: zod_mini.z.optional(zod_mini.z.looseObject({}))
|
|
54059
55004
|
});
|
|
54060
55005
|
/**
|
|
54061
55006
|
* Represents a ZCode skill directory.
|
|
@@ -54118,10 +55063,16 @@ var ZcodeSkill = class ZcodeSkill extends ToolSkill {
|
|
|
54118
55063
|
}
|
|
54119
55064
|
toRulesyncSkill() {
|
|
54120
55065
|
const frontmatter = this.getFrontmatter();
|
|
55066
|
+
const zcodeSection = {
|
|
55067
|
+
...frontmatter.when_to_use !== void 0 && { when_to_use: frontmatter.when_to_use },
|
|
55068
|
+
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
55069
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
55070
|
+
};
|
|
54121
55071
|
const rulesyncFrontmatter = {
|
|
54122
55072
|
name: frontmatter.name,
|
|
54123
55073
|
description: frontmatter.description,
|
|
54124
|
-
targets: ["*"]
|
|
55074
|
+
targets: ["*"],
|
|
55075
|
+
...Object.keys(zcodeSection).length > 0 && { zcode: zcodeSection }
|
|
54125
55076
|
};
|
|
54126
55077
|
return new RulesyncSkill({
|
|
54127
55078
|
outputRoot: this.outputRoot,
|
|
@@ -54136,9 +55087,21 @@ var ZcodeSkill = class ZcodeSkill extends ToolSkill {
|
|
|
54136
55087
|
}
|
|
54137
55088
|
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
54138
55089
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
55090
|
+
const zcodeSection = rulesyncFrontmatter.zcode;
|
|
55091
|
+
const license = resolveLicense({
|
|
55092
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
55093
|
+
section: zcodeSection
|
|
55094
|
+
});
|
|
55095
|
+
const metadata = resolveMetadata({
|
|
55096
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
55097
|
+
section: zcodeSection
|
|
55098
|
+
});
|
|
54139
55099
|
const zcodeFrontmatter = {
|
|
54140
55100
|
name: rulesyncFrontmatter.name,
|
|
54141
|
-
description: rulesyncFrontmatter.description
|
|
55101
|
+
description: rulesyncFrontmatter.description,
|
|
55102
|
+
...zcodeSection?.when_to_use !== void 0 && { when_to_use: zcodeSection.when_to_use },
|
|
55103
|
+
...license !== void 0 && { license },
|
|
55104
|
+
...metadata !== void 0 && { metadata }
|
|
54142
55105
|
};
|
|
54143
55106
|
const settablePaths = ZcodeSkill.getSettablePaths({ global });
|
|
54144
55107
|
return new ZcodeSkill({
|
|
@@ -62239,10 +63202,21 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
62239
63202
|
*/
|
|
62240
63203
|
const CodebuddyRuleFrontmatterSchema = zod_mini.z.object({
|
|
62241
63204
|
description: zod_mini.z.optional(zod_mini.z.string()),
|
|
62242
|
-
paths: zod_mini.z.optional(zod_mini.z.array(zod_mini.z.string())),
|
|
62243
|
-
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean())
|
|
63205
|
+
paths: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())])),
|
|
63206
|
+
alwaysApply: zod_mini.z.optional(zod_mini.z.boolean()),
|
|
63207
|
+
enabled: zod_mini.z.optional(zod_mini.z.boolean())
|
|
62244
63208
|
});
|
|
62245
63209
|
/**
|
|
63210
|
+
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
63211
|
+
* list form the rest of the adapter works with. An empty list and an empty
|
|
63212
|
+
* string both mean "no paths".
|
|
63213
|
+
*/
|
|
63214
|
+
function normalizeCodebuddyPaths(paths) {
|
|
63215
|
+
if (paths === void 0) return;
|
|
63216
|
+
const list = (typeof paths === "string" ? [paths] : paths).filter((path) => path.trim() !== "");
|
|
63217
|
+
return list.length > 0 ? list : void 0;
|
|
63218
|
+
}
|
|
63219
|
+
/**
|
|
62246
63220
|
* A universal glob (matching everything) is redundant on an Always Apply
|
|
62247
63221
|
* rule and, paired with `alwaysApply: true`, is the same semantic conflict
|
|
62248
63222
|
* `CursorRule.resolveCursorGlobs` avoids for Cursor: `alwaysApply` already
|
|
@@ -62260,7 +63234,7 @@ const UNIVERSAL_PATHS = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
|
62260
63234
|
* Rules format:
|
|
62261
63235
|
* - {project}/CODEBUDDY.md (root: true), also read from {project}/.codebuddy/CODEBUDDY.md
|
|
62262
63236
|
* - {project}/.codebuddy/rules/*.md (root: false, with optional
|
|
62263
|
-
* `description` / `paths` / `alwaysApply` frontmatter)
|
|
63237
|
+
* `description` / `paths` / `alwaysApply` / `enabled` frontmatter)
|
|
62264
63238
|
* - Global: ~/.codebuddy/CODEBUDDY.md and ~/.codebuddy/rules/*.md
|
|
62265
63239
|
*
|
|
62266
63240
|
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
@@ -62302,9 +63276,10 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62302
63276
|
this.body = body;
|
|
62303
63277
|
}
|
|
62304
63278
|
static generateFileContent(body, frontmatter) {
|
|
62305
|
-
if (frontmatter.description === void 0 && frontmatter.paths === void 0 && frontmatter.alwaysApply === void 0) return body;
|
|
63279
|
+
if (frontmatter.description === void 0 && frontmatter.paths === void 0 && frontmatter.alwaysApply === void 0 && frontmatter.enabled === void 0) return body;
|
|
62306
63280
|
return stringifyFrontmatter(body, {
|
|
62307
63281
|
description: frontmatter.description,
|
|
63282
|
+
enabled: frontmatter.enabled,
|
|
62308
63283
|
alwaysApply: frontmatter.alwaysApply,
|
|
62309
63284
|
paths: frontmatter.paths
|
|
62310
63285
|
});
|
|
@@ -62352,10 +63327,36 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62352
63327
|
root: isRoot
|
|
62353
63328
|
});
|
|
62354
63329
|
}
|
|
62355
|
-
|
|
62356
|
-
|
|
62357
|
-
|
|
62358
|
-
|
|
63330
|
+
/**
|
|
63331
|
+
* Resolves the `paths` / `alwaysApply` pair against CodeBuddy's Rule Type
|
|
63332
|
+
* Determination table, which reads:
|
|
63333
|
+
*
|
|
63334
|
+
* | `alwaysApply` | `paths` | Rule type |
|
|
63335
|
+
* | ---------------- | --------- | ---------------------------------- |
|
|
63336
|
+
* | `true` (default) | any | ALWAYS — always injected |
|
|
63337
|
+
* | `false` | has value | MANUAL — triggered on matching file |
|
|
63338
|
+
* | `false` | none | not supported; the rule is dropped |
|
|
63339
|
+
*
|
|
63340
|
+
* The default being `true` is the opposite of Cursor, which the adapter was
|
|
63341
|
+
* modeled on: `paths` alone scopes nothing, so a rule meant to be path
|
|
63342
|
+
* triggered has to carry an explicit `alwaysApply: false` alongside it.
|
|
63343
|
+
*
|
|
63344
|
+
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
63345
|
+
*/
|
|
63346
|
+
static resolveCodebuddyRuleType({ paths, alwaysApply }) {
|
|
63347
|
+
const scopedPaths = paths && paths.length > 0 && !paths.every((path) => UNIVERSAL_PATHS.has(path.trim())) ? paths : void 0;
|
|
63348
|
+
if (alwaysApply === true) return {
|
|
63349
|
+
paths: scopedPaths,
|
|
63350
|
+
alwaysApply: true
|
|
63351
|
+
};
|
|
63352
|
+
if (scopedPaths === void 0) return {
|
|
63353
|
+
paths: void 0,
|
|
63354
|
+
alwaysApply: void 0
|
|
63355
|
+
};
|
|
63356
|
+
return {
|
|
63357
|
+
paths: scopedPaths,
|
|
63358
|
+
alwaysApply: false
|
|
63359
|
+
};
|
|
62359
63360
|
}
|
|
62360
63361
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
62361
63362
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
@@ -62372,17 +63373,17 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62372
63373
|
root
|
|
62373
63374
|
});
|
|
62374
63375
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${rulesyncRule.getRelativeFilePath()}`);
|
|
62375
|
-
const codebuddyPaths = rulesyncFrontmatter.codebuddy?.paths;
|
|
63376
|
+
const codebuddyPaths = normalizeCodebuddyPaths(rulesyncFrontmatter.codebuddy?.paths);
|
|
62376
63377
|
const globs = rulesyncFrontmatter.globs;
|
|
62377
|
-
const
|
|
62378
|
-
const pathsValue = CodebuddyRule.resolveCodebuddyPaths({
|
|
63378
|
+
const ruleType = CodebuddyRule.resolveCodebuddyRuleType({
|
|
62379
63379
|
paths: codebuddyPaths ?? (globs?.length ? globs : void 0),
|
|
62380
|
-
alwaysApply: alwaysApply
|
|
63380
|
+
alwaysApply: rulesyncFrontmatter.codebuddy?.alwaysApply
|
|
62381
63381
|
});
|
|
62382
63382
|
const codebuddyFrontmatter = {
|
|
62383
63383
|
description: rulesyncFrontmatter.codebuddy?.description ?? rulesyncFrontmatter.description,
|
|
62384
|
-
paths:
|
|
62385
|
-
alwaysApply
|
|
63384
|
+
paths: ruleType.paths,
|
|
63385
|
+
alwaysApply: ruleType.alwaysApply,
|
|
63386
|
+
enabled: rulesyncFrontmatter.codebuddy?.enabled
|
|
62386
63387
|
};
|
|
62387
63388
|
return new CodebuddyRule({
|
|
62388
63389
|
outputRoot,
|
|
@@ -62412,17 +63413,19 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62412
63413
|
validate: true
|
|
62413
63414
|
});
|
|
62414
63415
|
}
|
|
62415
|
-
const
|
|
62416
|
-
const
|
|
63416
|
+
const sourcePaths = normalizeCodebuddyPaths(this.frontmatter.paths) ?? [];
|
|
63417
|
+
const isAlways = this.frontmatter.alwaysApply !== false;
|
|
62417
63418
|
const globs = sourcePaths.length === 0 && isAlways ? ["**/*"] : sourcePaths;
|
|
63419
|
+
const alwaysApply = this.frontmatter.alwaysApply ?? (sourcePaths.length > 0 ? true : void 0);
|
|
62418
63420
|
const rulesyncFrontmatter = {
|
|
62419
63421
|
targets,
|
|
62420
63422
|
root: false,
|
|
62421
63423
|
description: this.frontmatter.description,
|
|
62422
63424
|
globs,
|
|
62423
|
-
...(this.frontmatter.paths !== void 0 || this.frontmatter.alwaysApply !== void 0 || this.frontmatter.description !== void 0) && { codebuddy: {
|
|
62424
|
-
paths:
|
|
62425
|
-
alwaysApply
|
|
63425
|
+
...(this.frontmatter.paths !== void 0 || this.frontmatter.alwaysApply !== void 0 || this.frontmatter.enabled !== void 0 || this.frontmatter.description !== void 0) && { codebuddy: {
|
|
63426
|
+
paths: sourcePaths.length > 0 ? sourcePaths : void 0,
|
|
63427
|
+
alwaysApply,
|
|
63428
|
+
enabled: this.frontmatter.enabled,
|
|
62426
63429
|
description: this.frontmatter.description
|
|
62427
63430
|
} }
|
|
62428
63431
|
};
|
|
@@ -65205,33 +66208,49 @@ var ReplitRule = class ReplitRule extends ToolRule {
|
|
|
65205
66208
|
* Supports plain Markdown without frontmatter, mode-specific rules,
|
|
65206
66209
|
* and both directory-based and single-file configurations.
|
|
65207
66210
|
*
|
|
65208
|
-
* - Project scope writes the non-root directory `.roo/rules
|
|
66211
|
+
* - Project scope writes the non-root directory `.roo/rules/`, and the root
|
|
66212
|
+
* rule to the workspace-root `AGENTS.md` the extension reads from `cwd`.
|
|
65209
66213
|
* - Global scope writes the same non-root directory resolved under the home
|
|
65210
|
-
* directory (`~/.roo/rules/`), which Roo loads before workspace rules.
|
|
66214
|
+
* directory (`~/.roo/rules/`), which Roo loads before workspace rules. The
|
|
66215
|
+
* root rule joins it as `~/.roo/rules/AGENTS.md`: agent-rules discovery is
|
|
66216
|
+
* workspace-only, so the default project-scope root would resolve to a
|
|
66217
|
+
* `~/AGENTS.md` nothing ever reads.
|
|
65211
66218
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-instructions
|
|
65212
66219
|
*/
|
|
65213
66220
|
var RooRule = class RooRule extends ToolRule {
|
|
65214
|
-
static getSettablePaths(
|
|
65215
|
-
|
|
66221
|
+
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
66222
|
+
const rulesDirPath = buildToolPath(ROO_DIR, "rules", excludeToolDir);
|
|
66223
|
+
if (global) return {
|
|
66224
|
+
root: {
|
|
66225
|
+
relativeDirPath: rulesDirPath,
|
|
66226
|
+
relativeFilePath: ROO_GLOBAL_ROOT_RULE_FILE_NAME
|
|
66227
|
+
},
|
|
66228
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
66229
|
+
};
|
|
66230
|
+
return { nonRoot: { relativeDirPath: rulesDirPath } };
|
|
65216
66231
|
}
|
|
65217
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true }) {
|
|
65218
|
-
const
|
|
66232
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
66233
|
+
const paths = this.getSettablePaths({ global });
|
|
66234
|
+
const relativeDirPath = overrideDirPath !== void 0 && RooRule.extractModeFromDirPath(overrideDirPath) !== void 0 ? overrideDirPath : paths.nonRoot?.relativeDirPath ?? buildToolPath(".roo", "rules");
|
|
65219
66235
|
const fileContent = await readFileContent((0, node_path.join)(outputRoot, relativeDirPath, relativeFilePath));
|
|
66236
|
+
const isRoot = "root" in paths && relativeDirPath === paths.root.relativeDirPath && relativeFilePath === paths.root.relativeFilePath;
|
|
65220
66237
|
return new RooRule({
|
|
65221
66238
|
outputRoot,
|
|
65222
66239
|
relativeDirPath,
|
|
65223
66240
|
relativeFilePath,
|
|
65224
66241
|
fileContent,
|
|
65225
66242
|
validate,
|
|
65226
|
-
root:
|
|
66243
|
+
root: isRoot
|
|
65227
66244
|
});
|
|
65228
66245
|
}
|
|
65229
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
66246
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
66247
|
+
const paths = this.getSettablePaths({ global });
|
|
65230
66248
|
const params = this.buildToolRuleParamsDefault({
|
|
65231
66249
|
outputRoot,
|
|
65232
66250
|
rulesyncRule,
|
|
65233
66251
|
validate,
|
|
65234
|
-
|
|
66252
|
+
..."root" in paths && { rootPath: paths.root },
|
|
66253
|
+
nonRootPath: paths.nonRoot
|
|
65235
66254
|
});
|
|
65236
66255
|
const mode = rulesyncRule.getFrontmatter().roo?.mode;
|
|
65237
66256
|
if (!params.root && mode !== void 0 && mode !== "") {
|
|
@@ -66154,7 +67173,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
66154
67173
|
extension: "md",
|
|
66155
67174
|
supportsGlobal: true,
|
|
66156
67175
|
ruleDiscoveryMode: "auto",
|
|
66157
|
-
collisionPolicy: "fold"
|
|
67176
|
+
collisionPolicy: "fold",
|
|
67177
|
+
localRootMode: "separate-local-file",
|
|
67178
|
+
localRootFileName: CRUSH_LOCAL_RULE_FILE_NAME
|
|
66158
67179
|
}
|
|
66159
67180
|
}],
|
|
66160
67181
|
["cursor", {
|
|
@@ -66874,6 +67895,34 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
66874
67895
|
root: true,
|
|
66875
67896
|
localRoot
|
|
66876
67897
|
});
|
|
67898
|
+
if (isClassOrSubclassOf({
|
|
67899
|
+
candidate: factory.class,
|
|
67900
|
+
base: CodebuddyRule
|
|
67901
|
+
})) {
|
|
67902
|
+
const paths = CodebuddyRule.getSettablePaths({ global: this.global });
|
|
67903
|
+
return new CodebuddyRule({
|
|
67904
|
+
outputRoot: this.outputRoot,
|
|
67905
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
67906
|
+
relativeFilePath: fileName,
|
|
67907
|
+
frontmatter: {},
|
|
67908
|
+
body,
|
|
67909
|
+
validate: true,
|
|
67910
|
+
root: true,
|
|
67911
|
+
localRoot
|
|
67912
|
+
});
|
|
67913
|
+
}
|
|
67914
|
+
if (isClassOrSubclassOf({
|
|
67915
|
+
candidate: factory.class,
|
|
67916
|
+
base: CrushRule
|
|
67917
|
+
})) return new CrushRule({
|
|
67918
|
+
outputRoot: this.outputRoot,
|
|
67919
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
67920
|
+
relativeFilePath: fileName,
|
|
67921
|
+
fileContent: body,
|
|
67922
|
+
validate: true,
|
|
67923
|
+
root: true,
|
|
67924
|
+
localRoot
|
|
67925
|
+
});
|
|
66877
67926
|
if (isClassOrSubclassOf({
|
|
66878
67927
|
candidate: factory.class,
|
|
66879
67928
|
base: QwencodeRule
|
|
@@ -69002,6 +70051,7 @@ async function generateHooksCore(params) {
|
|
|
69002
70051
|
toolTarget,
|
|
69003
70052
|
global: config.getGlobal(),
|
|
69004
70053
|
dryRun: config.isPreviewMode(),
|
|
70054
|
+
preserveUnownedHooks: config.getPreserveUnownedHooks(),
|
|
69005
70055
|
logger
|
|
69006
70056
|
});
|
|
69007
70057
|
const result = await processFeatureWithRulesyncFiles({
|
|
@@ -69573,6 +70623,12 @@ Object.defineProperty(exports, "CONFLICTING_TARGET_PAIRS", {
|
|
|
69573
70623
|
return CONFLICTING_TARGET_PAIRS;
|
|
69574
70624
|
}
|
|
69575
70625
|
});
|
|
70626
|
+
Object.defineProperty(exports, "CRUSH_LOCAL_RULE_FILE_NAME", {
|
|
70627
|
+
enumerable: true,
|
|
70628
|
+
get: function() {
|
|
70629
|
+
return CRUSH_LOCAL_RULE_FILE_NAME;
|
|
70630
|
+
}
|
|
70631
|
+
});
|
|
69576
70632
|
Object.defineProperty(exports, "CURATED_RULES_FEATURE_SUBDIR", {
|
|
69577
70633
|
enumerable: true,
|
|
69578
70634
|
get: function() {
|