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
|
@@ -713,7 +713,8 @@ const hooksProcessorToolTargetTuple = [
|
|
|
713
713
|
"vibe",
|
|
714
714
|
"qwencode",
|
|
715
715
|
"reasonix",
|
|
716
|
-
"grokcli"
|
|
716
|
+
"grokcli",
|
|
717
|
+
"zcode"
|
|
717
718
|
];
|
|
718
719
|
const permissionsProcessorToolTargetTuple = [
|
|
719
720
|
"amp",
|
|
@@ -2393,6 +2394,7 @@ const ConfigParamsSchema = z.object({
|
|
|
2393
2394
|
simulateCommands: optional(z.boolean()),
|
|
2394
2395
|
simulateSubagents: optional(z.boolean()),
|
|
2395
2396
|
simulateSkills: optional(z.boolean()),
|
|
2397
|
+
preserveUnownedHooks: optional(z.boolean()),
|
|
2396
2398
|
deriveSubprojectPathFromGlobs: optional(z.boolean()),
|
|
2397
2399
|
flattenedCommandNaming: optional(FlattenedCommandNamingSchema),
|
|
2398
2400
|
/**
|
|
@@ -2541,6 +2543,7 @@ var Config = class Config {
|
|
|
2541
2543
|
simulateCommands;
|
|
2542
2544
|
simulateSubagents;
|
|
2543
2545
|
simulateSkills;
|
|
2546
|
+
preserveUnownedHooks;
|
|
2544
2547
|
deriveSubprojectPathFromGlobs;
|
|
2545
2548
|
flattenedCommandNaming;
|
|
2546
2549
|
language;
|
|
@@ -2567,7 +2570,7 @@ var Config = class Config {
|
|
|
2567
2570
|
inputRoots;
|
|
2568
2571
|
configFilePath;
|
|
2569
2572
|
sources;
|
|
2570
|
-
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
|
|
2573
|
+
constructor({ outputRoots, targets, features, verbose, delete: isDelete, global, silent, simulateCommands, simulateSubagents, simulateSkills, preserveUnownedHooks, deriveSubprojectPathFromGlobs, flattenedCommandNaming, language, gitignoreTargetsOnly, gitignoreDestination, dryRun, check, inputRoot, inputRoots, configFilePath, sources, configFileTargets }) {
|
|
2571
2574
|
assertTargetsFeaturesExclusive({
|
|
2572
2575
|
targets,
|
|
2573
2576
|
features
|
|
@@ -2599,6 +2602,7 @@ var Config = class Config {
|
|
|
2599
2602
|
this.simulateCommands = simulateCommands ?? false;
|
|
2600
2603
|
this.simulateSubagents = simulateSubagents ?? false;
|
|
2601
2604
|
this.simulateSkills = simulateSkills ?? false;
|
|
2605
|
+
this.preserveUnownedHooks = preserveUnownedHooks ?? false;
|
|
2602
2606
|
this.deriveSubprojectPathFromGlobs = deriveSubprojectPathFromGlobs ?? false;
|
|
2603
2607
|
this.flattenedCommandNaming = flattenedCommandNaming ?? "basename";
|
|
2604
2608
|
this.language = language;
|
|
@@ -2777,6 +2781,9 @@ var Config = class Config {
|
|
|
2777
2781
|
getSimulateCommands() {
|
|
2778
2782
|
return this.simulateCommands;
|
|
2779
2783
|
}
|
|
2784
|
+
getPreserveUnownedHooks() {
|
|
2785
|
+
return this.preserveUnownedHooks;
|
|
2786
|
+
}
|
|
2780
2787
|
getFlattenedCommandNaming() {
|
|
2781
2788
|
return this.flattenedCommandNaming;
|
|
2782
2789
|
}
|
|
@@ -2856,6 +2863,7 @@ const getDefaults = () => ({
|
|
|
2856
2863
|
simulateCommands: false,
|
|
2857
2864
|
simulateSubagents: false,
|
|
2858
2865
|
simulateSkills: false,
|
|
2866
|
+
preserveUnownedHooks: false,
|
|
2859
2867
|
deriveSubprojectPathFromGlobs: false,
|
|
2860
2868
|
flattenedCommandNaming: "basename",
|
|
2861
2869
|
gitignoreTargetsOnly: true,
|
|
@@ -2904,6 +2912,7 @@ const mergeConfigs = (baseConfig, localConfig) => {
|
|
|
2904
2912
|
simulateCommands: localConfig.simulateCommands ?? baseConfig.simulateCommands,
|
|
2905
2913
|
simulateSubagents: localConfig.simulateSubagents ?? baseConfig.simulateSubagents,
|
|
2906
2914
|
simulateSkills: localConfig.simulateSkills ?? baseConfig.simulateSkills,
|
|
2915
|
+
preserveUnownedHooks: localConfig.preserveUnownedHooks ?? baseConfig.preserveUnownedHooks,
|
|
2907
2916
|
deriveSubprojectPathFromGlobs: localConfig.deriveSubprojectPathFromGlobs ?? baseConfig.deriveSubprojectPathFromGlobs,
|
|
2908
2917
|
flattenedCommandNaming: localConfig.flattenedCommandNaming ?? baseConfig.flattenedCommandNaming,
|
|
2909
2918
|
language: localConfig.language ?? baseConfig.language,
|
|
@@ -3127,6 +3136,11 @@ var ConfigResolver = class {
|
|
|
3127
3136
|
file: configByFile.simulateSkills,
|
|
3128
3137
|
fallback: getDefaults().simulateSkills
|
|
3129
3138
|
}),
|
|
3139
|
+
preserveUnownedHooks: pick({
|
|
3140
|
+
cli: void 0,
|
|
3141
|
+
file: configByFile.preserveUnownedHooks,
|
|
3142
|
+
fallback: getDefaults().preserveUnownedHooks
|
|
3143
|
+
}),
|
|
3130
3144
|
deriveSubprojectPathFromGlobs: pick({
|
|
3131
3145
|
cli: deriveSubprojectPathFromGlobs,
|
|
3132
3146
|
file: configByFile.deriveSubprojectPathFromGlobs,
|
|
@@ -4686,6 +4700,48 @@ const KIMI_CODE_NATIVE_HOOK_EVENTS = [
|
|
|
4686
4700
|
];
|
|
4687
4701
|
const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_KIMI_CODE_EVENT_NAMES).map(([canonical, kimiCode]) => [kimiCode, canonical]));
|
|
4688
4702
|
/**
|
|
4703
|
+
* Hook events supported by ZCode.
|
|
4704
|
+
*
|
|
4705
|
+
* ZCode's configuration-file hooks expose exactly seven PascalCase events, all
|
|
4706
|
+
* of which have a clean canonical equivalent: `SessionStart`, `PreToolUse`,
|
|
4707
|
+
* `PostToolUse`, `PostToolUseFailure`, `PermissionRequest`, `Stop`, and
|
|
4708
|
+
* `UserPromptSubmit` ← `beforeSubmitPrompt`. An optional matcher is honored on
|
|
4709
|
+
* all of them except `UserPromptSubmit` and `Stop`, which expose no value to
|
|
4710
|
+
* match against. ZCode reads the matcher in one of two ways: a value made up
|
|
4711
|
+
* solely of letters, digits, underscores and `|` is an exact name list
|
|
4712
|
+
* (`Write|Edit` matches those two tool names, not the regex alternation), and
|
|
4713
|
+
* any other character makes the whole value a case-sensitive JavaScript regex.
|
|
4714
|
+
* Rulesync passes matchers through verbatim, so an authored value keeps
|
|
4715
|
+
* whichever reading ZCode gives it — worth knowing before escaping one.
|
|
4716
|
+
* Configuration hooks are read only from the user config
|
|
4717
|
+
* `~/.zcode/cli/config.json` (workspace config hooks are never executed) and
|
|
4718
|
+
* additionally require `hooks.enabled: true` to run.
|
|
4719
|
+
*
|
|
4720
|
+
* ZCode also accepts a native `process` hook type (an argv run without a
|
|
4721
|
+
* shell) which has no canonical equivalent; see ZcodeHooks.
|
|
4722
|
+
*
|
|
4723
|
+
* @see https://zcode.z.ai/en/docs
|
|
4724
|
+
*/
|
|
4725
|
+
const ZCODE_HOOK_EVENTS = [
|
|
4726
|
+
"sessionStart",
|
|
4727
|
+
"beforeSubmitPrompt",
|
|
4728
|
+
"preToolUse",
|
|
4729
|
+
"postToolUse",
|
|
4730
|
+
"postToolUseFailure",
|
|
4731
|
+
"permissionRequest",
|
|
4732
|
+
"stop"
|
|
4733
|
+
];
|
|
4734
|
+
const CANONICAL_TO_ZCODE_EVENT_NAMES = {
|
|
4735
|
+
sessionStart: "SessionStart",
|
|
4736
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
4737
|
+
preToolUse: "PreToolUse",
|
|
4738
|
+
postToolUse: "PostToolUse",
|
|
4739
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
4740
|
+
permissionRequest: "PermissionRequest",
|
|
4741
|
+
stop: "Stop"
|
|
4742
|
+
};
|
|
4743
|
+
const ZCODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CANONICAL_TO_ZCODE_EVENT_NAMES).map(([k, v]) => [v, k]));
|
|
4744
|
+
/**
|
|
4689
4745
|
* Hook events supported by Hermes Agent's native Shell Hooks system.
|
|
4690
4746
|
*
|
|
4691
4747
|
* Hermes validates hook events against a fixed `VALID_HOOKS` set — 37 entries as
|
|
@@ -4837,6 +4893,7 @@ const HooksConfigSchema = z.looseObject({
|
|
|
4837
4893
|
reasonix: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
4838
4894
|
grokcli: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
4839
4895
|
"kimi-code": z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
4896
|
+
zcode: z.optional(z.looseObject({ hooks: z.optional(hooksRecordSchema) })),
|
|
4840
4897
|
qwencode: z.optional(z.looseObject({
|
|
4841
4898
|
hooks: z.optional(hooksRecordSchema),
|
|
4842
4899
|
disableAllHooks: z.optional(z.boolean())
|
|
@@ -6827,12 +6884,37 @@ const DeepagentsStartupOverrideSchema = z.looseObject({
|
|
|
6827
6884
|
read_project_dotenv: z.optional(z.boolean())
|
|
6828
6885
|
});
|
|
6829
6886
|
/**
|
|
6887
|
+
* deepagents-cli's Python extension gate under `[extensions]` in
|
|
6888
|
+
* `~/.deepagents/config.toml`. `discover_extensions` auto-loads `*.py` from the
|
|
6889
|
+
* user's `~/.deepagents/extensions/` and from the checked-out project's
|
|
6890
|
+
* `<root>/.deepagents/extensions/`, so these two keys decide whether
|
|
6891
|
+
* repository-authored Python is imported into the agent process at all —
|
|
6892
|
+
* the same class of knob as `[startup].mode`, and one that ships defaulting to
|
|
6893
|
+
* a prompt a user can accept. Loose so a key added upstream passes through
|
|
6894
|
+
* verbatim.
|
|
6895
|
+
*
|
|
6896
|
+
* `extensions.extra_paths` is deliberately absent: it names machine-local files
|
|
6897
|
+
* and directories, which do not belong in a committed
|
|
6898
|
+
* `.rulesync/permissions.jsonc`, and it only ever widens what loads.
|
|
6899
|
+
*
|
|
6900
|
+
* @see https://docs.langchain.com/oss/deepagents/code/configuration
|
|
6901
|
+
*/
|
|
6902
|
+
const DeepagentsExtensionsOverrideSchema = z.looseObject({
|
|
6903
|
+
enabled: z.optional(z.boolean()),
|
|
6904
|
+
trust: z.optional(z.enum([
|
|
6905
|
+
"ask",
|
|
6906
|
+
"always",
|
|
6907
|
+
"never"
|
|
6908
|
+
]))
|
|
6909
|
+
});
|
|
6910
|
+
/**
|
|
6830
6911
|
* The `[shell].allow_list` array itself is rulesync-owned, driven by the
|
|
6831
6912
|
* shared `permission.bash` block, so it has no key here.
|
|
6832
6913
|
*/
|
|
6833
6914
|
const DeepagentsPermissionsOverrideSchema = z.looseObject({
|
|
6834
6915
|
permission: z.optional(ToolScopedPermissionSchema),
|
|
6835
|
-
startup: z.optional(DeepagentsStartupOverrideSchema)
|
|
6916
|
+
startup: z.optional(DeepagentsStartupOverrideSchema),
|
|
6917
|
+
extensions: z.optional(DeepagentsExtensionsOverrideSchema)
|
|
6836
6918
|
});
|
|
6837
6919
|
/**
|
|
6838
6920
|
* The actions Junie's allowlist accepts. Verified against the shipped Junie
|
|
@@ -7843,8 +7925,9 @@ const RulesyncRuleFrontmatterSchema = z.object({
|
|
|
7843
7925
|
agentsmd: z.optional(z.looseObject({ subprojectPath: z.optional(z.string()) })),
|
|
7844
7926
|
claudecode: z.optional(z.looseObject({ paths: z.optional(z.array(z.string())) })),
|
|
7845
7927
|
codebuddy: z.optional(z.looseObject({
|
|
7846
|
-
paths: z.optional(z.array(z.string())),
|
|
7928
|
+
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
7847
7929
|
alwaysApply: z.optional(z.boolean()),
|
|
7930
|
+
enabled: z.optional(z.boolean()),
|
|
7848
7931
|
description: z.optional(z.string())
|
|
7849
7932
|
})),
|
|
7850
7933
|
cursor: z.optional(z.looseObject({
|
|
@@ -9137,6 +9220,11 @@ const RulesyncSkillFrontmatterSchema = z.looseObject({
|
|
|
9137
9220
|
license: z.optional(z.string()),
|
|
9138
9221
|
compatibility: z.optional(z.union([z.string(), z.looseObject({})])),
|
|
9139
9222
|
metadata: z.optional(z.looseObject({}))
|
|
9223
|
+
})),
|
|
9224
|
+
zcode: z.optional(z.looseObject({
|
|
9225
|
+
when_to_use: z.optional(z.string()),
|
|
9226
|
+
license: z.optional(z.string()),
|
|
9227
|
+
metadata: z.optional(z.looseObject({}))
|
|
9140
9228
|
}))
|
|
9141
9229
|
});
|
|
9142
9230
|
/**
|
|
@@ -12387,14 +12475,15 @@ const KIMI_CODE_CONFIG_DECLARATION = {
|
|
|
12387
12475
|
};
|
|
12388
12476
|
/**
|
|
12389
12477
|
* ZCode's settings file, which also carries model/theme/permission keys
|
|
12390
|
-
* rulesync does not own.
|
|
12391
|
-
*
|
|
12392
|
-
*
|
|
12393
|
-
*
|
|
12394
|
-
*
|
|
12395
|
-
*
|
|
12396
|
-
|
|
12397
|
-
|
|
12478
|
+
* rulesync does not own. Both copies are the user's primary ZCode config, so
|
|
12479
|
+
* every writer refuses to read-modify-write a file it could not parse rather
|
|
12480
|
+
* than replacing it with generated output. `mcp` and `hooks` are owned as
|
|
12481
|
+
* whole keys because their writers recompute each from the existing file
|
|
12482
|
+
* (non-owned siblings carried over) before applying the patch. ZCode never
|
|
12483
|
+
* executes workspace config hooks, so the workspace copy is declared with
|
|
12484
|
+
* `mcp` alone and the user copy adds `hooks`.
|
|
12485
|
+
*/
|
|
12486
|
+
const ZCODE_WORKSPACE_CONFIG_DECLARATION = {
|
|
12398
12487
|
format: "json",
|
|
12399
12488
|
invalidRootPolicy: "error",
|
|
12400
12489
|
features: { mcp: {
|
|
@@ -12402,6 +12491,16 @@ const ZCODE_CONFIG_DECLARATION = {
|
|
|
12402
12491
|
ownedKeys: ["mcp"]
|
|
12403
12492
|
} }
|
|
12404
12493
|
};
|
|
12494
|
+
const ZCODE_USER_CONFIG_DECLARATION = {
|
|
12495
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12496
|
+
features: {
|
|
12497
|
+
...ZCODE_WORKSPACE_CONFIG_DECLARATION.features,
|
|
12498
|
+
hooks: {
|
|
12499
|
+
kind: "replace-owned-keys",
|
|
12500
|
+
ownedKeys: ["hooks"]
|
|
12501
|
+
}
|
|
12502
|
+
}
|
|
12503
|
+
};
|
|
12405
12504
|
/**
|
|
12406
12505
|
* What the two Claude Code settings files have in common: both are plain JSON,
|
|
12407
12506
|
* and both validate against the one published schema, which the gateway
|
|
@@ -12659,8 +12758,8 @@ const SHARED_CONFIG_OWNERSHIP = {
|
|
|
12659
12758
|
ownedKeys: ["mcp_servers", "schema_version"]
|
|
12660
12759
|
} }
|
|
12661
12760
|
},
|
|
12662
|
-
".zcode/config.json":
|
|
12663
|
-
".zcode/cli/config.json":
|
|
12761
|
+
".zcode/config.json": ZCODE_WORKSPACE_CONFIG_DECLARATION,
|
|
12762
|
+
".zcode/cli/config.json": ZCODE_USER_CONFIG_DECLARATION,
|
|
12664
12763
|
".kiro/agents/default.json": {
|
|
12665
12764
|
format: "json",
|
|
12666
12765
|
features: {
|
|
@@ -17708,6 +17807,20 @@ const ROO_MCP_FILE_NAME = "mcp.json";
|
|
|
17708
17807
|
*/
|
|
17709
17808
|
const ROO_MODE_SLUG_PATTERN = /^[a-zA-Z0-9-]+$/;
|
|
17710
17809
|
/**
|
|
17810
|
+
* `~/.roo/rules/AGENTS.md` — where the root rule goes in global scope.
|
|
17811
|
+
*
|
|
17812
|
+
* Roo/Zoo Code discover the agent-rules files (`AGENTS.md`, `AGENT.md`,
|
|
17813
|
+
* `AGENTS.local.md`) in the **workspace only**: `loadAllAgentRulesFiles` reads
|
|
17814
|
+
* `cwd`, plus subdirectories holding a `.roo` folder when `enableSubfolderRules`
|
|
17815
|
+
* is on. There is no home-directory branch, so a `~/AGENTS.md` is never read.
|
|
17816
|
+
* The global rules directory `~/.roo/rules/` *is* loaded, so the root rule is
|
|
17817
|
+
* written there alongside the non-root files rather than to an unread path.
|
|
17818
|
+
* The basename is kept so the file still reads as the root overview.
|
|
17819
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/core/prompts/sections/custom-instructions.ts
|
|
17820
|
+
* @see https://github.com/Zoo-Code-Org/Zoo-Code/blob/main/src/services/roo-config/index.ts
|
|
17821
|
+
*/
|
|
17822
|
+
const ROO_GLOBAL_ROOT_RULE_FILE_NAME = "AGENTS.md";
|
|
17823
|
+
/**
|
|
17711
17824
|
* `.roo/rules-{mode}/` — the mode-specific rule directory Roo/Zoo Code load
|
|
17712
17825
|
* INSTEAD of `.roo/rules/` while that mode is active. The relative path is the
|
|
17713
17826
|
* same in global scope, where it resolves under `~/.roo/`.
|
|
@@ -18324,6 +18437,8 @@ const ZCODE_SKILLS_DIR_PATH = join(ZCODE_DIR, "skills");
|
|
|
18324
18437
|
const ZCODE_CONFIG_FILE_NAME = "config.json";
|
|
18325
18438
|
const ZCODE_GLOBAL_CONFIG_DIR_PATH = join(ZCODE_DIR, "cli");
|
|
18326
18439
|
const ZCODE_MCP_SERVERS_KEY = "servers";
|
|
18440
|
+
const ZCODE_HOOKS_CONFIG_KEY = "hooks";
|
|
18441
|
+
const ZCODE_HOOKS_EVENTS_KEY = "events";
|
|
18327
18442
|
const ZCODE_AGENTS_DIR_PATH = join(ZCODE_DIR, "agents");
|
|
18328
18443
|
//#endregion
|
|
18329
18444
|
//#region src/features/commands/zcode-command.ts
|
|
@@ -19196,19 +19311,124 @@ function generateAmpPluginCode({ config, supportedEvents, eventMap }) {
|
|
|
19196
19311
|
lines.push("}", "");
|
|
19197
19312
|
return lines.join("\n");
|
|
19198
19313
|
}
|
|
19314
|
+
/**
|
|
19315
|
+
* Name of the ownership record written next to the hooks destination it
|
|
19316
|
+
* describes (e.g. `.claude/.rulesync-hooks-lock.json`). Only written when hook
|
|
19317
|
+
* preservation is enabled, so a project that never opts in never sees it.
|
|
19318
|
+
*/
|
|
19319
|
+
const HOOKS_OWNERSHIP_LOCK_FILE_NAME = ".rulesync-hooks-lock.json";
|
|
19320
|
+
const OwnedHookRefSchema = z.object({
|
|
19321
|
+
event: z.string(),
|
|
19322
|
+
matcher: z.optional(z.string()),
|
|
19323
|
+
identity: z.string()
|
|
19324
|
+
});
|
|
19325
|
+
const HooksOwnershipLockSchema = z.object({
|
|
19326
|
+
lockfileVersion: z.number(),
|
|
19327
|
+
owned: z.array(OwnedHookRefSchema)
|
|
19328
|
+
});
|
|
19329
|
+
/**
|
|
19330
|
+
* The comparable form of a hook entry: identical keys mean "the same hook, in
|
|
19331
|
+
* the same place". Encoded as JSON so no separator can collide with a matcher
|
|
19332
|
+
* or an identity that happens to contain one.
|
|
19333
|
+
*/
|
|
19334
|
+
function ownedHookKey({ event, matcher, identity }) {
|
|
19335
|
+
return JSON.stringify([
|
|
19336
|
+
event,
|
|
19337
|
+
matcher ?? null,
|
|
19338
|
+
identity
|
|
19339
|
+
]);
|
|
19340
|
+
}
|
|
19341
|
+
/**
|
|
19342
|
+
* Read the previous run's owned set. Anything unreadable — missing, empty,
|
|
19343
|
+
* malformed, or written by a different lock version — yields an empty set,
|
|
19344
|
+
* which makes every existing handler look third-party and therefore preserved.
|
|
19345
|
+
*/
|
|
19346
|
+
function parseHooksOwnershipLock(content) {
|
|
19347
|
+
if (content === null || content.trim() === "") return /* @__PURE__ */ new Set();
|
|
19348
|
+
let parsed;
|
|
19349
|
+
try {
|
|
19350
|
+
parsed = JSON.parse(content);
|
|
19351
|
+
} catch {
|
|
19352
|
+
return /* @__PURE__ */ new Set();
|
|
19353
|
+
}
|
|
19354
|
+
const result = HooksOwnershipLockSchema.safeParse(parsed);
|
|
19355
|
+
if (!result.success || result.data.lockfileVersion !== 1) return /* @__PURE__ */ new Set();
|
|
19356
|
+
return new Set(result.data.owned.map((ref) => ownedHookKey(ref)));
|
|
19357
|
+
}
|
|
19358
|
+
function compareOwnedHookRefs(a, b) {
|
|
19359
|
+
const left = ownedHookKey(a);
|
|
19360
|
+
const right = ownedHookKey(b);
|
|
19361
|
+
if (left === right) return 0;
|
|
19362
|
+
return left < right ? -1 : 1;
|
|
19363
|
+
}
|
|
19364
|
+
/** Serialize the owned set in a stable order so regenerating produces no diff. */
|
|
19365
|
+
function serializeHooksOwnershipLock(owned) {
|
|
19366
|
+
return `${JSON.stringify({
|
|
19367
|
+
lockfileVersion: 1,
|
|
19368
|
+
owned: [...owned].toSorted(compareOwnedHookRefs)
|
|
19369
|
+
}, null, 2)}\n`;
|
|
19370
|
+
}
|
|
19371
|
+
/**
|
|
19372
|
+
* The ownership record itself. It is a rulesync by-product rather than tool
|
|
19373
|
+
* configuration, so it is deletable: dropping the target should take it along.
|
|
19374
|
+
*/
|
|
19375
|
+
var HooksOwnershipLockFile = class extends ToolFile {
|
|
19376
|
+
isDeletable() {
|
|
19377
|
+
return true;
|
|
19378
|
+
}
|
|
19379
|
+
validate() {
|
|
19380
|
+
return {
|
|
19381
|
+
success: true,
|
|
19382
|
+
error: null
|
|
19383
|
+
};
|
|
19384
|
+
}
|
|
19385
|
+
};
|
|
19386
|
+
/** Build the lock file that records what this run generated. */
|
|
19387
|
+
function buildHooksOwnershipLockFile({ outputRoot, relativeDirPath, owned }) {
|
|
19388
|
+
return new HooksOwnershipLockFile({
|
|
19389
|
+
outputRoot,
|
|
19390
|
+
relativeDirPath,
|
|
19391
|
+
relativeFilePath: HOOKS_OWNERSHIP_LOCK_FILE_NAME,
|
|
19392
|
+
fileContent: serializeHooksOwnershipLock(owned),
|
|
19393
|
+
validate: false
|
|
19394
|
+
});
|
|
19395
|
+
}
|
|
19199
19396
|
//#endregion
|
|
19200
19397
|
//#region src/features/hooks/tool-hooks.ts
|
|
19201
19398
|
var ToolHooks = class extends ToolFile {
|
|
19399
|
+
ownedHookRefs;
|
|
19202
19400
|
constructor(params) {
|
|
19203
19401
|
super({
|
|
19204
19402
|
...params,
|
|
19205
19403
|
validate: true
|
|
19206
19404
|
});
|
|
19405
|
+
this.ownedHookRefs = params.ownedHookRefs;
|
|
19207
19406
|
if (params.validate) {
|
|
19208
19407
|
const result = this.validate();
|
|
19209
19408
|
if (!result.success) throw result.error;
|
|
19210
19409
|
}
|
|
19211
19410
|
}
|
|
19411
|
+
/**
|
|
19412
|
+
* The ownership record for this destination, or nothing when preservation is
|
|
19413
|
+
* off — in which case rulesync owns the whole list and needs no record.
|
|
19414
|
+
*/
|
|
19415
|
+
getOwnershipLockFiles() {
|
|
19416
|
+
if (this.ownedHookRefs === void 0) return [];
|
|
19417
|
+
return [buildHooksOwnershipLockFile({
|
|
19418
|
+
outputRoot: this.getOutputRoot(),
|
|
19419
|
+
relativeDirPath: this.getRelativeDirPath(),
|
|
19420
|
+
owned: this.ownedHookRefs
|
|
19421
|
+
})];
|
|
19422
|
+
}
|
|
19423
|
+
/**
|
|
19424
|
+
* Whether the adapter can keep handlers it did not generate. Destinations
|
|
19425
|
+
* rulesync owns outright (plugin bundles) must answer `false`: there is no
|
|
19426
|
+
* third party writing into them, and preserving there would only make
|
|
19427
|
+
* removals impossible.
|
|
19428
|
+
*/
|
|
19429
|
+
static supportsPreserveUnowned() {
|
|
19430
|
+
return false;
|
|
19431
|
+
}
|
|
19212
19432
|
static getSettablePaths(_options) {
|
|
19213
19433
|
throw new Error("Please implement this method in the subclass.");
|
|
19214
19434
|
}
|
|
@@ -20645,6 +20865,292 @@ var AugmentcodeHooks = class AugmentcodeHooks extends ToolHooks {
|
|
|
20645
20865
|
}
|
|
20646
20866
|
};
|
|
20647
20867
|
//#endregion
|
|
20868
|
+
//#region src/features/hooks/preserve-unowned-hook-commands.ts
|
|
20869
|
+
/**
|
|
20870
|
+
* Read the `hooks` value from a destination JSON file.
|
|
20871
|
+
*
|
|
20872
|
+
* `malformed` separates "the file has no hooks" from "the file could not be
|
|
20873
|
+
* read", so a caller that is about to preserve content can say out loud that
|
|
20874
|
+
* it is replacing a file it failed to parse.
|
|
20875
|
+
*/
|
|
20876
|
+
function parseExistingHooksValue(existingContent) {
|
|
20877
|
+
if (existingContent.trim() === "") return {
|
|
20878
|
+
hooks: void 0,
|
|
20879
|
+
malformed: false
|
|
20880
|
+
};
|
|
20881
|
+
try {
|
|
20882
|
+
const parsed = JSON.parse(existingContent);
|
|
20883
|
+
return {
|
|
20884
|
+
hooks: isPlainObject$1(parsed) ? parsed.hooks : void 0,
|
|
20885
|
+
malformed: false
|
|
20886
|
+
};
|
|
20887
|
+
} catch {
|
|
20888
|
+
return {
|
|
20889
|
+
hooks: void 0,
|
|
20890
|
+
malformed: true
|
|
20891
|
+
};
|
|
20892
|
+
}
|
|
20893
|
+
}
|
|
20894
|
+
/**
|
|
20895
|
+
* Produce the destination hooks list, optionally keeping handlers rulesync does
|
|
20896
|
+
* not own.
|
|
20897
|
+
*
|
|
20898
|
+
* Ownership is decided by `previouslyOwned` — the identities recorded in the
|
|
20899
|
+
* destination's ownership lock on the previous run — and never by guessing from
|
|
20900
|
+
* the command text. That is what makes removal work: a handler rulesync wrote
|
|
20901
|
+
* before and no longer generates is retracted, while a handler rulesync has
|
|
20902
|
+
* never written is left alone. Without a lock (the first run after opting in)
|
|
20903
|
+
* nothing is retracted, which is the non-destructive direction.
|
|
20904
|
+
*
|
|
20905
|
+
* Callers must pass the destination's shape. Plugin destinations are fully
|
|
20906
|
+
* owned by rulesync and must not call this.
|
|
20907
|
+
*/
|
|
20908
|
+
function mergeGeneratedHookLists({ existingContent, generatedHooks, shape, preserveUnowned, previouslyOwned, logger }) {
|
|
20909
|
+
const owned = collectOwnedRefs({
|
|
20910
|
+
generatedHooks,
|
|
20911
|
+
shape
|
|
20912
|
+
});
|
|
20913
|
+
if (!preserveUnowned) return {
|
|
20914
|
+
hooks: generatedHooks,
|
|
20915
|
+
owned
|
|
20916
|
+
};
|
|
20917
|
+
const { hooks: existingHooks, malformed } = parseExistingHooksValue(existingContent);
|
|
20918
|
+
if (malformed) logger?.warn("Replacing hooks wholesale: the existing file is not valid JSON, so no third-party hook in it can be preserved.");
|
|
20919
|
+
return {
|
|
20920
|
+
hooks: preserveUnownedHookCommands({
|
|
20921
|
+
existingHooks,
|
|
20922
|
+
generatedHooks,
|
|
20923
|
+
shape,
|
|
20924
|
+
owned,
|
|
20925
|
+
previouslyOwned: previouslyOwned ?? /* @__PURE__ */ new Set(),
|
|
20926
|
+
logger
|
|
20927
|
+
}),
|
|
20928
|
+
owned
|
|
20929
|
+
};
|
|
20930
|
+
}
|
|
20931
|
+
/**
|
|
20932
|
+
* Merge the destination's existing handlers into the generated set.
|
|
20933
|
+
*
|
|
20934
|
+
* Each existing handler falls into exactly one of three cases:
|
|
20935
|
+
* - this run generates it → the generated copy stands, the existing one is dropped;
|
|
20936
|
+
* - a previous run generated it and this one does not → it is retracted, with a warning;
|
|
20937
|
+
* - neither → it is unowned, and kept.
|
|
20938
|
+
*/
|
|
20939
|
+
function preserveUnownedHookCommands({ existingHooks, generatedHooks, shape, owned, previouslyOwned, logger }) {
|
|
20940
|
+
const result = cloneGeneratedHooks({
|
|
20941
|
+
generatedHooks,
|
|
20942
|
+
shape
|
|
20943
|
+
});
|
|
20944
|
+
if (!isPlainObject$1(existingHooks)) return result;
|
|
20945
|
+
const seen = new Set(owned.map((ref) => ownedHookKey(ref)));
|
|
20946
|
+
for (const [event, existingValue] of Object.entries(existingHooks)) {
|
|
20947
|
+
if (isPrototypePollutionKey(event)) continue;
|
|
20948
|
+
if (!Array.isArray(existingValue)) {
|
|
20949
|
+
warnSkip({
|
|
20950
|
+
logger,
|
|
20951
|
+
event,
|
|
20952
|
+
expected: "an array of hook entries"
|
|
20953
|
+
});
|
|
20954
|
+
continue;
|
|
20955
|
+
}
|
|
20956
|
+
const merged = shape === "matcher-groups" ? mergeMatcherGroups({
|
|
20957
|
+
existing: existingValue,
|
|
20958
|
+
generated: result[event] ?? [],
|
|
20959
|
+
event,
|
|
20960
|
+
seen,
|
|
20961
|
+
previouslyOwned,
|
|
20962
|
+
logger
|
|
20963
|
+
}) : mergeFlatHandlers({
|
|
20964
|
+
existing: existingValue,
|
|
20965
|
+
generated: result[event] ?? [],
|
|
20966
|
+
event,
|
|
20967
|
+
seen,
|
|
20968
|
+
previouslyOwned,
|
|
20969
|
+
logger
|
|
20970
|
+
});
|
|
20971
|
+
if (merged.length === 0) delete result[event];
|
|
20972
|
+
else result[event] = merged;
|
|
20973
|
+
}
|
|
20974
|
+
return result;
|
|
20975
|
+
}
|
|
20976
|
+
function cloneGeneratedHooks({ generatedHooks, shape }) {
|
|
20977
|
+
const result = Object.create(null);
|
|
20978
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
20979
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
20980
|
+
result[event] = shape === "matcher-groups" ? value.map((group) => cloneMatcherGroup(group)) : value.map((handler) => cloneHandler(handler));
|
|
20981
|
+
}
|
|
20982
|
+
return result;
|
|
20983
|
+
}
|
|
20984
|
+
/** The identities of every handler in the generated set, in destination order. */
|
|
20985
|
+
function collectOwnedRefs({ generatedHooks, shape }) {
|
|
20986
|
+
const owned = [];
|
|
20987
|
+
for (const [event, value] of Object.entries(generatedHooks)) {
|
|
20988
|
+
if (isPrototypePollutionKey(event) || !Array.isArray(value)) continue;
|
|
20989
|
+
if (shape === "flat") {
|
|
20990
|
+
for (const handler of value) if (isPlainObject$1(handler)) owned.push({
|
|
20991
|
+
event,
|
|
20992
|
+
identity: handlerIdentity(handler)
|
|
20993
|
+
});
|
|
20994
|
+
continue;
|
|
20995
|
+
}
|
|
20996
|
+
for (const group of value) {
|
|
20997
|
+
if (!isMatcherGroup(group)) continue;
|
|
20998
|
+
const matcher = matcherKey(group.matcher);
|
|
20999
|
+
for (const handler of group.hooks) if (isPlainObject$1(handler)) owned.push({
|
|
21000
|
+
event,
|
|
21001
|
+
matcher,
|
|
21002
|
+
identity: handlerIdentity(handler)
|
|
21003
|
+
});
|
|
21004
|
+
}
|
|
21005
|
+
}
|
|
21006
|
+
return owned;
|
|
21007
|
+
}
|
|
21008
|
+
function mergeMatcherGroups({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
21009
|
+
const merged = [...generated];
|
|
21010
|
+
for (const group of existing) {
|
|
21011
|
+
if (!isMatcherGroup(group)) {
|
|
21012
|
+
warnSkip({
|
|
21013
|
+
logger,
|
|
21014
|
+
event,
|
|
21015
|
+
expected: "a matcher group"
|
|
21016
|
+
});
|
|
21017
|
+
continue;
|
|
21018
|
+
}
|
|
21019
|
+
const matcher = matcherKey(group.matcher);
|
|
21020
|
+
const preserved = group.hooks.filter((handler) => shouldPreserve({
|
|
21021
|
+
handler,
|
|
21022
|
+
event,
|
|
21023
|
+
matcher,
|
|
21024
|
+
seen,
|
|
21025
|
+
previouslyOwned,
|
|
21026
|
+
logger
|
|
21027
|
+
})).map((handler) => cloneHandler(handler));
|
|
21028
|
+
if (preserved.length === 0) continue;
|
|
21029
|
+
const target = merged.find((candidate) => isMatcherGroup(candidate) && matcherKey(candidate.matcher) === matcher);
|
|
21030
|
+
if (target !== void 0 && isMatcherGroup(target)) target.hooks.push(...preserved);
|
|
21031
|
+
else merged.push({
|
|
21032
|
+
...omitPrototypePollutionKeys(group),
|
|
21033
|
+
hooks: preserved
|
|
21034
|
+
});
|
|
21035
|
+
}
|
|
21036
|
+
return merged;
|
|
21037
|
+
}
|
|
21038
|
+
function mergeFlatHandlers({ existing, generated, event, seen, previouslyOwned, logger }) {
|
|
21039
|
+
const preserved = [];
|
|
21040
|
+
for (const handler of existing) {
|
|
21041
|
+
if (isMatcherGroup(handler)) {
|
|
21042
|
+
warnSkip({
|
|
21043
|
+
logger,
|
|
21044
|
+
event,
|
|
21045
|
+
expected: "a flat handler"
|
|
21046
|
+
});
|
|
21047
|
+
continue;
|
|
21048
|
+
}
|
|
21049
|
+
if (!shouldPreserve({
|
|
21050
|
+
handler,
|
|
21051
|
+
event,
|
|
21052
|
+
matcher: void 0,
|
|
21053
|
+
seen,
|
|
21054
|
+
previouslyOwned,
|
|
21055
|
+
logger
|
|
21056
|
+
})) continue;
|
|
21057
|
+
preserved.push(cloneHandler(handler));
|
|
21058
|
+
}
|
|
21059
|
+
return [...generated, ...preserved];
|
|
21060
|
+
}
|
|
21061
|
+
function isMatcherGroup(value) {
|
|
21062
|
+
return isPlainObject$1(value) && Array.isArray(value.hooks);
|
|
21063
|
+
}
|
|
21064
|
+
function cloneMatcherGroup(group) {
|
|
21065
|
+
if (!isMatcherGroup(group)) return cloneHandler(group);
|
|
21066
|
+
return {
|
|
21067
|
+
...group,
|
|
21068
|
+
hooks: group.hooks.map((handler) => cloneHandler(handler))
|
|
21069
|
+
};
|
|
21070
|
+
}
|
|
21071
|
+
function cloneHandler(handler) {
|
|
21072
|
+
return isPlainObject$1(handler) ? omitPrototypePollutionKeys(handler) : handler;
|
|
21073
|
+
}
|
|
21074
|
+
/**
|
|
21075
|
+
* Decide one existing handler, and remember it so an exact duplicate later in
|
|
21076
|
+
* the same event is not appended twice.
|
|
21077
|
+
*/
|
|
21078
|
+
function shouldPreserve({ handler, event, matcher, seen, previouslyOwned, logger }) {
|
|
21079
|
+
if (!isPlainObject$1(handler)) {
|
|
21080
|
+
warnSkip({
|
|
21081
|
+
logger,
|
|
21082
|
+
event,
|
|
21083
|
+
expected: "a hook object"
|
|
21084
|
+
});
|
|
21085
|
+
return false;
|
|
21086
|
+
}
|
|
21087
|
+
const identity = handlerIdentity(handler);
|
|
21088
|
+
const key = ownedHookKey({
|
|
21089
|
+
event,
|
|
21090
|
+
matcher,
|
|
21091
|
+
identity
|
|
21092
|
+
});
|
|
21093
|
+
if (seen.has(key)) return false;
|
|
21094
|
+
if (previouslyOwned.has(key)) {
|
|
21095
|
+
logger?.warn(`Removing hook rulesync no longer generates on ${event}: ${identity}`);
|
|
21096
|
+
return false;
|
|
21097
|
+
}
|
|
21098
|
+
seen.add(key);
|
|
21099
|
+
logger?.warn(`Preserving unowned hook on ${event}: ${identity}`);
|
|
21100
|
+
return true;
|
|
21101
|
+
}
|
|
21102
|
+
/**
|
|
21103
|
+
* A handler's identity is its action, not its whole shape: two entries running
|
|
21104
|
+
* the same command are the same hook even if their timeouts differ, so the
|
|
21105
|
+
* generated one replaces the existing one instead of doubling it. Shapes with
|
|
21106
|
+
* no recognizable action fall back to their full structure, which keeps them
|
|
21107
|
+
* comparable across runs — the alternative, no identity at all, made them
|
|
21108
|
+
* accumulate on every generate.
|
|
21109
|
+
*/
|
|
21110
|
+
function handlerIdentity(handler) {
|
|
21111
|
+
const type = typeof handler.type === "string" ? handler.type : inferredType(handler);
|
|
21112
|
+
switch (type) {
|
|
21113
|
+
case "http": return taggedIdentity("http", handler.url) ?? structuralIdentity(handler);
|
|
21114
|
+
case "mcp_tool": return mcpToolIdentity(handler) ?? structuralIdentity(handler);
|
|
21115
|
+
case "prompt":
|
|
21116
|
+
case "agent": return taggedIdentity(type, handler.prompt) ?? structuralIdentity(handler);
|
|
21117
|
+
case "function": return taggedIdentity("function", handler.name) ?? structuralIdentity(handler);
|
|
21118
|
+
default: return taggedIdentity("command", handler.command) ?? taggedIdentity("prompt", handler.prompt) ?? structuralIdentity(handler);
|
|
21119
|
+
}
|
|
21120
|
+
}
|
|
21121
|
+
function inferredType(handler) {
|
|
21122
|
+
return typeof handler.url === "string" ? "http" : "command";
|
|
21123
|
+
}
|
|
21124
|
+
function taggedIdentity(kind, value) {
|
|
21125
|
+
return typeof value === "string" && value !== "" ? `${kind}:${value}` : void 0;
|
|
21126
|
+
}
|
|
21127
|
+
function mcpToolIdentity(handler) {
|
|
21128
|
+
const server = typeof handler.server === "string" ? handler.server : "";
|
|
21129
|
+
const tool = typeof handler.tool === "string" ? handler.tool : "";
|
|
21130
|
+
if (server === "" && tool === "") return;
|
|
21131
|
+
return `mcp_tool:${server}:${tool}:${stableStringify$1(handler.input)}`;
|
|
21132
|
+
}
|
|
21133
|
+
function structuralIdentity(handler) {
|
|
21134
|
+
return `json:${stableStringify$1(handler)}`;
|
|
21135
|
+
}
|
|
21136
|
+
/** Key-order-independent JSON, so an identity survives a rewritten file. */
|
|
21137
|
+
function stableStringify$1(value) {
|
|
21138
|
+
if (Array.isArray(value)) return `[${value.map((entry) => stableStringify$1(entry)).join(",")}]`;
|
|
21139
|
+
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(",")}}`;
|
|
21140
|
+
return JSON.stringify(value) ?? "null";
|
|
21141
|
+
}
|
|
21142
|
+
/**
|
|
21143
|
+
* Matcher groups are matched by the whole matcher value, not just strings: a
|
|
21144
|
+
* non-string matcher is a shape rulesync does not emit, and collapsing every
|
|
21145
|
+
* such group onto one key would merge unrelated third-party groups together.
|
|
21146
|
+
*/
|
|
21147
|
+
function matcherKey(matcher) {
|
|
21148
|
+
return matcher === void 0 ? "" : stableStringify$1(matcher);
|
|
21149
|
+
}
|
|
21150
|
+
function warnSkip({ logger, event, expected }) {
|
|
21151
|
+
logger?.warn(`Skipping existing hook entry on ${event}: expected ${expected}`);
|
|
21152
|
+
}
|
|
21153
|
+
//#endregion
|
|
20648
21154
|
//#region src/features/hooks/claudecode-hooks.ts
|
|
20649
21155
|
const CLAUDE_CONVERTER_CONFIG = {
|
|
20650
21156
|
supportedEvents: CLAUDE_HOOK_EVENTS,
|
|
@@ -20713,7 +21219,7 @@ const CLAUDE_CONVERTER_CONFIG = {
|
|
|
20713
21219
|
commandOnly: true
|
|
20714
21220
|
}]
|
|
20715
21221
|
};
|
|
20716
|
-
var ClaudecodeHooks = class extends ToolHooks {
|
|
21222
|
+
var ClaudecodeHooks = class ClaudecodeHooks extends ToolHooks {
|
|
20717
21223
|
constructor(params) {
|
|
20718
21224
|
super({
|
|
20719
21225
|
...params,
|
|
@@ -20748,7 +21254,13 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20748
21254
|
validate
|
|
20749
21255
|
});
|
|
20750
21256
|
}
|
|
20751
|
-
static
|
|
21257
|
+
static supportsPreserveUnowned() {
|
|
21258
|
+
return true;
|
|
21259
|
+
}
|
|
21260
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
21261
|
+
return toolHooks instanceof ClaudecodeHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
21262
|
+
}
|
|
21263
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
20752
21264
|
const paths = this.getSettablePaths({ global });
|
|
20753
21265
|
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
20754
21266
|
const existingContent = await readFileContentOrNull(filePath) ?? JSON.stringify({}, null, 2);
|
|
@@ -20759,11 +21271,20 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20759
21271
|
converterConfig: this.getConverterConfig(),
|
|
20760
21272
|
logger
|
|
20761
21273
|
});
|
|
21274
|
+
const preserving = preserveUnowned && this.supportsPreserveUnowned();
|
|
21275
|
+
const merged = mergeGeneratedHookLists({
|
|
21276
|
+
existingContent,
|
|
21277
|
+
generatedHooks: claudeHooks,
|
|
21278
|
+
shape: "matcher-groups",
|
|
21279
|
+
preserveUnowned: preserving,
|
|
21280
|
+
previouslyOwned: preserving ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21281
|
+
logger
|
|
21282
|
+
});
|
|
20762
21283
|
const fileContent = applySharedConfigPatch({
|
|
20763
21284
|
fileKey: CLAUDE_SETTINGS_SHARED_FILE_KEY,
|
|
20764
21285
|
feature: "hooks",
|
|
20765
21286
|
existingContent,
|
|
20766
|
-
patch: { hooks:
|
|
21287
|
+
patch: { hooks: merged.hooks },
|
|
20767
21288
|
filePath
|
|
20768
21289
|
});
|
|
20769
21290
|
return new this({
|
|
@@ -20771,6 +21292,7 @@ var ClaudecodeHooks = class extends ToolHooks {
|
|
|
20771
21292
|
relativeDirPath: paths.relativeDirPath,
|
|
20772
21293
|
relativeFilePath: paths.relativeFilePath,
|
|
20773
21294
|
fileContent,
|
|
21295
|
+
ownedHookRefs: preserving ? merged.owned : void 0,
|
|
20774
21296
|
validate
|
|
20775
21297
|
});
|
|
20776
21298
|
}
|
|
@@ -20828,6 +21350,14 @@ var ClaudecodePluginHooks = class extends ClaudecodeHooks {
|
|
|
20828
21350
|
projectDirVar: "$CLAUDE_PLUGIN_ROOT"
|
|
20829
21351
|
};
|
|
20830
21352
|
}
|
|
21353
|
+
/**
|
|
21354
|
+
* The plugin bundle is generated in full by rulesync — nothing else writes
|
|
21355
|
+
* into it — so preserving there would buy nothing and cost the ability to
|
|
21356
|
+
* remove a hook.
|
|
21357
|
+
*/
|
|
21358
|
+
static supportsPreserveUnowned() {
|
|
21359
|
+
return false;
|
|
21360
|
+
}
|
|
20831
21361
|
static getSettablePaths() {
|
|
20832
21362
|
return {
|
|
20833
21363
|
relativeDirPath: CLAUDECODE_PLUGIN_HOOKS_DIR,
|
|
@@ -21258,21 +21788,30 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21258
21788
|
validate
|
|
21259
21789
|
});
|
|
21260
21790
|
}
|
|
21261
|
-
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
21791
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21262
21792
|
const paths = CodexcliHooks.getSettablePaths({ global });
|
|
21793
|
+
const existingContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "";
|
|
21263
21794
|
const config = rulesyncHooks.getJson();
|
|
21264
|
-
const
|
|
21265
|
-
|
|
21266
|
-
|
|
21267
|
-
|
|
21795
|
+
const merged = mergeGeneratedHookLists({
|
|
21796
|
+
existingContent,
|
|
21797
|
+
generatedHooks: canonicalToToolHooks({
|
|
21798
|
+
config,
|
|
21799
|
+
toolOverrideHooks: config.codexcli?.hooks,
|
|
21800
|
+
converterConfig: CODEXCLI_CONVERTER_CONFIG,
|
|
21801
|
+
logger
|
|
21802
|
+
}),
|
|
21803
|
+
shape: "matcher-groups",
|
|
21804
|
+
preserveUnowned,
|
|
21805
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
21268
21806
|
logger
|
|
21269
21807
|
});
|
|
21270
|
-
const fileContent = JSON.stringify({ hooks:
|
|
21808
|
+
const fileContent = JSON.stringify({ hooks: merged.hooks }, null, 2);
|
|
21271
21809
|
return new CodexcliHooks({
|
|
21272
21810
|
outputRoot,
|
|
21273
21811
|
relativeDirPath: paths.relativeDirPath,
|
|
21274
21812
|
relativeFilePath: paths.relativeFilePath,
|
|
21275
21813
|
fileContent,
|
|
21814
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21276
21815
|
validate
|
|
21277
21816
|
});
|
|
21278
21817
|
}
|
|
@@ -21308,8 +21847,11 @@ var CodexcliHooks = class CodexcliHooks extends ToolHooks {
|
|
|
21308
21847
|
validate: false
|
|
21309
21848
|
});
|
|
21310
21849
|
}
|
|
21311
|
-
static
|
|
21312
|
-
return
|
|
21850
|
+
static supportsPreserveUnowned() {
|
|
21851
|
+
return true;
|
|
21852
|
+
}
|
|
21853
|
+
static async getAuxiliaryFiles({ outputRoot = process.cwd(), toolHooks } = {}) {
|
|
21854
|
+
return [await CodexcliConfigToml.fromOutputRoot({ outputRoot }), ...toolHooks instanceof CodexcliHooks ? toolHooks.getOwnershipLockFiles() : []];
|
|
21313
21855
|
}
|
|
21314
21856
|
};
|
|
21315
21857
|
//#endregion
|
|
@@ -21914,7 +22456,7 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21914
22456
|
validate
|
|
21915
22457
|
});
|
|
21916
22458
|
}
|
|
21917
|
-
static fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false }) {
|
|
22459
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, preserveUnowned = false, logger }) {
|
|
21918
22460
|
const config = rulesyncHooks.getJson();
|
|
21919
22461
|
const cursorSupported = new Set(CURSOR_HOOK_EVENTS);
|
|
21920
22462
|
const sharedHooks = {};
|
|
@@ -21941,21 +22483,36 @@ var CursorHooks = class CursorHooks extends ToolHooks {
|
|
|
21941
22483
|
}));
|
|
21942
22484
|
if (mappedDefs.length > 0) mappedHooks[cursorEventName] = mappedDefs;
|
|
21943
22485
|
}
|
|
22486
|
+
const paths = CursorHooks.getSettablePaths({ global });
|
|
22487
|
+
const merged = mergeGeneratedHookLists({
|
|
22488
|
+
existingContent: await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "",
|
|
22489
|
+
generatedHooks: mappedHooks,
|
|
22490
|
+
shape: "flat",
|
|
22491
|
+
preserveUnowned,
|
|
22492
|
+
previouslyOwned: preserveUnowned ? parseHooksOwnershipLock(await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, HOOKS_OWNERSHIP_LOCK_FILE_NAME))) : void 0,
|
|
22493
|
+
logger
|
|
22494
|
+
});
|
|
21944
22495
|
const cursorConfig = {
|
|
21945
22496
|
version: config.version ?? 1,
|
|
21946
|
-
hooks:
|
|
22497
|
+
hooks: merged.hooks
|
|
21947
22498
|
};
|
|
21948
22499
|
const fileContent = JSON.stringify(cursorConfig, null, 2);
|
|
21949
|
-
const paths = CursorHooks.getSettablePaths({ global });
|
|
21950
22500
|
return new CursorHooks({
|
|
21951
22501
|
outputRoot,
|
|
21952
22502
|
relativeDirPath: paths.relativeDirPath,
|
|
21953
22503
|
relativeFilePath: paths.relativeFilePath,
|
|
21954
22504
|
fileContent,
|
|
22505
|
+
ownedHookRefs: preserveUnowned ? merged.owned : void 0,
|
|
21955
22506
|
validate,
|
|
21956
22507
|
rulesyncHooks
|
|
21957
22508
|
});
|
|
21958
22509
|
}
|
|
22510
|
+
static supportsPreserveUnowned() {
|
|
22511
|
+
return true;
|
|
22512
|
+
}
|
|
22513
|
+
static async getAuxiliaryFiles({ toolHooks } = {}) {
|
|
22514
|
+
return toolHooks instanceof CursorHooks ? toolHooks.getOwnershipLockFiles() : [];
|
|
22515
|
+
}
|
|
21959
22516
|
toRulesyncHooks() {
|
|
21960
22517
|
const content = this.getFileContent();
|
|
21961
22518
|
const parsed = JSON.parse(content);
|
|
@@ -22323,7 +22880,7 @@ var DevinHooks = class DevinHooks extends ToolHooks {
|
|
|
22323
22880
|
* adapter so the settings reader in `src/utils/` can name the same list without
|
|
22324
22881
|
* importing a feature.
|
|
22325
22882
|
*
|
|
22326
|
-
* @see https://docs.factory.ai/cli/
|
|
22883
|
+
* @see https://docs.factory.ai/droid-cli/settings
|
|
22327
22884
|
*/
|
|
22328
22885
|
const FACTORYDROID_OVERRIDE_KEYS = [
|
|
22329
22886
|
"commandBlocklist",
|
|
@@ -22331,6 +22888,8 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
22331
22888
|
"sandbox",
|
|
22332
22889
|
"mcpPolicy",
|
|
22333
22890
|
"mcpAutonomyOverrides",
|
|
22891
|
+
"mcpAutonomyUrlOverrides",
|
|
22892
|
+
"builtInToolAutonomyOverrides",
|
|
22334
22893
|
"enableDroidShield",
|
|
22335
22894
|
"sessionDefaultSettings",
|
|
22336
22895
|
"maxAutonomyLevel",
|
|
@@ -22338,7 +22897,10 @@ const FACTORYDROID_OVERRIDE_KEYS = [
|
|
|
22338
22897
|
"interactionMode",
|
|
22339
22898
|
"extraKnownMarketplaces",
|
|
22340
22899
|
"enabledPlugins",
|
|
22900
|
+
"strictEnabledPlugins",
|
|
22901
|
+
"strictKnownMarketplaces",
|
|
22341
22902
|
"hooksDisabled",
|
|
22903
|
+
"allowManagedHooksOnly",
|
|
22342
22904
|
"disabledSkills",
|
|
22343
22905
|
"modelPolicy",
|
|
22344
22906
|
"missionPolicy"
|
|
@@ -25195,6 +25757,186 @@ var VibeHooks = class VibeHooks extends ToolHooks {
|
|
|
25195
25757
|
}
|
|
25196
25758
|
};
|
|
25197
25759
|
//#endregion
|
|
25760
|
+
//#region src/features/hooks/zcode-hooks.ts
|
|
25761
|
+
/**
|
|
25762
|
+
* Single spelling of the config.json codec/policy, matching the one in
|
|
25763
|
+
* ZcodeMcp: fail closed on an unparseable root rather than replacing the
|
|
25764
|
+
* user's primary ZCode config with generated output.
|
|
25765
|
+
*/
|
|
25766
|
+
function parseZcodeConfig$1(fileContent, filePath) {
|
|
25767
|
+
return parseSharedConfig({
|
|
25768
|
+
format: "json",
|
|
25769
|
+
fileContent,
|
|
25770
|
+
filePath,
|
|
25771
|
+
invalidRootPolicy: "error"
|
|
25772
|
+
});
|
|
25773
|
+
}
|
|
25774
|
+
/**
|
|
25775
|
+
* Drop ZCode's native `process` hooks from an `events` map before the shared
|
|
25776
|
+
* import converter runs. The converter would coerce the unknown type to
|
|
25777
|
+
* `command` and import the executable alone — losing the `args` vector that
|
|
25778
|
+
* *is* the process hook's command line — so the imported hook would run
|
|
25779
|
+
* something else than the file states. Matcher groups left without hooks are
|
|
25780
|
+
* dropped with them.
|
|
25781
|
+
*/
|
|
25782
|
+
function stripProcessHooks({ events, logger }) {
|
|
25783
|
+
if (!isRecord$1(events)) return events;
|
|
25784
|
+
const result = Object.create(null);
|
|
25785
|
+
for (const [eventName, entries] of Object.entries(events)) {
|
|
25786
|
+
if (!Array.isArray(entries)) {
|
|
25787
|
+
result[eventName] = entries;
|
|
25788
|
+
continue;
|
|
25789
|
+
}
|
|
25790
|
+
const kept = [];
|
|
25791
|
+
for (const entry of entries) {
|
|
25792
|
+
if (!isRecord$1(entry) || !Array.isArray(entry.hooks)) {
|
|
25793
|
+
kept.push(entry);
|
|
25794
|
+
continue;
|
|
25795
|
+
}
|
|
25796
|
+
const hooks = entry.hooks.filter((hook) => {
|
|
25797
|
+
if (isRecord$1(hook) && hook.type === "process") {
|
|
25798
|
+
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.`);
|
|
25799
|
+
return false;
|
|
25800
|
+
}
|
|
25801
|
+
return true;
|
|
25802
|
+
});
|
|
25803
|
+
if (hooks.length > 0) kept.push({
|
|
25804
|
+
...entry,
|
|
25805
|
+
hooks
|
|
25806
|
+
});
|
|
25807
|
+
}
|
|
25808
|
+
result[eventName] = kept;
|
|
25809
|
+
}
|
|
25810
|
+
return result;
|
|
25811
|
+
}
|
|
25812
|
+
const ZCODE_CONVERTER_CONFIG = {
|
|
25813
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
25814
|
+
canonicalToToolEventNames: CANONICAL_TO_ZCODE_EVENT_NAMES,
|
|
25815
|
+
toolToCanonicalEventNames: ZCODE_TO_CANONICAL_EVENT_NAMES,
|
|
25816
|
+
projectDirVar: "",
|
|
25817
|
+
noMatcherEvents: /* @__PURE__ */ new Set(["beforeSubmitPrompt", "stop"]),
|
|
25818
|
+
supportedHookTypes: /* @__PURE__ */ new Set(["command"]),
|
|
25819
|
+
booleanPassthroughFields: [{
|
|
25820
|
+
canonical: "async",
|
|
25821
|
+
tool: "async"
|
|
25822
|
+
}, {
|
|
25823
|
+
canonical: "enabled",
|
|
25824
|
+
tool: "enabled"
|
|
25825
|
+
}],
|
|
25826
|
+
stringPassthroughFields: [{
|
|
25827
|
+
canonical: "statusMessage",
|
|
25828
|
+
tool: "statusMessage"
|
|
25829
|
+
}, {
|
|
25830
|
+
canonical: "shell",
|
|
25831
|
+
tool: "shell",
|
|
25832
|
+
commandOnly: true
|
|
25833
|
+
}],
|
|
25834
|
+
wildcardMatcherMeansAll: true
|
|
25835
|
+
};
|
|
25836
|
+
/**
|
|
25837
|
+
* ZCode hooks.
|
|
25838
|
+
*
|
|
25839
|
+
* ZCode reads configuration-file hooks from the `hooks` block of its user
|
|
25840
|
+
* config file, `~/.zcode/cli/config.json`. Workspace config hooks are never
|
|
25841
|
+
* executed — the workspace file is ignored regardless of `hooks.enabled` — so
|
|
25842
|
+
* rulesync treats ZCode hooks as global-only. The event map is nested under
|
|
25843
|
+
* `hooks.events` beside the user-tunable `enabled` and `timeoutMs` siblings,
|
|
25844
|
+
* which are carried over while `events` is replaced.
|
|
25845
|
+
*
|
|
25846
|
+
* @see https://zcode.z.ai/en/docs
|
|
25847
|
+
*/
|
|
25848
|
+
var ZcodeHooks = class ZcodeHooks extends ToolHooks {
|
|
25849
|
+
json;
|
|
25850
|
+
constructor(params) {
|
|
25851
|
+
super(params);
|
|
25852
|
+
this.json = parseZcodeConfig$1(this.fileContent ?? "{}", join(this.relativeDirPath, this.relativeFilePath));
|
|
25853
|
+
}
|
|
25854
|
+
isDeletable() {
|
|
25855
|
+
return false;
|
|
25856
|
+
}
|
|
25857
|
+
static getSettablePaths(_options = {}) {
|
|
25858
|
+
return {
|
|
25859
|
+
relativeDirPath: ZCODE_GLOBAL_CONFIG_DIR_PATH,
|
|
25860
|
+
relativeFilePath: ZCODE_CONFIG_FILE_NAME
|
|
25861
|
+
};
|
|
25862
|
+
}
|
|
25863
|
+
static async fromFile({ outputRoot = process.cwd(), validate = true, global = false }) {
|
|
25864
|
+
const paths = this.getSettablePaths({ global });
|
|
25865
|
+
const fileContent = await readFileContentOrNull(join(outputRoot, paths.relativeDirPath, paths.relativeFilePath)) ?? "{}";
|
|
25866
|
+
return new ZcodeHooks({
|
|
25867
|
+
outputRoot,
|
|
25868
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25869
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25870
|
+
fileContent,
|
|
25871
|
+
validate,
|
|
25872
|
+
global
|
|
25873
|
+
});
|
|
25874
|
+
}
|
|
25875
|
+
static async fromRulesyncHooks({ outputRoot = process.cwd(), rulesyncHooks, validate = true, global = false, logger }) {
|
|
25876
|
+
const paths = this.getSettablePaths({ global });
|
|
25877
|
+
const filePath = join(outputRoot, paths.relativeDirPath, paths.relativeFilePath);
|
|
25878
|
+
const existingContent = await readFileContentOrNull(filePath) ?? "{}";
|
|
25879
|
+
const existing = parseZcodeConfig$1(existingContent, filePath);
|
|
25880
|
+
const config = rulesyncHooks.getJson();
|
|
25881
|
+
const events = canonicalToToolHooks({
|
|
25882
|
+
config,
|
|
25883
|
+
toolOverrideHooks: config.zcode?.hooks,
|
|
25884
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25885
|
+
logger
|
|
25886
|
+
});
|
|
25887
|
+
const existingHooks = isRecord$1(existing["hooks"]) ? existing[ZCODE_HOOKS_CONFIG_KEY] : {};
|
|
25888
|
+
const shouldStateEnabled = Object.keys(events).length > 0 && existingHooks.enabled === void 0;
|
|
25889
|
+
return new ZcodeHooks({
|
|
25890
|
+
outputRoot,
|
|
25891
|
+
relativeDirPath: paths.relativeDirPath,
|
|
25892
|
+
relativeFilePath: paths.relativeFilePath,
|
|
25893
|
+
fileContent: applySharedConfigPatch({
|
|
25894
|
+
fileKey: sharedConfigFileKey(paths),
|
|
25895
|
+
feature: "hooks",
|
|
25896
|
+
existingContent,
|
|
25897
|
+
patch: { [ZCODE_HOOKS_CONFIG_KEY]: {
|
|
25898
|
+
...existingHooks,
|
|
25899
|
+
...shouldStateEnabled ? { enabled: true } : {},
|
|
25900
|
+
[ZCODE_HOOKS_EVENTS_KEY]: events
|
|
25901
|
+
} },
|
|
25902
|
+
filePath
|
|
25903
|
+
}),
|
|
25904
|
+
validate,
|
|
25905
|
+
global
|
|
25906
|
+
});
|
|
25907
|
+
}
|
|
25908
|
+
toRulesyncHooks({ logger } = {}) {
|
|
25909
|
+
const hooks = toolHooksToCanonical({
|
|
25910
|
+
hooks: stripProcessHooks({
|
|
25911
|
+
events: (isRecord$1(this.json["hooks"]) ? this.json[ZCODE_HOOKS_CONFIG_KEY] : {})[ZCODE_HOOKS_EVENTS_KEY],
|
|
25912
|
+
logger
|
|
25913
|
+
}),
|
|
25914
|
+
converterConfig: ZCODE_CONVERTER_CONFIG,
|
|
25915
|
+
logger
|
|
25916
|
+
});
|
|
25917
|
+
return this.toRulesyncHooksDefault({ fileContent: JSON.stringify(buildImportedHooksConfig({
|
|
25918
|
+
hooks,
|
|
25919
|
+
overrideKey: "zcode"
|
|
25920
|
+
}), null, 2) });
|
|
25921
|
+
}
|
|
25922
|
+
validate() {
|
|
25923
|
+
return {
|
|
25924
|
+
success: true,
|
|
25925
|
+
error: null
|
|
25926
|
+
};
|
|
25927
|
+
}
|
|
25928
|
+
static forDeletion({ outputRoot = process.cwd(), relativeDirPath, relativeFilePath, global = false }) {
|
|
25929
|
+
return new ZcodeHooks({
|
|
25930
|
+
outputRoot,
|
|
25931
|
+
relativeDirPath,
|
|
25932
|
+
relativeFilePath,
|
|
25933
|
+
fileContent: JSON.stringify({ [ZCODE_HOOKS_CONFIG_KEY]: { [ZCODE_HOOKS_EVENTS_KEY]: {} } }, null, 2),
|
|
25934
|
+
validate: false,
|
|
25935
|
+
global
|
|
25936
|
+
});
|
|
25937
|
+
}
|
|
25938
|
+
};
|
|
25939
|
+
//#endregion
|
|
25198
25940
|
//#region src/features/hooks/hooks-processor.ts
|
|
25199
25941
|
const HooksProcessorToolTargetSchema = z.enum(hooksProcessorToolTargetTuple);
|
|
25200
25942
|
/**
|
|
@@ -25266,8 +26008,12 @@ const HOOKS_OVERRIDE_KEY_ALIASES = {
|
|
|
25266
26008
|
"kiro-cli": KIRO_HOOKS_OVERRIDE_KEY,
|
|
25267
26009
|
"kiro-ide": KIRO_HOOKS_OVERRIDE_KEY
|
|
25268
26010
|
};
|
|
25269
|
-
/** The targets
|
|
25270
|
-
const
|
|
26011
|
+
/** The targets whose hooks format carries a per-hook on-disk enable flag. */
|
|
26012
|
+
const PER_HOOK_ENABLED_TARGETS = /* @__PURE__ */ new Set([
|
|
26013
|
+
"kiro-cli",
|
|
26014
|
+
"kiro-ide",
|
|
26015
|
+
"zcode"
|
|
26016
|
+
]);
|
|
25271
26017
|
const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
25272
26018
|
["amp", {
|
|
25273
26019
|
class: AmpHooks,
|
|
@@ -25616,6 +26362,17 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
|
|
|
25616
26362
|
supportedEvents: GROKCLI_HOOK_EVENTS,
|
|
25617
26363
|
supportedHookTypes: ["command", "http"],
|
|
25618
26364
|
supportsMatcher: true
|
|
26365
|
+
}],
|
|
26366
|
+
["zcode", {
|
|
26367
|
+
class: ZcodeHooks,
|
|
26368
|
+
meta: {
|
|
26369
|
+
supportsProject: false,
|
|
26370
|
+
supportsGlobal: true,
|
|
26371
|
+
supportsImport: true
|
|
26372
|
+
},
|
|
26373
|
+
supportedEvents: ZCODE_HOOK_EVENTS,
|
|
26374
|
+
supportedHookTypes: ["command"],
|
|
26375
|
+
supportsMatcher: true
|
|
25619
26376
|
}]
|
|
25620
26377
|
]);
|
|
25621
26378
|
const hooksProcessorToolTargets = [...toolHooksFactories.entries()].filter(([, f]) => f.meta.supportsProject).map(([t]) => t);
|
|
@@ -25625,7 +26382,8 @@ const hooksProcessorToolTargetsGlobalImportable = [...toolHooksFactories.entries
|
|
|
25625
26382
|
var HooksProcessor = class extends FeatureProcessor {
|
|
25626
26383
|
toolTarget;
|
|
25627
26384
|
global;
|
|
25628
|
-
|
|
26385
|
+
preserveUnownedHooks;
|
|
26386
|
+
constructor({ outputRoot = process.cwd(), inputRoots, toolTarget, global = false, dryRun = false, preserveUnownedHooks = false, logger }) {
|
|
25629
26387
|
super({
|
|
25630
26388
|
outputRoot,
|
|
25631
26389
|
inputRoots,
|
|
@@ -25636,6 +26394,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25636
26394
|
if (!result.success) throw new Error(`Invalid tool target for HooksProcessor: ${toolTarget}. ${formatError(result.error)}`);
|
|
25637
26395
|
this.toolTarget = result.data;
|
|
25638
26396
|
this.global = global;
|
|
26397
|
+
this.preserveUnownedHooks = preserveUnownedHooks;
|
|
25639
26398
|
}
|
|
25640
26399
|
async loadRulesyncFiles() {
|
|
25641
26400
|
const relativePaths = getRulesyncSourceCandidates({ paths: RulesyncHooks.getSettablePaths() }).map((candidate) => candidate.relativeFilePath);
|
|
@@ -25727,14 +26486,14 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25727
26486
|
}
|
|
25728
26487
|
for (const [hookType, events] of unsupportedTypeToEvents) this.logger.warn(`Skipped ${hookType}-type hook(s) for ${this.toolTarget} (not supported): ${Array.from(events).join(", ")}`);
|
|
25729
26488
|
}
|
|
25730
|
-
if (!
|
|
26489
|
+
if (!PER_HOOK_ENABLED_TARGETS.has(this.toolTarget)) {
|
|
25731
26490
|
const skippedEvents = new Set(unsupportedEventNames({
|
|
25732
26491
|
factory,
|
|
25733
26492
|
sharedHooks,
|
|
25734
26493
|
effectiveHooks
|
|
25735
26494
|
}));
|
|
25736
26495
|
const eventsWithDisabledHooks = Object.entries(sharedHooks).filter(([event, defs]) => !skippedEvents.has(event) && defs.some((def) => def.enabled === false)).map(([event]) => event);
|
|
25737
|
-
if (eventsWithDisabledHooks.length > 0) this.logger.warn(`Emitting "enabled: false" hook(s) as active for ${this.toolTarget} (only the kiro-cli / kiro-ide
|
|
26496
|
+
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(", ")}`);
|
|
25738
26497
|
}
|
|
25739
26498
|
const eventsWithUnsupportedMatcher = unsupportedMatcherEventNames({
|
|
25740
26499
|
factory,
|
|
@@ -25746,6 +26505,7 @@ var HooksProcessor = class extends FeatureProcessor {
|
|
|
25746
26505
|
rulesyncHooks,
|
|
25747
26506
|
validate: true,
|
|
25748
26507
|
global: this.global,
|
|
26508
|
+
preserveUnowned: this.preserveUnownedHooks,
|
|
25749
26509
|
logger: withToolTargetPrefix({
|
|
25750
26510
|
logger: this.logger,
|
|
25751
26511
|
toolTarget: this.toolTarget
|
|
@@ -26181,6 +26941,7 @@ var ClineIgnore = class ClineIgnore extends ToolIgnore {
|
|
|
26181
26941
|
//#region src/constants/crush-paths.ts
|
|
26182
26942
|
const CRUSH_RULE_FILE_NAME = "CRUSH.md";
|
|
26183
26943
|
const CRUSH_GLOBAL_DIR = join(".config", "crush");
|
|
26944
|
+
const CRUSH_LOCAL_RULE_FILE_NAME = "CRUSH.local.md";
|
|
26184
26945
|
const CRUSH_IGNORE_FILE_NAME = ".crushignore";
|
|
26185
26946
|
const CRUSH_SKILLS_PROJECT_DIR = join(".crush", "skills");
|
|
26186
26947
|
const CRUSH_SKILLS_GLOBAL_DIR = join(CRUSH_GLOBAL_DIR, "skills");
|
|
@@ -32401,9 +33162,16 @@ function lookupTransport(map, key) {
|
|
|
32401
33162
|
}
|
|
32402
33163
|
/**
|
|
32403
33164
|
* Read the "put this server's own instructions into the agent's prompt" flag
|
|
32404
|
-
* off an unfiltered canonical entry, under either spelling.
|
|
32405
|
-
*
|
|
32406
|
-
*
|
|
33165
|
+
* off an unfiltered canonical entry, under either spelling. `undefined` means
|
|
33166
|
+
* the entry says nothing, which is Rovo Dev's own default.
|
|
33167
|
+
*
|
|
33168
|
+
* The flag is a **tri-state**, not a switch that only exists when on. Atlassian
|
|
33169
|
+
* inverted the default on 2026-09-02: a server's instructions are now surfaced
|
|
33170
|
+
* when the key is absent or `true`, and `false` is what suppresses them — the
|
|
33171
|
+
* opposite of the opt-in wording this adapter was first written against, where
|
|
33172
|
+
* absent and `false` meant the same thing. So `false` has to travel end to end;
|
|
33173
|
+
* collapsing it away is what makes a suppression impossible to author, and
|
|
33174
|
+
* erases one on import.
|
|
32407
33175
|
*
|
|
32408
33176
|
* The canonical key decides whenever it is present, the way codex resolves the
|
|
32409
33177
|
* same two-spelling conflict for `experimental_environment`. OR-ing the two
|
|
@@ -32411,28 +33179,25 @@ function lookupTransport(map, key) {
|
|
|
32411
33179
|
* `enable_instructions: true` copied out of Atlassian's docs — fail-open, on
|
|
32412
33180
|
* the one key whose whole purpose is a trust decision.
|
|
32413
33181
|
*
|
|
33182
|
+
* A non-boolean under either spelling reads as `undefined` rather than as a
|
|
33183
|
+
* suppression: Rovo Dev surfaces the instructions for anything that is not
|
|
33184
|
+
* `false`, so writing `false` there would invent a restriction the tool is not
|
|
33185
|
+
* applying. (The canonical key is a strict boolean in the schema, so only the
|
|
33186
|
+
* raw spelling can carry one.)
|
|
33187
|
+
*
|
|
32414
33188
|
* `isPlainObject` rather than `isRecord`: this walks a user-supplied key set,
|
|
32415
33189
|
* so a `constructor` entry must not resolve up the prototype chain.
|
|
32416
33190
|
*/
|
|
32417
33191
|
function readEnableInstructions(rawServer) {
|
|
32418
|
-
if (!isPlainObject$1(rawServer)) return
|
|
32419
|
-
|
|
32420
|
-
return
|
|
32421
|
-
|
|
32422
|
-
|
|
32423
|
-
* Names that were emitted with `enable_instructions: true` during one
|
|
32424
|
-
* `fromRulesyncMcp` call, so the run can say so once rather than per server.
|
|
32425
|
-
* Atlassian gates this key on trust, and it is the only thing generate writes
|
|
32426
|
-
* that widens what steers the model — the quietest possible write is the wrong
|
|
32427
|
-
* one for it.
|
|
32428
|
-
*/
|
|
32429
|
-
function warnEnabledInstructions(names, logger) {
|
|
32430
|
-
if (names.length === 0) return;
|
|
32431
|
-
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.`);
|
|
33192
|
+
if (!isPlainObject$1(rawServer)) return;
|
|
33193
|
+
const canonical = rawServer.rovodevEnableInstructions;
|
|
33194
|
+
if (canonical !== void 0) return typeof canonical === "boolean" ? canonical : void 0;
|
|
33195
|
+
const raw = rawServer.enable_instructions;
|
|
33196
|
+
return typeof raw === "boolean" ? raw : void 0;
|
|
32432
33197
|
}
|
|
32433
33198
|
function toRovodevServer(name, server, logger) {
|
|
32434
33199
|
const { type, transport, disabled: _disabled, rovodevEnableInstructions, ...rest } = server;
|
|
32435
|
-
if (rovodevEnableInstructions ===
|
|
33200
|
+
if (typeof rovodevEnableInstructions === "boolean") rest.enable_instructions = rovodevEnableInstructions;
|
|
32436
33201
|
const declared = typeof transport === "string" ? transport : typeof type === "string" ? type : void 0;
|
|
32437
33202
|
if (declared === void 0) return rest;
|
|
32438
33203
|
const mapped = lookupTransport(CANONICAL_TO_ROVODEV_TRANSPORT, declared);
|
|
@@ -32447,7 +33212,7 @@ function toRovodevServer(name, server, logger) {
|
|
|
32447
33212
|
}
|
|
32448
33213
|
function fromRovodevServer(server) {
|
|
32449
33214
|
const { transport, enable_instructions: enableInstructions, ...rest } = server;
|
|
32450
|
-
if (enableInstructions ===
|
|
33215
|
+
if (typeof enableInstructions === "boolean") rest.rovodevEnableInstructions = enableInstructions;
|
|
32451
33216
|
if (typeof transport !== "string") return rest;
|
|
32452
33217
|
const mapped = lookupTransport(ROVODEV_TO_CANONICAL_TRANSPORT, transport);
|
|
32453
33218
|
return mapped === void 0 ? rest : {
|
|
@@ -32888,10 +33653,10 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
32888
33653
|
canWriteDisableToggle = false;
|
|
32889
33654
|
}
|
|
32890
33655
|
const mcpServers = Object.fromEntries(Object.entries(rulesyncMcp.getMcpServers()).map(([name, server]) => {
|
|
32891
|
-
const
|
|
33656
|
+
const enableInstructions = readEnableInstructions(rulesyncMcp.getRawMcpServer(name));
|
|
32892
33657
|
const record = {
|
|
32893
33658
|
...server,
|
|
32894
|
-
...
|
|
33659
|
+
...enableInstructions !== void 0 && { rovodevEnableInstructions: enableInstructions }
|
|
32895
33660
|
};
|
|
32896
33661
|
if (record.disabled === true && !canWriteDisableToggle) {
|
|
32897
33662
|
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.`);
|
|
@@ -32900,7 +33665,6 @@ var RovodevMcp = class RovodevMcp extends ToolMcp {
|
|
|
32900
33665
|
const converted = toRovodevServer(name, record, logger);
|
|
32901
33666
|
return converted === null ? null : [name, converted];
|
|
32902
33667
|
}).filter((entry) => entry !== null));
|
|
32903
|
-
warnEnabledInstructions(Object.entries(mcpServers).filter(([, server]) => server.enable_instructions === true).map(([name]) => name), logger);
|
|
32904
33668
|
const rovodevConfig = {
|
|
32905
33669
|
...json,
|
|
32906
33670
|
mcpServers
|
|
@@ -39044,6 +39808,34 @@ const DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS = {
|
|
|
39044
39808
|
yolo_switcher: true,
|
|
39045
39809
|
read_project_dotenv: true
|
|
39046
39810
|
};
|
|
39811
|
+
/**
|
|
39812
|
+
* `[extensions]` gates dcode's Python extension system. `discover_extensions`
|
|
39813
|
+
* auto-loads `*.py` from the user's `~/.deepagents/extensions/` and from the
|
|
39814
|
+
* checked-out project's `<root>/.deepagents/extensions/`, so this table decides
|
|
39815
|
+
* whether a cloned repository's Python is imported into the agent process.
|
|
39816
|
+
*/
|
|
39817
|
+
const EXTENSIONS_TABLE_KEY = "extensions";
|
|
39818
|
+
/**
|
|
39819
|
+
* Keys lifted back into the override on import. `extensions.extra_paths` is
|
|
39820
|
+
* deliberately absent: it names machine-local files and directories, which do
|
|
39821
|
+
* not belong in a committed `.rulesync/permissions.jsonc`, and it only ever
|
|
39822
|
+
* widens what loads.
|
|
39823
|
+
*/
|
|
39824
|
+
const DEEPAGENTS_EXTENSIONS_KEYS = ["enabled", "trust"];
|
|
39825
|
+
/** `TrustPolicy` in `extensions/settings.py`; dcode reads nothing else. */
|
|
39826
|
+
const DEEPAGENTS_EXTENSION_TRUST_POLICIES = [
|
|
39827
|
+
"ask",
|
|
39828
|
+
"always",
|
|
39829
|
+
"never"
|
|
39830
|
+
];
|
|
39831
|
+
/**
|
|
39832
|
+
* `[extensions].enabled` defaults to `true` upstream, so writing `true` on a
|
|
39833
|
+
* machine that has not set it changes nothing — the same reasoning as
|
|
39834
|
+
* `DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS`.
|
|
39835
|
+
*
|
|
39836
|
+
* @see https://github.com/langchain-ai/deepagents `config_manifest.py`
|
|
39837
|
+
*/
|
|
39838
|
+
const DEEPAGENTS_EXTENSIONS_ENABLED_DEFAULT = true;
|
|
39047
39839
|
const ALLOW_ALL_SENTINEL = "all";
|
|
39048
39840
|
const RECOMMENDED_SENTINEL = "recommended";
|
|
39049
39841
|
const GLOB_CHARACTERS_PATTERN = /[*?[\]]/;
|
|
@@ -39113,7 +39905,10 @@ const TRAILING_ARGUMENT_WILDCARD_PATTERN = /:\*$/;
|
|
|
39113
39905
|
* `DeepagentsPermissionsOverrideSchema`): `[startup].mode`
|
|
39114
39906
|
* (`manual` / `auto` / `yolo`), `[startup].yolo_switcher` and
|
|
39115
39907
|
* `[startup].read_project_dotenv` are merged into the file on generate and
|
|
39116
|
-
* lifted back into the override on import.
|
|
39908
|
+
* lifted back into the override on import. The same override carries
|
|
39909
|
+
* `[extensions].enabled` and `[extensions].trust` (`ask` / `always` / `never`),
|
|
39910
|
+
* which decide whether the Python in a checked-out project's
|
|
39911
|
+
* `.deepagents/extensions/` is imported into the agent process.
|
|
39117
39912
|
*
|
|
39118
39913
|
* On **import** the allowlist comes back as `bash` `allow` rules named by the
|
|
39119
39914
|
* executable — skipping any entry dcode could not match in the first place, so
|
|
@@ -39215,6 +40010,13 @@ var DeepagentsPermissions = class DeepagentsPermissions extends ToolPermissions
|
|
|
39215
40010
|
filePath,
|
|
39216
40011
|
logger
|
|
39217
40012
|
});
|
|
40013
|
+
const extensionsOverride = config.deepagents?.extensions;
|
|
40014
|
+
if (isPlainObject$1(extensionsOverride) && Object.keys(extensionsOverride).length > 0) mergeExtensionsOverride({
|
|
40015
|
+
settings,
|
|
40016
|
+
extensionsOverride,
|
|
40017
|
+
filePath,
|
|
40018
|
+
logger
|
|
40019
|
+
});
|
|
39218
40020
|
return new DeepagentsPermissions({
|
|
39219
40021
|
outputRoot,
|
|
39220
40022
|
relativeDirPath: paths.relativeDirPath,
|
|
@@ -39239,8 +40041,15 @@ var DeepagentsPermissions = class DeepagentsPermissions extends ToolPermissions
|
|
|
39239
40041
|
startup: isPlainObject$1(settings[STARTUP_TABLE_KEY]) ? settings[STARTUP_TABLE_KEY] : {},
|
|
39240
40042
|
selfPath
|
|
39241
40043
|
});
|
|
40044
|
+
const extensionsOverride = liftExtensionsOverride({
|
|
40045
|
+
extensions: isPlainObject$1(settings[EXTENSIONS_TABLE_KEY]) ? settings[EXTENSIONS_TABLE_KEY] : {},
|
|
40046
|
+
selfPath
|
|
40047
|
+
});
|
|
39242
40048
|
const result = { ...config };
|
|
39243
|
-
|
|
40049
|
+
const deepagents = {};
|
|
40050
|
+
if (Object.keys(startupOverride).length > 0) deepagents.startup = startupOverride;
|
|
40051
|
+
if (Object.keys(extensionsOverride).length > 0) deepagents.extensions = extensionsOverride;
|
|
40052
|
+
if (Object.keys(deepagents).length > 0) result.deepagents = deepagents;
|
|
39244
40053
|
return this.toRulesyncPermissionsDefault({ fileContent: JSON.stringify(result, null, 2) });
|
|
39245
40054
|
}
|
|
39246
40055
|
validate() {
|
|
@@ -39327,57 +40136,192 @@ function warnAboutUnwrittenBashRules({ allowAll, requestedAllowAll, askPatterns,
|
|
|
39327
40136
|
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.");
|
|
39328
40137
|
}
|
|
39329
40138
|
/**
|
|
39330
|
-
* Lift the
|
|
39331
|
-
* override, keeping only values dcode itself accepts.
|
|
40139
|
+
* Lift the keys rulesync models from one `config.toml` table back into the
|
|
40140
|
+
* `deepagents` override, keeping only values dcode itself accepts.
|
|
39332
40141
|
*
|
|
39333
|
-
* A
|
|
39334
|
-
*
|
|
39335
|
-
*
|
|
39336
|
-
*
|
|
39337
|
-
*
|
|
40142
|
+
* A value outside what the option's own parser reads is `Invalid` upstream —
|
|
40143
|
+
* dcode ignores it and falls back to its default. Importing it anyway would
|
|
40144
|
+
* record a setting the tool is not applying and, because the canonical schema
|
|
40145
|
+
* is stricter than TOML, would write a `.rulesync/permissions.jsonc` the next
|
|
40146
|
+
* `rulesync generate` cannot even read.
|
|
40147
|
+
*
|
|
40148
|
+
* `normalize` returns the value to keep, or `null` for one dcode would not
|
|
40149
|
+
* read — a wrapper rather than the bare value, so a legitimately falsy setting
|
|
40150
|
+
* is not mistaken for a rejection.
|
|
39338
40151
|
*/
|
|
39339
|
-
function
|
|
39340
|
-
const
|
|
40152
|
+
function liftOverrideTable({ table, tableKey, keys, normalize, selfPath }) {
|
|
40153
|
+
const override = {};
|
|
39341
40154
|
const rejected = [];
|
|
39342
|
-
for (const key of
|
|
39343
|
-
const value =
|
|
40155
|
+
for (const key of keys) {
|
|
40156
|
+
const value = table[key];
|
|
39344
40157
|
if (value === void 0) continue;
|
|
39345
|
-
|
|
40158
|
+
const normalized = normalize({
|
|
40159
|
+
key,
|
|
40160
|
+
value
|
|
40161
|
+
});
|
|
40162
|
+
if (normalized) override[key] = normalized.value;
|
|
39346
40163
|
else rejected.push(`${key} = ${JSON.stringify(value)}`);
|
|
39347
40164
|
}
|
|
39348
|
-
if (rejected.length > 0) warnWithFallback(void 0, `deepagents-cli falls back to its own default for a '[${
|
|
39349
|
-
return
|
|
40165
|
+
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.`);
|
|
40166
|
+
return override;
|
|
39350
40167
|
}
|
|
39351
40168
|
/**
|
|
39352
|
-
*
|
|
39353
|
-
*
|
|
39354
|
-
*
|
|
39355
|
-
* user has it rather than replaced.
|
|
40169
|
+
* Lift the `[startup]` keys rulesync models back into the `deepagents`
|
|
40170
|
+
* override. A `mode` outside the three approval modes, or a non-boolean where
|
|
40171
|
+
* a switch belongs, is one dcode ignores.
|
|
39356
40172
|
*/
|
|
39357
|
-
function
|
|
39358
|
-
|
|
39359
|
-
|
|
39360
|
-
|
|
40173
|
+
function liftStartupOverride({ startup, selfPath }) {
|
|
40174
|
+
return liftOverrideTable({
|
|
40175
|
+
table: startup,
|
|
40176
|
+
tableKey: STARTUP_TABLE_KEY,
|
|
40177
|
+
keys: DEEPAGENTS_STARTUP_KEYS,
|
|
40178
|
+
normalize: ({ key, value }) => {
|
|
40179
|
+
if (key === "mode") return DEEPAGENTS_STARTUP_MODES.includes(value) ? { value } : null;
|
|
40180
|
+
return typeof value === "boolean" ? { value } : null;
|
|
40181
|
+
},
|
|
40182
|
+
selfPath
|
|
40183
|
+
});
|
|
40184
|
+
}
|
|
40185
|
+
/**
|
|
40186
|
+
* Lift the `[extensions]` keys rulesync models back into the `deepagents`
|
|
40187
|
+
* override.
|
|
40188
|
+
*
|
|
40189
|
+
* `trust` is read the way `parse_trust_policy` reads it — trimmed and
|
|
40190
|
+
* lowercased — so `"Always"` comes back as the policy dcode is actually
|
|
40191
|
+
* applying rather than being dropped, the same courtesy the allowlist
|
|
40192
|
+
* sentinels already get. The canonical enum holds only the lowercase
|
|
40193
|
+
* spellings, so keeping the value verbatim would write a permissions file the
|
|
40194
|
+
* next generate could not parse.
|
|
40195
|
+
*/
|
|
40196
|
+
function liftExtensionsOverride({ extensions, selfPath }) {
|
|
40197
|
+
return liftOverrideTable({
|
|
40198
|
+
table: extensions,
|
|
40199
|
+
tableKey: EXTENSIONS_TABLE_KEY,
|
|
40200
|
+
keys: DEEPAGENTS_EXTENSIONS_KEYS,
|
|
40201
|
+
normalize: ({ key, value }) => {
|
|
40202
|
+
if (key === "trust") {
|
|
40203
|
+
if (typeof value !== "string") return null;
|
|
40204
|
+
const policy = value.trim().toLowerCase();
|
|
40205
|
+
return DEEPAGENTS_EXTENSION_TRUST_POLICIES.includes(policy) ? { value: policy } : null;
|
|
40206
|
+
}
|
|
40207
|
+
return typeof value === "boolean" ? { value } : null;
|
|
40208
|
+
},
|
|
40209
|
+
selfPath
|
|
40210
|
+
});
|
|
40211
|
+
}
|
|
40212
|
+
/**
|
|
40213
|
+
* Merge one block of the `deepagents` override into its `config.toml` table,
|
|
40214
|
+
* preserving every other key of that table. A table that is not a table at all
|
|
40215
|
+
* is something rulesync did not write and cannot merge into, so it is left
|
|
40216
|
+
* exactly as the user has it rather than replaced.
|
|
40217
|
+
*/
|
|
40218
|
+
function mergeOverrideTable({ settings, tableKey, override, knownKeys, isDroppedKey, warnAboutRelaxations, filePath, logger }) {
|
|
40219
|
+
const existing = settings[tableKey];
|
|
40220
|
+
if (existing !== void 0 && !isPlainObject$1(existing)) {
|
|
40221
|
+
warnWithFallback(logger, `deepagents-cli: '${tableKey}' in ${filePath} is not a table, so the deepagents ${tableKey} override was skipped rather than overwriting it.`);
|
|
39361
40222
|
return;
|
|
39362
40223
|
}
|
|
39363
|
-
const
|
|
39364
|
-
const
|
|
40224
|
+
const table = isPlainObject$1(existing) ? { ...existing } : {};
|
|
40225
|
+
const previous = { ...table };
|
|
39365
40226
|
const writtenKeys = [];
|
|
39366
|
-
for (const [key, value] of Object.entries(
|
|
40227
|
+
for (const [key, value] of Object.entries(override)) {
|
|
39367
40228
|
if (isPrototypePollutionKey(key)) continue;
|
|
39368
|
-
if (key
|
|
40229
|
+
if (isDroppedKey?.(key)) continue;
|
|
39369
40230
|
if (value === null || value === void 0) continue;
|
|
39370
|
-
|
|
40231
|
+
table[key] = value;
|
|
39371
40232
|
writtenKeys.push(key);
|
|
39372
40233
|
}
|
|
39373
|
-
|
|
39374
|
-
|
|
39375
|
-
|
|
40234
|
+
warnAboutRelaxations({
|
|
40235
|
+
override,
|
|
40236
|
+
previous,
|
|
40237
|
+
filePath,
|
|
40238
|
+
logger
|
|
40239
|
+
});
|
|
40240
|
+
warnAboutUncheckedKeys({
|
|
40241
|
+
tableKey,
|
|
40242
|
+
knownKeys,
|
|
39376
40243
|
writtenKeys,
|
|
39377
40244
|
filePath,
|
|
39378
40245
|
logger
|
|
39379
40246
|
});
|
|
39380
|
-
if (Object.keys(
|
|
40247
|
+
if (Object.keys(table).length > 0) settings[tableKey] = table;
|
|
40248
|
+
}
|
|
40249
|
+
/**
|
|
40250
|
+
* Name the keys the merge wrote that rulesync does not model. The override is a
|
|
40251
|
+
* loose object so a key added upstream still reaches the config, but this one
|
|
40252
|
+
* writes into the machine's global file: what rulesync cannot judge, it at
|
|
40253
|
+
* least names.
|
|
40254
|
+
*/
|
|
40255
|
+
function warnAboutUncheckedKeys({ tableKey, knownKeys, writtenKeys, filePath, logger }) {
|
|
40256
|
+
const known = new Set(knownKeys);
|
|
40257
|
+
const unknownKeys = writtenKeys.filter((key) => !known.has(key));
|
|
40258
|
+
if (unknownKeys.length === 0) return;
|
|
40259
|
+
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.`);
|
|
40260
|
+
}
|
|
40261
|
+
/**
|
|
40262
|
+
* Merge the `deepagents.startup` override into `[startup]`.
|
|
40263
|
+
*/
|
|
40264
|
+
function mergeStartupOverride({ settings, startupOverride, filePath, logger }) {
|
|
40265
|
+
mergeOverrideTable({
|
|
40266
|
+
settings,
|
|
40267
|
+
tableKey: STARTUP_TABLE_KEY,
|
|
40268
|
+
override: startupOverride,
|
|
40269
|
+
knownKeys: DEEPAGENTS_STARTUP_KEYS,
|
|
40270
|
+
isDroppedKey: (key) => key === STARTUP_RECENT_KEY,
|
|
40271
|
+
warnAboutRelaxations: warnAboutStartupRelaxations,
|
|
40272
|
+
filePath,
|
|
40273
|
+
logger
|
|
40274
|
+
});
|
|
40275
|
+
}
|
|
40276
|
+
/**
|
|
40277
|
+
* Merge the `deepagents.extensions` override into `[extensions]`. Nothing is
|
|
40278
|
+
* dropped here — `extra_paths` is not modeled, but it is a key the user may
|
|
40279
|
+
* legitimately want carried, so it passes through as any other unknown key
|
|
40280
|
+
* does, named by the unchecked-key warning.
|
|
40281
|
+
*/
|
|
40282
|
+
function mergeExtensionsOverride({ settings, extensionsOverride, filePath, logger }) {
|
|
40283
|
+
mergeOverrideTable({
|
|
40284
|
+
settings,
|
|
40285
|
+
tableKey: EXTENSIONS_TABLE_KEY,
|
|
40286
|
+
override: extensionsOverride,
|
|
40287
|
+
knownKeys: DEEPAGENTS_EXTENSIONS_KEYS,
|
|
40288
|
+
warnAboutRelaxations: warnAboutExtensionsRelaxations,
|
|
40289
|
+
filePath,
|
|
40290
|
+
logger
|
|
40291
|
+
});
|
|
40292
|
+
}
|
|
40293
|
+
/**
|
|
40294
|
+
* Name the value an override key replaces, so a setting the user had turned
|
|
40295
|
+
* down is visible as such rather than reported as a bare new value.
|
|
40296
|
+
*/
|
|
40297
|
+
function describeOverriddenValue({ key, value, previous }) {
|
|
40298
|
+
const previousValue = previous[key];
|
|
40299
|
+
return previousValue === void 0 || previousValue === value ? `${key} = ${JSON.stringify(value)}` : `${key} = ${JSON.stringify(value)} (was ${JSON.stringify(previousValue)})`;
|
|
40300
|
+
}
|
|
40301
|
+
/**
|
|
40302
|
+
* Warn when the `deepagents` extensions override widens what dcode loads on its
|
|
40303
|
+
* own, for the same reason the startup one does: this block is written into the
|
|
40304
|
+
* user's **global** config from a `.rulesync/permissions.jsonc` a repository
|
|
40305
|
+
* can carry.
|
|
40306
|
+
*
|
|
40307
|
+
* `trust` decides what happens to the Python in a checked-out project's
|
|
40308
|
+
* `.deepagents/extensions/` — `always` imports it into the agent process with
|
|
40309
|
+
* no prompt at all — and `enabled` is the switch above it. `never` and `false`
|
|
40310
|
+
* restrict, so neither is reported.
|
|
40311
|
+
*/
|
|
40312
|
+
function warnAboutExtensionsRelaxations({ override, previous, filePath, logger }) {
|
|
40313
|
+
const relaxations = [];
|
|
40314
|
+
if (override.trust === "always") relaxations.push(describeOverriddenValue({
|
|
40315
|
+
key: "trust",
|
|
40316
|
+
value: "always",
|
|
40317
|
+
previous
|
|
40318
|
+
}));
|
|
40319
|
+
if (override.enabled === true && (previous.enabled ?? DEEPAGENTS_EXTENSIONS_ENABLED_DEFAULT) !== true) relaxations.push(describeOverriddenValue({
|
|
40320
|
+
key: "enabled",
|
|
40321
|
+
value: true,
|
|
40322
|
+
previous
|
|
40323
|
+
}));
|
|
40324
|
+
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.`);
|
|
39381
40325
|
}
|
|
39382
40326
|
/**
|
|
39383
40327
|
* Warn when the `deepagents` startup override relaxes what dcode may do on its
|
|
@@ -39392,12 +40336,13 @@ function mergeStartupOverride({ settings, startupOverride, filePath, logger }) {
|
|
|
39392
40336
|
* process environment. The value being replaced is named alongside, so a
|
|
39393
40337
|
* setting the user had turned down is visible as such.
|
|
39394
40338
|
*/
|
|
39395
|
-
function warnAboutStartupRelaxations({ startupOverride, previousStartup,
|
|
40339
|
+
function warnAboutStartupRelaxations({ override: startupOverride, previous: previousStartup, filePath, logger }) {
|
|
39396
40340
|
const relaxations = [];
|
|
39397
|
-
const describe = (key, value) => {
|
|
39398
|
-
|
|
39399
|
-
|
|
39400
|
-
|
|
40341
|
+
const describe = (key, value) => describeOverriddenValue({
|
|
40342
|
+
key,
|
|
40343
|
+
value,
|
|
40344
|
+
previous: previousStartup
|
|
40345
|
+
});
|
|
39401
40346
|
const mode = startupOverride.mode;
|
|
39402
40347
|
if (mode === "auto" || mode === "yolo") relaxations.push(describe("mode", mode));
|
|
39403
40348
|
for (const [key, upstreamDefault] of Object.entries(DEEPAGENTS_STARTUP_BOOLEAN_DEFAULTS)) {
|
|
@@ -39407,9 +40352,6 @@ function warnAboutStartupRelaxations({ startupOverride, previousStartup, written
|
|
|
39407
40352
|
}
|
|
39408
40353
|
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.`);
|
|
39409
40354
|
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.`);
|
|
39410
|
-
const known = new Set(DEEPAGENTS_STARTUP_KEYS);
|
|
39411
|
-
const unknownKeys = writtenKeys.filter((key) => !known.has(key));
|
|
39412
|
-
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.`);
|
|
39413
40355
|
}
|
|
39414
40356
|
/**
|
|
39415
40357
|
* Read `[shell].allow_list` the way dcode does: a TOML array is taken element
|
|
@@ -54030,7 +54972,10 @@ var WarpSkill = class WarpSkill extends ToolSkill {
|
|
|
54030
54972
|
//#region src/features/skills/zcode-skill.ts
|
|
54031
54973
|
const ZcodeSkillFrontmatterSchema = z.looseObject({
|
|
54032
54974
|
name: z.string(),
|
|
54033
|
-
description: z.string()
|
|
54975
|
+
description: z.string(),
|
|
54976
|
+
when_to_use: z.optional(z.string()),
|
|
54977
|
+
license: z.optional(z.string()),
|
|
54978
|
+
metadata: z.optional(z.looseObject({}))
|
|
54034
54979
|
});
|
|
54035
54980
|
/**
|
|
54036
54981
|
* Represents a ZCode skill directory.
|
|
@@ -54093,10 +55038,16 @@ var ZcodeSkill = class ZcodeSkill extends ToolSkill {
|
|
|
54093
55038
|
}
|
|
54094
55039
|
toRulesyncSkill() {
|
|
54095
55040
|
const frontmatter = this.getFrontmatter();
|
|
55041
|
+
const zcodeSection = {
|
|
55042
|
+
...frontmatter.when_to_use !== void 0 && { when_to_use: frontmatter.when_to_use },
|
|
55043
|
+
...frontmatter.license !== void 0 && { license: frontmatter.license },
|
|
55044
|
+
...frontmatter.metadata !== void 0 && { metadata: frontmatter.metadata }
|
|
55045
|
+
};
|
|
54096
55046
|
const rulesyncFrontmatter = {
|
|
54097
55047
|
name: frontmatter.name,
|
|
54098
55048
|
description: frontmatter.description,
|
|
54099
|
-
targets: ["*"]
|
|
55049
|
+
targets: ["*"],
|
|
55050
|
+
...Object.keys(zcodeSection).length > 0 && { zcode: zcodeSection }
|
|
54100
55051
|
};
|
|
54101
55052
|
return new RulesyncSkill({
|
|
54102
55053
|
outputRoot: this.outputRoot,
|
|
@@ -54111,9 +55062,21 @@ var ZcodeSkill = class ZcodeSkill extends ToolSkill {
|
|
|
54111
55062
|
}
|
|
54112
55063
|
static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
|
|
54113
55064
|
const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
|
|
55065
|
+
const zcodeSection = rulesyncFrontmatter.zcode;
|
|
55066
|
+
const license = resolveLicense({
|
|
55067
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
55068
|
+
section: zcodeSection
|
|
55069
|
+
});
|
|
55070
|
+
const metadata = resolveMetadata({
|
|
55071
|
+
rootFrontmatter: rulesyncFrontmatter,
|
|
55072
|
+
section: zcodeSection
|
|
55073
|
+
});
|
|
54114
55074
|
const zcodeFrontmatter = {
|
|
54115
55075
|
name: rulesyncFrontmatter.name,
|
|
54116
|
-
description: rulesyncFrontmatter.description
|
|
55076
|
+
description: rulesyncFrontmatter.description,
|
|
55077
|
+
...zcodeSection?.when_to_use !== void 0 && { when_to_use: zcodeSection.when_to_use },
|
|
55078
|
+
...license !== void 0 && { license },
|
|
55079
|
+
...metadata !== void 0 && { metadata }
|
|
54117
55080
|
};
|
|
54118
55081
|
const settablePaths = ZcodeSkill.getSettablePaths({ global });
|
|
54119
55082
|
return new ZcodeSkill({
|
|
@@ -62214,10 +63177,21 @@ var ClineRule = class ClineRule extends ToolRule {
|
|
|
62214
63177
|
*/
|
|
62215
63178
|
const CodebuddyRuleFrontmatterSchema = z.object({
|
|
62216
63179
|
description: z.optional(z.string()),
|
|
62217
|
-
paths: z.optional(z.array(z.string())),
|
|
62218
|
-
alwaysApply: z.optional(z.boolean())
|
|
63180
|
+
paths: z.optional(z.union([z.string(), z.array(z.string())])),
|
|
63181
|
+
alwaysApply: z.optional(z.boolean()),
|
|
63182
|
+
enabled: z.optional(z.boolean())
|
|
62219
63183
|
});
|
|
62220
63184
|
/**
|
|
63185
|
+
* Normalizes the documented `string` / `string[]` shapes of `paths` to the
|
|
63186
|
+
* list form the rest of the adapter works with. An empty list and an empty
|
|
63187
|
+
* string both mean "no paths".
|
|
63188
|
+
*/
|
|
63189
|
+
function normalizeCodebuddyPaths(paths) {
|
|
63190
|
+
if (paths === void 0) return;
|
|
63191
|
+
const list = (typeof paths === "string" ? [paths] : paths).filter((path) => path.trim() !== "");
|
|
63192
|
+
return list.length > 0 ? list : void 0;
|
|
63193
|
+
}
|
|
63194
|
+
/**
|
|
62221
63195
|
* A universal glob (matching everything) is redundant on an Always Apply
|
|
62222
63196
|
* rule and, paired with `alwaysApply: true`, is the same semantic conflict
|
|
62223
63197
|
* `CursorRule.resolveCursorGlobs` avoids for Cursor: `alwaysApply` already
|
|
@@ -62235,7 +63209,7 @@ const UNIVERSAL_PATHS = /* @__PURE__ */ new Set(["**/*", "*"]);
|
|
|
62235
63209
|
* Rules format:
|
|
62236
63210
|
* - {project}/CODEBUDDY.md (root: true), also read from {project}/.codebuddy/CODEBUDDY.md
|
|
62237
63211
|
* - {project}/.codebuddy/rules/*.md (root: false, with optional
|
|
62238
|
-
* `description` / `paths` / `alwaysApply` frontmatter)
|
|
63212
|
+
* `description` / `paths` / `alwaysApply` / `enabled` frontmatter)
|
|
62239
63213
|
* - Global: ~/.codebuddy/CODEBUDDY.md and ~/.codebuddy/rules/*.md
|
|
62240
63214
|
*
|
|
62241
63215
|
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
@@ -62277,9 +63251,10 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62277
63251
|
this.body = body;
|
|
62278
63252
|
}
|
|
62279
63253
|
static generateFileContent(body, frontmatter) {
|
|
62280
|
-
if (frontmatter.description === void 0 && frontmatter.paths === void 0 && frontmatter.alwaysApply === void 0) return body;
|
|
63254
|
+
if (frontmatter.description === void 0 && frontmatter.paths === void 0 && frontmatter.alwaysApply === void 0 && frontmatter.enabled === void 0) return body;
|
|
62281
63255
|
return stringifyFrontmatter(body, {
|
|
62282
63256
|
description: frontmatter.description,
|
|
63257
|
+
enabled: frontmatter.enabled,
|
|
62283
63258
|
alwaysApply: frontmatter.alwaysApply,
|
|
62284
63259
|
paths: frontmatter.paths
|
|
62285
63260
|
});
|
|
@@ -62327,10 +63302,36 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62327
63302
|
root: isRoot
|
|
62328
63303
|
});
|
|
62329
63304
|
}
|
|
62330
|
-
|
|
62331
|
-
|
|
62332
|
-
|
|
62333
|
-
|
|
63305
|
+
/**
|
|
63306
|
+
* Resolves the `paths` / `alwaysApply` pair against CodeBuddy's Rule Type
|
|
63307
|
+
* Determination table, which reads:
|
|
63308
|
+
*
|
|
63309
|
+
* | `alwaysApply` | `paths` | Rule type |
|
|
63310
|
+
* | ---------------- | --------- | ---------------------------------- |
|
|
63311
|
+
* | `true` (default) | any | ALWAYS — always injected |
|
|
63312
|
+
* | `false` | has value | MANUAL — triggered on matching file |
|
|
63313
|
+
* | `false` | none | not supported; the rule is dropped |
|
|
63314
|
+
*
|
|
63315
|
+
* The default being `true` is the opposite of Cursor, which the adapter was
|
|
63316
|
+
* modeled on: `paths` alone scopes nothing, so a rule meant to be path
|
|
63317
|
+
* triggered has to carry an explicit `alwaysApply: false` alongside it.
|
|
63318
|
+
*
|
|
63319
|
+
* @see https://www.codebuddy.ai/docs/cli/memory
|
|
63320
|
+
*/
|
|
63321
|
+
static resolveCodebuddyRuleType({ paths, alwaysApply }) {
|
|
63322
|
+
const scopedPaths = paths && paths.length > 0 && !paths.every((path) => UNIVERSAL_PATHS.has(path.trim())) ? paths : void 0;
|
|
63323
|
+
if (alwaysApply === true) return {
|
|
63324
|
+
paths: scopedPaths,
|
|
63325
|
+
alwaysApply: true
|
|
63326
|
+
};
|
|
63327
|
+
if (scopedPaths === void 0) return {
|
|
63328
|
+
paths: void 0,
|
|
63329
|
+
alwaysApply: void 0
|
|
63330
|
+
};
|
|
63331
|
+
return {
|
|
63332
|
+
paths: scopedPaths,
|
|
63333
|
+
alwaysApply: false
|
|
63334
|
+
};
|
|
62334
63335
|
}
|
|
62335
63336
|
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
62336
63337
|
const rulesyncFrontmatter = rulesyncRule.getFrontmatter();
|
|
@@ -62347,17 +63348,17 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62347
63348
|
root
|
|
62348
63349
|
});
|
|
62349
63350
|
if (!paths.nonRoot) throw new Error(`nonRoot path is not set for ${rulesyncRule.getRelativeFilePath()}`);
|
|
62350
|
-
const codebuddyPaths = rulesyncFrontmatter.codebuddy?.paths;
|
|
63351
|
+
const codebuddyPaths = normalizeCodebuddyPaths(rulesyncFrontmatter.codebuddy?.paths);
|
|
62351
63352
|
const globs = rulesyncFrontmatter.globs;
|
|
62352
|
-
const
|
|
62353
|
-
const pathsValue = CodebuddyRule.resolveCodebuddyPaths({
|
|
63353
|
+
const ruleType = CodebuddyRule.resolveCodebuddyRuleType({
|
|
62354
63354
|
paths: codebuddyPaths ?? (globs?.length ? globs : void 0),
|
|
62355
|
-
alwaysApply: alwaysApply
|
|
63355
|
+
alwaysApply: rulesyncFrontmatter.codebuddy?.alwaysApply
|
|
62356
63356
|
});
|
|
62357
63357
|
const codebuddyFrontmatter = {
|
|
62358
63358
|
description: rulesyncFrontmatter.codebuddy?.description ?? rulesyncFrontmatter.description,
|
|
62359
|
-
paths:
|
|
62360
|
-
alwaysApply
|
|
63359
|
+
paths: ruleType.paths,
|
|
63360
|
+
alwaysApply: ruleType.alwaysApply,
|
|
63361
|
+
enabled: rulesyncFrontmatter.codebuddy?.enabled
|
|
62361
63362
|
};
|
|
62362
63363
|
return new CodebuddyRule({
|
|
62363
63364
|
outputRoot,
|
|
@@ -62387,17 +63388,19 @@ var CodebuddyRule = class CodebuddyRule extends ToolRule {
|
|
|
62387
63388
|
validate: true
|
|
62388
63389
|
});
|
|
62389
63390
|
}
|
|
62390
|
-
const
|
|
62391
|
-
const
|
|
63391
|
+
const sourcePaths = normalizeCodebuddyPaths(this.frontmatter.paths) ?? [];
|
|
63392
|
+
const isAlways = this.frontmatter.alwaysApply !== false;
|
|
62392
63393
|
const globs = sourcePaths.length === 0 && isAlways ? ["**/*"] : sourcePaths;
|
|
63394
|
+
const alwaysApply = this.frontmatter.alwaysApply ?? (sourcePaths.length > 0 ? true : void 0);
|
|
62393
63395
|
const rulesyncFrontmatter = {
|
|
62394
63396
|
targets,
|
|
62395
63397
|
root: false,
|
|
62396
63398
|
description: this.frontmatter.description,
|
|
62397
63399
|
globs,
|
|
62398
|
-
...(this.frontmatter.paths !== void 0 || this.frontmatter.alwaysApply !== void 0 || this.frontmatter.description !== void 0) && { codebuddy: {
|
|
62399
|
-
paths:
|
|
62400
|
-
alwaysApply
|
|
63400
|
+
...(this.frontmatter.paths !== void 0 || this.frontmatter.alwaysApply !== void 0 || this.frontmatter.enabled !== void 0 || this.frontmatter.description !== void 0) && { codebuddy: {
|
|
63401
|
+
paths: sourcePaths.length > 0 ? sourcePaths : void 0,
|
|
63402
|
+
alwaysApply,
|
|
63403
|
+
enabled: this.frontmatter.enabled,
|
|
62401
63404
|
description: this.frontmatter.description
|
|
62402
63405
|
} }
|
|
62403
63406
|
};
|
|
@@ -65180,33 +66183,49 @@ var ReplitRule = class ReplitRule extends ToolRule {
|
|
|
65180
66183
|
* Supports plain Markdown without frontmatter, mode-specific rules,
|
|
65181
66184
|
* and both directory-based and single-file configurations.
|
|
65182
66185
|
*
|
|
65183
|
-
* - Project scope writes the non-root directory `.roo/rules
|
|
66186
|
+
* - Project scope writes the non-root directory `.roo/rules/`, and the root
|
|
66187
|
+
* rule to the workspace-root `AGENTS.md` the extension reads from `cwd`.
|
|
65184
66188
|
* - Global scope writes the same non-root directory resolved under the home
|
|
65185
|
-
* directory (`~/.roo/rules/`), which Roo loads before workspace rules.
|
|
66189
|
+
* directory (`~/.roo/rules/`), which Roo loads before workspace rules. The
|
|
66190
|
+
* root rule joins it as `~/.roo/rules/AGENTS.md`: agent-rules discovery is
|
|
66191
|
+
* workspace-only, so the default project-scope root would resolve to a
|
|
66192
|
+
* `~/AGENTS.md` nothing ever reads.
|
|
65186
66193
|
* @see https://roocodeinc.github.io/Roo-Code/features/custom-instructions
|
|
65187
66194
|
*/
|
|
65188
66195
|
var RooRule = class RooRule extends ToolRule {
|
|
65189
|
-
static getSettablePaths(
|
|
65190
|
-
|
|
66196
|
+
static getSettablePaths({ global, excludeToolDir } = {}) {
|
|
66197
|
+
const rulesDirPath = buildToolPath(ROO_DIR, "rules", excludeToolDir);
|
|
66198
|
+
if (global) return {
|
|
66199
|
+
root: {
|
|
66200
|
+
relativeDirPath: rulesDirPath,
|
|
66201
|
+
relativeFilePath: ROO_GLOBAL_ROOT_RULE_FILE_NAME
|
|
66202
|
+
},
|
|
66203
|
+
nonRoot: { relativeDirPath: rulesDirPath }
|
|
66204
|
+
};
|
|
66205
|
+
return { nonRoot: { relativeDirPath: rulesDirPath } };
|
|
65191
66206
|
}
|
|
65192
|
-
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true }) {
|
|
65193
|
-
const
|
|
66207
|
+
static async fromFile({ outputRoot = process.cwd(), relativeFilePath, relativeDirPath: overrideDirPath, validate = true, global = false }) {
|
|
66208
|
+
const paths = this.getSettablePaths({ global });
|
|
66209
|
+
const relativeDirPath = overrideDirPath !== void 0 && RooRule.extractModeFromDirPath(overrideDirPath) !== void 0 ? overrideDirPath : paths.nonRoot?.relativeDirPath ?? buildToolPath(".roo", "rules");
|
|
65194
66210
|
const fileContent = await readFileContent(join(outputRoot, relativeDirPath, relativeFilePath));
|
|
66211
|
+
const isRoot = "root" in paths && relativeDirPath === paths.root.relativeDirPath && relativeFilePath === paths.root.relativeFilePath;
|
|
65195
66212
|
return new RooRule({
|
|
65196
66213
|
outputRoot,
|
|
65197
66214
|
relativeDirPath,
|
|
65198
66215
|
relativeFilePath,
|
|
65199
66216
|
fileContent,
|
|
65200
66217
|
validate,
|
|
65201
|
-
root:
|
|
66218
|
+
root: isRoot
|
|
65202
66219
|
});
|
|
65203
66220
|
}
|
|
65204
|
-
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true }) {
|
|
66221
|
+
static fromRulesyncRule({ outputRoot = process.cwd(), rulesyncRule, validate = true, global = false }) {
|
|
66222
|
+
const paths = this.getSettablePaths({ global });
|
|
65205
66223
|
const params = this.buildToolRuleParamsDefault({
|
|
65206
66224
|
outputRoot,
|
|
65207
66225
|
rulesyncRule,
|
|
65208
66226
|
validate,
|
|
65209
|
-
|
|
66227
|
+
..."root" in paths && { rootPath: paths.root },
|
|
66228
|
+
nonRootPath: paths.nonRoot
|
|
65210
66229
|
});
|
|
65211
66230
|
const mode = rulesyncRule.getFrontmatter().roo?.mode;
|
|
65212
66231
|
if (!params.root && mode !== void 0 && mode !== "") {
|
|
@@ -66129,7 +67148,9 @@ const toolRuleFactories = /* @__PURE__ */ new Map([
|
|
|
66129
67148
|
extension: "md",
|
|
66130
67149
|
supportsGlobal: true,
|
|
66131
67150
|
ruleDiscoveryMode: "auto",
|
|
66132
|
-
collisionPolicy: "fold"
|
|
67151
|
+
collisionPolicy: "fold",
|
|
67152
|
+
localRootMode: "separate-local-file",
|
|
67153
|
+
localRootFileName: CRUSH_LOCAL_RULE_FILE_NAME
|
|
66133
67154
|
}
|
|
66134
67155
|
}],
|
|
66135
67156
|
["cursor", {
|
|
@@ -66849,6 +67870,34 @@ var RulesProcessor = class extends FeatureProcessor {
|
|
|
66849
67870
|
root: true,
|
|
66850
67871
|
localRoot
|
|
66851
67872
|
});
|
|
67873
|
+
if (isClassOrSubclassOf({
|
|
67874
|
+
candidate: factory.class,
|
|
67875
|
+
base: CodebuddyRule
|
|
67876
|
+
})) {
|
|
67877
|
+
const paths = CodebuddyRule.getSettablePaths({ global: this.global });
|
|
67878
|
+
return new CodebuddyRule({
|
|
67879
|
+
outputRoot: this.outputRoot,
|
|
67880
|
+
relativeDirPath: relativeDirPath ?? paths.root.relativeDirPath,
|
|
67881
|
+
relativeFilePath: fileName,
|
|
67882
|
+
frontmatter: {},
|
|
67883
|
+
body,
|
|
67884
|
+
validate: true,
|
|
67885
|
+
root: true,
|
|
67886
|
+
localRoot
|
|
67887
|
+
});
|
|
67888
|
+
}
|
|
67889
|
+
if (isClassOrSubclassOf({
|
|
67890
|
+
candidate: factory.class,
|
|
67891
|
+
base: CrushRule
|
|
67892
|
+
})) return new CrushRule({
|
|
67893
|
+
outputRoot: this.outputRoot,
|
|
67894
|
+
relativeDirPath: relativeDirPath ?? ".",
|
|
67895
|
+
relativeFilePath: fileName,
|
|
67896
|
+
fileContent: body,
|
|
67897
|
+
validate: true,
|
|
67898
|
+
root: true,
|
|
67899
|
+
localRoot
|
|
67900
|
+
});
|
|
66852
67901
|
if (isClassOrSubclassOf({
|
|
66853
67902
|
candidate: factory.class,
|
|
66854
67903
|
base: QwencodeRule
|
|
@@ -68977,6 +70026,7 @@ async function generateHooksCore(params) {
|
|
|
68977
70026
|
toolTarget,
|
|
68978
70027
|
global: config.getGlobal(),
|
|
68979
70028
|
dryRun: config.isPreviewMode(),
|
|
70029
|
+
preserveUnownedHooks: config.getPreserveUnownedHooks(),
|
|
68980
70030
|
logger
|
|
68981
70031
|
});
|
|
68982
70032
|
const result = await processFeatureWithRulesyncFiles({
|
|
@@ -69446,6 +70496,6 @@ async function importChecksCore(params) {
|
|
|
69446
70496
|
return writtenCount;
|
|
69447
70497
|
}
|
|
69448
70498
|
//#endregion
|
|
69449
|
-
export {
|
|
70499
|
+
export { RulesyncCheck as $, ALL_TOOL_TARGETS as $t, CLAUDECODE_SKILLS_DIR_PATH as A, RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH as An, ensureDir as At, RulesyncSkill as B, quoteForLog as Bn, pathEscapesRoot as Bt, ChecksProcessor as C, RULESYNC_PERMISSIONS_FILE_NAME as Cn, applyFileMode as Ct, CLAUDECODE_LOCAL_RULE_FILE_NAME as D, RULESYNC_RELATIVE_DIR_PATH as Dn, checkPathTraversal as Dt, CLAUDECODE_DIR as E, RULESYNC_PERMISSIONS_SCHEMA_URL as En, assertWritablePathInsideRoot as Et, AUGMENTCODE_DIR as F, DEPRECATED_FEATURE_REPLACEMENTS as Fn, isFileSystemError as Ft, RulesyncMcp as G, removeFile as Gt, RulesyncRule as H, stripControlCharactersKeepingLineFeeds as Hn, readFileContentOrNull as Ht, AUGMENTCODE_SETTINGS_LOCAL_FILE_NAME as I, formatError as In, isSymlink as It, getRulesyncSourceCandidates as J, resolvePath as Jt, RulesyncIgnore as K, removeFileStrict as Kt, getLocalSkillDirNames as L, truncateText as Ln, listDirectoryEntryNames as Lt, FACTORYDROID_SETTINGS_LOCAL_FILE_NAME as M, parseCommaSeparatedList as Mn, getFileSize as Mt, caseFoldIdentity as N, ALL_FEATURES as Nn, getHomeDirectory as Nt, CLAUDECODE_MEMORIES_DIR_NAME as O, RULESYNC_RULES_RELATIVE_DIR_PATH as On, createTempDirectory as Ot, groupSpellingsByCaseFoldedIdentity as P, ALL_FEATURES_WITH_WILDCARD as Pn, isFileNotFoundError as Pt, RulesyncCommandFrontmatterSchema as Q, writeFileContent as Qt, RulesyncSubagent as R, hasDeceptiveHiddenCharacters as Rn, listFilePathsRecursively as Rt, QWENCODE_LOCAL_RULE_FILE_NAME as S, RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH as Sn, ErrorCodes as St, CODEXCLI_DIR as T, RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH as Tn, assertTreeContainsNoSymlinks as Tt, RulesyncRuleFrontmatterSchema as U, stripHiddenCharacters as Un, removeDirectory as Ut, RulesyncSkillFrontmatterSchema as V, stripControlCharacters as Vn, readFileContent as Vt, RulesyncPermissions as W, removeDirectoryStrict as Wt, parseJsonc as X, toPosixPath as Xt, resolveRulesyncSourceWritePath as Y, runWithDirectoryRollback as Yt, RulesyncCommand as Z, writeFileBuffer as Zt, IgnoreProcessor as _, RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH as _n, warnOnConflictingFlags as _t, getProcessorRegistryEntry as a, RULESYNC_AIIGNORE_FILE_NAME as an, ConfigResolver as at, CommandsProcessor as b, RULESYNC_MCP_RELATIVE_FILE_PATH as bn, withWarnOnceScope as bt, RulesProcessor as c, RULESYNC_COMMANDS_RELATIVE_DIR_PATH as cn, CONFLICTING_TARGET_PAIRS as ct, CODEBUDDY_DIR as d, RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH as dn, SourceEntrySchema as dt, ALL_TOOL_TARGETS_WITH_WILDCARD as en, RulesyncCheckFrontmatterSchema as et, CODEBUDDY_LOCAL_RULE_FILE_NAME as f, RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH as fn, findControlCharacter as ft, McpProcessor as g, RULESYNC_IGNORE_RELATIVE_FILE_PATH as gn, fallbackLogger as gt, shortenToWidth as h, RULESYNC_HOOKS_RELATIVE_FILE_PATH as hn, WarningCollectingLogger as ht, inspectInputRoots as i, MAX_FILE_SIZE as in, SKILL_FILE_NAME as it, FACTORYDROID_DIR as j, RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH as jn, fileExists as jt, CLAUDECODE_SETTINGS_LOCAL_FILE_NAME as k, RULESYNC_SKILLS_RELATIVE_DIR_PATH as kn, directoryExists as kt, SubagentsProcessor as l, RULESYNC_CONFIG_RELATIVE_FILE_PATH as ln, ConfigFileSchema as lt, displayWidthOf as m, RULESYNC_HOOKS_LEGACY_FILE_NAME as mn, JsonLogger as mt, formatSourceLoadFailure as n, ToolTargetSchema as nn, loadYaml as nt, convertFromTool as o, RULESYNC_AIIGNORE_RELATIVE_FILE_PATH as on, mergeInputRootConfigs as ot, ELLIPSIS_WIDTH as p, RULESYNC_HOOKS_FILE_NAME as pn, ConsoleLogger as pt, RulesyncHooks as q, removeTempDirectory as qt, generate as r, CURATED_RULES_FEATURE_SUBDIR as rn, SHARED_USER_MANAGED_CONFIG_PATHS as rt, isPackagingToolTarget as s, RULESYNC_CHECKS_RELATIVE_DIR_PATH as sn, resolveEffectiveInputRoots as st, importFromTool as t, PACKAGING_TOOL_TARGETS as tn, stringifyFrontmatter as tt, SkillsProcessor as u, RULESYNC_CONFIG_SCHEMA_URL as un, GITIGNORE_DESTINATION_KEY as ut, CRUSH_LOCAL_RULE_FILE_NAME as v, RULESYNC_MCP_FILE_NAME as vn, withFallbackLoggerTarget as vt, CODEXCLI_BASH_RULES_FILE_NAME as w, RULESYNC_PERMISSIONS_LEGACY_FILE_NAME as wn, assertDirectoryIfExists as wt, QWENCODE_DIR as x, RULESYNC_MCP_SCHEMA_URL as xn, CLIError as xt, HooksProcessor as y, RULESYNC_MCP_LEGACY_FILE_NAME as yn, resetRunWarningState as yt, RulesyncSubagentFrontmatterSchema as z, hasEnclosingMarkOutsideKeycap as zn, listSubdirectoryNames as zt };
|
|
69450
70500
|
|
|
69451
|
-
//# sourceMappingURL=import-
|
|
70501
|
+
//# sourceMappingURL=import-BlHibyDS.js.map
|