agentsmesh 0.39.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
  }
@@ -2867,6 +2886,9 @@ var init_link_token_context = __esm({
2867
2886
  });
2868
2887
 
2869
2888
  // src/core/reference/link-rebaser.ts
2889
+ function isSlashCommandToken(normalizedToken) {
2890
+ return /^\/[A-Za-z][A-Za-z0-9-]*$/.test(normalizedToken);
2891
+ }
2870
2892
  function rewriteFileLinks(input) {
2871
2893
  const missing = /* @__PURE__ */ new Set();
2872
2894
  const protectedRefRanges = protectedRanges(input.content);
@@ -2917,6 +2939,7 @@ function rewriteFileLinks(input) {
2917
2939
  if (targetIsDirectory && !normalizedCandidate.includes("/") && !normalizedCandidate.includes("\\")) {
2918
2940
  return match;
2919
2941
  }
2942
+ if (isSlashCommandToken(normalizedCandidate)) return match;
2920
2943
  if (resolvedBeforeTranslate === null) {
2921
2944
  let normTok = normalizeSeparators(punctStripped);
2922
2945
  if (normTok.startsWith("agentsmesh/")) {
@@ -7041,10 +7064,6 @@ function generateAgents4(canonical) {
7041
7064
  content: serializeAntigravityAgent(agent)
7042
7065
  }));
7043
7066
  }
7044
- function generateIgnore3(canonical) {
7045
- if (canonical.ignore.length === 0) return [];
7046
- return [{ path: ANTIGRAVITY_IGNORE_FILE, content: canonical.ignore.join("\n") }];
7047
- }
7048
7067
  function renderAntigravityGlobalInstructions(canonical) {
7049
7068
  const root = canonical.rules.find((rule) => rule.root);
7050
7069
  const nonRootRules = canonical.rules.filter((rule) => {
@@ -7059,7 +7078,7 @@ function generateHooks2(canonical) {
7059
7078
  if (Object.keys(hooks).length === 0) return [];
7060
7079
  return [{ path: ANTIGRAVITY_HOOKS_FILE, content: JSON.stringify(hooks, null, 2) }];
7061
7080
  }
7062
- var generatePermissions3;
7081
+ var generateIgnore3, generatePermissions3;
7063
7082
  var init_generator4 = __esm({
7064
7083
  "src/targets/antigravity/generator.ts"() {
7065
7084
  init_no_outputs();
@@ -7068,6 +7087,8 @@ var init_generator4 = __esm({
7068
7087
  init_hooks_format2();
7069
7088
  init_agents_format();
7070
7089
  init_constants5();
7090
+ init_ignore_output();
7091
+ generateIgnore3 = ignoreOutput(ANTIGRAVITY_IGNORE_FILE);
7071
7092
  generatePermissions3 = NO_OUTPUTS;
7072
7093
  }
7073
7094
  });
@@ -8661,16 +8682,14 @@ function generateHooks3(canonical) {
8661
8682
  const content = JSON.stringify({ hooks: claudeHooks }, null, 2);
8662
8683
  return [{ path: CLAUDE_SETTINGS, content }];
8663
8684
  }
8664
- function generateIgnore5(canonical) {
8665
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
8666
- const content = canonical.ignore.join("\n");
8667
- return [{ path: CLAUDE_IGNORE, content }];
8668
- }
8685
+ var generateIgnore5;
8669
8686
  var init_generator6 = __esm({
8670
8687
  "src/targets/claude-code/generator.ts"() {
8671
8688
  init_markdown();
8672
8689
  init_constants7();
8673
8690
  init_hooks_format2();
8691
+ init_ignore_output();
8692
+ generateIgnore5 = ignoreOutput(CLAUDE_IGNORE);
8674
8693
  }
8675
8694
  });
8676
8695
  function ruleSectionTitle(rule) {
@@ -10489,11 +10508,7 @@ function generateMcp5(canonical) {
10489
10508
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
10490
10509
  return [{ path: CODEBUFF_MCP_FILE, content: serializeCodebuffMcp(canonical.mcp) }];
10491
10510
  }
10492
- function generateIgnore7(canonical) {
10493
- if (canonical.ignore.length === 0) return [];
10494
- return [{ path: CODEBUFF_IGNORE_FILE, content: canonical.ignore.join("\n") }];
10495
- }
10496
- var generateAgents8, generateHooks5, generatePermissions6;
10511
+ var generateIgnore7, generateAgents8, generateHooks5, generatePermissions6;
10497
10512
  var init_generator8 = __esm({
10498
10513
  "src/targets/codebuff/generator.ts"() {
10499
10514
  init_no_outputs();
@@ -10503,6 +10518,8 @@ var init_generator8 = __esm({
10503
10518
  init_nested_rules();
10504
10519
  init_mcp_format2();
10505
10520
  init_constants9();
10521
+ init_ignore_output();
10522
+ generateIgnore7 = ignoreOutput(CODEBUFF_IGNORE_FILE);
10506
10523
  generateAgents8 = NO_OUTPUTS;
10507
10524
  generateHooks5 = NO_OUTPUTS;
10508
10525
  generatePermissions6 = NO_OUTPUTS;
@@ -13015,53 +13032,37 @@ var init_mcp_generator = __esm({
13015
13032
  }
13016
13033
  });
13017
13034
 
13018
- // src/targets/copilot/hook-entry.ts
13019
- function hasHookCommand2(entry) {
13020
- return hasHookCommand(entry);
13021
- }
13022
- var init_hook_entry = __esm({
13023
- "src/targets/copilot/hook-entry.ts"() {
13024
- init_hook_command();
13025
- }
13026
- });
13027
-
13028
13035
  // src/targets/copilot/hook-format.ts
13029
- function mapHookEvent(event) {
13030
- switch (event) {
13031
- case "PreToolUse":
13032
- return "preToolUse";
13033
- case "PostToolUse":
13034
- return "postToolUse";
13035
- case "Notification":
13036
- return "notification";
13037
- case "UserPromptSubmit":
13038
- return "userPromptSubmitted";
13039
- default:
13040
- return null;
13041
- }
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`;
13042
13048
  }
13043
13049
  function buildCopilotHooksObject(hooks) {
13044
- if (!hooks) return null;
13045
- const result2 = Object.fromEntries(
13046
- Object.entries(hooks).flatMap(([event, entries]) => {
13047
- const mappedEvent = mapHookEvent(event);
13048
- if (!mappedEvent || !Array.isArray(entries)) return [];
13049
- const mappedEntries = entries.filter(
13050
- (entry) => typeof entry === "object" && entry !== null && hasHookCommand2(entry)
13051
- ).map((entry, index) => {
13052
- 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) => {
13053
13056
  const hook = {
13054
13057
  type: "command",
13055
- bash: `./scripts/${safePhase}-${index}.sh`
13058
+ bash: `./scripts/${wrapperScriptName(event, index)}`
13056
13059
  };
13057
13060
  if (entry.matcher && entry.matcher !== "*") hook.matcher = entry.matcher;
13058
13061
  if (entry.timeout !== void 0) hook.timeoutSec = Math.ceil(entry.timeout / 1e3);
13059
13062
  return hook;
13060
- });
13061
- return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
13062
- })
13063
+ })
13064
+ ])
13063
13065
  );
13064
- return Object.keys(result2).length > 0 ? result2 : null;
13065
13066
  }
13066
13067
  function generateHooks8(canonical) {
13067
13068
  const hooks = buildCopilotHooksObject(canonical.hooks);
@@ -13073,10 +13074,24 @@ function generateHooks8(canonical) {
13073
13074
  }
13074
13075
  ];
13075
13076
  }
13077
+ var COPILOT_HOOK_CONTEXT_EVENTS, CANONICAL_TO_COPILOT;
13076
13078
  var init_hook_format = __esm({
13077
13079
  "src/targets/copilot/hook-format.ts"() {
13078
13080
  init_constants32();
13079
- 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
+ ]);
13080
13095
  }
13081
13096
  });
13082
13097
  function ruleSlug2(source) {
@@ -13182,18 +13197,7 @@ var init_generator12 = __esm({
13182
13197
  }
13183
13198
  });
13184
13199
  function mapCopilotHookEvent(event) {
13185
- switch (event) {
13186
- case "preToolUse":
13187
- return "PreToolUse";
13188
- case "postToolUse":
13189
- return "PostToolUse";
13190
- case "notification":
13191
- return "Notification";
13192
- case "userPromptSubmitted":
13193
- return "UserPromptSubmit";
13194
- default:
13195
- return null;
13196
- }
13200
+ return COPILOT_TO_CANONICAL.get(event) ?? null;
13197
13201
  }
13198
13202
  function extractMatcher(comment) {
13199
13203
  if (typeof comment !== "string") return "*";
@@ -13281,11 +13285,16 @@ async function importHooks(projectRoot, results, options = {}) {
13281
13285
  feature: "hooks"
13282
13286
  });
13283
13287
  }
13288
+ var COPILOT_TO_CANONICAL;
13284
13289
  var init_hook_parser = __esm({
13285
13290
  "src/targets/copilot/hook-parser.ts"() {
13286
13291
  init_canonical_paths();
13287
13292
  init_fs();
13288
13293
  init_constants32();
13294
+ init_hook_format();
13295
+ COPILOT_TO_CANONICAL = new Map(
13296
+ [...CANONICAL_TO_COPILOT].map(([canonical, copilot]) => [copilot, canonical])
13297
+ );
13289
13298
  }
13290
13299
  });
13291
13300
  async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
@@ -13522,7 +13531,7 @@ function lintCommands4(canonical) {
13522
13531
  }
13523
13532
  function lintHooks9(canonical) {
13524
13533
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
13525
- const supported = ["PreToolUse", "PostToolUse", "Notification", "UserPromptSubmit"];
13534
+ const supported = [...CANONICAL_TO_COPILOT.keys()];
13526
13535
  const diagnostics = unsupportedHookEventNames(canonical.hooks, supported).map(
13527
13536
  (event) => createUnsupportedHookWarning(event, "copilot", supported, {
13528
13537
  unsupportedBy: "Copilot hooks"
@@ -13545,11 +13554,9 @@ function lintHooks9(canonical) {
13545
13554
  var init_lint10 = __esm({
13546
13555
  "src/targets/copilot/lint.ts"() {
13547
13556
  init_helpers();
13557
+ init_hook_format();
13548
13558
  }
13549
13559
  });
13550
- function safePhaseName(phase) {
13551
- return phase.replace(/[^a-zA-Z0-9]/g, "-").toLowerCase();
13552
- }
13553
13560
  function toRepoRelative(projectRoot, sourcePath) {
13554
13561
  const repoRelative = relative(projectRoot, sourcePath).replace(/\\/g, "/");
13555
13562
  if (!repoRelative || repoRelative.startsWith("../")) return null;
@@ -13577,9 +13584,6 @@ async function buildAssetOutput(projectRoot, command, hooksDirRel) {
13577
13584
  rewrittenCommand: rewriteWrapperCommand(command, repoRelative)
13578
13585
  };
13579
13586
  }
13580
- function wrapperPath(event, index, hooksDirRel) {
13581
- return `${hooksDirRel}/scripts/${safePhaseName(event)}-${index}.sh`;
13582
- }
13583
13587
  function safeShellLine2(value) {
13584
13588
  return value.replace(/[\r\n]+/g, " ");
13585
13589
  }
@@ -13594,15 +13598,13 @@ function buildWrapper(command, matcher) {
13594
13598
  ].join("\n");
13595
13599
  }
13596
13600
  async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel = COPILOT_HOOKS_DIR) {
13597
- if (!canonical.hooks) return outputs;
13601
+ const groups = copilotHookGroups(canonical.hooks);
13602
+ if (groups.length === 0) return outputs;
13598
13603
  const wrapperOutputs = [];
13599
13604
  const assetOutputs = /* @__PURE__ */ new Map();
13600
- for (const [event, entries] of Object.entries(canonical.hooks)) {
13601
- if (!Array.isArray(entries)) continue;
13602
- let index = 0;
13603
- for (const entry of entries) {
13604
- if (!hasHookCommand2(entry)) continue;
13605
- 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)}`;
13606
13608
  let command = entry.command;
13607
13609
  const asset = await buildAssetOutput(projectRoot, entry.command, hooksDirRel);
13608
13610
  if (asset) {
@@ -13616,7 +13618,6 @@ async function addHookScriptAssets(projectRoot, canonical, outputs, hooksDirRel
13616
13618
  'set -eu\nHOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"\n'
13617
13619
  );
13618
13620
  wrapperOutputs.push({ path: scriptPath, content: wrapper });
13619
- index++;
13620
13621
  }
13621
13622
  }
13622
13623
  return [...outputs, ...wrapperOutputs, ...assetOutputs.values()];
@@ -13626,7 +13627,7 @@ var init_hook_assets = __esm({
13626
13627
  "src/targets/copilot/hook-assets.ts"() {
13627
13628
  init_fs();
13628
13629
  init_constants32();
13629
- init_hook_entry();
13630
+ init_hook_format();
13630
13631
  SCRIPT_PREFIX_RE = /^(?<prefix>\s*(?:(?:bash|sh|zsh)\s+)?)["']?(?<path>(?:\.\.\/|\.\/|[^/\s"'`]+\/)[^\s"'`]+)["']?(?<suffix>(?:\s.*)?)$/;
13631
13632
  }
13632
13633
  });
@@ -13944,6 +13945,7 @@ var init_copilot2 = __esm({
13944
13945
  init_command_prompt();
13945
13946
  init_lint10();
13946
13947
  init_hook_assets();
13948
+ init_hook_format();
13947
13949
  init_scope_extras2();
13948
13950
  init_importer_spec2();
13949
13951
  init_capabilities6();
@@ -14074,6 +14076,7 @@ var init_copilot2 = __esm({
14074
14076
  permissions: lintPermissions7
14075
14077
  },
14076
14078
  postProcessHookOutputs: async (projectRoot, canonical, outputs) => addHookScriptAssets(projectRoot, canonical, [...outputs]),
14079
+ hookContextEvents: COPILOT_HOOK_CONTEXT_EVENTS,
14077
14080
  mergeGeneratedOutputContent: mergeCopilotMcpJson,
14078
14081
  project: project7,
14079
14082
  globalSupport: {
@@ -14161,11 +14164,6 @@ function generatePermissions8(canonical) {
14161
14164
  });
14162
14165
  return [{ path: CRUSH_CONFIG_FILE, content: JSON.stringify(crushConfig, null, 2) }];
14163
14166
  }
14164
- function generateIgnore9(canonical) {
14165
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
14166
- const content = canonical.ignore.join("\n");
14167
- return [{ path: CRUSH_IGNORE, content }];
14168
- }
14169
14167
  function buildCrushHooksFromCanonical(canonical) {
14170
14168
  if (!canonical.hooks) return {};
14171
14169
  const result2 = {};
@@ -14183,7 +14181,7 @@ function buildCrushHooksFromCanonical(canonical) {
14183
14181
  }
14184
14182
  return result2;
14185
14183
  }
14186
- var generateRules12;
14184
+ var generateRules12, generateIgnore9;
14187
14185
  var init_generator13 = __esm({
14188
14186
  "src/targets/crush/generator.ts"() {
14189
14187
  init_managed_blocks();
@@ -14192,7 +14190,9 @@ var init_generator13 = __esm({
14192
14190
  init_command_skill();
14193
14191
  init_constants12();
14194
14192
  init_config_format();
14193
+ init_ignore_output();
14195
14194
  generateRules12 = (canonical) => embeddedRootRule(canonical, CRUSH_TARGET, CRUSH_ROOT_FILE);
14195
+ generateIgnore9 = ignoreOutput(CRUSH_IGNORE);
14196
14196
  }
14197
14197
  });
14198
14198
  async function importFromCrush(projectRoot, options = {}) {
@@ -14728,7 +14728,7 @@ function cursorHooksToCanonical(hooks) {
14728
14728
  }
14729
14729
  return result2;
14730
14730
  }
14731
- var CANONICAL_TO_CURSOR, CURSOR_TO_CANONICAL;
14731
+ var CANONICAL_TO_CURSOR, CURSOR_HOOK_CONTEXT_EVENTS, CURSOR_TO_CANONICAL;
14732
14732
  var init_hook_format2 = __esm({
14733
14733
  "src/targets/cursor/hook-format.ts"() {
14734
14734
  init_hook_types();
@@ -14736,6 +14736,7 @@ var init_hook_format2 = __esm({
14736
14736
  CANONICAL_TO_CURSOR = {
14737
14737
  PreToolUse: "preToolUse",
14738
14738
  PostToolUse: "postToolUse",
14739
+ PostToolUseFailure: "postToolUseFailure",
14739
14740
  UserPromptSubmit: "beforeSubmitPrompt",
14740
14741
  SubagentStart: "subagentStart",
14741
14742
  SubagentStop: "subagentStop",
@@ -14744,6 +14745,11 @@ var init_hook_format2 = __esm({
14744
14745
  SessionEnd: "sessionEnd",
14745
14746
  PreCompact: "preCompact"
14746
14747
  };
14748
+ CURSOR_HOOK_CONTEXT_EVENTS = [
14749
+ "SessionStart",
14750
+ "PostToolUse",
14751
+ "PostToolUseFailure"
14752
+ ];
14747
14753
  CURSOR_TO_CANONICAL = new Map(
14748
14754
  Object.entries(CANONICAL_TO_CURSOR).map(([canonical, cursor]) => [cursor, canonical])
14749
14755
  );
@@ -14752,7 +14758,7 @@ var init_hook_format2 = __esm({
14752
14758
 
14753
14759
  // src/targets/cursor/generator/hooks.ts
14754
14760
  function generateHooks10(canonical) {
14755
- if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
14761
+ if (!canonical.hooks) return [];
14756
14762
  const cursorHooks = toCursorHooks(canonical.hooks);
14757
14763
  if (Object.keys(cursorHooks).length === 0) return [];
14758
14764
  const content = JSON.stringify({ version: 1, hooks: cursorHooks }, null, 2);
@@ -14766,14 +14772,12 @@ var init_hooks3 = __esm({
14766
14772
  });
14767
14773
 
14768
14774
  // src/targets/cursor/generator/ignore.ts
14769
- function generateIgnore10(canonical) {
14770
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
14771
- const content = canonical.ignore.join("\n");
14772
- return [{ path: CURSOR_IGNORE, content }];
14773
- }
14775
+ var generateIgnore10;
14774
14776
  var init_ignore = __esm({
14775
14777
  "src/targets/cursor/generator/ignore.ts"() {
14776
14778
  init_constants13();
14779
+ init_ignore_output();
14780
+ generateIgnore10 = ignoreOutput(CURSOR_IGNORE);
14777
14781
  }
14778
14782
  });
14779
14783
 
@@ -15469,6 +15473,7 @@ var init_cursor2 = __esm({
15469
15473
  init_linter13();
15470
15474
  init_import_map_builders();
15471
15475
  init_lint12();
15476
+ init_hook_format2();
15472
15477
  target13 = {
15473
15478
  name: "cursor",
15474
15479
  primaryRootInstructionPath: CURSOR_GENERAL_RULE,
@@ -15590,6 +15595,7 @@ var init_cursor2 = __esm({
15590
15595
  permissions: "native"
15591
15596
  },
15592
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,
15593
15599
  lintRules: lintRules13,
15594
15600
  lint: {
15595
15601
  commands: lintCommands6,
@@ -16851,25 +16857,127 @@ var init_skills3 = __esm({
16851
16857
  }
16852
16858
  });
16853
16859
 
16854
- // src/targets/gemini-cli/generator/settings.ts
16855
- function mapHookEvent2(event) {
16856
- switch (event) {
16857
- case "PreToolUse":
16858
- return "BeforeTool";
16859
- case "PostToolUse":
16860
- return "AfterTool";
16861
- case "Notification":
16862
- return "Notification";
16863
- case "SubagentStart":
16864
- return "BeforeAgent";
16865
- case "SubagentStop":
16866
- return "AfterAgent";
16867
- case "SessionStart":
16868
- return "SessionStart";
16869
- default:
16870
- 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
+ }
16871
16970
  }
16971
+ return Object.keys(result2).length > 0 ? result2 : null;
16872
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
16873
16981
  function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16874
16982
  const settings = {};
16875
16983
  let hasAnyNativeSettings = false;
@@ -16881,29 +16989,10 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16881
16989
  settings.experimental = { enableAgents: true };
16882
16990
  hasAnyNativeSettings = true;
16883
16991
  }
16884
- if (enabledFeatures.has("hooks") && canonical.hooks) {
16885
- const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
16886
- const mappedEvent = mapHookEvent2(event);
16887
- if (!mappedEvent || !Array.isArray(entries)) return [];
16888
- const mappedEntries = entries.filter(
16889
- (entry) => typeof entry === "object" && entry !== null && hasHookCommand(entry)
16890
- ).map((entry, index) => ({
16891
- matcher: entry.matcher,
16892
- hooks: [
16893
- {
16894
- name: `${mappedEvent}-${index + 1}`,
16895
- type: "command",
16896
- command: getHookCommand(entry),
16897
- timeout: entry.timeout
16898
- }
16899
- ]
16900
- }));
16901
- return mappedEntries.length > 0 ? [[mappedEvent, mappedEntries]] : [];
16902
- });
16903
- if (hookEntries.length > 0) {
16904
- settings.hooks = Object.fromEntries(hookEntries);
16905
- hasAnyNativeSettings = true;
16906
- }
16992
+ const hooks = enabledFeatures.has("hooks") ? buildGeminiHooks(canonical.hooks) : null;
16993
+ if (hooks) {
16994
+ settings.hooks = hooks;
16995
+ hasAnyNativeSettings = true;
16907
16996
  }
16908
16997
  if (hasAnyNativeSettings) {
16909
16998
  settings.context = { fileName: [GEMINI_ROOT, GEMINI_COMPAT_AGENTS] };
@@ -16913,19 +17002,18 @@ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
16913
17002
  }
16914
17003
  var init_settings2 = __esm({
16915
17004
  "src/targets/gemini-cli/generator/settings.ts"() {
16916
- init_hook_command();
16917
17005
  init_constants33();
17006
+ init_hooks4();
16918
17007
  }
16919
17008
  });
16920
17009
 
16921
17010
  // src/targets/gemini-cli/generator/ignore.ts
16922
- function generateIgnore13(canonical) {
16923
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
16924
- return [{ path: GEMINI_IGNORE, content: canonical.ignore.join("\n") }];
16925
- }
17011
+ var generateIgnore13;
16926
17012
  var init_ignore2 = __esm({
16927
17013
  "src/targets/gemini-cli/generator/ignore.ts"() {
16928
17014
  init_constants33();
17015
+ init_ignore_output();
17016
+ generateIgnore13 = ignoreOutput(GEMINI_IGNORE);
16929
17017
  }
16930
17018
  });
16931
17019
 
@@ -17204,27 +17292,6 @@ var init_layout6 = __esm({
17204
17292
  };
17205
17293
  }
17206
17294
  });
17207
- function mapGeminiHookEvent(event) {
17208
- switch (event) {
17209
- case "BeforeTool":
17210
- case "preToolUse":
17211
- return "PreToolUse";
17212
- case "AfterTool":
17213
- case "postToolUse":
17214
- return "PostToolUse";
17215
- case "Notification":
17216
- case "notification":
17217
- return "Notification";
17218
- case "BeforeAgent":
17219
- return "SubagentStart";
17220
- case "AfterAgent":
17221
- return "SubagentStop";
17222
- case "SessionStart":
17223
- return "SessionStart";
17224
- default:
17225
- return null;
17226
- }
17227
- }
17228
17295
  function parseFlexibleFrontmatter(content) {
17229
17296
  const yamlOpen = content.indexOf("---");
17230
17297
  const tomlOpen = content.indexOf("+++");
@@ -17298,7 +17365,7 @@ async function importGeminiSettings(projectRoot, results) {
17298
17365
  (entry) => entry.hooks.filter(
17299
17366
  (hook) => hook !== null && typeof hook === "object" && hasHookCommand(hook)
17300
17367
  ).map((hook) => ({
17301
- matcher: entry.matcher,
17368
+ matcher: fromGeminiMatcher(event, entry.matcher),
17302
17369
  command: getHookCommand(hook),
17303
17370
  type: "command",
17304
17371
  timeout: typeof hook.timeout === "number" ? hook.timeout : void 0
@@ -17308,7 +17375,7 @@ async function importGeminiSettings(projectRoot, results) {
17308
17375
  const legacyMapped = value.filter(
17309
17376
  (entry) => entry !== null && typeof entry === "object" && typeof entry.matcher === "string" && hasHookCommand(entry)
17310
17377
  ).map((entry) => ({
17311
- matcher: entry.matcher,
17378
+ matcher: fromGeminiMatcher(event, entry.matcher),
17312
17379
  command: getHookCommand(entry),
17313
17380
  type: "command"
17314
17381
  }));
@@ -17337,7 +17404,7 @@ var init_format_helpers_settings = __esm({
17337
17404
  init_hook_command();
17338
17405
  init_fs();
17339
17406
  init_constants33();
17340
- init_format_helpers_shared();
17407
+ init_hook_map();
17341
17408
  }
17342
17409
  });
17343
17410
  async function importGeminiIgnore(projectRoot, results) {
@@ -17833,14 +17900,7 @@ function lintPermissions10(canonical, options) {
17833
17900
  }
17834
17901
  function lintHooks12(canonical) {
17835
17902
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
17836
- const supported = [
17837
- "PreToolUse",
17838
- "PostToolUse",
17839
- "Notification",
17840
- "SubagentStart",
17841
- "SubagentStop",
17842
- "SessionStart"
17843
- ];
17903
+ const supported = [...CANONICAL_TO_GEMINI.keys()];
17844
17904
  return unsupportedHookEventNames(canonical.hooks, supported).map(
17845
17905
  (event) => createUnsupportedHookWarning(event, "gemini-cli", supported)
17846
17906
  );
@@ -17848,6 +17908,7 @@ function lintHooks12(canonical) {
17848
17908
  var init_lint15 = __esm({
17849
17909
  "src/targets/gemini-cli/lint.ts"() {
17850
17910
  init_helpers();
17911
+ init_hook_map();
17851
17912
  }
17852
17913
  });
17853
17914
 
@@ -17884,6 +17945,7 @@ var init_gemini_cli2 = __esm({
17884
17945
  init_import_map_builders();
17885
17946
  init_lint15();
17886
17947
  init_scoped_settings_emit();
17948
+ init_hook_map();
17887
17949
  init_settings();
17888
17950
  target16 = {
17889
17951
  name: "gemini-cli",
@@ -17934,6 +17996,7 @@ var init_gemini_cli2 = __esm({
17934
17996
  permissions: lintPermissions10
17935
17997
  },
17936
17998
  emitScopedSettings: emitScopedGeminiSettings,
17999
+ hookContextEvents: GEMINI_HOOK_CONTEXT_EVENTS,
17937
18000
  mergeGeneratedOutputContent(existing, pending, newContent, resolvedPath) {
17938
18001
  const base = pending?.content ?? existing;
17939
18002
  if (base !== null && resolvedPath === GEMINI_SETTINGS) {
@@ -18026,14 +18089,10 @@ function generateAgents17(canonical) {
18026
18089
  content: serializeProjectedAgentSkill(agent)
18027
18090
  }));
18028
18091
  }
18029
- function generateIgnore14(canonical) {
18030
- if (canonical.ignore.length === 0) return [];
18031
- return [{ path: GOOSE_IGNORE, content: canonical.ignore.join("\n") }];
18032
- }
18033
18092
  function generateHooks12(canonical) {
18034
18093
  return buildWrappedCommandHooks(canonical, GOOSE_HOOKS_FILE);
18035
18094
  }
18036
- var generateRules17, generatePermissions12;
18095
+ var generateRules17, generateIgnore14, generatePermissions12;
18037
18096
  var init_generator20 = __esm({
18038
18097
  "src/targets/goose/generator.ts"() {
18039
18098
  init_managed_blocks();
@@ -18043,7 +18102,9 @@ var init_generator20 = __esm({
18043
18102
  init_command_skill();
18044
18103
  init_wrapped_command_hooks();
18045
18104
  init_constants16();
18105
+ init_ignore_output();
18046
18106
  generateRules17 = (canonical) => embeddedRootRule(canonical, GOOSE_TARGET, GOOSE_ROOT_FILE);
18107
+ generateIgnore14 = ignoreOutput(GOOSE_IGNORE);
18047
18108
  generatePermissions12 = NO_OUTPUTS;
18048
18109
  }
18049
18110
  });
@@ -18948,10 +19009,6 @@ function generateAgents18(canonical) {
18948
19009
  };
18949
19010
  });
18950
19011
  }
18951
- function generateIgnore16(canonical) {
18952
- if (canonical.ignore.length === 0) return [];
18953
- return [{ path: JUNIE_IGNORE, content: canonical.ignore.join("\n") }];
18954
- }
18955
19012
  function generateSkills17(canonical) {
18956
19013
  return generateEmbeddedSkills(canonical, JUNIE_SKILLS_DIR);
18957
19014
  }
@@ -18963,6 +19020,7 @@ function renderJunieGlobalInstructions(canonical) {
18963
19020
  });
18964
19021
  return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
18965
19022
  }
19023
+ var generateIgnore16;
18966
19024
  var init_generator22 = __esm({
18967
19025
  "src/targets/junie/generator.ts"() {
18968
19026
  init_mcp_servers();
@@ -18970,7 +19028,9 @@ var init_generator22 = __esm({
18970
19028
  init_managed_blocks();
18971
19029
  init_markdown();
18972
19030
  init_constants18();
19031
+ init_ignore_output();
18973
19032
  init_global_config2();
19033
+ generateIgnore16 = ignoreOutput(JUNIE_IGNORE);
18974
19034
  }
18975
19035
  });
18976
19036
 
@@ -19386,10 +19446,6 @@ function generateMcp15(canonical) {
19386
19446
  }
19387
19447
  ];
19388
19448
  }
19389
- function generateIgnore17(canonical) {
19390
- if (canonical.ignore.length === 0) return [];
19391
- return [{ path: KILO_CODE_IGNORE, content: canonical.ignore.join("\n") }];
19392
- }
19393
19449
  function generatePermissions15(canonical) {
19394
19450
  if (!canonical.permissions) return [];
19395
19451
  const { allow, deny } = canonical.permissions;
@@ -19404,11 +19460,14 @@ function generatePermissions15(canonical) {
19404
19460
  function generateSkills18(canonical) {
19405
19461
  return generateEmbeddedSkills(canonical, KILO_CODE_SKILLS_DIR);
19406
19462
  }
19463
+ var generateIgnore17;
19407
19464
  var init_generator23 = __esm({
19408
19465
  "src/targets/kilo-code/generator.ts"() {
19409
19466
  init_embedded_skill();
19410
19467
  init_markdown();
19411
19468
  init_constants19();
19469
+ init_ignore_output();
19470
+ generateIgnore17 = ignoreOutput(KILO_CODE_IGNORE);
19412
19471
  }
19413
19472
  });
19414
19473
  var kiloNonRootRuleMapper, kiloCommandMapper, kiloAgentMapper;
@@ -21153,11 +21212,7 @@ function buildKiroAgentOutputs(canonical, rules = []) {
21153
21212
  function generateAgents21(canonical) {
21154
21213
  return buildKiroAgentOutputs(canonical);
21155
21214
  }
21156
- function generateIgnore18(canonical) {
21157
- if (canonical.ignore.length === 0) return [];
21158
- return [{ path: KIRO_IGNORE, content: canonical.ignore.join("\n") }];
21159
- }
21160
- var generatePermissions17;
21215
+ var generateIgnore18, generatePermissions17;
21161
21216
  var init_generator25 = __esm({
21162
21217
  "src/targets/kiro/generator.ts"() {
21163
21218
  init_no_outputs();
@@ -21166,6 +21221,8 @@ var init_generator25 = __esm({
21166
21221
  init_markdown();
21167
21222
  init_hook_format3();
21168
21223
  init_constants21();
21224
+ init_ignore_output();
21225
+ generateIgnore18 = ignoreOutput(KIRO_IGNORE);
21169
21226
  generatePermissions17 = NO_OUTPUTS;
21170
21227
  }
21171
21228
  });
@@ -23808,10 +23865,6 @@ function generateMcp19(canonical) {
23808
23865
  const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
23809
23866
  return [{ path: QWEN_SETTINGS, content }];
23810
23867
  }
23811
- function generateIgnore21(canonical) {
23812
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
23813
- return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
23814
- }
23815
23868
  function generateHooks18(canonical) {
23816
23869
  if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
23817
23870
  const hooks = buildClaudeHooksObjectFromCanonical(canonical);
@@ -23829,11 +23882,14 @@ function generatePermissions21(canonical) {
23829
23882
  if (ask.length > 0) permissions.ask = ask;
23830
23883
  return [{ path: QWEN_SETTINGS, content: JSON.stringify({ permissions }, null, 2) }];
23831
23884
  }
23885
+ var generateIgnore21;
23832
23886
  var init_generator29 = __esm({
23833
23887
  "src/targets/qwen-code/generator.ts"() {
23834
23888
  init_markdown();
23835
23889
  init_hooks_format2();
23836
23890
  init_constants25();
23891
+ init_ignore_output();
23892
+ generateIgnore21 = ignoreOutput(QWEN_IGNORE);
23837
23893
  }
23838
23894
  });
23839
23895
 
@@ -25531,10 +25587,6 @@ function generateMcp23(canonical) {
25531
25587
  }
25532
25588
  ];
25533
25589
  }
25534
- function generateIgnore24(canonical) {
25535
- if (canonical.ignore.length === 0) return [];
25536
- return [{ path: TRAE_IGNORE, content: canonical.ignore.join("\n") }];
25537
- }
25538
25590
  function generateHooks20(canonical) {
25539
25591
  if (!canonical.hooks) return [];
25540
25592
  const hooks = {};
@@ -25555,12 +25607,15 @@ function generateHooks20(canonical) {
25555
25607
  if (Object.keys(hooks).length === 0) return [];
25556
25608
  return [{ path: TRAE_HOOKS_FILE, content: JSON.stringify({ version: 1, hooks }, null, 2) }];
25557
25609
  }
25610
+ var generateIgnore24;
25558
25611
  var init_generator33 = __esm({
25559
25612
  "src/targets/trae/generator.ts"() {
25560
25613
  init_embedded_skill();
25561
25614
  init_markdown();
25562
25615
  init_hook_command();
25563
25616
  init_constants29();
25617
+ init_ignore_output();
25618
+ generateIgnore24 = ignoreOutput(TRAE_IGNORE);
25564
25619
  }
25565
25620
  });
25566
25621
 
@@ -26267,11 +26322,7 @@ function generateMcp24(canonical, ctx) {
26267
26322
  const content = JSON.stringify({ mcpServers: canonical.mcp.mcpServers }, null, 2);
26268
26323
  return [{ path, content }];
26269
26324
  }
26270
- function generateIgnore25(canonical) {
26271
- if (canonical.ignore.length === 0) return [];
26272
- return [{ path: WARP_IGNORE_FILE, content: canonical.ignore.join("\n") }];
26273
- }
26274
- var generateRules31, generatePermissions24, generateHooks21;
26325
+ var generateRules31, generatePermissions24, generateHooks21, generateIgnore25;
26275
26326
  var init_generator34 = __esm({
26276
26327
  "src/targets/warp/generator.ts"() {
26277
26328
  init_managed_blocks();
@@ -26280,9 +26331,11 @@ var init_generator34 = __esm({
26280
26331
  init_projected_agent_skill();
26281
26332
  init_command_skill();
26282
26333
  init_constants30();
26334
+ init_ignore_output();
26283
26335
  generateRules31 = (canonical) => embeddedRootRule(canonical, WARP_TARGET, WARP_ROOT_FILE);
26284
26336
  generatePermissions24 = NO_OUTPUTS;
26285
26337
  generateHooks21 = NO_OUTPUTS;
26338
+ generateIgnore25 = ignoreOutput(WARP_IGNORE_FILE);
26286
26339
  }
26287
26340
  });
26288
26341
 
@@ -26907,13 +26960,12 @@ var init_rules4 = __esm({
26907
26960
  });
26908
26961
 
26909
26962
  // src/targets/windsurf/generator/ignore.ts
26910
- function generateIgnore26(canonical) {
26911
- if (!canonical.ignore || canonical.ignore.length === 0) return [];
26912
- return [{ path: CODEIUM_IGNORE, content: canonical.ignore.join("\n") }];
26913
- }
26963
+ var generateIgnore26;
26914
26964
  var init_ignore3 = __esm({
26915
26965
  "src/targets/windsurf/generator/ignore.ts"() {
26916
26966
  init_constants34();
26967
+ init_ignore_output();
26968
+ generateIgnore26 = ignoreOutput(CODEIUM_IGNORE);
26917
26969
  }
26918
26970
  });
26919
26971
 
@@ -26979,7 +27031,7 @@ function canonicalHookEventName(event) {
26979
27031
  if (KNOWN_CANONICAL_HOOK_EVENTS.includes(event)) return event;
26980
27032
  return WINDSURF_TO_CANONICAL.get(event) ?? null;
26981
27033
  }
26982
- var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_TO_CANONICAL;
27034
+ var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_HOOK_CONTEXT_EVENTS, WINDSURF_TO_CANONICAL;
26983
27035
  var init_hook_events = __esm({
26984
27036
  "src/targets/windsurf/hook-events.ts"() {
26985
27037
  init_hook_types();
@@ -26992,6 +27044,7 @@ var init_hook_events = __esm({
26992
27044
  "SubagentStop",
26993
27045
  ...BEST_EFFORT_HOOK_EVENTS
26994
27046
  ];
27047
+ WINDSURF_HOOK_CONTEXT_EVENTS = [];
26995
27048
  WINDSURF_TO_CANONICAL = new Map(
26996
27049
  KNOWN_CANONICAL_HOOK_EVENTS.map((event) => [windsurfEventName(event), event])
26997
27050
  );
@@ -27017,12 +27070,12 @@ function toWindsurfHooks(hooks) {
27017
27070
  return result2;
27018
27071
  }
27019
27072
  function generateHooks22(canonical) {
27020
- if (!canonical.hooks || Object.keys(canonical.hooks).length === 0) return [];
27073
+ if (!canonical.hooks) return [];
27021
27074
  const hooks = toWindsurfHooks(canonical.hooks);
27022
27075
  if (Object.keys(hooks).length === 0) return [];
27023
27076
  return [{ path: WINDSURF_HOOKS_FILE, content: JSON.stringify({ hooks }, null, 2) }];
27024
27077
  }
27025
- var init_hooks4 = __esm({
27078
+ var init_hooks5 = __esm({
27026
27079
  "src/targets/windsurf/generator/hooks.ts"() {
27027
27080
  init_hook_command();
27028
27081
  init_constants34();
@@ -27074,7 +27127,7 @@ var init_generator35 = __esm({
27074
27127
  init_workflows();
27075
27128
  init_agents4();
27076
27129
  init_mcp4();
27077
- init_hooks4();
27130
+ init_hooks5();
27078
27131
  init_skills4();
27079
27132
  init_permissions6();
27080
27133
  }
@@ -27193,7 +27246,7 @@ async function parseHooks(hooksPath, onParseError) {
27193
27246
  return result2;
27194
27247
  }
27195
27248
  var VALID_TYPES;
27196
- var init_hooks5 = __esm({
27249
+ var init_hooks6 = __esm({
27197
27250
  "src/canonical/features/hooks.ts"() {
27198
27251
  init_syntax_error();
27199
27252
  init_fs();
@@ -27274,7 +27327,7 @@ var WILDCARD_MATCHER;
27274
27327
  var init_importer_hooks2 = __esm({
27275
27328
  "src/targets/windsurf/importer-hooks.ts"() {
27276
27329
  init_canonical_paths();
27277
- init_hooks5();
27330
+ init_hooks6();
27278
27331
  init_fs();
27279
27332
  init_constants34();
27280
27333
  init_hook_events();
@@ -27575,6 +27628,7 @@ var init_windsurf2 = __esm({
27575
27628
  init_merge14();
27576
27629
  init_linter32();
27577
27630
  init_lint31();
27631
+ init_hook_events();
27578
27632
  init_import_map_builders();
27579
27633
  init_conversions();
27580
27634
  init_projected_agent_skill();
@@ -27706,6 +27760,7 @@ var init_windsurf2 = __esm({
27706
27760
  },
27707
27761
  emptyImportMessage: "No Windsurf config found (.windsurfrules, .windsurf/rules, .windsurfignore, or .codeiumignore).",
27708
27762
  supportsConversion: { agents: true },
27763
+ hookContextEvents: WINDSURF_HOOK_CONTEXT_EVENTS,
27709
27764
  lintRules: lintRules32,
27710
27765
  lint: {
27711
27766
  commands: lintCommands10,
@@ -28850,6 +28905,23 @@ var init_builtin_targets = __esm({
28850
28905
  }
28851
28906
  });
28852
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
+
28853
28925
  // src/core/generate/engine.ts
28854
28926
  init_builtin_targets();
28855
28927
  init_registry();
@@ -29752,11 +29824,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
29752
29824
  const gen = resolveTargetFeatureGenerator(target34, "hooks", config, scope) ?? getDescriptor(target34)?.generators.generateHooks;
29753
29825
  if (!gen) continue;
29754
29826
  const ctx = featureContext(target34, "hooks", scope);
29755
- let outputs = [...gen(canonical, ctx)];
29827
+ const projected = withTargetRecallHooks(canonical, target34);
29828
+ let outputs = [...gen(projected, ctx)];
29756
29829
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29757
29830
  const post = descriptor34?.postProcessHookOutputs;
29758
29831
  if (post) {
29759
- outputs = [...await post(projectRoot, canonical, outputs)];
29832
+ outputs = [...await post(projectRoot, projected, outputs)];
29760
29833
  }
29761
29834
  const options = outputMergeOptions(target34);
29762
29835
  for (const out2 of outputs) {
@@ -29769,7 +29842,7 @@ async function generateScopedSettingsFeature(results, targets, canonical, projec
29769
29842
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29770
29843
  const emit = descriptor34?.emitScopedSettings;
29771
29844
  if (!emit) continue;
29772
- const outputs = emit(canonical, scope, enabledFeatures);
29845
+ const outputs = emit(withTargetRecallHooks(canonical, target34), scope, enabledFeatures);
29773
29846
  if (outputs.length === 0) continue;
29774
29847
  const options = outputMergeOptions(target34);
29775
29848
  for (const out2 of outputs) {
@@ -29812,7 +29885,12 @@ async function generate(ctx) {
29812
29885
  const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
29813
29886
  const scopeExtras = descriptor34?.globalSupport?.scopeExtras;
29814
29887
  if (scopeExtras) {
29815
- const extras = await scopeExtras(canonical, projectRoot, scope, enabledFeatures);
29888
+ const extras = await scopeExtras(
29889
+ withTargetRecallHooks(canonical, target34),
29890
+ projectRoot,
29891
+ scope,
29892
+ enabledFeatures
29893
+ );
29816
29894
  await emitScopeExtras(results, target34, extras, projectRoot);
29817
29895
  }
29818
29896
  }
@@ -31119,7 +31197,7 @@ async function parsePermissions(permissionsPath, onParseError) {
31119
31197
  }
31120
31198
 
31121
31199
  // src/canonical/load/loader.ts
31122
- init_hooks5();
31200
+ init_hooks6();
31123
31201
 
31124
31202
  // src/canonical/features/ignore.ts
31125
31203
  init_fs();
@@ -31168,10 +31246,10 @@ function ruleSlug4(r) {
31168
31246
  function mergeByKey(base, overlay, key) {
31169
31247
  return [...new Map([...base, ...overlay].map((item) => [key(item), item])).values()];
31170
31248
  }
31171
- function mergeCanonicalFiles(base, overlay) {
31249
+ function mergeCanonicalFiles(base, overlay, options = {}) {
31172
31250
  const mcp = mergeMcp7(base.mcp, overlay.mcp);
31173
31251
  const permissions = mergePermissions2(base.permissions, overlay.permissions);
31174
- const hooks = mergeHooks5(base.hooks, overlay.hooks);
31252
+ const hooks = options.hooks === "combine" ? combineHooks(base.hooks, overlay.hooks) : overrideHooks(base.hooks, overlay.hooks);
31175
31253
  const ignore = mergeUniqueStrings(base.ignore, overlay.ignore);
31176
31254
  return {
31177
31255
  rules: mergeByKey(base.rules, overlay.rules, ruleSlug4),
@@ -31210,17 +31288,31 @@ function mergeUniqueStrings(base, overlay) {
31210
31288
  }
31211
31289
  return merged;
31212
31290
  }
31213
- function mergeHooks5(base, overlay) {
31291
+ function overrideHooks(base, overlay) {
31214
31292
  if (!base && !overlay) return null;
31215
31293
  const result2 = {};
31216
- const keys = /* @__PURE__ */ new Set([...Object.keys(base ?? {}), ...Object.keys(overlay ?? {})]);
31217
- for (const k of keys) {
31294
+ for (const k of hookEvents(base, overlay)) {
31218
31295
  const o = overlay?.[k];
31219
- const b = base?.[k];
31220
- 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)))];
31221
31307
  }
31222
31308
  return result2;
31223
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
+ }
31224
31316
 
31225
31317
  // src/config/resolve/native-format-detector.ts
31226
31318
  init_fs();
@@ -31991,7 +32083,7 @@ function gateExtendElevatedArtifacts(canonical, ext) {
31991
32083
  });
31992
32084
  }
31993
32085
  init_mcp();
31994
- init_hooks5();
32086
+ init_hooks6();
31995
32087
 
31996
32088
  // src/install/pack/pack-reader.ts
31997
32089
  init_fs();
@@ -32078,7 +32170,7 @@ async function loadPacksCanonical(abDir) {
32078
32170
  const canonical = await loadPackCanonical(packDir);
32079
32171
  const filtered = filterCanonicalByFeatures(canonical, meta.features);
32080
32172
  const picked = applyExtendPick(filtered, meta.features, meta.pick, meta.name);
32081
- merged = mergeCanonicalFiles(merged, picked);
32173
+ merged = mergeCanonicalFiles(merged, picked, { hooks: "combine" });
32082
32174
  }
32083
32175
  return merged;
32084
32176
  }
@@ -32129,9 +32221,10 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
32129
32221
  merged = mergeCanonicalFiles(merged, picked);
32130
32222
  }
32131
32223
  const packsCanonical = await loadPacksCanonical(canonicalDir);
32132
- merged = mergeCanonicalFiles(merged, packsCanonical);
32224
+ merged = mergeCanonicalFiles(merged, packsCanonical, { hooks: "combine" });
32133
32225
  const localCanonical = await loadCanonicalFiles(canonicalDir);
32134
32226
  merged = mergeCanonicalFiles(merged, localCanonical);
32227
+ merged = { ...merged, hooks: combineHooks(merged.hooks, packsCanonical.hooks) };
32135
32228
  return { canonical: merged, resolvedExtends };
32136
32229
  }
32137
32230
 
@@ -32410,6 +32503,9 @@ function lintRuleScopeInversion(input) {
32410
32503
  }
32411
32504
  return out2;
32412
32505
  }
32506
+
32507
+ // src/lessons/graph-store.ts
32508
+ init_fs_text_encoding();
32413
32509
  var VersionSchema = z.union([z.literal(1), z.literal(2)]);
32414
32510
  var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
32415
32511
  var DateSchema = z.string().regex(
@@ -32453,13 +32549,13 @@ function parseGraph(raw) {
32453
32549
  }
32454
32550
 
32455
32551
  // src/lessons/graph-store.ts
32456
- var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
32552
+ var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
32457
32553
  function graphFilePath(projectRoot) {
32458
- return resolve(projectRoot, GRAPH_REL_PATH);
32554
+ return resolve(projectRoot, LESSONS_GRAPH_PATH);
32459
32555
  }
32460
32556
  function loadLessonsGraph(projectRoot) {
32461
32557
  const raw = readFileSync(graphFilePath(projectRoot), "utf8");
32462
- return parseGraph(JSON.parse(raw));
32558
+ return parseGraph(JSON.parse(stripBom(raw)));
32463
32559
  }
32464
32560
  var BASE_REL = ".agentsmesh/lessons";
32465
32561
  function lessonsPaths(projectRoot) {
@@ -32476,9 +32572,76 @@ function lessonsPaths(projectRoot) {
32476
32572
  function toRelPath(projectRoot, absolute) {
32477
32573
  return relative(projectRoot, absolute).split(sep).join("/");
32478
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
32479
32636
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
32480
32637
  var MAX_FILES = 2e5;
32481
- 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) {
32482
32645
  const out2 = /* @__PURE__ */ new Set();
32483
32646
  try {
32484
32647
  const stack = [projectRoot];
@@ -32489,14 +32652,14 @@ function listProjectFiles(projectRoot) {
32489
32652
  if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
32490
32653
  } else if (entry.isFile()) {
32491
32654
  out2.add(toRelPath(projectRoot, join(dir, entry.name)));
32492
- if (out2.size > MAX_FILES) return out2;
32655
+ if (out2.size > maxFiles) return null;
32493
32656
  }
32494
32657
  }
32495
32658
  }
32496
32659
  } catch {
32497
32660
  return null;
32498
32661
  }
32499
- return out2;
32662
+ return projectFilesOf(out2, () => readGitPathHistory(projectRoot));
32500
32663
  }
32501
32664
 
32502
32665
  // src/lessons/validate-checks.ts
@@ -32673,10 +32836,307 @@ function collectOrphans(graph, findings) {
32673
32836
  }
32674
32837
  }
32675
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
32676
33010
  function normalizeRule(rule) {
32677
33011
  return rule.trim().replace(/\s+/g, " ").toLowerCase();
32678
33012
  }
32679
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
+
32680
33140
  // src/lessons/regex-linear/nfa-compile.ts
32681
33141
  var MAX_NFA_STATES = 2e3;
32682
33142
  var Builder = class {
@@ -32868,6 +33328,9 @@ function escapeClass(c2) {
32868
33328
  var HEX2 = /^[0-9a-fA-F]{2}$/;
32869
33329
  var HEX4 = /^[0-9a-fA-F]{4}$/;
32870
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
+ }
32871
33334
  if (c2 === "x") {
32872
33335
  const hex2 = src.slice(i, i + 2);
32873
33336
  return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
@@ -32957,7 +33420,7 @@ function parseRegex(src) {
32957
33420
  function parseAtom() {
32958
33421
  const c2 = peek();
32959
33422
  if (c2 === "(") return parseGroup2();
32960
- if (c2 === "[") return parseClass();
33423
+ if (c2 === "[") return parseClass2();
32961
33424
  if (c2 === "\\") return parseEscape();
32962
33425
  if (c2 === ".") {
32963
33426
  i += 1;
@@ -33016,7 +33479,7 @@ function parseRegex(src) {
33016
33479
  }
33017
33480
  return { k: "char", ch: escapeLiteral(c2) };
33018
33481
  }
33019
- function parseClass() {
33482
+ function parseClass2() {
33020
33483
  i += 1;
33021
33484
  const negate = peek() === "^";
33022
33485
  if (negate) i += 1;
@@ -33069,17 +33532,17 @@ function parseRegex(src) {
33069
33532
  }
33070
33533
 
33071
33534
  // src/lessons/regex-linear/index.ts
33072
- var cache = /* @__PURE__ */ new Map();
33535
+ var cache3 = /* @__PURE__ */ new Map();
33073
33536
  function compileLinearMatcher(pattern) {
33074
- const hit = cache.get(pattern);
33075
- 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;
33076
33539
  let matcher;
33077
33540
  try {
33078
33541
  matcher = buildMatcher(parseRegex(pattern));
33079
33542
  } catch {
33080
33543
  matcher = null;
33081
33544
  }
33082
- cache.set(pattern, matcher);
33545
+ cache3.set(pattern, matcher);
33083
33546
  return matcher;
33084
33547
  }
33085
33548
 
@@ -33120,6 +33583,8 @@ function collectDuplicateRules(graph, findings) {
33120
33583
  }
33121
33584
  function collectInvalidTriggerPatterns(graph, findings) {
33122
33585
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33586
+ const globFinding = unsafeGlobFinding(triggerId, trigger);
33587
+ if (globFinding !== null) findings.push(globFinding);
33123
33588
  if (trigger.kind !== "command_pattern") continue;
33124
33589
  try {
33125
33590
  new RegExp(trigger.pattern);
@@ -33127,7 +33592,7 @@ function collectInvalidTriggerPatterns(graph, findings) {
33127
33592
  findings.push({
33128
33593
  level: "error",
33129
33594
  code: "INVALID_TRIGGER_PATTERN",
33130
- 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)}.`,
33131
33596
  triggerId
33132
33597
  });
33133
33598
  continue;
@@ -33136,12 +33601,16 @@ function collectInvalidTriggerPatterns(graph, findings) {
33136
33601
  findings.push({
33137
33602
  level: "error",
33138
33603
  code: "UNSAFE_TRIGGER_PATTERN",
33139
- 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.`,
33140
33605
  triggerId
33141
33606
  });
33142
33607
  }
33143
33608
  }
33144
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
+ }
33145
33614
  function collectBackslashGlobPatterns(graph, findings) {
33146
33615
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33147
33616
  if (trigger.kind !== "file_glob") continue;
@@ -33222,7 +33691,11 @@ function collectTriggerSetCollisions(graph, findings) {
33222
33691
 
33223
33692
  // src/lessons/glob-breadth.ts
33224
33693
  var WILDCARD = /[*?[\]]/;
33694
+ function isNegatedGlob(pattern) {
33695
+ return pattern.startsWith("!");
33696
+ }
33225
33697
  function globNarrowness(pattern) {
33698
+ if (isNegatedGlob(pattern)) return 0;
33226
33699
  const segments = pattern.replaceAll("\\", "/").split("/").filter((segment) => segment !== "" && segment !== ".");
33227
33700
  if (segments.length === 0) return 0;
33228
33701
  let literal = 0;
@@ -33273,6 +33746,15 @@ function isBroadCommandPattern(pattern) {
33273
33746
  }
33274
33747
  return hits > COMMAND_PROBE_CORPUS.length * BROAD_HIT_RATIO;
33275
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
+ }
33276
33758
 
33277
33759
  // src/lessons/validate-liveness.ts
33278
33760
  function activeTriggerIds(graph) {
@@ -33283,24 +33765,33 @@ function activeTriggerIds(graph) {
33283
33765
  }
33284
33766
  return ids;
33285
33767
  }
33286
- function deadFileGlobIds(graph, knownPaths) {
33287
- const active = activeTriggerIds(graph);
33768
+ function fileGlobLiveness(graph, knownPaths, triggerIds) {
33769
+ const active = activeTriggerIds(graph) ;
33288
33770
  const paths2 = [...knownPaths];
33289
- const dead = /* @__PURE__ */ new Set();
33771
+ const missing = [];
33290
33772
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33291
- if (trigger.kind !== "file_glob") continue;
33292
- if (!active.has(triggerId)) continue;
33293
- const isMatch = picomatch(trigger.pattern, { dot: true });
33294
- 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);
33295
33786
  }
33296
- return dead;
33787
+ return { dead, pending };
33297
33788
  }
33298
33789
  function collectDeadFileGlobs(graph, findings, knownPaths) {
33299
- for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
33790
+ for (const triggerId of fileGlobLiveness(graph, knownPaths).dead) {
33300
33791
  findings.push({
33301
33792
  level: "warning",
33302
33793
  code: "DEAD_FILE_GLOB",
33303
- 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\`.`,
33304
33795
  triggerId
33305
33796
  });
33306
33797
  }
@@ -33372,14 +33863,14 @@ var STOP = /* @__PURE__ */ new Set([
33372
33863
  "its",
33373
33864
  "must"
33374
33865
  ]);
33375
- function tokenize(text) {
33866
+ function tokenize2(text) {
33376
33867
  return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
33377
33868
  }
33378
33869
 
33379
33870
  // src/lessons/keyword-signal.ts
33380
33871
  var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
33381
33872
  function isLowSignalKeyword(pattern) {
33382
- return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33873
+ return tokenize2(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33383
33874
  }
33384
33875
  function splitRawTokens(pattern) {
33385
33876
  return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
@@ -33387,7 +33878,7 @@ function splitRawTokens(pattern) {
33387
33878
  function keywordNeedleLosesTokens(pattern) {
33388
33879
  const raw = splitRawTokens(pattern);
33389
33880
  if (raw.length < 2) return false;
33390
- return tokenize(pattern).length !== raw.length;
33881
+ return tokenize2(pattern).length !== raw.length;
33391
33882
  }
33392
33883
 
33393
33884
  // src/lessons/validate-keywords.ts
@@ -33410,7 +33901,7 @@ function collectStopwordKeywords(graph, findings) {
33410
33901
  for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33411
33902
  if (trigger.kind !== "keyword") continue;
33412
33903
  if (!active.has(triggerId)) continue;
33413
- if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
33904
+ if (tokenize2(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
33414
33905
  continue;
33415
33906
  }
33416
33907
  findings.push({
@@ -33458,7 +33949,6 @@ function validateLessonsGraph(graph, options = {}) {
33458
33949
 
33459
33950
  // src/core/lint/shared/lessons.ts
33460
33951
  var LESSONS_TARGET = "lessons";
33461
- var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
33462
33952
  var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
33463
33953
  var LESSONS_HEADING = /^## Lessons \(/m;
33464
33954
  function lintLessonsSubsystem(projectRoot, scope) {
@@ -33473,7 +33963,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
33473
33963
  return [
33474
33964
  diag(
33475
33965
  "error",
33476
- GRAPH_REL,
33966
+ LESSONS_GRAPH_PATH,
33477
33967
  `lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
33478
33968
  )
33479
33969
  ];
@@ -33481,7 +33971,7 @@ function lintLessonsSubsystem(projectRoot, scope) {
33481
33971
  const knownPaths = listProjectFiles(projectRoot) ?? void 0;
33482
33972
  const report = validateLessonsGraph(graph, { knownPaths });
33483
33973
  for (const finding of report.findings) {
33484
- out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
33974
+ out2.push(diag(finding.level, LESSONS_GRAPH_PATH, `[${finding.code}] ${finding.message}`));
33485
33975
  }
33486
33976
  const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
33487
33977
  const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
@@ -33503,8 +33993,8 @@ function diag(level, file, message) {
33503
33993
  // src/core/lint/linter.ts
33504
33994
  var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
33505
33995
  function isExcludedProjectPath(rel2) {
33506
- const posix8 = rel2.replaceAll("\\", "/");
33507
- 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}/`));
33508
33998
  }
33509
33999
  async function getProjectFiles(projectRoot) {
33510
34000
  const all = await readDirRecursive(projectRoot);
@@ -33897,14 +34387,22 @@ async function findUntrackedManagedDirFiles(args) {
33897
34387
  return [...found].sort();
33898
34388
  }
33899
34389
 
34390
+ // src/lessons/conflict-markers.ts
34391
+ function hasConflictMarkers(text) {
34392
+ return /^(?:<{7}|>{7})(?: |\r?$)/m.test(text);
34393
+ }
34394
+
33900
34395
  // src/core/check/lock-sync.ts
34396
+ init_fs();
33901
34397
  async function checkLockSync(opts) {
33902
34398
  const { config, configDir, canonicalDir, rootBase, scope = "project" } = opts;
33903
34399
  const lock = await readLock(canonicalDir);
33904
34400
  if (lock === null) {
34401
+ const text = await readFileSafe(join(canonicalDir, ".lock"));
33905
34402
  return {
33906
34403
  inSync: false,
33907
34404
  hasLock: false,
34405
+ lockConflict: text !== null && hasConflictMarkers(text),
33908
34406
  canonicalDrift: false,
33909
34407
  outputDrift: false,
33910
34408
  modified: [],
@@ -33974,6 +34472,7 @@ async function checkLockSync(opts) {
33974
34472
  return {
33975
34473
  inSync,
33976
34474
  hasLock: true,
34475
+ lockConflict: false,
33977
34476
  canonicalDrift,
33978
34477
  outputDrift,
33979
34478
  modified,