rulesync 15.0.0 → 15.1.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.
@@ -127,27 +127,27 @@ const isFeatureValueEnabled = (value) => {
127
127
  };
128
128
  //#endregion
129
129
  //#region src/constants/rulesync-paths.ts
130
- const { join: join$272 } = node_path.posix;
130
+ const { join: join$273 } = node_path.posix;
131
131
  const RULESYNC_CONFIG_RELATIVE_FILE_PATH = "rulesync.jsonc";
132
132
  const RULESYNC_LOCAL_CONFIG_RELATIVE_FILE_PATH = "rulesync.local.jsonc";
133
133
  const RULESYNC_RELATIVE_DIR_PATH = ".rulesync";
134
- const RULESYNC_RULES_RELATIVE_DIR_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "rules");
135
- const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$272(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
136
- const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "commands");
137
- const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "subagents");
138
- const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "checks");
139
- const RULESYNC_MCP_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
140
- const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
141
- const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
142
- join$272(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
143
- const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
144
- const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
134
+ const RULESYNC_RULES_RELATIVE_DIR_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "rules");
135
+ const RULESYNC_CURATED_RULES_RELATIVE_DIR_PATH = join$273(RULESYNC_RULES_RELATIVE_DIR_PATH, ".curated");
136
+ const RULESYNC_COMMANDS_RELATIVE_DIR_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "commands");
137
+ const RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "subagents");
138
+ const RULESYNC_CHECKS_RELATIVE_DIR_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "checks");
139
+ const RULESYNC_MCP_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "mcp.jsonc");
140
+ const RULESYNC_HOOKS_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "hooks.jsonc");
141
+ const RULESYNC_PERMISSIONS_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "permissions.jsonc");
142
+ join$273(RULESYNC_RELATIVE_DIR_PATH, "mcp.json");
143
+ const RULESYNC_HOOKS_LEGACY_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "hooks.json");
144
+ const RULESYNC_PERMISSIONS_LEGACY_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "permissions.json");
145
145
  const RULESYNC_AIIGNORE_FILE_NAME = ".aiignore";
146
- const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
146
+ const RULESYNC_AIIGNORE_RELATIVE_FILE_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, ".aiignore");
147
147
  const RULESYNC_IGNORE_RELATIVE_FILE_PATH = ".rulesyncignore";
148
148
  const RULESYNC_OVERVIEW_FILE_NAME = "overview.md";
149
- const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$272(RULESYNC_RELATIVE_DIR_PATH, "skills");
150
- const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$272(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
149
+ const RULESYNC_SKILLS_RELATIVE_DIR_PATH = join$273(RULESYNC_RELATIVE_DIR_PATH, "skills");
150
+ const RULESYNC_CURATED_SKILLS_RELATIVE_DIR_PATH = join$273(RULESYNC_SKILLS_RELATIVE_DIR_PATH, ".curated");
151
151
  const RULESYNC_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync.lock";
152
152
  const RULESYNC_NPM_SOURCES_LOCK_RELATIVE_FILE_PATH = "rulesync-npm.lock.json";
153
153
  const RULESYNC_MCP_FILE_NAME = "mcp.jsonc";
@@ -2598,15 +2598,14 @@ const KIMI_CODE_TO_CANONICAL_EVENT_NAMES = Object.fromEntries(Object.entries(CAN
2598
2598
  *
2599
2599
  * Hermes validates hook events against a fixed `VALID_HOOKS` set:
2600
2600
  * `pre_tool_call`, `post_tool_call`, `pre_llm_call`, `post_llm_call`,
2601
- * `pre_verify`, `on_session_start`, `on_session_end`, `on_session_finalize`,
2601
+ * `pre_verify`, `pre_api_request`, `post_api_request`, `api_request_error`,
2602
+ * `on_session_start`, `on_session_end`, `on_session_finalize`,
2602
2603
  * `on_session_reset`, `subagent_start`, `subagent_stop`, `pre_gateway_dispatch`,
2603
2604
  * `pre_approval_request`, `post_approval_response`, `transform_tool_result`,
2604
- * `transform_terminal_output`, `transform_llm_output`. Only the events with a
2605
- * clean 1:1 canonical equivalent are mapped here; the remaining `VALID_HOOKS`
2606
- * entries (`pre_verify`, `on_session_finalize`, `on_session_reset`,
2607
- * `pre_gateway_dispatch`, `pre_approval_request`, `post_approval_response`, the
2608
- * `transform_*` result-rewriting hooks) have no canonical rulesync equivalent,
2609
- * so no canonical event maps to them.
2605
+ * `transform_terminal_output`, `transform_llm_output`, and the three
2606
+ * `kanban_task_*` events. Only the events with a clean 1:1 canonical equivalent
2607
+ * are mapped here. All other native events round-trip through
2608
+ * `hermesagent.hooks`.
2610
2609
  * @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
2611
2610
  */
2612
2611
  const HERMESAGENT_HOOK_EVENTS = [
@@ -2619,6 +2618,31 @@ const HERMESAGENT_HOOK_EVENTS = [
2619
2618
  "subagentStart",
2620
2619
  "subagentStop"
2621
2620
  ];
2621
+ const HERMESAGENT_NATIVE_HOOK_EVENTS = [
2622
+ "pre_tool_call",
2623
+ "post_tool_call",
2624
+ "transform_terminal_output",
2625
+ "transform_tool_result",
2626
+ "transform_llm_output",
2627
+ "pre_llm_call",
2628
+ "post_llm_call",
2629
+ "pre_verify",
2630
+ "pre_api_request",
2631
+ "post_api_request",
2632
+ "api_request_error",
2633
+ "on_session_start",
2634
+ "on_session_end",
2635
+ "on_session_finalize",
2636
+ "on_session_reset",
2637
+ "subagent_start",
2638
+ "subagent_stop",
2639
+ "pre_gateway_dispatch",
2640
+ "pre_approval_request",
2641
+ "post_approval_response",
2642
+ "kanban_task_claimed",
2643
+ "kanban_task_completed",
2644
+ "kanban_task_blocked"
2645
+ ];
2622
2646
  /**
2623
2647
  * Map canonical camelCase event names to Hermes Agent's native `VALID_HOOKS`
2624
2648
  * snake_case keys under the `hooks:` block of `~/.hermes/config.yaml`.
@@ -4766,6 +4790,7 @@ const RulesyncSkillFrontmatterSchema = zod_mini.z.looseObject({
4766
4790
  metadata: zod_mini.z.optional(zod_mini.z.looseObject({})),
4767
4791
  "allowed-tools": zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.array(zod_mini.z.string())]))
4768
4792
  })),
4793
+ hermesagent: zod_mini.z.optional(zod_mini.z.looseObject({})),
4769
4794
  vibe: zod_mini.z.optional(zod_mini.z.looseObject({
4770
4795
  license: zod_mini.z.optional(zod_mini.z.string()),
4771
4796
  compatibility: zod_mini.z.optional(zod_mini.z.union([zod_mini.z.string(), zod_mini.z.looseObject({})])),
@@ -5338,6 +5363,7 @@ const HERMESAGENT_GLOBAL_DIR = ".hermes";
5338
5363
  /** MCP servers and other settings live in `config.yaml` under `~/.hermes/`. */
5339
5364
  const HERMESAGENT_CONFIG_FILE_NAME = "config.yaml";
5340
5365
  const HERMESAGENT_CONFIG_FILE_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, HERMESAGENT_CONFIG_FILE_NAME);
5366
+ const HERMESAGENT_PROJECT_PLUGINS_ENV_VAR = "HERMES_ENABLE_PROJECT_PLUGINS";
5341
5367
  const HERMESAGENT_SKILLS_DIR_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, "skills");
5342
5368
  const HERMESAGENT_RULESYNC_DIR_PATH = (0, node_path.join)(HERMESAGENT_GLOBAL_DIR, "rulesync");
5343
5369
  const HERMESAGENT_RULESYNC_COMMANDS_DIR_PATH = (0, node_path.join)(HERMESAGENT_RULESYNC_DIR_PATH, "commands");
@@ -8267,7 +8293,12 @@ var AgentsSkillsSkill = class AgentsSkillsSkill extends ToolSkill {
8267
8293
  };
8268
8294
  //#endregion
8269
8295
  //#region src/features/skills/hermesagent-skill.ts
8270
- var HermesagentSkill = class extends AgentsSkillsSkill {
8296
+ const SHARED_AGENT_SKILL_FIELDS = /* @__PURE__ */ new Set([
8297
+ "license",
8298
+ "compatibility",
8299
+ "allowed-tools"
8300
+ ]);
8301
+ var HermesagentSkill = class HermesagentSkill extends AgentsSkillsSkill {
8271
8302
  static isTargetedByRulesyncSkill(rulesyncSkill) {
8272
8303
  const targets = rulesyncSkill.getFrontmatter().targets;
8273
8304
  return targets.includes("*") || targets.includes("agentsskills") || targets.includes("hermesagent");
@@ -8281,6 +8312,87 @@ var HermesagentSkill = class extends AgentsSkillsSkill {
8281
8312
  relativeDirPath: HERMESAGENT_SKILLS_DIR_PATH
8282
8313
  });
8283
8314
  }
8315
+ static fromRulesyncSkill({ outputRoot = process.cwd(), rulesyncSkill, validate = true, global = false }) {
8316
+ const rulesyncFrontmatter = rulesyncSkill.getFrontmatter();
8317
+ const shared = rulesyncFrontmatter.agentsskills ?? {};
8318
+ const hermes = rulesyncFrontmatter.hermesagent ?? {};
8319
+ return new this({
8320
+ outputRoot,
8321
+ relativeDirPath: HERMESAGENT_SKILLS_DIR_PATH,
8322
+ dirName: rulesyncSkill.getDirName(),
8323
+ frontmatter: {
8324
+ ...shared,
8325
+ ...hermes,
8326
+ name: rulesyncFrontmatter.name,
8327
+ description: rulesyncFrontmatter.description
8328
+ },
8329
+ body: rulesyncSkill.getBody(),
8330
+ otherFiles: rulesyncSkill.getOtherFiles(),
8331
+ validate,
8332
+ global
8333
+ });
8334
+ }
8335
+ toRulesyncSkill() {
8336
+ const frontmatter = this.getFrontmatter();
8337
+ const agentsskills = {
8338
+ ...frontmatter.license !== void 0 && { license: frontmatter.license },
8339
+ ...frontmatter.compatibility !== void 0 && { compatibility: frontmatter.compatibility },
8340
+ ...frontmatter["allowed-tools"] !== void 0 && { "allowed-tools": frontmatter["allowed-tools"] }
8341
+ };
8342
+ const hermesagent = {};
8343
+ for (const [key, value] of Object.entries(frontmatter)) {
8344
+ if (key === "name" || key === "description") continue;
8345
+ if (!SHARED_AGENT_SKILL_FIELDS.has(key)) hermesagent[key] = value;
8346
+ }
8347
+ const rulesyncFrontmatter = {
8348
+ name: frontmatter.name,
8349
+ description: frontmatter.description,
8350
+ targets: ["*"],
8351
+ ...Object.keys(agentsskills).length > 0 && { agentsskills },
8352
+ ...Object.keys(hermesagent).length > 0 && { hermesagent }
8353
+ };
8354
+ return new RulesyncSkill({
8355
+ outputRoot: this.outputRoot,
8356
+ relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
8357
+ dirName: this.getDirName(),
8358
+ frontmatter: rulesyncFrontmatter,
8359
+ body: this.getBody(),
8360
+ otherFiles: this.getOtherFiles(),
8361
+ validate: true,
8362
+ global: this.global
8363
+ });
8364
+ }
8365
+ static async fromDir(params) {
8366
+ const loaded = await this.loadSkillDirContent({
8367
+ ...params,
8368
+ getSettablePaths: HermesagentSkill.getSettablePaths
8369
+ });
8370
+ return new this({
8371
+ outputRoot: loaded.outputRoot,
8372
+ relativeDirPath: loaded.relativeDirPath,
8373
+ dirName: loaded.dirName,
8374
+ frontmatter: loaded.frontmatter,
8375
+ body: loaded.body,
8376
+ otherFiles: loaded.otherFiles,
8377
+ validate: true,
8378
+ global: loaded.global
8379
+ });
8380
+ }
8381
+ static forDeletion({ outputRoot = process.cwd(), relativeDirPath, dirName, global = false }) {
8382
+ return new this({
8383
+ outputRoot,
8384
+ relativeDirPath: relativeDirPath ?? HERMESAGENT_SKILLS_DIR_PATH,
8385
+ dirName,
8386
+ frontmatter: {
8387
+ name: "",
8388
+ description: ""
8389
+ },
8390
+ body: "",
8391
+ otherFiles: [],
8392
+ validate: false,
8393
+ global
8394
+ });
8395
+ }
8284
8396
  };
8285
8397
  //#endregion
8286
8398
  //#region src/features/commands/hermesagent-command.ts
@@ -8307,7 +8419,7 @@ import json
8307
8419
  from pathlib import Path
8308
8420
 
8309
8421
 
8310
- COMMANDS_DIR = Path.home() / ".hermes" / "rulesync" / "commands"
8422
+ COMMANDS_DIR = Path(__file__).resolve().parents[2] / "rulesync" / "commands"
8311
8423
 
8312
8424
 
8313
8425
  def _load_commands():
@@ -10760,7 +10872,7 @@ function isToolMatcherEntry(x) {
10760
10872
  /**
10761
10873
  * Filter the shared canonical hooks to the supported events and merge tool overrides on top.
10762
10874
  */
10763
- function buildEffectiveHooks$2({ config, toolOverrideHooks, supportedEvents }) {
10875
+ function buildEffectiveHooks$1({ config, toolOverrideHooks, supportedEvents }) {
10764
10876
  const supported = new Set(supportedEvents);
10765
10877
  const sharedHooks = {};
10766
10878
  for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) sharedHooks[event] = defs;
@@ -10890,7 +11002,7 @@ function buildToolHooks({ defs, converterConfig }) {
10890
11002
  * (e.g. beforeSubmitPrompt → UserPromptSubmit).
10891
11003
  */
10892
11004
  function canonicalToToolHooks({ config, toolOverrideHooks, converterConfig, logger }) {
10893
- const effectiveHooks = buildEffectiveHooks$2({
11005
+ const effectiveHooks = buildEffectiveHooks$1({
10894
11006
  config,
10895
11007
  toolOverrideHooks,
10896
11008
  supportedEvents: converterConfig.supportedEvents
@@ -12924,20 +13036,9 @@ var GrokcliHooks = class GrokcliHooks extends ToolHooks {
12924
13036
  * `matcher`.
12925
13037
  * @see https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/features/hooks.md
12926
13038
  */
12927
- const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["preToolUse", "postToolUse"]);
12928
- /**
12929
- * Filter the shared canonical hooks to the events Hermes understands and merge
12930
- * the `hermesagent`-specific override block on top.
12931
- */
12932
- function buildEffectiveHooks$1(config, toolOverrideHooks) {
12933
- const supported = new Set(HERMESAGENT_HOOK_EVENTS);
12934
- const shared = {};
12935
- for (const [event, defs] of Object.entries(config.hooks)) if (supported.has(event)) shared[event] = defs;
12936
- return {
12937
- ...shared,
12938
- ...toolOverrideHooks
12939
- };
12940
- }
13039
+ const HERMESAGENT_MATCHER_EVENTS = /* @__PURE__ */ new Set(["pre_tool_call", "post_tool_call"]);
13040
+ const HERMESAGENT_CANONICAL_EVENTS = new Set(HERMESAGENT_HOOK_EVENTS);
13041
+ const HERMESAGENT_NATIVE_EVENTS = new Set(HERMESAGENT_NATIVE_HOOK_EVENTS);
12941
13042
  /**
12942
13043
  * Convert the canonical hooks config into Hermes's native
12943
13044
  * `hooks: { <event>: [{ matcher?, command, timeout? }] }` shape.
@@ -12950,24 +13051,62 @@ function buildEffectiveHooks$1(config, toolOverrideHooks) {
12950
13051
  * warning, mirroring how other adapters (e.g. AugmentCode) handle
12951
13052
  * matcher-less lifecycle events.
12952
13053
  */
13054
+ function definitionsToHermesEntries({ event, sourceEvent = event, definitions, logger }) {
13055
+ const supportsMatcher = HERMESAGENT_MATCHER_EVENTS.has(event);
13056
+ const entries = [];
13057
+ for (const definition of definitions) {
13058
+ if ((definition.type ?? "command") !== "command" || typeof definition.command !== "string" || definition.command === "") continue;
13059
+ const entry = { command: definition.command };
13060
+ if (typeof definition.matcher === "string" && definition.matcher !== "") if (supportsMatcher) entry.matcher = definition.matcher;
13061
+ else logger?.warn(`matcher "${definition.matcher}" on "${sourceEvent}" hook will be ignored — Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
13062
+ if (typeof definition.timeout === "number") entry.timeout = definition.timeout;
13063
+ entries.push(entry);
13064
+ }
13065
+ return entries;
13066
+ }
13067
+ function setHermesHookEntries({ result, event, sourceEvent, definitions, logger }) {
13068
+ if (PROTOTYPE_POLLUTION_KEYS.has(event)) return;
13069
+ const entries = definitionsToHermesEntries({
13070
+ event,
13071
+ sourceEvent,
13072
+ definitions,
13073
+ logger
13074
+ });
13075
+ if (entries.length > 0) result[event] = entries;
13076
+ }
12953
13077
  function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
12954
- const effectiveHooks = buildEffectiveHooks$1(config, toolOverrideHooks);
12955
13078
  const result = {};
12956
- for (const [canonicalEvent, defs] of Object.entries(effectiveHooks)) {
13079
+ for (const [canonicalEvent, definitions] of Object.entries(config.hooks)) {
13080
+ if (!HERMESAGENT_CANONICAL_EVENTS.has(canonicalEvent)) continue;
12957
13081
  const nativeEvent = CANONICAL_TO_HERMESAGENT_EVENT_NAMES[canonicalEvent];
12958
- if (!nativeEvent) continue;
12959
- const supportsMatcher = HERMESAGENT_MATCHER_EVENTS.has(canonicalEvent);
12960
- const entries = [];
12961
- for (const def of defs) {
12962
- if ((def.type ?? "command") !== "command") continue;
12963
- if (typeof def.command !== "string" || def.command === "") continue;
12964
- const entry = { command: def.command };
12965
- if (typeof def.matcher === "string" && def.matcher !== "") if (supportsMatcher) entry.matcher = def.matcher;
12966
- else logger?.warn(`matcher "${def.matcher}" on "${canonicalEvent}" hook will be ignored Hermes Agent only supports matchers on pre_tool_call/post_tool_call`);
12967
- if (typeof def.timeout === "number") entry.timeout = def.timeout;
12968
- entries.push(entry);
12969
- }
12970
- if (entries.length > 0) result[nativeEvent] = entries;
13082
+ if (nativeEvent) setHermesHookEntries({
13083
+ result,
13084
+ event: nativeEvent,
13085
+ sourceEvent: canonicalEvent,
13086
+ definitions,
13087
+ logger
13088
+ });
13089
+ }
13090
+ for (const [canonicalEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
13091
+ if (!HERMESAGENT_CANONICAL_EVENTS.has(canonicalEvent)) continue;
13092
+ const nativeEvent = CANONICAL_TO_HERMESAGENT_EVENT_NAMES[canonicalEvent];
13093
+ if (nativeEvent) setHermesHookEntries({
13094
+ result,
13095
+ event: nativeEvent,
13096
+ sourceEvent: canonicalEvent,
13097
+ definitions,
13098
+ logger
13099
+ });
13100
+ }
13101
+ for (const [nativeEvent, definitions] of Object.entries(toolOverrideHooks ?? {})) {
13102
+ if (HERMESAGENT_CANONICAL_EVENTS.has(nativeEvent)) continue;
13103
+ if (!HERMESAGENT_NATIVE_EVENTS.has(nativeEvent)) logger?.warn(`Hermes hook event "${nativeEvent}" is not documented by Hermes Agent v0.19.0; preserving it for forward compatibility.`);
13104
+ setHermesHookEntries({
13105
+ result,
13106
+ event: nativeEvent,
13107
+ definitions,
13108
+ logger
13109
+ });
12971
13110
  }
12972
13111
  return result;
12973
13112
  }
@@ -12975,16 +13114,15 @@ function canonicalToHermesHooks({ config, toolOverrideHooks, logger }) {
12975
13114
  * Reverse {@link canonicalToHermesHooks}: parse Hermes's native
12976
13115
  * `hooks: { <event>: [...] }` map back into a canonical event → definition[]
12977
13116
  * record. Native events with no canonical equivalent (`pre_verify`,
12978
- * `transform_tool_result`, ...) are skipped since there is nothing to round
12979
- * -trip them into.
13117
+ * `transform_tool_result`, ...) retain their native names so
13118
+ * {@link buildImportedHooksConfig} places them under `hermesagent.hooks`.
12980
13119
  */
12981
13120
  function hermesHooksToCanonical(hooks) {
12982
13121
  const canonical = {};
12983
13122
  if (hooks === null || typeof hooks !== "object" || Array.isArray(hooks)) return canonical;
12984
13123
  for (const [nativeEvent, entries] of Object.entries(hooks)) {
12985
13124
  if (PROTOTYPE_POLLUTION_KEYS.has(nativeEvent) || !Array.isArray(entries)) continue;
12986
- const canonicalEvent = HERMESAGENT_TO_CANONICAL_EVENT_NAMES[nativeEvent];
12987
- if (!canonicalEvent) continue;
13125
+ const rulesyncEvent = HERMESAGENT_TO_CANONICAL_EVENT_NAMES[nativeEvent] ?? nativeEvent;
12988
13126
  const defs = [];
12989
13127
  for (const raw of entries) {
12990
13128
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) continue;
@@ -12994,11 +13132,11 @@ function hermesHooksToCanonical(hooks) {
12994
13132
  type: "command",
12995
13133
  command: entry.command
12996
13134
  };
12997
- if (typeof entry.matcher === "string" && entry.matcher !== "") def.matcher = entry.matcher;
13135
+ if (HERMESAGENT_MATCHER_EVENTS.has(nativeEvent) && typeof entry.matcher === "string" && entry.matcher !== "") def.matcher = entry.matcher;
12998
13136
  if (typeof entry.timeout === "number") def.timeout = entry.timeout;
12999
13137
  defs.push(def);
13000
13138
  }
13001
- if (defs.length > 0) canonical[canonicalEvent] = defs;
13139
+ if (defs.length > 0) canonical[rulesyncEvent] = defs;
13002
13140
  }
13003
13141
  return canonical;
13004
13142
  }
@@ -15055,7 +15193,8 @@ const toolHooksFactories = /* @__PURE__ */ new Map([
15055
15193
  },
15056
15194
  supportedEvents: HERMESAGENT_HOOK_EVENTS,
15057
15195
  supportedHookTypes: ["command"],
15058
- supportsMatcher: true
15196
+ supportsMatcher: true,
15197
+ passthroughOverrideEvents: true
15059
15198
  }],
15060
15199
  ["kimi-code", {
15061
15200
  class: KimiCodeHooks,
@@ -18792,12 +18931,51 @@ function resolveHermesTimeout(config) {
18792
18931
  * canonical `McpServerSchema` is a `looseObject`), so this serves export and
18793
18932
  * import alike. See the Hermes mcp-config-reference.
18794
18933
  */
18934
+ function copyHermesOauth(source) {
18935
+ if (!isRecord(source)) return;
18936
+ const oauth = {};
18937
+ for (const key of [
18938
+ "redirect_uri",
18939
+ "redirect_host",
18940
+ "client_id",
18941
+ "client_secret"
18942
+ ]) if (typeof source[key] === "string") oauth[key] = source[key];
18943
+ if (typeof source.redirect_port === "number") oauth.redirect_port = source.redirect_port;
18944
+ if (isStringArray$1(source.scopes)) oauth.scopes = source.scopes;
18945
+ return Object.keys(oauth).length > 0 ? oauth : void 0;
18946
+ }
18795
18947
  function copyHermesAdvancedFields(source, target) {
18796
- if (typeof source.auth === "string") target.auth = source.auth;
18797
- if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) target.client_cert = source.client_cert;
18798
- if (typeof source.client_key === "string") target.client_key = source.client_key;
18799
- if (typeof source.connect_timeout === "number") target.connect_timeout = source.connect_timeout;
18800
- if (typeof source.supports_parallel_tool_calls === "boolean") target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
18948
+ let copied = false;
18949
+ if (typeof source.auth === "string") {
18950
+ target.auth = source.auth;
18951
+ copied = true;
18952
+ }
18953
+ if (typeof source.client_cert === "string" || isStringArray$1(source.client_cert)) {
18954
+ target.client_cert = source.client_cert;
18955
+ copied = true;
18956
+ }
18957
+ if (typeof source.client_key === "string") {
18958
+ target.client_key = source.client_key;
18959
+ copied = true;
18960
+ }
18961
+ if (typeof source.connect_timeout === "number") {
18962
+ target.connect_timeout = source.connect_timeout;
18963
+ copied = true;
18964
+ }
18965
+ if (typeof source.supports_parallel_tool_calls === "boolean") {
18966
+ target.supports_parallel_tool_calls = source.supports_parallel_tool_calls;
18967
+ copied = true;
18968
+ }
18969
+ const oauth = copyHermesOauth(source.oauth);
18970
+ if (oauth) {
18971
+ target.oauth = oauth;
18972
+ copied = true;
18973
+ }
18974
+ for (const key of ["idle_timeout_seconds", "max_lifetime_seconds"]) if (typeof source[key] === "number") {
18975
+ target[key] = source[key];
18976
+ copied = true;
18977
+ }
18978
+ return copied;
18801
18979
  }
18802
18980
  /**
18803
18981
  * Builds Hermes's per-server `tools` block from a canonical server config. The
@@ -18899,6 +19077,7 @@ function mergeHermesMcpServers(config, mcpServers) {
18899
19077
  */
18900
19078
  function convertFromHermesFormat(mcpServers) {
18901
19079
  const result = {};
19080
+ const hermesOverrides = {};
18902
19081
  for (const [name, config] of Object.entries(mcpServers)) {
18903
19082
  if (PROTOTYPE_POLLUTION_KEYS.has(name) || !isRecord(config)) continue;
18904
19083
  const server = {};
@@ -18909,11 +19088,15 @@ function convertFromHermesFormat(mcpServers) {
18909
19088
  if (isPlainObject$1(config.headers)) server.headers = omitPrototypePollutionKeys(config.headers);
18910
19089
  if (config.enabled === false) server.disabled = true;
18911
19090
  if (typeof config.timeout === "number") server.networkTimeout = config.timeout;
18912
- copyHermesAdvancedFields(config, server);
18913
19091
  if (isRecord(config.tools)) applyHermesToolsBlock(config.tools, server);
18914
19092
  result[name] = server;
19093
+ const hermesServer = { ...server };
19094
+ if (copyHermesAdvancedFields(config, hermesServer)) hermesOverrides[name] = hermesServer;
18915
19095
  }
18916
- return result;
19096
+ return {
19097
+ mcpServers: result,
19098
+ hermesOverrides
19099
+ };
18917
19100
  }
18918
19101
  /**
18919
19102
  * Hermes Agent MCP servers.
@@ -18998,8 +19181,11 @@ var HermesagentMcp = class HermesagentMcp extends ToolMcp {
18998
19181
  });
18999
19182
  }
19000
19183
  toRulesyncMcp() {
19001
- const servers = convertFromHermesFormat(isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
19002
- return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({ mcpServers: servers }, null, 2) });
19184
+ const { mcpServers: servers, hermesOverrides } = convertFromHermesFormat(isRecord(this.config.mcp_servers) ? this.config.mcp_servers : {});
19185
+ return this.toRulesyncMcpDefault({ fileContent: JSON.stringify({
19186
+ mcpServers: servers,
19187
+ ...Object.keys(hermesOverrides).length > 0 && { hermesagent: { mcpServers: hermesOverrides } }
19188
+ }, null, 2) });
19003
19189
  }
19004
19190
  validate() {
19005
19191
  return {
@@ -21009,8 +21195,8 @@ const toolMcpFactories = /* @__PURE__ */ new Map([
21009
21195
  meta: {
21010
21196
  supportsProject: false,
21011
21197
  supportsGlobal: true,
21012
- supportsEnabledTools: false,
21013
- supportsDisabledTools: false
21198
+ supportsEnabledTools: true,
21199
+ supportsDisabledTools: true
21014
21200
  }
21015
21201
  }],
21016
21202
  ["kimi-code", {
@@ -25024,6 +25210,59 @@ function deriveGrokPermissionMode(config) {
25024
25210
  function patternsByAction(category, action) {
25025
25211
  return Object.entries(category ?? {}).filter(([, value]) => value === action).map(([pattern]) => pattern);
25026
25212
  }
25213
+ function clonePermissionBlock(permission) {
25214
+ return Object.fromEntries(Object.entries(permission).map(([category, rules]) => [category, { ...rules }]));
25215
+ }
25216
+ function deleteRulesByAction(rules, action) {
25217
+ for (const [pattern, currentAction] of Object.entries(rules)) if (currentAction === action) delete rules[pattern];
25218
+ }
25219
+ function ensureCategory(permission, category) {
25220
+ return permission[category] ??= {};
25221
+ }
25222
+ function removeEmptyCategories(permission) {
25223
+ for (const [category, rules] of Object.entries(permission)) if (Object.keys(rules).length === 0) delete permission[category];
25224
+ }
25225
+ function reconcileCommandAllowlist({ permission, commandAllowlist }) {
25226
+ const nativeAllows = new Set(commandAllowlist);
25227
+ const existingAllowCategories = /* @__PURE__ */ new Map();
25228
+ for (const [category, rules] of Object.entries(permission)) for (const [pattern, action] of Object.entries(rules)) {
25229
+ if (action !== "allow") continue;
25230
+ existingAllowCategories.set(pattern, category);
25231
+ if (!nativeAllows.has(pattern)) delete rules[pattern];
25232
+ }
25233
+ for (const pattern of nativeAllows) {
25234
+ const existingCategory = existingAllowCategories.get(pattern);
25235
+ if (existingCategory) ensureCategory(permission, existingCategory)[pattern] = "allow";
25236
+ else ensureCategory(permission, "bash")[pattern] = "allow";
25237
+ }
25238
+ }
25239
+ function reconcileNativeDenies({ permission, category, patterns }) {
25240
+ const rules = ensureCategory(permission, category);
25241
+ deleteRulesByAction(rules, "deny");
25242
+ for (const pattern of patterns) rules[pattern] = "deny";
25243
+ }
25244
+ function withoutKey(record, key) {
25245
+ return Object.fromEntries(Object.entries(record).filter(([entryKey]) => entryKey !== key));
25246
+ }
25247
+ function buildHermesOverride(config, provenance) {
25248
+ const base = isRecord(provenance.hermes) ? { ...provenance.hermes } : {};
25249
+ const approvalsOverride = withoutKey(isRecord(config.approvals) ? config.approvals : {}, "deny");
25250
+ if (Object.keys(approvalsOverride).length > 0) base.approvals = approvalsOverride;
25251
+ else delete base.approvals;
25252
+ const security = isRecord(config.security) ? { ...config.security } : {};
25253
+ const blocklist = isRecord(security.website_blocklist) ? { ...security.website_blocklist } : void 0;
25254
+ if (blocklist?.enabled === true) {
25255
+ delete blocklist.domains;
25256
+ delete blocklist.enabled;
25257
+ }
25258
+ if (blocklist && Object.keys(blocklist).length > 0) security.website_blocklist = blocklist;
25259
+ else delete security.website_blocklist;
25260
+ if (Object.keys(security).length > 0) base.security = security;
25261
+ else delete base.security;
25262
+ for (const key of ["skills", "memory"]) if (isRecord(config[key])) base[key] = config[key];
25263
+ else delete base[key];
25264
+ return base;
25265
+ }
25027
25266
  var HermesagentPermissions = class HermesagentPermissions extends ToolPermissions {
25028
25267
  static getSettablePaths() {
25029
25268
  return {
@@ -25080,12 +25319,40 @@ var HermesagentPermissions = class HermesagentPermissions extends ToolPermission
25080
25319
  format: "yaml",
25081
25320
  fileContent: this.getFileContent()
25082
25321
  });
25083
- const permissions = config.permissions && typeof config.permissions === "object" ? config.permissions.rulesync : {};
25322
+ const permissionsRoot = isRecord(config.permissions) ? config.permissions : {};
25323
+ const parsedProvenance = RulesyncPermissionsFileSchema.safeParse(permissionsRoot.rulesync);
25324
+ const provenance = parsedProvenance.success ? parsedProvenance.data : { permission: {} };
25325
+ const permission = clonePermissionBlock(provenance.permission);
25326
+ reconcileCommandAllowlist({
25327
+ permission,
25328
+ commandAllowlist: isStringArray$1(config.command_allowlist) ? config.command_allowlist : []
25329
+ });
25330
+ const approvals = isRecord(config.approvals) ? config.approvals : {};
25331
+ reconcileNativeDenies({
25332
+ permission,
25333
+ category: "bash",
25334
+ patterns: isStringArray$1(approvals.deny) ? approvals.deny : []
25335
+ });
25336
+ const security = isRecord(config.security) ? config.security : {};
25337
+ const websiteBlocklist = isRecord(security.website_blocklist) ? security.website_blocklist : {};
25338
+ reconcileNativeDenies({
25339
+ permission,
25340
+ category: "webfetch",
25341
+ patterns: websiteBlocklist.enabled === true && isStringArray$1(websiteBlocklist.domains) ? websiteBlocklist.domains : []
25342
+ });
25343
+ removeEmptyCategories(permission);
25344
+ const { permission: _permission, hermes: _hermes, ...otherProvenance } = provenance;
25345
+ const hermes = buildHermesOverride(config, provenance);
25346
+ const imported = {
25347
+ ...otherProvenance,
25348
+ permission,
25349
+ ...Object.keys(hermes).length > 0 && { hermes }
25350
+ };
25084
25351
  return new RulesyncPermissions({
25085
25352
  outputRoot: this.outputRoot,
25086
25353
  relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
25087
25354
  relativeFilePath: RULESYNC_PERMISSIONS_FILE_NAME,
25088
- fileContent: JSON.stringify(permissions ?? {}, null, 2)
25355
+ fileContent: JSON.stringify(imported, null, 2)
25089
25356
  });
25090
25357
  }
25091
25358
  static fromRulesyncPermissions({ outputRoot, rulesyncPermissions }) {
@@ -35828,7 +36095,7 @@ import json
35828
36095
  from pathlib import Path
35829
36096
 
35830
36097
 
35831
- SUBAGENTS_DIR = Path.home() / ".hermes" / "rulesync" / "subagents"
36098
+ SUBAGENTS_DIR = Path(__file__).resolve().parents[2] / "rulesync" / "subagents"
35832
36099
 
35833
36100
 
35834
36101
  def _load_subagents():
@@ -35937,12 +36204,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
35937
36204
  validate: false
35938
36205
  });
35939
36206
  }
35940
- static async fromFile({ global = false, outputRoot = process.cwd(), relativeFilePath, validate = true }) {
36207
+ static async fromFile({ global = false, outputRoot = process.cwd(), relativeDirPath, relativeFilePath, validate = true }) {
35941
36208
  return new HermesagentSubagent({
35942
- fileContent: await (0, node_fs_promises.readFile)((0, node_path.join)(outputRoot, relativeFilePath), "utf8"),
36209
+ fileContent: await (0, node_fs_promises.readFile)((0, node_path.join)(outputRoot, relativeDirPath ?? HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, relativeFilePath), "utf8"),
35943
36210
  global,
35944
36211
  outputRoot,
35945
- relativeDirPath: (0, node_path.dirname)(relativeFilePath),
36212
+ relativeDirPath: relativeDirPath ?? (0, node_path.dirname)(relativeFilePath),
35946
36213
  relativeFilePath: (0, node_path.basename)(relativeFilePath),
35947
36214
  validate
35948
36215
  });
@@ -35951,7 +36218,7 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
35951
36218
  const targets = rulesyncSubagent.getFrontmatter().targets;
35952
36219
  return !targets || targets.includes("*") || targets.includes("hermesagent");
35953
36220
  }
35954
- static fromRulesyncSubagents({ rulesyncSubagents, outputRoot }) {
36221
+ static fromRulesyncSubagents({ rulesyncSubagents, outputRoot, global = false }) {
35955
36222
  return [
35956
36223
  ...rulesyncSubagents.map((rulesyncSubagent) => HermesagentSubagent.fromRulesyncSubagent({
35957
36224
  relativeDirPath: HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH,
@@ -35970,12 +36237,12 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
35970
36237
  fileContent: "",
35971
36238
  outputRoot
35972
36239
  }),
35973
- new HermesagentSubagent({
36240
+ ...global ? [new HermesagentSubagent({
35974
36241
  relativeDirPath: HERMESAGENT_GLOBAL_DIR,
35975
36242
  relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH),
35976
36243
  fileContent: "",
35977
36244
  outputRoot
35978
- })
36245
+ })] : []
35979
36246
  ];
35980
36247
  }
35981
36248
  static fromRulesyncSubagent({ rulesyncSubagent, outputRoot }) {
@@ -35996,11 +36263,11 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
35996
36263
  * shared `~/.hermes/config.yaml` (enabling the `rulesync-subagents` plugin),
35997
36264
  * so the write must be declared for the shared-file order derivation.
35998
36265
  */
35999
- static getExtraSharedWritePaths() {
36000
- return [{
36266
+ static getExtraSharedWritePaths({ global = false } = {}) {
36267
+ return global ? [{
36001
36268
  relativeDirPath: HERMESAGENT_GLOBAL_DIR,
36002
36269
  relativeFilePath: (0, node_path.basename)(HERMESAGENT_CONFIG_FILE_PATH)
36003
- }];
36270
+ }] : [];
36004
36271
  }
36005
36272
  static getSettablePathsForRulesyncSubagent(rulesyncSubagent) {
36006
36273
  return [(0, node_path.join)(HERMESAGENT_RULESYNC_SUBAGENTS_DIR_PATH, `${subagentSlug(rulesyncSubagent.getRelativePathFromCwd())}.json`)];
@@ -36010,13 +36277,13 @@ var HermesagentSubagent = class HermesagentSubagent extends ToolSubagent {
36010
36277
  const json = JSON.parse(this.getFileContent());
36011
36278
  return new RulesyncSubagent({
36012
36279
  relativeDirPath: RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH,
36013
- relativeFilePath: (0, node_path.join)(RULESYNC_SUBAGENTS_RELATIVE_DIR_PATH, `${slug}.md`),
36280
+ relativeFilePath: `${slug}.md`,
36014
36281
  body: json.prompt ?? "",
36015
36282
  frontmatter: {
36016
36283
  name: json.name ?? slug,
36017
36284
  description: json.description
36018
36285
  },
36019
- outputRoot: this.outputRoot
36286
+ outputRoot: this.global ? this.outputRoot : process.cwd()
36020
36287
  });
36021
36288
  }
36022
36289
  validate() {
@@ -43666,6 +43933,74 @@ function buildChecksStrategy(ctx) {
43666
43933
  };
43667
43934
  }
43668
43935
  //#endregion
43936
+ //#region src/features/shared/hermes-project-plugin-activation.ts
43937
+ function mergeEnabledPlugins({ existingContent, filePath, pluginNames }) {
43938
+ const config = parseSharedConfig({
43939
+ format: "yaml",
43940
+ fileContent: existingContent,
43941
+ filePath,
43942
+ invalidRootPolicy: "error"
43943
+ });
43944
+ const plugins = isPlainObject$1(config.plugins) ? config.plugins : {};
43945
+ const disabled = Array.isArray(plugins.disabled) ? plugins.disabled.filter((value) => typeof value === "string") : [];
43946
+ const conflicts = pluginNames.filter((pluginName) => disabled.includes(pluginName));
43947
+ if (conflicts.length > 0) throw new Error(`Cannot activate Hermes project plugin(s) ${conflicts.join(", ")} because ${filePath} explicitly lists them in plugins.disabled. Remove the conflicting entries or exclude those RuleSync features.`);
43948
+ const enabled = Array.isArray(plugins.enabled) ? plugins.enabled : [];
43949
+ return stringifySharedConfig({
43950
+ format: "yaml",
43951
+ document: {
43952
+ ...config,
43953
+ plugins: {
43954
+ ...plugins,
43955
+ enabled: Array.from(/* @__PURE__ */ new Set([...enabled, ...pluginNames]))
43956
+ }
43957
+ }
43958
+ });
43959
+ }
43960
+ async function writeActivationFile({ filePath, relativePath, expectedContent, dryRun, logger }) {
43961
+ const existingContent = await readFileContentOrNull(filePath);
43962
+ const normalizedExpected = addTrailingNewline(expectedContent);
43963
+ if (fileContentsEquivalent({
43964
+ filePath,
43965
+ expected: normalizedExpected,
43966
+ existing: existingContent
43967
+ })) return false;
43968
+ if (dryRun) logger.info(`[DRY RUN] Would write: ${filePath}`);
43969
+ else await writeFileContent(filePath, normalizedExpected);
43970
+ logger.debug(`Hermes project-plugin activation requires ${relativePath}`);
43971
+ return true;
43972
+ }
43973
+ async function activateHermesProjectPlugins({ pluginNames, dryRun, logger }) {
43974
+ if (pluginNames.length === 0) return {
43975
+ count: 0,
43976
+ paths: [],
43977
+ hasDiff: false
43978
+ };
43979
+ const configuredHermesHome = process.env.HERMES_HOME?.trim();
43980
+ const configRoot = configuredHermesHome ? (0, node_path.resolve)(configuredHermesHome) : getHomeDirectory();
43981
+ const relativeConfigPath = configuredHermesHome ? HERMESAGENT_CONFIG_FILE_NAME : HERMESAGENT_CONFIG_FILE_PATH;
43982
+ const configPath = (0, node_path.join)(configRoot, relativeConfigPath);
43983
+ const expectedConfig = mergeEnabledPlugins({
43984
+ existingContent: await readFileContentOrNull(configPath) ?? "",
43985
+ filePath: configPath,
43986
+ pluginNames
43987
+ });
43988
+ const paths = [];
43989
+ logger.warn(`Hermes project plugins require explicit trust. Run Hermes from this trusted repository with ${HERMESAGENT_PROJECT_PLUGINS_ENV_VAR}=true. RuleSync does not persist this global setting.`);
43990
+ if (await writeActivationFile({
43991
+ filePath: configPath,
43992
+ relativePath: relativeConfigPath,
43993
+ expectedContent: expectedConfig,
43994
+ dryRun,
43995
+ logger
43996
+ })) paths.push(relativeConfigPath);
43997
+ return {
43998
+ count: paths.length,
43999
+ paths,
44000
+ hasDiff: paths.length > 0
44001
+ };
44002
+ }
44003
+ //#endregion
43669
44004
  //#region src/types/processor-registry.ts
43670
44005
  const PROCESSOR_REGISTRY = [
43671
44006
  {
@@ -44127,6 +44462,41 @@ async function warnSkillSubagentNameCollisions(params) {
44127
44462
  for (const name of skillNames) if (subagentNames.has(name)) logger.warn(`Skill "${name}" and subagent "${name}" both target '${toolTarget}' and write the same path '${(0, node_path.join)(subagentsDirPath, name)}'; the later generation step overwrites the other's output. Rename one of them or narrow their targets.`);
44128
44463
  }
44129
44464
  }
44465
+ async function collectHermesProjectPluginNames({ config, resultsById }) {
44466
+ if (config.getGlobal() || !config.getTargets().includes("hermesagent")) return [];
44467
+ const outputRoots = config.getOutputRoots("hermesagent");
44468
+ const enabledFeatures = config.getFeatures("hermesagent");
44469
+ const descriptors = [
44470
+ {
44471
+ feature: "ignore",
44472
+ pluginName: "rulesync-ignore",
44473
+ manifestPath: HERMESAGENT_IGNORE_PLUGIN_MANIFEST_PATH
44474
+ },
44475
+ {
44476
+ feature: "subagents",
44477
+ pluginName: "rulesync-subagents",
44478
+ manifestPath: HERMESAGENT_RULESYNC_SUBAGENTS_PLUGIN_MANIFEST_PATH
44479
+ },
44480
+ {
44481
+ feature: "checks",
44482
+ pluginName: "rulesync-checks",
44483
+ manifestPath: HERMESAGENT_CHECKS_PLUGIN_MANIFEST_PATH
44484
+ }
44485
+ ];
44486
+ const pluginNames = [];
44487
+ for (const descriptor of descriptors) {
44488
+ if (!enabledFeatures.includes(descriptor.feature)) continue;
44489
+ const relativeManifestPath = toPosixPath(descriptor.manifestPath);
44490
+ const willWriteManifest = resultsById.get(descriptor.feature)?.paths.some((generatedPath) => toPosixPath(generatedPath) === relativeManifestPath) ?? false;
44491
+ let manifestExists = false;
44492
+ for (const outputRoot of outputRoots) if (await fileExists((0, node_path.join)(outputRoot, descriptor.manifestPath))) {
44493
+ manifestExists = true;
44494
+ break;
44495
+ }
44496
+ if (willWriteManifest || manifestExists) pluginNames.push(descriptor.pluginName);
44497
+ }
44498
+ return pluginNames;
44499
+ }
44130
44500
  /**
44131
44501
  * Generate configuration files for AI tools.
44132
44502
  * @throws Error if generation fails
@@ -44190,13 +44560,21 @@ async function generate(params) {
44190
44560
  })));
44191
44561
  const resultsById = /* @__PURE__ */ new Map();
44192
44562
  for (const step of orderedSteps) resultsById.set(step.id, await step.run());
44563
+ const activationResult = await activateHermesProjectPlugins({
44564
+ pluginNames: await collectHermesProjectPluginNames({
44565
+ config,
44566
+ resultsById
44567
+ }),
44568
+ dryRun: config.isPreviewMode(),
44569
+ logger
44570
+ });
44193
44571
  if (!skillsResult) throw new Error("Skills generation step did not run.");
44194
44572
  const get = (id) => {
44195
44573
  const result = resultsById.get(id);
44196
44574
  if (!result) throw new Error(`Missing generation result for step '${id}'.`);
44197
44575
  return result;
44198
44576
  };
44199
- const hasDiff = orderedSteps.some((step) => get(step.id).hasDiff);
44577
+ const hasDiff = activationResult.hasDiff || orderedSteps.some((step) => get(step.id).hasDiff);
44200
44578
  return {
44201
44579
  rulesCount: get("rules").count,
44202
44580
  rulesPaths: get("rules").paths,
@@ -44216,6 +44594,8 @@ async function generate(params) {
44216
44594
  permissionsPaths: get("permissions").paths,
44217
44595
  checksCount: get("checks").count,
44218
44596
  checksPaths: get("checks").paths,
44597
+ activationCount: activationResult.count,
44598
+ activationPaths: activationResult.paths,
44219
44599
  skills: skillsResult.skills,
44220
44600
  hasDiff
44221
44601
  };