agentsmesh 0.40.0 → 0.41.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/dist/engine.js CHANGED
@@ -1,7 +1,7 @@
1
- import { z } from 'zod';
2
- import { parse, stringify, parseDocument, isMap, Document, isSeq, isScalar, Scalar, Pair, YAMLSeq } from 'yaml';
3
1
  import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
4
2
  import { basename, join, dirname, relative, extname, win32, posix, sep, resolve } from 'path';
3
+ import { parse, stringify, parseDocument, isMap, Document, isSeq, isScalar, Scalar, Pair, YAMLSeq } from 'yaml';
4
+ import { z } from 'zod';
5
5
  import { readFile, rm, mkdir, readdir, lstat, open, access, stat, realpath, rename, writeFile, mkdtemp, cp } from 'fs/promises';
6
6
  import { setTimeout } from 'timers/promises';
7
7
  import { randomUUID, createHash } from 'crypto';
@@ -9,7 +9,7 @@ import picomatch from 'picomatch';
9
9
  import { parse as parse$1, stringify as stringify$1 } from 'smol-toml';
10
10
  import { Buffer } from 'buffer';
11
11
  import { homedir, tmpdir } from 'os';
12
- import { execFile } from 'child_process';
12
+ import { execFile, spawnSync } from 'child_process';
13
13
  import { fileURLToPath, pathToFileURL, URL as URL$1 } from 'url';
14
14
  import { promisify } from 'util';
15
15
  import * as tar from 'tar';
@@ -24,6 +24,21 @@ var __export = (target34, all) => {
24
24
  for (var name in all)
25
25
  __defProp(target34, name, { get: all[name], enumerable: true });
26
26
  };
27
+ var init_cli_invocation = __esm({
28
+ "src/lessons/cli-invocation.ts"() {
29
+ }
30
+ });
31
+ function isRecallHookCommand(command) {
32
+ return typeof command === "string" && command.includes(RECALL_HOOK_COMMAND);
33
+ }
34
+ var RECALL_SUBCOMMAND, RECALL_HOOK_COMMAND;
35
+ var init_recall_hook_scaffold = __esm({
36
+ "src/lessons/recall-hook-scaffold.ts"() {
37
+ init_cli_invocation();
38
+ RECALL_SUBCOMMAND = "lessons hook";
39
+ RECALL_HOOK_COMMAND = `agentsmesh ${RECALL_SUBCOMMAND}`;
40
+ }
41
+ });
27
42
  function capabilityLevel(capability) {
28
43
  return typeof capability === "string" ? capability : capability.level;
29
44
  }
@@ -183,6 +198,7 @@ var init_target_descriptor_schema = __esm({
183
198
  emitScopedSettings: z.function().optional(),
184
199
  mergeGeneratedOutputContent: z.function().optional(),
185
200
  postProcessHookOutputs: z.function().optional(),
201
+ hookContextEvents: z.array(z.string()).optional(),
186
202
  preservesManualActivation: z.boolean().optional()
187
203
  }).passthrough();
188
204
  targetDescriptorSchema = targetDescriptorSchemaBase.superRefine((value, ctx) => {
@@ -578,12 +594,6 @@ var init_guards = __esm({
578
594
  "src/utils/types/guards.ts"() {
579
595
  }
580
596
  });
581
- var RECALL_HOOK_COMMAND;
582
- var init_recall_hook_scaffold = __esm({
583
- "src/lessons/recall-hook-scaffold.ts"() {
584
- RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
585
- }
586
- });
587
597
 
588
598
  // src/core/hook-types.ts
589
599
  function isBestEffortHookEvent(event, entries) {
@@ -911,8 +921,10 @@ function executableModeFor(path) {
911
921
  }
912
922
  function normalizeTextPayload(path, content) {
913
923
  if (!shouldNormalizeLineEndings(path)) return content;
914
- const withoutBom = content.startsWith(UTF8_BOM) ? content.slice(UTF8_BOM.length) : content;
915
- return normalizeLineEndings(withoutBom);
924
+ return normalizeLineEndings(stripBom(content));
925
+ }
926
+ function stripBom(text) {
927
+ return text.startsWith(UTF8_BOM) ? text.slice(UTF8_BOM.length) : text;
916
928
  }
917
929
  var UTF8_BOM, TEXT_EXTENSIONS, TEXT_DOTFILES, BINARY_EXTENSIONS, EXECUTABLE_SCRIPT_EXTENSIONS;
918
930
  var init_fs_text_encoding = __esm({
@@ -2110,6 +2122,15 @@ var init_constants = __esm({
2110
2122
  }
2111
2123
  });
2112
2124
 
2125
+ // src/targets/catalog/ignore-output.ts
2126
+ function ignoreOutput(path) {
2127
+ return (canonical) => canonical.ignore.length === 0 ? [] : [{ path, content: canonical.ignore.join("\n") }];
2128
+ }
2129
+ var init_ignore_output = __esm({
2130
+ "src/targets/catalog/ignore-output.ts"() {
2131
+ }
2132
+ });
2133
+
2113
2134
  // src/targets/aider/generator.ts
2114
2135
  function buildAiderConventions(canonical) {
2115
2136
  const root = canonical.rules.find((rule) => rule.root);
@@ -2139,11 +2160,7 @@ function generateAgents(canonical) {
2139
2160
  content: serializeProjectedAgentSkill(agent)
2140
2161
  }));
2141
2162
  }
2142
- function generateIgnore(canonical) {
2143
- if (canonical.ignore.length === 0) return [];
2144
- return [{ path: AIDER_IGNORE, content: canonical.ignore.join("\n") }];
2145
- }
2146
- var generateMcp, generatePermissions;
2163
+ var generateIgnore, generateMcp, generatePermissions;
2147
2164
  var init_generator = __esm({
2148
2165
  "src/targets/aider/generator.ts"() {
2149
2166
  init_no_outputs();
@@ -2152,6 +2169,8 @@ var init_generator = __esm({
2152
2169
  init_projected_agent_skill();
2153
2170
  init_command_skill();
2154
2171
  init_constants();
2172
+ init_ignore_output();
2173
+ generateIgnore = ignoreOutput(AIDER_IGNORE);
2155
2174
  generateMcp = NO_OUTPUTS;
2156
2175
  generatePermissions = NO_OUTPUTS;
2157
2176
  }
@@ -7045,10 +7064,6 @@ function generateAgents4(canonical) {
7045
7064
  content: serializeAntigravityAgent(agent)
7046
7065
  }));
7047
7066
  }
7048
- function generateIgnore3(canonical) {
7049
- if (canonical.ignore.length === 0) return [];
7050
- return [{ path: ANTIGRAVITY_IGNORE_FILE, content: canonical.ignore.join("\n") }];
7051
- }
7052
7067
  function renderAntigravityGlobalInstructions(canonical) {
7053
7068
  const root = canonical.rules.find((rule) => rule.root);
7054
7069
  const nonRootRules = canonical.rules.filter((rule) => {
@@ -7063,7 +7078,7 @@ function generateHooks2(canonical) {
7063
7078
  if (Object.keys(hooks).length === 0) return [];
7064
7079
  return [{ path: ANTIGRAVITY_HOOKS_FILE, content: JSON.stringify(hooks, null, 2) }];
7065
7080
  }
7066
- var generatePermissions3;
7081
+ var generateIgnore3, generatePermissions3;
7067
7082
  var init_generator4 = __esm({
7068
7083
  "src/targets/antigravity/generator.ts"() {
7069
7084
  init_no_outputs();
@@ -7072,6 +7087,8 @@ var init_generator4 = __esm({
7072
7087
  init_hooks_format2();
7073
7088
  init_agents_format();
7074
7089
  init_constants5();
7090
+ init_ignore_output();
7091
+ generateIgnore3 = ignoreOutput(ANTIGRAVITY_IGNORE_FILE);
7075
7092
  generatePermissions3 = NO_OUTPUTS;
7076
7093
  }
7077
7094
  });
@@ -8665,16 +8682,14 @@ function generateHooks3(canonical) {
8665
8682
  const content = JSON.stringify({ hooks: claudeHooks }, null, 2);
8666
8683
  return [{ path: CLAUDE_SETTINGS, content }];
8667
8684
  }
8668
- function generateIgnore5(canonical) {
8669
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
8670
- const content = canonical.ignore.join("\n");
8671
- return [{ path: CLAUDE_IGNORE, content }];
8672
- }
8685
+ var generateIgnore5;
8673
8686
  var init_generator6 = __esm({
8674
8687
  "src/targets/claude-code/generator.ts"() {
8675
8688
  init_markdown();
8676
8689
  init_constants7();
8677
8690
  init_hooks_format2();
8691
+ init_ignore_output();
8692
+ generateIgnore5 = ignoreOutput(CLAUDE_IGNORE);
8678
8693
  }
8679
8694
  });
8680
8695
  function ruleSectionTitle(rule) {
@@ -10493,11 +10508,7 @@ function generateMcp5(canonical) {
10493
10508
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
10494
10509
  return [{ path: CODEBUFF_MCP_FILE, content: serializeCodebuffMcp(canonical.mcp) }];
10495
10510
  }
10496
- function generateIgnore7(canonical) {
10497
- if (canonical.ignore.length === 0) return [];
10498
- return [{ path: CODEBUFF_IGNORE_FILE, content: canonical.ignore.join("\n") }];
10499
- }
10500
- var generateAgents8, generateHooks5, generatePermissions6;
10511
+ var generateIgnore7, generateAgents8, generateHooks5, generatePermissions6;
10501
10512
  var init_generator8 = __esm({
10502
10513
  "src/targets/codebuff/generator.ts"() {
10503
10514
  init_no_outputs();
@@ -10507,6 +10518,8 @@ var init_generator8 = __esm({
10507
10518
  init_nested_rules();
10508
10519
  init_mcp_format2();
10509
10520
  init_constants9();
10521
+ init_ignore_output();
10522
+ generateIgnore7 = ignoreOutput(CODEBUFF_IGNORE_FILE);
10510
10523
  generateAgents8 = NO_OUTPUTS;
10511
10524
  generateHooks5 = NO_OUTPUTS;
10512
10525
  generatePermissions6 = NO_OUTPUTS;
@@ -13019,53 +13032,37 @@ var init_mcp_generator = __esm({
13019
13032
  }
13020
13033
  });
13021
13034
 
13022
- // src/targets/copilot/hook-entry.ts
13023
- function hasHookCommand2(entry) {
13024
- return hasHookCommand(entry);
13025
- }
13026
- var init_hook_entry = __esm({
13027
- "src/targets/copilot/hook-entry.ts"() {
13028
- init_hook_command();
13029
- }
13030
- });
13031
-
13032
13035
  // src/targets/copilot/hook-format.ts
13033
- function mapHookEvent(event) {
13034
- switch (event) {
13035
- case "PreToolUse":
13036
- return "preToolUse";
13037
- case "PostToolUse":
13038
- return "postToolUse";
13039
- case "Notification":
13040
- return "notification";
13041
- case "UserPromptSubmit":
13042
- return "userPromptSubmitted";
13043
- default:
13044
- return null;
13045
- }
13036
+ function copilotHookGroups(hooks) {
13037
+ return Object.entries(hooks ?? {}).flatMap(([event, entries]) => {
13038
+ const copilotEvent = CANONICAL_TO_COPILOT.get(event);
13039
+ if (!copilotEvent || !Array.isArray(entries)) return [];
13040
+ const kept = entries.filter(
13041
+ (entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
13042
+ );
13043
+ return kept.length > 0 ? [{ event, copilotEvent, entries: kept }] : [];
13044
+ });
13045
+ }
13046
+ function wrapperScriptName(event, index) {
13047
+ return `${event.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase()}-${index}.sh`;
13046
13048
  }
13047
13049
  function buildCopilotHooksObject(hooks) {
13048
- if (!hooks) return null;
13049
- const result2 = Object.fromEntries(
13050
- Object.entries(hooks).flatMap(([event, entries]) => {
13051
- const mappedEvent = mapHookEvent(event);
13052
- if (!mappedEvent || !Array.isArray(entries)) return [];
13053
- const mappedEntries = entries.filter(
13054
- (entry) => typeof entry === "object" && entry !== null && hasHookCommand2(entry)
13055
- ).map((entry, index) => {
13056
- const safePhase = event.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
13050
+ const groups = copilotHookGroups(hooks);
13051
+ if (groups.length === 0) return null;
13052
+ return Object.fromEntries(
13053
+ groups.map(({ event, copilotEvent, entries }) => [
13054
+ copilotEvent,
13055
+ entries.map((entry, index) => {
13057
13056
  const hook = {
13058
13057
  type: "command",
13059
- bash: `./scripts/${safePhase}-${index}.sh`
13058
+ bash: `./scripts/${wrapperScriptName(event, index)}`
13060
13059
  };
13061
13060
  if (entry.matcher && entry.matcher !== "*") hook.matcher = entry.matcher;
13062
13061
  if (entry.timeout !== void 0) hook.timeoutSec = Math.ceil(entry.timeout / 1e3);
13063
13062
  return hook;
13064
- });
13065
- return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
13066
- })
13063
+ })
13064
+ ])
13067
13065
  );
13068
- return Object.keys(result2).length > 0 ? result2 : null;
13069
13066
  }
13070
13067
  function generateHooks8(canonical) {
13071
13068
  const hooks = buildCopilotHooksObject(canonical.hooks);
@@ -13077,10 +13074,24 @@ function generateHooks8(canonical) {
13077
13074
  }
13078
13075
  ];
13079
13076
  }
13077
+ var COPILOT_HOOK_CONTEXT_EVENTS, CANONICAL_TO_COPILOT;
13080
13078
  var init_hook_format = __esm({
13081
13079
  "src/targets/copilot/hook-format.ts"() {
13082
13080
  init_constants32();
13083
- init_hook_entry();
13081
+ init_hook_command();
13082
+ COPILOT_HOOK_CONTEXT_EVENTS = [
13083
+ "SessionStart",
13084
+ "PostToolUse",
13085
+ "PostToolUseFailure"
13086
+ ];
13087
+ CANONICAL_TO_COPILOT = /* @__PURE__ */ new Map([
13088
+ ["PreToolUse", "preToolUse"],
13089
+ ["PostToolUse", "postToolUse"],
13090
+ ["PostToolUseFailure", "postToolUseFailure"],
13091
+ ["Notification", "notification"],
13092
+ ["UserPromptSubmit", "userPromptSubmitted"],
13093
+ ["SessionStart", "sessionStart"]
13094
+ ]);
13084
13095
  }
13085
13096
  });
13086
13097
  function ruleSlug2(source) {
@@ -13186,18 +13197,7 @@ var init_generator12 = __esm({
13186
13197
  }
13187
13198
  });
13188
13199
  function mapCopilotHookEvent(event) {
13189
- switch (event) {
13190
- case "preToolUse":
13191
- return "PreToolUse";
13192
- case "postToolUse":
13193
- return "PostToolUse";
13194
- case "notification":
13195
- return "Notification";
13196
- case "userPromptSubmitted":
13197
- return "UserPromptSubmit";
13198
- default:
13199
- return null;
13200
- }
13200
+ return COPILOT_TO_CANONICAL.get(event) ?? null;
13201
13201
  }
13202
13202
  function extractMatcher(comment) {
13203
13203
  if (typeof comment !== "string") return "*";
@@ -13285,11 +13285,16 @@ async function importHooks(projectRoot, results, options = {}) {
13285
13285
  feature: "hooks"
13286
13286
  });
13287
13287
  }
13288
+ var COPILOT_TO_CANONICAL;
13288
13289
  var init_hook_parser = __esm({
13289
13290
  "src/targets/copilot/hook-parser.ts"() {
13290
13291
  init_canonical_paths();
13291
13292
  init_fs();
13292
13293
  init_constants32();
13294
+ init_hook_format();
13295
+ COPILOT_TO_CANONICAL = new Map(
13296
+ [...CANONICAL_TO_COPILOT].map(([canonical, copilot]) => [copilot, canonical])
13297
+ );
13293
13298
  }
13294
13299
  });
13295
13300
  async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
@@ -13526,7 +13531,7 @@ function lintCommands4(canonical) {
13526
13531
  }
13527
13532
  function lintHooks9(canonical) {
13528
13533
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
13529
- const supported = ["PreToolUse", "PostToolUse", "Notification", "UserPromptSubmit"];
13534
+ const supported = [...CANONICAL_TO_COPILOT.keys()];
13530
13535
  const diagnostics = unsupportedHookEventNames(canonical.hooks, supported).map(
13531
13536
  (event) => createUnsupportedHookWarning(event, "copilot", supported, {
13532
13537
  unsupportedBy: "Copilot hooks"
@@ -13549,11 +13554,9 @@ function lintHooks9(canonical) {
13549
13554
  var init_lint10 = __esm({
13550
13555
  "src/targets/copilot/lint.ts"() {
13551
13556
  init_helpers();
13557
+ init_hook_format();
13552
13558
  }
13553
13559
  });
13554
- function safePhaseName(phase) {
13555
- return phase.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
13556
- }
13557
13560
  function toRepoRelative(projectRoot, sourcePath) {
13558
13561
  const repoRelative = relative(projectRoot, sourcePath).replace(/\\/g, "/");
13559
13562
  if (!repoRelative || repoRelative.startsWith("../")) return null;
@@ -13581,9 +13584,6 @@ async function buildAssetOutput(projectRoot, command, hooksDirRel) {
13581
13584
  rewrittenCommand: rewriteWrapperCommand(command, repoRelative)
13582
13585
  };
13583
13586
  }
13584
- function wrapperPath(event, index, hooksDirRel) {
13585
- return `${hooksDirRel}/scripts/${safePhaseName(event)}-${index}.sh`;
13586
- }
13587
13587
  function safeShellLine2(value) {
13588
13588
  return value.replace(/[\r\n]+/g, " ");
13589
13589
  }
@@ -13598,15 +13598,13 @@ function buildWrapper(command, matcher) {
13598
13598
  ].join("\n");
13599
13599
  }
13600
13600
  async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel = COPILOT_HOOKS_DIR) {
13601
- if (!canonical.hooks) return outputs;
13601
+ const groups = copilotHookGroups(canonical.hooks);
13602
+ if (groups.length === 0) return outputs;
13602
13603
  const wrapperOutputs = [];
13603
13604
  const assetOutputs = /* @__PURE__ */ new Map();
13604
- for (const [event, entries] of Object.entries(canonical.hooks)) {
13605
- if (!Array.isArray(entries)) continue;
13606
- let index = 0;
13607
- for (const entry of entries) {
13608
- if (!hasHookCommand2(entry)) continue;
13609
- const scriptPath = wrapperPath(event, index, hooksDirRel);
13605
+ for (const { event, entries } of groups) {
13606
+ for (const [index, entry] of entries.entries()) {
13607
+ const scriptPath = `${hooksDirRel}/scripts/${wrapperScriptName(event, index)}`;
13610
13608
  let command = entry.command;
13611
13609
  const asset = await buildAssetOutput(projectRoot, entry.command, hooksDirRel);
13612
13610
  if (asset) {
@@ -13620,7 +13618,6 @@ async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel
13620
13618
  'set -eu\nHOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\n'
13621
13619
  );
13622
13620
  wrapperOutputs.push({ path: scriptPath, content: wrapper });
13623
- index++;
13624
13621
  }
13625
13622
  }
13626
13623
  return [...outputs, ...wrapperOutputs, ...assetOutputs.values()];
@@ -13630,7 +13627,7 @@ var init_hook_assets = __esm({
13630
13627
  "src/targets/copilot/hook-assets.ts"() {
13631
13628
  init_fs();
13632
13629
  init_constants32();
13633
- init_hook_entry();
13630
+ init_hook_format();
13634
13631
  SCRIPT_PREFIX_RE = /^(?<prefix>\s*(?:(?:bash|sh|zsh)\s+)?)["']?(?<path>(?:\.\.\/|\.\/|[^/\s"'`]+\/)[^\s"'`]+)["']?(?<suffix>(?:\s.*)?)$/;
13635
13632
  }
13636
13633
  });
@@ -13948,6 +13945,7 @@ var init_copilot2 = __esm({
13948
13945
  init_command_prompt();
13949
13946
  init_lint10();
13950
13947
  init_hook_assets();
13948
+ init_hook_format();
13951
13949
  init_scope_extras2();
13952
13950
  init_importer_spec2();
13953
13951
  init_capabilities6();
@@ -14078,6 +14076,7 @@ var init_copilot2 = __esm({
14078
14076
  permissions: lintPermissions7
14079
14077
  },
14080
14078
  postProcessHookOutputs: async (projectRoot, canonical, outputs) => addHookScriptAssets(projectRoot, canonical, [...outputs]),
14079
+ hookContextEvents: COPILOT_HOOK_CONTEXT_EVENTS,
14081
14080
  mergeGeneratedOutputContent: mergeCopilotMcpJson,
14082
14081
  project: project7,
14083
14082
  globalSupport: {
@@ -14165,11 +14164,6 @@ function generatePermissions8(canonical) {
14165
14164
  });
14166
14165
  return [{ path: CRUSH_CONFIG_FILE, content: JSON.stringify(crushConfig, null, 2) }];
14167
14166
  }
14168
- function generateIgnore9(canonical) {
14169
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
14170
- const content = canonical.ignore.join("\n");
14171
- return [{ path: CRUSH_IGNORE, content }];
14172
- }
14173
14167
  function buildCrushHooksFromCanonical(canonical) {
14174
14168
  if (!canonical.hooks) return {};
14175
14169
  const result2 = {};
@@ -14187,7 +14181,7 @@ function buildCrushHooksFromCanonical(canonical) {
14187
14181
  }
14188
14182
  return result2;
14189
14183
  }
14190
- var generateRules12;
14184
+ var generateRules12, generateIgnore9;
14191
14185
  var init_generator13 = __esm({
14192
14186
  "src/targets/crush/generator.ts"() {
14193
14187
  init_managed_blocks();
@@ -14196,7 +14190,9 @@ var init_generator13 = __esm({
14196
14190
  init_command_skill();
14197
14191
  init_constants12();
14198
14192
  init_config_format();
14193
+ init_ignore_output();
14199
14194
  generateRules12 = (canonical) => embeddedRootRule(canonical, CRUSH_TARGET, CRUSH_ROOT_FILE);
14195
+ generateIgnore9 = ignoreOutput(CRUSH_IGNORE);
14200
14196
  }
14201
14197
  });
14202
14198
  async function importFromCrush(projectRoot, options = {}) {
@@ -14732,7 +14728,7 @@ function cursorHooksToCanonical(hooks) {
14732
14728
  }
14733
14729
  return result2;
14734
14730
  }
14735
- var CANONICAL_TO_CURSOR, CURSOR_TO_CANONICAL;
14731
+ var CANONICAL_TO_CURSOR, CURSOR_HOOK_CONTEXT_EVENTS, CURSOR_TO_CANONICAL;
14736
14732
  var init_hook_format2 = __esm({
14737
14733
  "src/targets/cursor/hook-format.ts"() {
14738
14734
  init_hook_types();
@@ -14740,6 +14736,7 @@ var init_hook_format2 = __esm({
14740
14736
  CANONICAL_TO_CURSOR = {
14741
14737
  PreToolUse: "preToolUse",
14742
14738
  PostToolUse: "postToolUse",
14739
+ PostToolUseFailure: "postToolUseFailure",
14743
14740
  UserPromptSubmit: "beforeSubmitPrompt",
14744
14741
  SubagentStart: "subagentStart",
14745
14742
  SubagentStop: "subagentStop",
@@ -14748,6 +14745,11 @@ var init_hook_format2 = __esm({
14748
14745
  SessionEnd: "sessionEnd",
14749
14746
  PreCompact: "preCompact"
14750
14747
  };
14748
+ CURSOR_HOOK_CONTEXT_EVENTS = [
14749
+ "SessionStart",
14750
+ "PostToolUse",
14751
+ "PostToolUseFailure"
14752
+ ];
14751
14753
  CURSOR_TO_CANONICAL = new Map(
14752
14754
  Object.entries(CANONICAL_TO_CURSOR).map(([canonical, cursor]) => [cursor, canonical])
14753
14755
  );
@@ -14756,7 +14758,7 @@ var init_hook_format2 = __esm({
14756
14758
 
14757
14759
  // src/targets/cursor/generator/hooks.ts
14758
14760
  function generateHooks10(canonical) {
14759
- if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
14761
+ if (!canonical.hooks) return [];
14760
14762
  const cursorHooks = toCursorHooks(canonical.hooks);
14761
14763
  if (Object.keys(cursorHooks).length === 0) return [];
14762
14764
  const content = JSON.stringify({ version: 1, hooks: cursorHooks }, null, 2);
@@ -14770,14 +14772,12 @@ var init_hooks3 = __esm({
14770
14772
  });
14771
14773
 
14772
14774
  // src/targets/cursor/generator/ignore.ts
14773
- function generateIgnore10(canonical) {
14774
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
14775
- const content = canonical.ignore.join("\n");
14776
- return [{ path: CURSOR_IGNORE, content }];
14777
- }
14775
+ var generateIgnore10;
14778
14776
  var init_ignore = __esm({
14779
14777
  "src/targets/cursor/generator/ignore.ts"() {
14780
14778
  init_constants13();
14779
+ init_ignore_output();
14780
+ generateIgnore10 = ignoreOutput(CURSOR_IGNORE);
14781
14781
  }
14782
14782
  });
14783
14783
 
@@ -15473,6 +15473,7 @@ var init_cursor2 = __esm({
15473
15473
  init_linter13();
15474
15474
  init_import_map_builders();
15475
15475
  init_lint12();
15476
+ init_hook_format2();
15476
15477
  target13 = {
15477
15478
  name: "cursor",
15478
15479
  primaryRootInstructionPath: CURSOR_GENERAL_RULE,
@@ -15594,6 +15595,7 @@ var init_cursor2 = __esm({
15594
15595
  permissions: "native"
15595
15596
  },
15596
15597
  emptyImportMessage: "No Cursor config found (AGENTS.md or .cursor/rules/*.mdc; with --global: ~/.cursor/{rules/*.mdc,AGENTS.md,mcp.json,hooks.json,cursorignore,skills/,agents/,commands/} and legacy ~/.agentsmesh-exports/cursor/user-rules.md).",
15598
+ hookContextEvents: CURSOR_HOOK_CONTEXT_EVENTS,
15597
15599
  lintRules: lintRules13,
15598
15600
  lint: {
15599
15601
  commands: lintCommands6,
@@ -16855,25 +16857,127 @@ var init_skills3 = __esm({
16855
16857
  }
16856
16858
  });
16857
16859
 
16858
- // src/targets/gemini-cli/generator/settings.ts
16859
- function mapHookEvent2(event) {
16860
- switch (event) {
16861
- case "PreToolUse":
16862
- return "BeforeTool";
16863
- case "PostToolUse":
16864
- return "AfterTool";
16865
- case "Notification":
16866
- return "Notification";
16867
- case "SubagentStart":
16868
- return "BeforeAgent";
16869
- case "SubagentStop":
16870
- return "AfterAgent";
16871
- case "SessionStart":
16872
- return "SessionStart";
16873
- default:
16874
- return null;
16860
+ // src/targets/gemini-cli/hook-map.ts
16861
+ function geminiHookEvent(event) {
16862
+ return CANONICAL_TO_GEMINI.get(event) ?? null;
16863
+ }
16864
+ function mapGeminiHookEvent(event) {
16865
+ return GEMINI_TO_CANONICAL.get(event) ?? null;
16866
+ }
16867
+ function splitNames(list, separator) {
16868
+ return list.split(separator).map((name) => name.trim()).filter((name) => name.length > 0);
16869
+ }
16870
+ function toGeminiMatcher(geminiEvent, matcher) {
16871
+ if (!TOOL_EVENTS.has(geminiEvent) || !NAME_LIST.test(matcher)) return matcher;
16872
+ const tools = unique(
16873
+ splitNames(matcher, /[|,]/).flatMap((name) => CANONICAL_TO_GEMINI_TOOLS.get(name) ?? [name])
16874
+ );
16875
+ return tools.length > 0 ? `^(?:${tools.join("|")})$` : matcher;
16876
+ }
16877
+ function fromGeminiMatcher(geminiEvent, matcher) {
16878
+ if (!TOOL_EVENTS.has(geminiEvent)) return matcher;
16879
+ const list = ANCHORED_LIST.exec(matcher)?.[1] ?? matcher;
16880
+ if (!NAME_LIST.test(list) || list.includes(",")) return matcher;
16881
+ return unique(
16882
+ splitNames(list, /\|/).map((name) => GEMINI_TO_CANONICAL_TOOLS.get(name) ?? name)
16883
+ ).join("|");
16884
+ }
16885
+ var GEMINI_HOOK_CONTEXT_EVENTS, CANONICAL_TO_GEMINI, GEMINI_TO_CANONICAL, TOOL_EVENTS, CANONICAL_TO_GEMINI_TOOLS, GEMINI_TO_CANONICAL_TOOLS, NAME_LIST, ANCHORED_LIST, unique;
16886
+ var init_hook_map = __esm({
16887
+ "src/targets/gemini-cli/hook-map.ts"() {
16888
+ GEMINI_HOOK_CONTEXT_EVENTS = [
16889
+ "UserPromptSubmit",
16890
+ "SessionStart",
16891
+ "PostToolUse"
16892
+ ];
16893
+ CANONICAL_TO_GEMINI = /* @__PURE__ */ new Map([
16894
+ ["PreToolUse", "BeforeTool"],
16895
+ ["PostToolUse", "AfterTool"],
16896
+ ["Notification", "Notification"],
16897
+ // BeforeAgent fires after the user submits a prompt and carries `prompt`.
16898
+ ["UserPromptSubmit", "BeforeAgent"],
16899
+ // Older mapping, kept so existing SubagentStart hooks still reach Gemini.
16900
+ ["SubagentStart", "BeforeAgent"],
16901
+ ["SubagentStop", "AfterAgent"],
16902
+ ["SessionStart", "SessionStart"]
16903
+ ]);
16904
+ GEMINI_TO_CANONICAL = new Map([
16905
+ ...[...CANONICAL_TO_GEMINI].map(([canonical, gemini]) => [gemini, canonical]),
16906
+ // BeforeAgent is shared with SubagentStart; import it as the prompt event.
16907
+ ["BeforeAgent", "UserPromptSubmit"],
16908
+ // Legacy lowercase names.
16909
+ ["preToolUse", "PreToolUse"],
16910
+ ["postToolUse", "PostToolUse"],
16911
+ ["notification", "Notification"]
16912
+ ]);
16913
+ TOOL_EVENTS = /* @__PURE__ */ new Set(["BeforeTool", "AfterTool"]);
16914
+ CANONICAL_TO_GEMINI_TOOLS = /* @__PURE__ */ new Map([
16915
+ ["Bash", ["run_shell_command"]],
16916
+ ["PowerShell", ["run_shell_command"]],
16917
+ ["Edit", ["replace"]],
16918
+ ["MultiEdit", ["replace"]],
16919
+ ["Write", ["write_file"]],
16920
+ ["NotebookEdit", []],
16921
+ ["Read", ["read_file", "read_many_files"]],
16922
+ ["Grep", ["grep_search"]],
16923
+ ["Glob", ["glob"]],
16924
+ ["LS", ["list_directory"]],
16925
+ ["WebFetch", ["web_fetch"]],
16926
+ ["WebSearch", ["google_web_search"]],
16927
+ ["TodoWrite", ["write_todos"]]
16928
+ ]);
16929
+ GEMINI_TO_CANONICAL_TOOLS = /* @__PURE__ */ new Map([
16930
+ ["run_shell_command", "Bash"],
16931
+ ["replace", "Edit"],
16932
+ ["write_file", "Write"],
16933
+ ["read_file", "Read"],
16934
+ ["read_many_files", "Read"],
16935
+ ["grep_search", "Grep"],
16936
+ ["search_file_content", "Grep"],
16937
+ ["glob", "Glob"],
16938
+ ["list_directory", "LS"],
16939
+ ["web_fetch", "WebFetch"],
16940
+ ["google_web_search", "WebSearch"],
16941
+ ["write_todos", "TodoWrite"]
16942
+ ]);
16943
+ NAME_LIST = /^[\w\s,|-]+$/;
16944
+ ANCHORED_LIST = /^\^\(\?:(.*)\)\$$/;
16945
+ unique = (values) => [...new Set(values)];
16946
+ }
16947
+ });
16948
+
16949
+ // src/targets/gemini-cli/generator/hooks.ts
16950
+ function buildGeminiHooks(hooks) {
16951
+ const result2 = {};
16952
+ for (const [event, entries] of Object.entries(hooks ?? {})) {
16953
+ const geminiEvent = geminiHookEvent(event);
16954
+ if (!geminiEvent || !Array.isArray(entries)) continue;
16955
+ for (const entry of entries) {
16956
+ if (typeof entry !== "object" || entry === null || !hasHookCommand(entry)) continue;
16957
+ const list = result2[geminiEvent] ??= [];
16958
+ list.push({
16959
+ matcher: typeof entry.matcher === "string" ? toGeminiMatcher(geminiEvent, entry.matcher) : entry.matcher,
16960
+ hooks: [
16961
+ {
16962
+ name: `${geminiEvent}-${list.length + 1}`,
16963
+ type: "command",
16964
+ command: getHookCommand(entry),
16965
+ timeout: entry.timeout
16966
+ }
16967
+ ]
16968
+ });
16969
+ }
16875
16970
  }
16971
+ return Object.keys(result2).length > 0 ? result2 : null;
16876
16972
  }
16973
+ var init_hooks4 = __esm({
16974
+ "src/targets/gemini-cli/generator/hooks.ts"() {
16975
+ init_hook_command();
16976
+ init_hook_map();
16977
+ }
16978
+ });
16979
+
16980
+ // src/targets/gemini-cli/generator/settings.ts
16877
16981
  function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16878
16982
  const settings = {};
16879
16983
  let hasAnyNativeSettings = false;
@@ -16885,29 +16989,10 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16885
16989
  settings.experimental = { enableAgents: true };
16886
16990
  hasAnyNativeSettings = true;
16887
16991
  }
16888
- if (enabledFeatures.has("hooks") && canonical.hooks) {
16889
- const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
16890
- const mappedEvent = mapHookEvent2(event);
16891
- if (!mappedEvent || !Array.isArray(entries)) return [];
16892
- const mappedEntries = entries.filter(
16893
- (entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
16894
- ).map((entry, index) => ({
16895
- matcher: entry.matcher,
16896
- hooks: [
16897
- {
16898
- name: `${mappedEvent}-${index + 1}`,
16899
- type: "command",
16900
- command: getHookCommand(entry),
16901
- timeout: entry.timeout
16902
- }
16903
- ]
16904
- }));
16905
- return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
16906
- });
16907
- if (hookEntries.length > 0) {
16908
- settings.hooks = Object.fromEntries(hookEntries);
16909
- hasAnyNativeSettings = true;
16910
- }
16992
+ const hooks = enabledFeatures.has("hooks") ? buildGeminiHooks(canonical.hooks) : null;
16993
+ if (hooks) {
16994
+ settings.hooks = hooks;
16995
+ hasAnyNativeSettings = true;
16911
16996
  }
16912
16997
  if (hasAnyNativeSettings) {
16913
16998
  settings.context = { fileName: [GEMINI_ROOT, GEMINI_COMPAT_AGENTS] };
@@ -16917,19 +17002,18 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16917
17002
  }
16918
17003
  var init_settings2 = __esm({
16919
17004
  "src/targets/gemini-cli/generator/settings.ts"() {
16920
- init_hook_command();
16921
17005
  init_constants33();
17006
+ init_hooks4();
16922
17007
  }
16923
17008
  });
16924
17009
 
16925
17010
  // src/targets/gemini-cli/generator/ignore.ts
16926
- function generateIgnore13(canonical) {
16927
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
16928
- return [{ path: GEMINI_IGNORE, content: canonical.ignore.join("\n") }];
16929
- }
17011
+ var generateIgnore13;
16930
17012
  var init_ignore2 = __esm({
16931
17013
  "src/targets/gemini-cli/generator/ignore.ts"() {
16932
17014
  init_constants33();
17015
+ init_ignore_output();
17016
+ generateIgnore13 = ignoreOutput(GEMINI_IGNORE);
16933
17017
  }
16934
17018
  });
16935
17019
 
@@ -17208,27 +17292,6 @@ var init_layout6 = __esm({
17208
17292
  };
17209
17293
  }
17210
17294
  });
17211
- function mapGeminiHookEvent(event) {
17212
- switch (event) {
17213
- case "BeforeTool":
17214
- case "preToolUse":
17215
- return "PreToolUse";
17216
- case "AfterTool":
17217
- case "postToolUse":
17218
- return "PostToolUse";
17219
- case "Notification":
17220
- case "notification":
17221
- return "Notification";
17222
- case "BeforeAgent":
17223
- return "SubagentStart";
17224
- case "AfterAgent":
17225
- return "SubagentStop";
17226
- case "SessionStart":
17227
- return "SessionStart";
17228
- default:
17229
- return null;
17230
- }
17231
- }
17232
17295
  function parseFlexibleFrontmatter(content) {
17233
17296
  const yamlOpen = content.indexOf("---");
17234
17297
  const tomlOpen = content.indexOf("+++");
@@ -17302,7 +17365,7 @@ async function importGeminiSettings(projectRoot, results) {
17302
17365
  (entry) => entry.hooks.filter(
17303
17366
  (hook) => hook !== null && typeof hook === "object" && hasHookCommand(hook)
17304
17367
  ).map((hook) => ({
17305
- matcher: entry.matcher,
17368
+ matcher: fromGeminiMatcher(event, entry.matcher),
17306
17369
  command: getHookCommand(hook),
17307
17370
  type: "command",
17308
17371
  timeout: typeof hook.timeout === "number" ? hook.timeout : void 0
@@ -17312,7 +17375,7 @@ async function importGeminiSettings(projectRoot, results) {
17312
17375
  const legacyMapped = value.filter(
17313
17376
  (entry) => entry !== null && typeof entry === "object" && typeof entry.matcher === "string" && hasHookCommand(entry)
17314
17377
  ).map((entry) => ({
17315
- matcher: entry.matcher,
17378
+ matcher: fromGeminiMatcher(event, entry.matcher),
17316
17379
  command: getHookCommand(entry),
17317
17380
  type: "command"
17318
17381
  }));
@@ -17341,7 +17404,7 @@ var init_format_helpers_settings = __esm({
17341
17404
  init_hook_command();
17342
17405
  init_fs();
17343
17406
  init_constants33();
17344
- init_format_helpers_shared();
17407
+ init_hook_map();
17345
17408
  }
17346
17409
  });
17347
17410
  async function importGeminiIgnore(projectRoot, results) {
@@ -17837,14 +17900,7 @@ function lintPermissions10(canonical, options) {
17837
17900
  }
17838
17901
  function lintHooks12(canonical) {
17839
17902
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
17840
- const supported = [
17841
- "PreToolUse",
17842
- "PostToolUse",
17843
- "Notification",
17844
- "SubagentStart",
17845
- "SubagentStop",
17846
- "SessionStart"
17847
- ];
17903
+ const supported = [...CANONICAL_TO_GEMINI.keys()];
17848
17904
  return unsupportedHookEventNames(canonical.hooks, supported).map(
17849
17905
  (event) => createUnsupportedHookWarning(event, "gemini-cli", supported)
17850
17906
  );
@@ -17852,6 +17908,7 @@ function lintHooks12(canonical) {
17852
17908
  var init_lint15 = __esm({
17853
17909
  "src/targets/gemini-cli/lint.ts"() {
17854
17910
  init_helpers();
17911
+ init_hook_map();
17855
17912
  }
17856
17913
  });
17857
17914
 
@@ -17888,6 +17945,7 @@ var init_gemini_cli2 = __esm({
17888
17945
  init_import_map_builders();
17889
17946
  init_lint15();
17890
17947
  init_scoped_settings_emit();
17948
+ init_hook_map();
17891
17949
  init_settings();
17892
17950
  target16 = {
17893
17951
  name: "gemini-cli",
@@ -17938,6 +17996,7 @@ var init_gemini_cli2 = __esm({
17938
17996
  permissions: lintPermissions10
17939
17997
  },
17940
17998
  emitScopedSettings: emitScopedGeminiSettings,
17999
+ hookContextEvents: GEMINI_HOOK_CONTEXT_EVENTS,
17941
18000
  mergeGeneratedOutputContent(existing, pending, newContent, resolvedPath) {
17942
18001
  const base = pending?.content ?? existing;
17943
18002
  if (base !== null && resolvedPath === GEMINI_SETTINGS) {
@@ -18030,14 +18089,10 @@ function generateAgents17(canonical) {
18030
18089
  content: serializeProjectedAgentSkill(agent)
18031
18090
  }));
18032
18091
  }
18033
- function generateIgnore14(canonical) {
18034
- if (canonical.ignore.length === 0) return [];
18035
- return [{ path: GOOSE_IGNORE, content: canonical.ignore.join("\n") }];
18036
- }
18037
18092
  function generateHooks12(canonical) {
18038
18093
  return buildWrappedCommandHooks(canonical, GOOSE_HOOKS_FILE);
18039
18094
  }
18040
- var generateRules17, generatePermissions12;
18095
+ var generateRules17, generateIgnore14, generatePermissions12;
18041
18096
  var init_generator20 = __esm({
18042
18097
  "src/targets/goose/generator.ts"() {
18043
18098
  init_managed_blocks();
@@ -18047,7 +18102,9 @@ var init_generator20 = __esm({
18047
18102
  init_command_skill();
18048
18103
  init_wrapped_command_hooks();
18049
18104
  init_constants16();
18105
+ init_ignore_output();
18050
18106
  generateRules17 = (canonical) => embeddedRootRule(canonical, GOOSE_TARGET, GOOSE_ROOT_FILE);
18107
+ generateIgnore14 = ignoreOutput(GOOSE_IGNORE);
18051
18108
  generatePermissions12 = NO_OUTPUTS;
18052
18109
  }
18053
18110
  });
@@ -18952,10 +19009,6 @@ function generateAgents18(canonical) {
18952
19009
  };
18953
19010
  });
18954
19011
  }
18955
- function generateIgnore16(canonical) {
18956
- if (canonical.ignore.length === 0) return [];
18957
- return [{ path: JUNIE_IGNORE, content: canonical.ignore.join("\n") }];
18958
- }
18959
19012
  function generateSkills17(canonical) {
18960
19013
  return generateEmbeddedSkills(canonical, JUNIE_SKILLS_DIR);
18961
19014
  }
@@ -18967,6 +19020,7 @@ function renderJunieGlobalInstructions(canonical) {
18967
19020
  });
18968
19021
  return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
18969
19022
  }
19023
+ var generateIgnore16;
18970
19024
  var init_generator22 = __esm({
18971
19025
  "src/targets/junie/generator.ts"() {
18972
19026
  init_mcp_servers();
@@ -18974,7 +19028,9 @@ var init_generator22 = __esm({
18974
19028
  init_managed_blocks();
18975
19029
  init_markdown();
18976
19030
  init_constants18();
19031
+ init_ignore_output();
18977
19032
  init_global_config2();
19033
+ generateIgnore16 = ignoreOutput(JUNIE_IGNORE);
18978
19034
  }
18979
19035
  });
18980
19036
 
@@ -19390,10 +19446,6 @@ function generateMcp15(canonical) {
19390
19446
  }
19391
19447
  ];
19392
19448
  }
19393
- function generateIgnore17(canonical) {
19394
- if (canonical.ignore.length === 0) return [];
19395
- return [{ path: KILO_CODE_IGNORE, content: canonical.ignore.join("\n") }];
19396
- }
19397
19449
  function generatePermissions15(canonical) {
19398
19450
  if (!canonical.permissions) return [];
19399
19451
  const { allow, deny } = canonical.permissions;
@@ -19408,11 +19460,14 @@ function generatePermissions15(canonical) {
19408
19460
  function generateSkills18(canonical) {
19409
19461
  return generateEmbeddedSkills(canonical, KILO_CODE_SKILLS_DIR);
19410
19462
  }
19463
+ var generateIgnore17;
19411
19464
  var init_generator23 = __esm({
19412
19465
  "src/targets/kilo-code/generator.ts"() {
19413
19466
  init_embedded_skill();
19414
19467
  init_markdown();
19415
19468
  init_constants19();
19469
+ init_ignore_output();
19470
+ generateIgnore17 = ignoreOutput(KILO_CODE_IGNORE);
19416
19471
  }
19417
19472
  });
19418
19473
  var kiloNonRootRuleMapper, kiloCommandMapper, kiloAgentMapper;
@@ -21157,11 +21212,7 @@ function buildKiroAgentOutputs(canonical, rules = []) {
21157
21212
  function generateAgents21(canonical) {
21158
21213
  return buildKiroAgentOutputs(canonical);
21159
21214
  }
21160
- function generateIgnore18(canonical) {
21161
- if (canonical.ignore.length === 0) return [];
21162
- return [{ path: KIRO_IGNORE, content: canonical.ignore.join("\n") }];
21163
- }
21164
- var generatePermissions17;
21215
+ var generateIgnore18, generatePermissions17;
21165
21216
  var init_generator25 = __esm({
21166
21217
  "src/targets/kiro/generator.ts"() {
21167
21218
  init_no_outputs();
@@ -21170,6 +21221,8 @@ var init_generator25 = __esm({
21170
21221
  init_markdown();
21171
21222
  init_hook_format3();
21172
21223
  init_constants21();
21224
+ init_ignore_output();
21225
+ generateIgnore18 = ignoreOutput(KIRO_IGNORE);
21173
21226
  generatePermissions17 = NO_OUTPUTS;
21174
21227
  }
21175
21228
  });
@@ -23812,10 +23865,6 @@ function generateMcp19(canonical) {
23812
23865
  const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
23813
23866
  return [{ path: QWEN_SETTINGS, content }];
23814
23867
  }
23815
- function generateIgnore21(canonical) {
23816
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
23817
- return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
23818
- }
23819
23868
  function generateHooks18(canonical) {
23820
23869
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
23821
23870
  const hooks = buildClaudeHooksObjectFromCanonical(canonical);
@@ -23833,11 +23882,14 @@ function generatePermissions21(canonical) {
23833
23882
  if (ask.length > 0) permissions.ask = ask;
23834
23883
  return [{ path: QWEN_SETTINGS, content: JSON.stringify({ permissions }, null, 2) }];
23835
23884
  }
23885
+ var generateIgnore21;
23836
23886
  var init_generator29 = __esm({
23837
23887
  "src/targets/qwen-code/generator.ts"() {
23838
23888
  init_markdown();
23839
23889
  init_hooks_format2();
23840
23890
  init_constants25();
23891
+ init_ignore_output();
23892
+ generateIgnore21 = ignoreOutput(QWEN_IGNORE);
23841
23893
  }
23842
23894
  });
23843
23895
 
@@ -25535,10 +25587,6 @@ function generateMcp23(canonical) {
25535
25587
  }
25536
25588
  ];
25537
25589
  }
25538
- function generateIgnore24(canonical) {
25539
- if (canonical.ignore.length === 0) return [];
25540
- return [{ path: TRAE_IGNORE, content: canonical.ignore.join("\n") }];
25541
- }
25542
25590
  function generateHooks20(canonical) {
25543
25591
  if (!canonical.hooks) return [];
25544
25592
  const hooks = {};
@@ -25559,12 +25607,15 @@ function generateHooks20(canonical) {
25559
25607
  if (Object.keys(hooks).length === 0) return [];
25560
25608
  return [{ path: TRAE_HOOKS_FILE, content: JSON.stringify({ version: 1, hooks }, null, 2) }];
25561
25609
  }
25610
+ var generateIgnore24;
25562
25611
  var init_generator33 = __esm({
25563
25612
  "src/targets/trae/generator.ts"() {
25564
25613
  init_embedded_skill();
25565
25614
  init_markdown();
25566
25615
  init_hook_command();
25567
25616
  init_constants29();
25617
+ init_ignore_output();
25618
+ generateIgnore24 = ignoreOutput(TRAE_IGNORE);
25568
25619
  }
25569
25620
  });
25570
25621
 
@@ -26271,11 +26322,7 @@ function generateMcp24(canonical, ctx) {
26271
26322
  const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
26272
26323
  return [{ path, content }];
26273
26324
  }
26274
- function generateIgnore25(canonical) {
26275
- if (canonical.ignore.length === 0) return [];
26276
- return [{ path: WARP_IGNORE_FILE, content: canonical.ignore.join("\n") }];
26277
- }
26278
- var generateRules31, generatePermissions24, generateHooks21;
26325
+ var generateRules31, generatePermissions24, generateHooks21, generateIgnore25;
26279
26326
  var init_generator34 = __esm({
26280
26327
  "src/targets/warp/generator.ts"() {
26281
26328
  init_managed_blocks();
@@ -26284,9 +26331,11 @@ var init_generator34 = __esm({
26284
26331
  init_projected_agent_skill();
26285
26332
  init_command_skill();
26286
26333
  init_constants30();
26334
+ init_ignore_output();
26287
26335
  generateRules31 = (canonical) => embeddedRootRule(canonical, WARP_TARGET, WARP_ROOT_FILE);
26288
26336
  generatePermissions24 = NO_OUTPUTS;
26289
26337
  generateHooks21 = NO_OUTPUTS;
26338
+ generateIgnore25 = ignoreOutput(WARP_IGNORE_FILE);
26290
26339
  }
26291
26340
  });
26292
26341
 
@@ -26911,13 +26960,12 @@ var init_rules4 = __esm({
26911
26960
  });
26912
26961
 
26913
26962
  // src/targets/windsurf/generator/ignore.ts
26914
- function generateIgnore26(canonical) {
26915
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
26916
- return [{ path: CODEIUM_IGNORE, content: canonical.ignore.join("\n") }];
26917
- }
26963
+ var generateIgnore26;
26918
26964
  var init_ignore3 = __esm({
26919
26965
  "src/targets/windsurf/generator/ignore.ts"() {
26920
26966
  init_constants34();
26967
+ init_ignore_output();
26968
+ generateIgnore26 = ignoreOutput(CODEIUM_IGNORE);
26921
26969
  }
26922
26970
  });
26923
26971
 
@@ -26983,7 +27031,7 @@ function canonicalHookEventName(event) {
26983
27031
  if (KNOWN_CANONICAL_HOOK_EVENTS.includes(event)) return event;
26984
27032
  return WINDSURF_TO_CANONICAL.get(event) ?? null;
26985
27033
  }
26986
- var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_TO_CANONICAL;
27034
+ var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_HOOK_CONTEXT_EVENTS, WINDSURF_TO_CANONICAL;
26987
27035
  var init_hook_events = __esm({
26988
27036
  "src/targets/windsurf/hook-events.ts"() {
26989
27037
  init_hook_types();
@@ -26996,6 +27044,7 @@ var init_hook_events = __esm({
26996
27044
  "SubagentStop",
26997
27045
  ...BEST_EFFORT_HOOK_EVENTS
26998
27046
  ];
27047
+ WINDSURF_HOOK_CONTEXT_EVENTS = [];
26999
27048
  WINDSURF_TO_CANONICAL = new Map(
27000
27049
  KNOWN_CANONICAL_HOOK_EVENTS.map((event) => [windsurfEventName(event), event])
27001
27050
  );
@@ -27021,12 +27070,12 @@ function toWindsurfHooks(hooks) {
27021
27070
  return result2;
27022
27071
  }
27023
27072
  function generateHooks22(canonical) {
27024
- if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
27073
+ if (!canonical.hooks) return [];
27025
27074
  const hooks = toWindsurfHooks(canonical.hooks);
27026
27075
  if (Object.keys(hooks).length === 0) return [];
27027
27076
  return [{ path: WINDSURF_HOOKS_FILE, content: JSON.stringify({ hooks }, null, 2) }];
27028
27077
  }
27029
- var init_hooks4 = __esm({
27078
+ var init_hooks5 = __esm({
27030
27079
  "src/targets/windsurf/generator/hooks.ts"() {
27031
27080
  init_hook_command();
27032
27081
  init_constants34();
@@ -27078,7 +27127,7 @@ var init_generator35 = __esm({
27078
27127
  init_workflows();
27079
27128
  init_agents4();
27080
27129
  init_mcp4();
27081
- init_hooks4();
27130
+ init_hooks5();
27082
27131
  init_skills4();
27083
27132
  init_permissions6();
27084
27133
  }
@@ -27197,7 +27246,7 @@ async function parseHooks(hooksPath, onParseError) {
27197
27246
  return result2;
27198
27247
  }
27199
27248
  var VALID_TYPES;
27200
- var init_hooks5 = __esm({
27249
+ var init_hooks6 = __esm({
27201
27250
  "src/canonical/features/hooks.ts"() {
27202
27251
  init_syntax_error();
27203
27252
  init_fs();
@@ -27278,7 +27327,7 @@ var WILDCARD_MATCHER;
27278
27327
  var init_importer_hooks2 = __esm({
27279
27328
  "src/targets/windsurf/importer-hooks.ts"() {
27280
27329
  init_canonical_paths();
27281
- init_hooks5();
27330
+ init_hooks6();
27282
27331
  init_fs();
27283
27332
  init_constants34();
27284
27333
  init_hook_events();
@@ -27579,6 +27628,7 @@ var init_windsurf2 = __esm({
27579
27628
  init_merge14();
27580
27629
  init_linter32();
27581
27630
  init_lint31();
27631
+ init_hook_events();
27582
27632
  init_import_map_builders();
27583
27633
  init_conversions();
27584
27634
  init_projected_agent_skill();
@@ -27710,6 +27760,7 @@ var init_windsurf2 = __esm({
27710
27760
  },
27711
27761
  emptyImportMessage: "No Windsurf config found (.windsurfrules, .windsurf/rules, .windsurfignore, or .codeiumignore).",
27712
27762
  supportsConversion: { agents: true },
27763
+ hookContextEvents: WINDSURF_HOOK_CONTEXT_EVENTS,
27713
27764
  lintRules: lintRules32,
27714
27765
  lint: {
27715
27766
  commands: lintCommands10,
@@ -28854,6 +28905,23 @@ var init_builtin_targets = __esm({
28854
28905
  }
28855
28906
  });
28856
28907
 
28908
+ // src/targets/catalog/recall-hook-targets.ts
28909
+ init_recall_hook_scaffold();
28910
+ init_builtin_targets();
28911
+ init_registry();
28912
+ function withTargetRecallHooks(canonical, target34) {
28913
+ const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
28914
+ const contextEvents = descriptor34?.hookContextEvents;
28915
+ if (contextEvents === void 0 || canonical.hooks === null) return canonical;
28916
+ const hooks = {};
28917
+ for (const [event, entries] of Object.entries(canonical.hooks)) {
28918
+ if (!Array.isArray(entries)) continue;
28919
+ const kept = contextEvents.includes(event) ? entries : entries.filter((entry) => !isRecallHookCommand(entry?.command));
28920
+ if (kept.length > 0) hooks[event] = kept;
28921
+ }
28922
+ return { ...canonical, hooks };
28923
+ }
28924
+
28857
28925
  // src/core/generate/engine.ts
28858
28926
  init_builtin_targets();
28859
28927
  init_registry();
@@ -29756,11 +29824,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
29756
29824
  const gen = resolveTargetFeatureGenerator(target34, "hooks", config, scope) ?? getDescriptor(target34)?.generators.generateHooks;
29757
29825
  if (!gen) continue;
29758
29826
  const ctx = featureContext(target34, "hooks", scope);
29759
- let outputs = [...gen(canonical, ctx)];
29827
+ const projected = withTargetRecallHooks(canonical, target34);
29828
+ let outputs = [...gen(projected, ctx)];
29760
29829
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29761
29830
  const post = descriptor34?.postProcessHookOutputs;
29762
29831
  if (post) {
29763
- outputs = [...await post(projectRoot, canonical, outputs)];
29832
+ outputs = [...await post(projectRoot, projected, outputs)];
29764
29833
  }
29765
29834
  const options = outputMergeOptions(target34);
29766
29835
  for (const out2 of outputs) {
@@ -29773,7 +29842,7 @@ async function generateScopedSettingsFeature(results, targets, canonical, projec
29773
29842
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29774
29843
  const emit = descriptor34?.emitScopedSettings;
29775
29844
  if (!emit) continue;
29776
- const outputs = emit(canonical, scope, enabledFeatures);
29845
+ const outputs = emit(withTargetRecallHooks(canonical, target34), scope, enabledFeatures);
29777
29846
  if (outputs.length === 0) continue;
29778
29847
  const options = outputMergeOptions(target34);
29779
29848
  for (const out2 of outputs) {
@@ -29816,7 +29885,12 @@ async function generate(ctx) {
29816
29885
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29817
29886
  const scopeExtras = descriptor34?.globalSupport?.scopeExtras;
29818
29887
  if (scopeExtras) {
29819
- const extras = await scopeExtras(canonical, projectRoot, scope, enabledFeatures);
29888
+ const extras = await scopeExtras(
29889
+ withTargetRecallHooks(canonical, target34),
29890
+ projectRoot,
29891
+ scope,
29892
+ enabledFeatures
29893
+ );
29820
29894
  await emitScopeExtras(results, target34, extras, projectRoot);
29821
29895
  }
29822
29896
  }
@@ -31123,7 +31197,7 @@ async function parsePermissions(permissionsPath, onParseError) {
31123
31197
  }
31124
31198
 
31125
31199
  // src/canonical/load/loader.ts
31126
- init_hooks5();
31200
+ init_hooks6();
31127
31201
 
31128
31202
  // src/canonical/features/ignore.ts
31129
31203
  init_fs();
@@ -31172,10 +31246,10 @@ function ruleSlug4(r) {
31172
31246
  function mergeByKey(base, overlay, key) {
31173
31247
  return [...new Map([...base, ...overlay].map((item) => [key(item), item])).values()];
31174
31248
  }
31175
- function mergeCanonicalFiles(base, overlay) {
31249
+ function mergeCanonicalFiles(base, overlay, options = {}) {
31176
31250
  const mcp = mergeMcp7(base.mcp, overlay.mcp);
31177
31251
  const permissions = mergePermissions2(base.permissions, overlay.permissions);
31178
- const hooks = mergeHooks5(base.hooks, overlay.hooks);
31252
+ const hooks = options.hooks === "combine" ? combineHooks(base.hooks, overlay.hooks) : overrideHooks(base.hooks, overlay.hooks);
31179
31253
  const ignore = mergeUniqueStrings(base.ignore, overlay.ignore);
31180
31254
  return {
31181
31255
  rules: mergeByKey(base.rules, overlay.rules, ruleSlug4),
@@ -31214,17 +31288,31 @@ function mergeUniqueStrings(base, overlay) {
31214
31288
  }
31215
31289
  return merged;
31216
31290
  }
31217
- function mergeHooks5(base, overlay) {
31291
+ function overrideHooks(base, overlay) {
31218
31292
  if (!base && !overlay) return null;
31219
31293
  const result2 = {};
31220
- const keys = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overlay ?? {})]);
31221
- for (const k of keys) {
31294
+ for (const k of hookEvents(base, overlay)) {
31222
31295
  const o = overlay?.[k];
31223
- const b = base?.[k];
31224
- result2[k] = o !== void 0 && o.length > 0 ? o : b ?? [];
31296
+ result2[k] = o !== void 0 && o.length > 0 ? o : base?.[k] ?? [];
31297
+ }
31298
+ return result2;
31299
+ }
31300
+ function combineHooks(first, then) {
31301
+ if (!first && !then) return null;
31302
+ const result2 = {};
31303
+ for (const k of hookEvents(first, then)) {
31304
+ const kept = first?.[k] ?? [];
31305
+ const defined = new Set(kept.map(hookKey));
31306
+ result2[k] = [...kept, ...(then?.[k] ?? []).filter((entry) => !defined.has(hookKey(entry)))];
31225
31307
  }
31226
31308
  return result2;
31227
31309
  }
31310
+ function hookEvents(a, b) {
31311
+ return [.../* @__PURE__ */ new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})])];
31312
+ }
31313
+ function hookKey(entry) {
31314
+ return JSON.stringify([entry.type ?? "command", entry.matcher, entry.command]);
31315
+ }
31228
31316
 
31229
31317
  // src/config/resolve/native-format-detector.ts
31230
31318
  init_fs();
@@ -31995,7 +32083,7 @@ function gateExtendElevatedArtifacts(canonical, ext) {
31995
32083
  });
31996
32084
  }
31997
32085
  init_mcp();
31998
- init_hooks5();
32086
+ init_hooks6();
31999
32087
 
32000
32088
  // src/install/pack/pack-reader.ts
32001
32089
  init_fs();
@@ -32082,7 +32170,7 @@ async function loadPacksCanonical(abDir) {
32082
32170
  const canonical = await loadPackCanonical(packDir);
32083
32171
  const filtered = filterCanonicalByFeatures(canonical, meta.features);
32084
32172
  const picked = applyExtendPick(filtered, meta.features, meta.pick, meta.name);
32085
- merged = mergeCanonicalFiles(merged, picked);
32173
+ merged = mergeCanonicalFiles(merged, picked, { hooks: "combine" });
32086
32174
  }
32087
32175
  return merged;
32088
32176
  }
@@ -32133,9 +32221,10 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
32133
32221
  merged = mergeCanonicalFiles(merged, picked);
32134
32222
  }
32135
32223
  const packsCanonical = await loadPacksCanonical(canonicalDir);
32136
- merged = mergeCanonicalFiles(merged, packsCanonical);
32224
+ merged = mergeCanonicalFiles(merged, packsCanonical, { hooks: "combine" });
32137
32225
  const localCanonical = await loadCanonicalFiles(canonicalDir);
32138
32226
  merged = mergeCanonicalFiles(merged, localCanonical);
32227
+ merged = { ...merged, hooks: combineHooks(merged.hooks, packsCanonical.hooks) };
32139
32228
  return { canonical: merged, resolvedExtends };
32140
32229
  }
32141
32230
 
@@ -32414,6 +32503,9 @@ function lintRuleScopeInversion(input) {
32414
32503
  }
32415
32504
  return out2;
32416
32505
  }
32506
+
32507
+ // src/lessons/graph-store.ts
32508
+ init_fs_text_encoding();
32417
32509
  var VersionSchema = z.union([z.literal(1), z.literal(2)]);
32418
32510
  var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
32419
32511
  var DateSchema = z.string().regex(
@@ -32457,13 +32549,13 @@ function parseGraph(raw) {
32457
32549
  }
32458
32550
 
32459
32551
  // src/lessons/graph-store.ts
32460
- var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
32552
+ var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
32461
32553
  function graphFilePath(projectRoot) {
32462
- return resolve(projectRoot, GRAPH_REL_PATH);
32554
+ return resolve(projectRoot, LESSONS_GRAPH_PATH);
32463
32555
  }
32464
32556
  function loadLessonsGraph(projectRoot) {
32465
32557
  const raw = readFileSync(graphFilePath(projectRoot), "utf8");
32466
- return parseGraph(JSON.parse(raw));
32558
+ return parseGraph(JSON.parse(stripBom(raw)));
32467
32559
  }
32468
32560
  var BASE_REL = ".agentsmesh/lessons";
32469
32561
  function lessonsPaths(projectRoot) {
@@ -32480,9 +32572,76 @@ function lessonsPaths(projectRoot) {
32480
32572
  function toRelPath(projectRoot, absolute) {
32481
32573
  return relative(projectRoot, absolute).split(sep).join("/");
32482
32574
  }
32575
+ var MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
32576
+ function runGit2(cwd, args, timeoutMs) {
32577
+ const r = spawnSync("git", [...args], {
32578
+ cwd,
32579
+ encoding: "utf8",
32580
+ maxBuffer: MAX_OUTPUT_BYTES,
32581
+ timeout: timeoutMs,
32582
+ windowsHide: true
32583
+ });
32584
+ const status = r.error === void 0 ? r.status ?? -1 : -1;
32585
+ return { status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
32586
+ }
32587
+
32588
+ // src/lessons/git-path-history.ts
32589
+ var GIT_SCAN_TIMEOUT_MS = 3e3;
32590
+ var cache = /* @__PURE__ */ new Map();
32591
+ function readGitPathHistory(projectRoot) {
32592
+ if (!cache.has(projectRoot)) cache.set(projectRoot, scanGitPathHistory(projectRoot));
32593
+ return cache.get(projectRoot) ?? null;
32594
+ }
32595
+ var LOG_ARGS = [
32596
+ "log",
32597
+ "HEAD",
32598
+ "--relative",
32599
+ "-M",
32600
+ "--diff-filter=DR",
32601
+ "--name-status",
32602
+ "-z",
32603
+ "--no-color",
32604
+ "--no-show-signature",
32605
+ "--pretty=format:"
32606
+ ];
32607
+ function scanGitPathHistory(projectRoot, timeoutMs = GIT_SCAN_TIMEOUT_MS) {
32608
+ const tracked = runGit2(projectRoot, ["ls-files", "-z"], timeoutMs);
32609
+ if (tracked.status !== 0) return null;
32610
+ const log = runGit2(projectRoot, LOG_ARGS, timeoutMs);
32611
+ if (log.status !== 0) return null;
32612
+ return {
32613
+ tracked: new Set(tracked.stdout.split("\0").filter(Boolean)),
32614
+ ...parseRemovals(log.stdout)
32615
+ };
32616
+ }
32617
+ function parseRemovals(out2) {
32618
+ const deleted = /* @__PURE__ */ new Set();
32619
+ const renamedAway = /* @__PURE__ */ new Set();
32620
+ const tokens = out2.split("\0");
32621
+ for (let i = 0; i < tokens.length; i += 1) {
32622
+ const status = tokens[i] ?? "";
32623
+ const path = tokens[i + 1] ?? "";
32624
+ if (status.startsWith("D")) {
32625
+ if (path !== "") deleted.add(path);
32626
+ i += 1;
32627
+ } else if (status.startsWith("R")) {
32628
+ if (path !== "") renamedAway.add(path);
32629
+ i += 2;
32630
+ }
32631
+ }
32632
+ return { deleted, renamedAway };
32633
+ }
32634
+
32635
+ // src/lessons/project-files.ts
32483
32636
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
32484
32637
  var MAX_FILES = 2e5;
32485
- function listProjectFiles(projectRoot) {
32638
+ function projectFilesOf(paths2, gitHistory) {
32639
+ return Object.assign(new Set(paths2), { gitHistory });
32640
+ }
32641
+ function gitHistoryOf(paths2) {
32642
+ return paths2.gitHistory?.() ?? null;
32643
+ }
32644
+ function listProjectFiles(projectRoot, maxFiles = MAX_FILES) {
32486
32645
  const out2 = /* @__PURE__ */ new Set();
32487
32646
  try {
32488
32647
  const stack = [projectRoot];
@@ -32493,14 +32652,14 @@ function listProjectFiles(projectRoot) {
32493
32652
  if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
32494
32653
  } else if (entry.isFile()) {
32495
32654
  out2.add(toRelPath(projectRoot, join(dir, entry.name)));
32496
- if (out2.size > MAX_FILES) return out2;
32655
+ if (out2.size > maxFiles) return null;
32497
32656
  }
32498
32657
  }
32499
32658
  }
32500
32659
  } catch {
32501
32660
  return null;
32502
32661
  }
32503
- return out2;
32662
+ return projectFilesOf(out2, () => readGitPathHistory(projectRoot));
32504
32663
  }
32505
32664
 
32506
32665
  // src/lessons/validate-checks.ts
@@ -32677,10 +32836,307 @@ function collectOrphans(graph, findings) {
32677
32836
  }
32678
32837
  }
32679
32838
  }
32839
+
32840
+ // src/lessons/glob-expand.ts
32841
+ var MARK = "\0";
32842
+ var LED = "";
32843
+ var MAX_EXPANSIONS = 64;
32844
+ function fail(reason) {
32845
+ throw new Error(reason);
32846
+ }
32847
+ function skipClass(s, i) {
32848
+ const end = s.indexOf("]", i + 1);
32849
+ return end === -1 ? i + 1 : end + 1;
32850
+ }
32851
+ function markLedStars(body) {
32852
+ let out2 = "";
32853
+ let depth = 0;
32854
+ for (let i = 0; i < body.length; ) {
32855
+ const c2 = body[i];
32856
+ if (c2 === "[") {
32857
+ const end = skipClass(body, i);
32858
+ out2 += body.slice(i, end);
32859
+ i = end;
32860
+ continue;
32861
+ }
32862
+ if (c2 === "{") depth += 1;
32863
+ if (c2 === "}" && depth > 0) depth -= 1;
32864
+ const prev = body[i - 1];
32865
+ if (c2 === "*" && prev === "." && depth > 0) fail(".* inside {\u2026} is not supported");
32866
+ const segmentStart = i === 0 || prev === "/";
32867
+ const afterLeadingDot = prev === "." && (i === 1 || body[i - 2] === "/");
32868
+ const led = c2 === "*" && body[i + 1] !== "*" && prev !== "*" && (segmentStart || afterLeadingDot);
32869
+ out2 += led ? LED : c2;
32870
+ i += 1;
32871
+ }
32872
+ return out2;
32873
+ }
32874
+ function expandBraces(s) {
32875
+ let open2 = -1;
32876
+ for (let i = 0; i < s.length && open2 === -1; ) {
32877
+ if (s[i] === "[") i = skipClass(s, i);
32878
+ else if (s[i] === "}") fail("unbalanced }");
32879
+ else if (s[i] === "{") open2 = i;
32880
+ else i += 1;
32881
+ }
32882
+ if (open2 === -1) return [s];
32883
+ const options = [];
32884
+ let depth = 0;
32885
+ let start = open2 + 1;
32886
+ let close = -1;
32887
+ for (let i = open2 + 1; i < s.length && close === -1; ) {
32888
+ const c2 = s[i];
32889
+ if (c2 === "[") {
32890
+ i = skipClass(s, i);
32891
+ continue;
32892
+ }
32893
+ if (c2 === "{") depth += 1;
32894
+ else if (c2 === "}" && depth > 0) depth -= 1;
32895
+ else if (c2 === "}") close = i;
32896
+ else if (c2 === "," && depth === 0) {
32897
+ options.push(s.slice(start, i));
32898
+ start = i + 1;
32899
+ }
32900
+ i += 1;
32901
+ }
32902
+ if (close === -1) fail("unclosed {");
32903
+ if (options.length === 0)
32904
+ fail("brace groups need a comma, e.g. {a,b} (ranges are not supported)");
32905
+ options.push(s.slice(start, close));
32906
+ const suffixes = expandBraces(s.slice(close + 1));
32907
+ const out2 = [];
32908
+ for (const option of options) {
32909
+ for (const head of expandBraces(option)) {
32910
+ for (const tail of suffixes) {
32911
+ out2.push(s.slice(0, open2) + MARK + head + MARK + tail);
32912
+ if (out2.length > MAX_EXPANSIONS) fail(`more than ${MAX_EXPANSIONS} brace expansions`);
32913
+ }
32914
+ }
32915
+ }
32916
+ return out2;
32917
+ }
32918
+
32919
+ // src/lessons/glob-parse.ts
32920
+ var MAX_GLOB_LENGTH = 256;
32921
+ function parseGlob(pattern) {
32922
+ try {
32923
+ return parseOrThrow(pattern);
32924
+ } catch (err) {
32925
+ return err instanceof Error ? err.message : String(err);
32926
+ }
32927
+ }
32928
+ function parseOrThrow(pattern) {
32929
+ if (pattern.length > MAX_GLOB_LENGTH) fail(`longer than ${MAX_GLOB_LENGTH} characters`);
32930
+ if (pattern.includes("\\")) fail("backslash escapes are not supported (use / as separator)");
32931
+ if (/["\u0000-\u001f]/.test(pattern)) fail("quotes and control characters are not supported");
32932
+ if (/[()|]/.test(pattern.replace(/\[[^\]/]*\]/g, ""))) {
32933
+ fail("extglobs and (\u2026)/| groups are not supported (use {a,b})");
32934
+ }
32935
+ if (/[[\]{}]\+/.test(pattern)) fail("+ after a class or brace is a regex quantifier");
32936
+ const negated = pattern.startsWith("!");
32937
+ let body = negated ? pattern.slice(1) : pattern;
32938
+ if (body.startsWith("./")) body = body.slice(2);
32939
+ if (body.startsWith("!") || body.startsWith("./")) fail("use a single leading ! and ./");
32940
+ if (body === "") fail("empty pattern");
32941
+ const fastPath = !negated && (body === "*.*" || body === "**/*.*");
32942
+ if (fastPath) body = `${body.slice(0, -1)}?*`;
32943
+ return { negated, alternatives: expandBraces(markLedStars(body)).map(parseAlternative) };
32944
+ }
32945
+ function parseAlternative(alt) {
32946
+ const raw = alt.split("/");
32947
+ const out2 = [];
32948
+ raw.forEach((segment, i) => {
32949
+ if (segment.includes("**")) {
32950
+ if (segment !== "**") fail("** must be a whole segment, outside {\u2026} (use * in a segment)");
32951
+ if (out2.at(-1)?.k === "globstar") return;
32952
+ const before = raw[i - 1] ?? "";
32953
+ const trailing = raw.slice(i + 1).every((s) => s === "**");
32954
+ const min1 = trailing && (before.endsWith("*") || before.endsWith(LED));
32955
+ out2.push({ k: "globstar", min1 });
32956
+ return;
32957
+ }
32958
+ const tokens = tokenize(segment, alt);
32959
+ const guarded = segment.includes(LED);
32960
+ const literal = tokens.every((t) => t.k === "lit") ? tokens.map((t) => t.k === "lit" ? t.ch : "").join("") : null;
32961
+ const matchesEmpty = segment !== "" && !guarded && tokens.every((t) => t.k === "star");
32962
+ out2.push({ k: "segment", tokens, literal, guarded, matchesEmpty });
32963
+ });
32964
+ return out2;
32965
+ }
32966
+ function tokenize(segment, alt) {
32967
+ const tokens = [];
32968
+ for (let i = 0; i < segment.length; i += 1) {
32969
+ const c2 = segment[i];
32970
+ if (c2 === MARK) continue;
32971
+ if (c2 === "*" || c2 === LED) tokens.push({ k: "star" });
32972
+ else if (c2 === "?") tokens.push({ k: "one" });
32973
+ else if (c2 === "[") {
32974
+ const end = segment.indexOf("]", i + 1);
32975
+ if (end === -1) {
32976
+ if (alt.includes("]")) fail("a [...] class cannot span /");
32977
+ tokens.push({ k: "lit", ch: c2 });
32978
+ continue;
32979
+ }
32980
+ tokens.push(parseClass(segment.slice(i + 1, end)));
32981
+ i = end;
32982
+ } else tokens.push({ k: "lit", ch: c2 });
32983
+ }
32984
+ return tokens;
32985
+ }
32986
+ var CLASS_SPECIAL = /[-*+?.^${}(|)[\]]/;
32987
+ function parseClass(body) {
32988
+ if (body === "") fail("empty [] class");
32989
+ if (body.startsWith("!")) fail("[!...] is not a negation here; use [^...]");
32990
+ if (body.includes("[")) fail("POSIX [:classes:] and nested [ are not supported");
32991
+ const negated = body.startsWith("^");
32992
+ const members = negated ? body.slice(1) : body;
32993
+ if (members === "") fail("empty [^] class");
32994
+ const ranges = [];
32995
+ for (let i = 0; i < members.length; i += 1) {
32996
+ const lo = members[i];
32997
+ const hi = members[i + 2];
32998
+ if (members[i + 1] === "-" && hi !== void 0) {
32999
+ if (hi < lo) fail(`class range out of order: ${lo}-${hi}`);
33000
+ ranges.push([lo, hi]);
33001
+ i += 2;
33002
+ } else ranges.push([lo, lo]);
33003
+ }
33004
+ const inSet = (c2) => ranges.some(([lo, hi]) => c2 >= lo && c2 <= hi);
33005
+ const literal = CLASS_SPECIAL.test(body) ? null : `[${body}]`;
33006
+ return { k: "class", test: negated ? (c2) => c2 !== "/" && !inSet(c2) : inSet, literal };
33007
+ }
33008
+
33009
+ // src/lessons/add-helpers.ts
32680
33010
  function normalizeRule(rule) {
32681
33011
  return rule.trim().replace(/\s+/g, " ").toLowerCase();
32682
33012
  }
32683
33013
 
33014
+ // src/lessons/glob-dp.ts
33015
+ var isDotSegment = (s) => s === "." || s === "..";
33016
+ function matchSegments(alt, segments, work) {
33017
+ const n = segments.length;
33018
+ let next = new Uint8Array(n + 1);
33019
+ next[n] = 1;
33020
+ for (let i = alt.length - 1; i >= 0; i -= 1) {
33021
+ const seg = alt[i];
33022
+ const cur = new Uint8Array(n + 1);
33023
+ for (let j = n; j >= 0; j -= 1) {
33024
+ if (--work.remaining <= 0) return false;
33025
+ if (seg.k === "globstar") {
33026
+ const eat = j < n && !isDotSegment(segments[j]) && (cur[j + 1] === 1 || next[j + 1] === 1);
33027
+ cur[j] = !seg.min1 && next[j] === 1 || eat ? 1 : 0;
33028
+ } else if (j < n && next[j + 1] === 1) {
33029
+ const text = segments[j];
33030
+ const ok = seg.literal !== null ? text === seg.literal : matchSegment(seg, text, j < n - 1, work);
33031
+ cur[j] = ok ? 1 : 0;
33032
+ }
33033
+ }
33034
+ if (seg.k === "globstar" && i >= 1 && tailMatchesNothing(alt, i + 1)) cur[n] = 1;
33035
+ next = cur;
33036
+ }
33037
+ return next[0] === 1;
33038
+ }
33039
+ function tailMatchesNothing(alt, from) {
33040
+ const empty = alt[from];
33041
+ const rest = alt[from + 1];
33042
+ if (empty?.k !== "segment" || !empty.matchesEmpty) return false;
33043
+ return rest === void 0 || rest.k === "globstar" && !rest.min1 && from + 2 === alt.length;
33044
+ }
33045
+ function matchSegment(seg, text, followedBySlash, work) {
33046
+ if (seg.guarded && (isDotSegment(text) || text === "" && !followedBySlash)) return false;
33047
+ const tokens = seg.tokens;
33048
+ const len = text.length;
33049
+ let next = new Uint8Array(len + 1);
33050
+ next[len] = 1;
33051
+ for (let k = tokens.length - 1; k >= 0; k -= 1) {
33052
+ const tok = tokens[k];
33053
+ const cur = new Uint8Array(len + 1);
33054
+ work.remaining -= len + 1;
33055
+ if (work.remaining <= 0) return false;
33056
+ for (let c2 = len; c2 >= 0; c2 -= 1) {
33057
+ cur[c2] = tokenMatches(tok, text, c2, next, cur) ? 1 : 0;
33058
+ }
33059
+ next = cur;
33060
+ }
33061
+ return next[0] === 1;
33062
+ }
33063
+ function tokenMatches(tok, text, c2, next, cur) {
33064
+ const ch = text[c2];
33065
+ switch (tok.k) {
33066
+ case "star":
33067
+ return next[c2] === 1 || ch !== void 0 && cur[c2 + 1] === 1;
33068
+ case "one":
33069
+ return ch !== void 0 && next[c2 + 1] === 1;
33070
+ case "lit":
33071
+ return ch === tok.ch && next[c2 + 1] === 1;
33072
+ case "class":
33073
+ return ch !== void 0 && tok.test(ch) && next[c2 + 1] === 1 || tok.literal !== null && text.startsWith(tok.literal, c2) && next[c2 + tok.literal.length] === 1;
33074
+ }
33075
+ }
33076
+
33077
+ // src/lessons/glob-safety.ts
33078
+ var MAX_GLOB_PATH_LENGTH = 4096;
33079
+ var MATCH_WORK_LIMIT = 2e5;
33080
+ var CACHE_LIMIT = 2e3;
33081
+ var cache2 = /* @__PURE__ */ new Map();
33082
+ function unsafeGlobFinding(triggerId, trigger) {
33083
+ if (trigger.kind !== "file_glob" || trigger.pattern.includes("\\")) return null;
33084
+ const reason = parseGlob(trigger.pattern);
33085
+ if (typeof reason !== "string") return null;
33086
+ return {
33087
+ level: "error",
33088
+ code: "UNSAFE_GLOB_PATTERN",
33089
+ message: `Trigger "${triggerId}" has a file_glob outside the safe glob subset (${trigger.pattern.slice(0, 120)}): ${reason}. Recall treats it as a non-match. Use only *, **, ?, [...] and {a,b}.`,
33090
+ triggerId
33091
+ };
33092
+ }
33093
+ function getGlobMatcher(pattern) {
33094
+ const hit = cache2.get(pattern);
33095
+ if (hit !== void 0) return hit;
33096
+ if (cache2.size >= CACHE_LIMIT) cache2.clear();
33097
+ const composed = pattern.normalize("NFC");
33098
+ const parsed = parseGlob(composed);
33099
+ const matcher = typeof parsed === "string" ? null : build(composed, parsed);
33100
+ cache2.set(pattern, matcher);
33101
+ return matcher;
33102
+ }
33103
+ function build(pattern, parsed) {
33104
+ const alts = parsed.alternatives.map(prepare);
33105
+ return {
33106
+ test(rawPath, budget) {
33107
+ const path = rawPath.normalize("NFC");
33108
+ if (path === pattern) return true;
33109
+ if (path === "" || path.length > MAX_GLOB_PATH_LENGTH) return false;
33110
+ const live = alts.filter((a) => mayMatch(a, path));
33111
+ if (live.length === 0) return parsed.negated;
33112
+ const limit = Math.min(MATCH_WORK_LIMIT, budget?.remaining ?? MATCH_WORK_LIMIT);
33113
+ const work = { remaining: limit };
33114
+ const segments = path.split("/");
33115
+ const hit = live.some((a) => matchSegments(a.alt, segments, work));
33116
+ if (budget !== void 0) budget.remaining -= limit - work.remaining;
33117
+ return work.remaining > 0 && hit !== parsed.negated;
33118
+ }
33119
+ };
33120
+ }
33121
+ function mayMatch({ head, tail }, path) {
33122
+ if (!path.endsWith(tail)) return false;
33123
+ if (head === null) return true;
33124
+ return path.startsWith(head) && (path.length === head.length || path[head.length] === "/");
33125
+ }
33126
+ function prepare(alt) {
33127
+ const first = alt[0];
33128
+ const last = alt[alt.length - 1];
33129
+ let tail = "";
33130
+ if (last?.k === "segment" && !last.matchesEmpty) {
33131
+ for (let k = last.tokens.length - 1; k >= 0; k -= 1) {
33132
+ const tok = last.tokens[k];
33133
+ if (tok.k !== "lit") break;
33134
+ tail = tok.ch + tail;
33135
+ }
33136
+ }
33137
+ return { alt, head: first?.k === "segment" ? first.literal : null, tail };
33138
+ }
33139
+
32684
33140
  // src/lessons/regex-linear/nfa-compile.ts
32685
33141
  var MAX_NFA_STATES = 2e3;
32686
33142
  var Builder = class {
@@ -32872,6 +33328,9 @@ function escapeClass(c2) {
32872
33328
  var HEX2 = /^[0-9a-fA-F]{2}$/;
32873
33329
  var HEX4 = /^[0-9a-fA-F]{4}$/;
32874
33330
  function readUnicodeEscape(src, i, c2) {
33331
+ if (c2 === "u" && src[i] === "{") {
33332
+ throw new UnsupportedRegexError("\\u{\u2026} code point escapes are not supported; use \\uHHHH");
33333
+ }
32875
33334
  if (c2 === "x") {
32876
33335
  const hex2 = src.slice(i, i + 2);
32877
33336
  return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
@@ -32961,7 +33420,7 @@ function parseRegex(src) {
32961
33420
  function parseAtom() {
32962
33421
  const c2 = peek();
32963
33422
  if (c2 === "(") return parseGroup2();
32964
- if (c2 === "[") return parseClass();
33423
+ if (c2 === "[") return parseClass2();
32965
33424
  if (c2 === "\\") return parseEscape();
32966
33425
  if (c2 === ".") {
32967
33426
  i += 1;
@@ -33020,7 +33479,7 @@ function parseRegex(src) {
33020
33479
  }
33021
33480
  return { k: "char", ch: escapeLiteral(c2) };
33022
33481
  }
33023
- function parseClass() {
33482
+ function parseClass2() {
33024
33483
  i += 1;
33025
33484
  const negate = peek() === "^";
33026
33485
  if (negate) i += 1;
@@ -33073,17 +33532,17 @@ function parseRegex(src) {
33073
33532
  }
33074
33533
 
33075
33534
  // src/lessons/regex-linear/index.ts
33076
- var cache = /* @__PURE__ */ new Map();
33535
+ var cache3 = /* @__PURE__ */ new Map();
33077
33536
  function compileLinearMatcher(pattern) {
33078
- const hit = cache.get(pattern);
33079
- if (hit !== void 0 || cache.has(pattern)) return hit ?? null;
33537
+ const hit = cache3.get(pattern);
33538
+ if (hit !== void 0 || cache3.has(pattern)) return hit ?? null;
33080
33539
  let matcher;
33081
33540
  try {
33082
33541
  matcher = buildMatcher(parseRegex(pattern));
33083
33542
  } catch {
33084
33543
  matcher = null;
33085
33544
  }
33086
- cache.set(pattern, matcher);
33545
+ cache3.set(pattern, matcher);
33087
33546
  return matcher;
33088
33547
  }
33089
33548
 
@@ -33124,6 +33583,8 @@ function collectDuplicateRules(graph, findings) {
33124
33583
  }
33125
33584
  function collectInvalidTriggerPatterns(graph, findings) {
33126
33585
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33586
+ const globFinding = unsafeGlobFinding(triggerId, trigger);
33587
+ if (globFinding !== null) findings.push(globFinding);
33127
33588
  if (trigger.kind !== "command_pattern") continue;
33128
33589
  try {
33129
33590
  new RegExp(trigger.pattern);
@@ -33131,7 +33592,7 @@ function collectInvalidTriggerPatterns(graph, findings) {
33131
33592
  findings.push({
33132
33593
  level: "error",
33133
33594
  code: "INVALID_TRIGGER_PATTERN",
33134
- message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${err instanceof Error ? err.message : String(err)}.`,
33595
+ message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${regexSyntaxReason(err)}.`,
33135
33596
  triggerId
33136
33597
  });
33137
33598
  continue;
@@ -33140,12 +33601,16 @@ function collectInvalidTriggerPatterns(graph, findings) {
33140
33601
  findings.push({
33141
33602
  level: "error",
33142
33603
  code: "UNSAFE_TRIGGER_PATTERN",
33143
- message: `Trigger "${triggerId}" has a command_pattern regex outside the provably-linear subset (${trigger.pattern}): it can backtrack catastrophically (e.g. a quantified group like (a+)+ or (a|aa)+, adjacent repetition like a+a+, or a backreference/lookaround). Rewrite using a linear pattern.`,
33604
+ message: `Trigger "${triggerId}" has a command_pattern regex the linear matcher cannot run (${trigger.pattern}). It does not support backreferences (\\1, \\k<name>), lookarounds ((?=x), (?!x), (?<=x), (?<!x)), or patterns too large to run, such as (a{1000}){10}. Nested quantifiers such as (a+)+ are fine. Rewrite the pattern without the unsupported part.`,
33144
33605
  triggerId
33145
33606
  });
33146
33607
  }
33147
33608
  }
33148
33609
  }
33610
+ function regexSyntaxReason(err) {
33611
+ const text = err instanceof Error ? err.message : String(err);
33612
+ return text.replace(/^Invalid regular expression: \/[\s\S]*\/[a-z]*: /, "");
33613
+ }
33149
33614
  function collectBackslashGlobPatterns(graph, findings) {
33150
33615
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33151
33616
  if (trigger.kind !== "file_glob") continue;
@@ -33226,7 +33691,11 @@ function collectTriggerSetCollisions(graph, findings) {
33226
33691
 
33227
33692
  // src/lessons/glob-breadth.ts
33228
33693
  var WILDCARD = /[*?[\]]/;
33694
+ function isNegatedGlob(pattern) {
33695
+ return pattern.startsWith("!");
33696
+ }
33229
33697
  function globNarrowness(pattern) {
33698
+ if (isNegatedGlob(pattern)) return 0;
33230
33699
  const segments = pattern.replaceAll("\\", "/").split("/").filter((segment) => segment !== "" && segment !== ".");
33231
33700
  if (segments.length === 0) return 0;
33232
33701
  let literal = 0;
@@ -33277,6 +33746,15 @@ function isBroadCommandPattern(pattern) {
33277
33746
  }
33278
33747
  return hits > COMMAND_PROBE_CORPUS.length * BROAD_HIT_RATIO;
33279
33748
  }
33749
+ function missingGlobState(pattern, history) {
33750
+ const matcher = getGlobMatcher(pattern);
33751
+ if (history === null || matcher === null) return "pending";
33752
+ const matchesAny = (paths2) => [...paths2].some((p) => matcher.test(p));
33753
+ if (matchesAny(history.tracked)) return "live";
33754
+ if (matchesAny(history.renamedAway)) return "dead";
33755
+ if (!picomatch.scan(pattern).isGlob && matchesAny(history.deleted)) return "dead";
33756
+ return "pending";
33757
+ }
33280
33758
 
33281
33759
  // src/lessons/validate-liveness.ts
33282
33760
  function activeTriggerIds(graph) {
@@ -33287,24 +33765,33 @@ function activeTriggerIds(graph) {
33287
33765
  }
33288
33766
  return ids;
33289
33767
  }
33290
- function deadFileGlobIds(graph, knownPaths) {
33291
- const active = activeTriggerIds(graph);
33768
+ function fileGlobLiveness(graph, knownPaths, triggerIds) {
33769
+ const active = activeTriggerIds(graph) ;
33292
33770
  const paths2 = [...knownPaths];
33293
- const dead = /* @__PURE__ */ new Set();
33771
+ const missing = [];
33294
33772
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33295
- if (trigger.kind !== "file_glob") continue;
33296
- if (!active.has(triggerId)) continue;
33297
- const isMatch = picomatch(trigger.pattern, { dot: true });
33298
- if (!paths2.some((p) => isMatch(p))) dead.add(triggerId);
33773
+ if (trigger.kind !== "file_glob" || !active.has(triggerId)) continue;
33774
+ const matcher = getGlobMatcher(trigger.pattern);
33775
+ if (matcher === null) continue;
33776
+ if (!paths2.some((p) => matcher.test(p))) missing.push([triggerId, trigger.pattern]);
33777
+ }
33778
+ const dead = /* @__PURE__ */ new Set();
33779
+ const pending = /* @__PURE__ */ new Set();
33780
+ if (missing.length === 0) return { dead, pending };
33781
+ const history = gitHistoryOf(knownPaths);
33782
+ for (const [triggerId, pattern] of missing) {
33783
+ const state = missingGlobState(pattern, history);
33784
+ if (state === "dead") dead.add(triggerId);
33785
+ else if (state === "pending") pending.add(triggerId);
33299
33786
  }
33300
- return dead;
33787
+ return { dead, pending };
33301
33788
  }
33302
33789
  function collectDeadFileGlobs(graph, findings, knownPaths) {
33303
- for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
33790
+ for (const triggerId of fileGlobLiveness(graph, knownPaths).dead) {
33304
33791
  findings.push({
33305
33792
  level: "warning",
33306
33793
  code: "DEAD_FILE_GLOB",
33307
- message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file in the working tree \u2014 the lesson is unreachable via this trigger (a rename likely moved the path). Re-point it at the current path, or detach it with \`lessons untrigger\`, or run \`lessons prune --apply\`.`,
33794
+ message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file, and git history shows its path was renamed or deleted \u2014 the lesson is unreachable via this trigger. Re-point it at the current path, or detach it with \`lessons untrigger\`, or run \`lessons prune --apply\`.`,
33308
33795
  triggerId
33309
33796
  });
33310
33797
  }
@@ -33376,14 +33863,14 @@ var STOP = /* @__PURE__ */ new Set([
33376
33863
  "its",
33377
33864
  "must"
33378
33865
  ]);
33379
- function tokenize(text) {
33866
+ function tokenize2(text) {
33380
33867
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
33381
33868
  }
33382
33869
 
33383
33870
  // src/lessons/keyword-signal.ts
33384
33871
  var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
33385
33872
  function isLowSignalKeyword(pattern) {
33386
- return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33873
+ return tokenize2(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33387
33874
  }
33388
33875
  function splitRawTokens(pattern) {
33389
33876
  return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
@@ -33391,7 +33878,7 @@ function splitRawTokens(pattern) {
33391
33878
  function keywordNeedleLosesTokens(pattern) {
33392
33879
  const raw = splitRawTokens(pattern);
33393
33880
  if (raw.length < 2) return false;
33394
- return tokenize(pattern).length !== raw.length;
33881
+ return tokenize2(pattern).length !== raw.length;
33395
33882
  }
33396
33883
 
33397
33884
  // src/lessons/validate-keywords.ts
@@ -33414,7 +33901,7 @@ function collectStopwordKeywords(graph, findings) {
33414
33901
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33415
33902
  if (trigger.kind !== "keyword") continue;
33416
33903
  if (!active.has(triggerId)) continue;
33417
- if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
33904
+ if (tokenize2(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
33418
33905
  continue;
33419
33906
  }
33420
33907
  findings.push({
@@ -33462,7 +33949,6 @@ function validateLessonsGraph(graph, options = {}) {
33462
33949
 
33463
33950
  // src/core/lint/shared/lessons.ts
33464
33951
  var LESSONS_TARGET = "lessons";
33465
- var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
33466
33952
  var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
33467
33953
  var LESSONS_HEADING = /^## Lessons \(/m;
33468
33954
  function lintLessonsSubsystem(projectRoot, scope) {
@@ -33477,7 +33963,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
33477
33963
  return [
33478
33964
  diag(
33479
33965
  "error",
33480
- GRAPH_REL,
33966
+ LESSONS_GRAPH_PATH,
33481
33967
  `lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
33482
33968
  )
33483
33969
  ];
@@ -33485,7 +33971,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
33485
33971
  const knownPaths = listProjectFiles(projectRoot) ?? void 0;
33486
33972
  const report = validateLessonsGraph(graph, { knownPaths });
33487
33973
  for (const finding of report.findings) {
33488
- out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
33974
+ out2.push(diag(finding.level, LESSONS_GRAPH_PATH, `[${finding.code}] ${finding.message}`));
33489
33975
  }
33490
33976
  const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
33491
33977
  const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
@@ -33507,8 +33993,8 @@ function diag(level, file, message) {
33507
33993
  // src/core/lint/linter.ts
33508
33994
  var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
33509
33995
  function isExcludedProjectPath(rel2) {
33510
- const posix8 = rel2.replaceAll("\\", "/");
33511
- return EXCLUDE_DIRS.some((d) => posix8.includes(`/${d}/`) || posix8.startsWith(`${d}/`));
33996
+ const posix9 = rel2.replaceAll("\\", "/");
33997
+ return EXCLUDE_DIRS.some((d) => posix9.includes(`/${d}/`) || posix9.startsWith(`${d}/`));
33512
33998
  }
33513
33999
  async function getProjectFiles(projectRoot) {
33514
34000
  const all = await readDirRecursive(projectRoot);
@@ -33901,14 +34387,22 @@ async function findUntrackedManagedDirFiles(args) {
33901
34387
  return [...found].sort();
33902
34388
  }
33903
34389
 
34390
+ // src/lessons/conflict-markers.ts
34391
+ function hasConflictMarkers(text) {
34392
+ return /^(?:<{7}|>{7})(?: |\r?$)/m.test(text);
34393
+ }
34394
+
33904
34395
  // src/core/check/lock-sync.ts
34396
+ init_fs();
33905
34397
  async function checkLockSync(opts) {
33906
34398
  const { config, configDir, canonicalDir, rootBase, scope = "project" } = opts;
33907
34399
  const lock = await readLock(canonicalDir);
33908
34400
  if (lock === null) {
34401
+ const text = await readFileSafe(join(canonicalDir, ".lock"));
33909
34402
  return {
33910
34403
  inSync: false,
33911
34404
  hasLock: false,
34405
+ lockConflict: text !== null && hasConflictMarkers(text),
33912
34406
  canonicalDrift: false,
33913
34407
  outputDrift: false,
33914
34408
  modified: [],
@@ -33978,6 +34472,7 @@ async function checkLockSync(opts) {
33978
34472
  return {
33979
34473
  inSync,
33980
34474
  hasLock: true,
34475
+ lockConflict: false,
33981
34476
  canonicalDrift,
33982
34477
  outputDrift,
33983
34478
  modified,