agentsmesh 0.22.0 → 0.23.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/index.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import { z } from 'zod';
2
- import { parse, stringify } from 'yaml';
3
- import { join, relative, sep, dirname, isAbsolute, resolve, basename, win32, posix, extname } from 'path';
4
- import { access, readdir, readFile, realpath, stat, rm, mkdir, lstat, unlink, writeFile, rename, chmod, mkdtemp, cp } from 'fs/promises';
5
- import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync, constants, realpathSync, statSync } from 'fs';
2
+ import { stringify, parse, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
3
+ import { join, resolve, relative, sep, dirname, basename, win32, posix, extname } from 'path';
4
+ import { mkdir, access, readdir, rm, readFile, realpath, stat, writeFile, lstat, unlink, rename, chmod, mkdtemp, cp } from 'fs/promises';
5
+ import { setTimeout as setTimeout$1 } from 'timers/promises';
6
+ import { readFileSync, existsSync, mkdirSync, writeFileSync, constants, rmSync, renameSync, readdirSync, realpathSync, statSync } from 'fs';
6
7
  import { parse as parse$1 } from 'smol-toml';
7
8
  import { Buffer } from 'buffer';
8
- import { homedir, tmpdir } from 'os';
9
+ import { homedir, hostname, tmpdir } from 'os';
9
10
  import { createHash } from 'crypto';
10
11
  import { execFile } from 'child_process';
11
12
  import { fileURLToPath, pathToFileURL, URL } from 'url';
12
13
  import { promisify } from 'util';
13
14
  import * as tar from 'tar';
14
- import { createTwoFilesPatch } from 'diff';
15
15
  import picomatch from 'picomatch';
16
+ import { createTwoFilesPatch } from 'diff';
16
17
 
17
18
  var __defProp = Object.defineProperty;
18
19
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -46,7 +47,7 @@ function validateCapabilityImplementations(descriptor31, capabilities17, ctx, pa
46
47
  function validateDescriptor(value) {
47
48
  return targetDescriptorSchema.parse(value);
48
49
  }
49
- var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
50
+ var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, nativePickStrategySchema, nativeInstallSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
50
51
  var init_target_descriptor_schema = __esm({
51
52
  "src/targets/catalog/target-descriptor.schema.ts"() {
52
53
  capabilityLevelSchema = z.union([
@@ -120,6 +121,22 @@ var init_target_descriptor_schema = __esm({
120
121
  officialUrl: z.string().min(1),
121
122
  shortDescription: z.string().min(1)
122
123
  }).passthrough();
124
+ nativePickStrategySchema = z.discriminatedUnion("kind", [
125
+ z.object({ kind: z.literal("basename"), suffix: z.string().min(1) }),
126
+ z.object({ kind: z.literal("skillDir") }),
127
+ z.object({ kind: z.literal("firstSegment") })
128
+ ]);
129
+ nativeInstallSchema = z.object({
130
+ pickPaths: z.array(
131
+ z.object({
132
+ prefix: z.string().min(1),
133
+ feature: z.enum(["commands", "rules", "agents", "skills"]),
134
+ strategy: nativePickStrategySchema
135
+ })
136
+ ).optional(),
137
+ inferPick: z.function().optional(),
138
+ dialectHints: z.array(z.object({ frontmatterKey: z.string().min(1) })).optional()
139
+ }).strict();
123
140
  targetDescriptorSchemaBase = z.object({
124
141
  id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Target id must be lowercase with hyphens"),
125
142
  metadata: metadataSchema,
@@ -131,6 +148,7 @@ var init_target_descriptor_schema = __esm({
131
148
  globalSupport: globalSupportSchema.optional(),
132
149
  buildImportPaths: z.function(),
133
150
  detectionPaths: z.array(z.string()),
151
+ nativeInstall: nativeInstallSchema.optional(),
134
152
  excludeFromStarterInit: z.boolean().optional(),
135
153
  conversionDefaults: conversionDefaultsSchema.optional(),
136
154
  emitScopedSettings: z.function().optional(),
@@ -631,15 +649,19 @@ var init_errors = __esm({
631
649
  LockAcquisitionError = class extends AgentsMeshError {
632
650
  lockPath;
633
651
  holder;
652
+ /** Human-readable lock name surfaced in the message, e.g. "lessons lock". */
653
+ label;
634
654
  constructor(lockPath, holder, options) {
655
+ const label = options?.label ?? "lock";
635
656
  super(
636
657
  "AM_LOCK_ACQUISITION_FAILED",
637
- `Could not acquire generate lock at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
658
+ `Could not acquire ${label} at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
638
659
  options
639
660
  );
640
661
  this.name = "LockAcquisitionError";
641
662
  this.lockPath = lockPath;
642
663
  this.holder = holder;
664
+ this.label = label;
643
665
  }
644
666
  };
645
667
  FileSystemError = class extends AgentsMeshError {
@@ -804,6 +826,27 @@ var init_fs_traverse = __esm({
804
826
  MAX_SEGMENT_REPETITIONS = 3;
805
827
  }
806
828
  });
829
+ async function renameWithRetry(from, to, options = {}) {
830
+ const attempts = options.attempts ?? 5;
831
+ const delayMs = options.delayMs ?? 50;
832
+ for (let attempt = 0; ; attempt++) {
833
+ try {
834
+ await rename(from, to);
835
+ return;
836
+ } catch (err) {
837
+ const code = err.code;
838
+ const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
839
+ if (!transient || attempt >= attempts - 1) throw err;
840
+ await setTimeout$1(delayMs * 2 ** attempt);
841
+ }
842
+ }
843
+ }
844
+ var TRANSIENT_RENAME_CODES;
845
+ var init_rename_retry = __esm({
846
+ "src/utils/filesystem/rename-retry.ts"() {
847
+ TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
848
+ }
849
+ });
807
850
  async function readFileSafe(path) {
808
851
  try {
809
852
  const data = await readFile(path, "utf-8");
@@ -890,6 +933,7 @@ var init_fs = __esm({
890
933
  init_fs_text_encoding();
891
934
  init_fs_traverse();
892
935
  init_fs_text_encoding();
936
+ init_rename_retry();
893
937
  }
894
938
  });
895
939
  function escapeRegExp(value) {
@@ -898,19 +942,24 @@ function escapeRegExp(value) {
898
942
  function managedBlockPattern(start, end) {
899
943
  return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
900
944
  }
901
- function replaceManagedBlock(content, start, end, block) {
902
- const pattern = managedBlockPattern(start, end);
903
- if (pattern.test(content)) {
904
- return content.replace(pattern, block).trim();
905
- }
906
- const trimmed = content.trim();
907
- return trimmed ? `${trimmed}
908
-
909
- ${block}` : block;
910
- }
911
945
  function stripManagedBlock(content, start, end) {
912
946
  return content.replace(managedBlockPattern(start, end), "").trim();
913
947
  }
948
+ function splitFrontmatterPrefix(content) {
949
+ if (content.indexOf("---") !== 0) return { prefix: "", body: content.trim() };
950
+ const close = content.indexOf("---", 3);
951
+ if (close === -1) return { prefix: "", body: content.trim() };
952
+ return { prefix: content.slice(0, close + 3), body: content.slice(close + 3).trim() };
953
+ }
954
+ function insertAtBodyTop(content, block) {
955
+ const { prefix, body } = splitFrontmatterPrefix(content);
956
+ const placed = body ? `${block}
957
+
958
+ ${body}` : block;
959
+ return prefix ? `${prefix}
960
+
961
+ ${placed}` : placed;
962
+ }
914
963
  function ruleSource(source) {
915
964
  const normalized = source.replace(/\\/g, "/");
916
965
  const meshIndex = normalized.lastIndexOf(".agentsmesh/");
@@ -998,11 +1047,13 @@ function extractEmbeddedRules(content) {
998
1047
  });
999
1048
  return { rootContent: rootContent.trim(), rules };
1000
1049
  }
1001
- var ROOT_CONTRACT_START, ROOT_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
1050
+ var ROOT_CONTRACT_START, ROOT_CONTRACT_END, LESSONS_CONTRACT_START, LESSONS_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
1002
1051
  var init_managed_blocks = __esm({
1003
1052
  "src/targets/projection/managed-blocks.ts"() {
1004
1053
  ROOT_CONTRACT_START = "<!-- agentsmesh:root-generation-contract:start -->";
1005
1054
  ROOT_CONTRACT_END = "<!-- agentsmesh:root-generation-contract:end -->";
1055
+ LESSONS_CONTRACT_START = "<!-- agentsmesh:lessons-contract:start -->";
1056
+ LESSONS_CONTRACT_END = "<!-- agentsmesh:lessons-contract:end -->";
1006
1057
  EMBEDDED_RULES_START = "<!-- agentsmesh:embedded-rules:start -->";
1007
1058
  EMBEDDED_RULES_END = "<!-- agentsmesh:embedded-rules:end -->";
1008
1059
  EMBEDDED_RULE_END = "<!-- agentsmesh:embedded-rule:end -->";
@@ -1012,31 +1063,9 @@ var init_managed_blocks = __esm({
1012
1063
  });
1013
1064
 
1014
1065
  // src/targets/projection/root-instruction-paragraph.ts
1015
- function normalizeWhitespace(value) {
1016
- return value.replace(/\s+/g, " ").trim();
1017
- }
1018
1066
  function appendAgentsmeshRootInstructionParagraph(content) {
1019
- const trimmed = content.trim();
1020
- if (trimmed.includes(ROOT_CONTRACT_START) && trimmed.includes(ROOT_CONTRACT_END)) {
1021
- return replaceManagedBlock(
1022
- trimmed,
1023
- ROOT_CONTRACT_START,
1024
- ROOT_CONTRACT_END,
1025
- AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH
1026
- );
1027
- }
1028
- const norm = normalizeWhitespace(trimmed);
1029
- if (norm.includes(normalizeWhitespace(AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH))) {
1030
- return trimmed;
1031
- }
1032
- for (const legacy of LEGACY_FORMS) {
1033
- if (norm.includes(normalizeWhitespace(legacy))) {
1034
- return trimmed.replace(legacy, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
1035
- }
1036
- }
1037
- return trimmed ? `${trimmed}
1038
-
1039
- ${AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH}` : AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH;
1067
+ const withoutPrior = stripAgentsmeshRootInstructionParagraph(content);
1068
+ return insertAtBodyTop(withoutPrior, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
1040
1069
  }
1041
1070
  function stripAgentsmeshRootInstructionParagraph(content) {
1042
1071
  let result = stripManagedBlock(content, ROOT_CONTRACT_START, ROOT_CONTRACT_END);
@@ -1286,7 +1315,7 @@ function generateEmbeddedSkills(canonical, skillsDir) {
1286
1315
  }
1287
1316
  return outputs;
1288
1317
  }
1289
- async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, normalize2) {
1318
+ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, normalize) {
1290
1319
  const entries = await readdir(join(projectRoot, skillsDir), {
1291
1320
  encoding: "utf8",
1292
1321
  withFileTypes: true
@@ -1301,7 +1330,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1301
1330
  const destinationSkillDir = join(projectRoot, AB_SKILLS, entry.name);
1302
1331
  const destinationSkillFile = join(destinationSkillDir, "SKILL.md");
1303
1332
  const { frontmatter, body } = parseFrontmatter(
1304
- normalize2(sourceSkillContent, sourceSkillFile, destinationSkillFile)
1333
+ normalize(sourceSkillContent, sourceSkillFile, destinationSkillFile)
1305
1334
  );
1306
1335
  const projectedCommand = parseCommandSkillFrontmatter(frontmatter, entry.name);
1307
1336
  if (projectedCommand) {
@@ -1311,7 +1340,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1311
1340
  const commandPath = join(destDir, `${projectedCommand.name}.md`);
1312
1341
  await writeFileAtomic(
1313
1342
  commandPath,
1314
- serializeImportedCommand(projectedCommand, normalize2(body, sourceSkillFile, commandPath))
1343
+ serializeImportedCommand(projectedCommand, normalize(body, sourceSkillFile, commandPath))
1315
1344
  );
1316
1345
  results.push({
1317
1346
  fromTool,
@@ -1329,7 +1358,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1329
1358
  const agentPath = join(destDir, `${projectedAgent.name}.md`);
1330
1359
  await writeFileAtomic(
1331
1360
  agentPath,
1332
- serializeImportedAgent(projectedAgent, normalize2(body, sourceSkillFile, agentPath))
1361
+ serializeImportedAgent(projectedAgent, normalize(body, sourceSkillFile, agentPath))
1333
1362
  );
1334
1363
  results.push({
1335
1364
  fromTool,
@@ -1360,7 +1389,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1360
1389
  if (sourceContent === null) continue;
1361
1390
  const destinationPath = join(destinationSkillDir, relativePath);
1362
1391
  await mkdirp(dirname(destinationPath));
1363
- await writeFileAtomic(destinationPath, normalize2(sourceContent, sourcePath, destinationPath));
1392
+ await writeFileAtomic(destinationPath, normalize(sourceContent, sourcePath, destinationPath));
1364
1393
  results.push({
1365
1394
  fromTool,
1366
1395
  fromPath: sourcePath,
@@ -2010,9 +2039,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
2010
2039
  const before = fullContent[start - 1];
2011
2040
  const after = fullContent[end];
2012
2041
  if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
2013
- if (before === "'" && after === "'" || before === '"' && after === '"' || before === "`" && after === "`") {
2014
- return true;
2015
- }
2042
+ if (before === "`" && after === "`") return true;
2043
+ if (before === "'" && after === "'" || before === '"' && after === '"') return false;
2016
2044
  if (before === "<" && after === ">") return true;
2017
2045
  if (before === "[" && after === "]") {
2018
2046
  if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
@@ -2517,7 +2545,7 @@ var init_import_descriptor = __esm({
2517
2545
  ];
2518
2546
  }
2519
2547
  });
2520
- async function runSingleFile(spec, sources, projectRoot, fromTool, normalize2) {
2548
+ async function runSingleFile(spec, sources, projectRoot, fromTool, normalize) {
2521
2549
  if (!spec.canonicalRootFilename) {
2522
2550
  throw new Error(`singleFile spec for ${spec.feature} must set canonicalRootFilename`);
2523
2551
  }
@@ -2528,7 +2556,7 @@ async function runSingleFile(spec, sources, projectRoot, fromTool, normalize2) {
2528
2556
  if (content === null) continue;
2529
2557
  await mkdirp(destDir);
2530
2558
  const destPath = join(destDir, spec.canonicalRootFilename);
2531
- const normalizeTo = (destinationFile) => normalize2(content, srcPath, destinationFile);
2559
+ const normalizeTo = (destinationFile) => normalize(content, srcPath, destinationFile);
2532
2560
  if (spec.map) {
2533
2561
  let mapping;
2534
2562
  try {
@@ -2571,7 +2599,7 @@ async function runSingleFile(spec, sources, projectRoot, fromTool, normalize2) {
2571
2599
  }
2572
2600
  return [];
2573
2601
  }
2574
- async function runDirectory(spec, sources, projectRoot, fromTool, normalize2) {
2602
+ async function runDirectory(spec, sources, projectRoot, fromTool, normalize) {
2575
2603
  const mapper = resolveMapper(spec);
2576
2604
  const destDir = join(projectRoot, spec.canonicalDir);
2577
2605
  const results = [];
@@ -2582,7 +2610,7 @@ async function runDirectory(spec, sources, projectRoot, fromTool, normalize2) {
2582
2610
  destDir,
2583
2611
  extensions: [...spec.extensions ?? [".md"]],
2584
2612
  fromTool,
2585
- normalize: normalize2,
2613
+ normalize,
2586
2614
  mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
2587
2615
  if (isPreservedBoilerplate(basename(srcPath))) return null;
2588
2616
  let mapping;
@@ -2687,28 +2715,28 @@ async function runMcpJson(spec, sources, projectRoot, fromTool) {
2687
2715
  }
2688
2716
  return [];
2689
2717
  }
2690
- function dispatchSpec(spec, sources, projectRoot, fromTool, normalize2) {
2718
+ function dispatchSpec(spec, sources, projectRoot, fromTool, normalize) {
2691
2719
  switch (spec.mode) {
2692
2720
  case "singleFile":
2693
- return runSingleFile(spec, sources, projectRoot, fromTool, normalize2);
2721
+ return runSingleFile(spec, sources, projectRoot, fromTool, normalize);
2694
2722
  case "directory":
2695
- return runDirectory(spec, sources, projectRoot, fromTool, normalize2);
2723
+ return runDirectory(spec, sources, projectRoot, fromTool, normalize);
2696
2724
  case "flatFile":
2697
2725
  return runFlatFile(spec, sources, projectRoot, fromTool);
2698
2726
  case "mcpJson":
2699
2727
  return runMcpJson(spec, sources, projectRoot, fromTool);
2700
2728
  }
2701
2729
  }
2702
- async function runSpec(spec, scope, projectRoot, fromTool, normalize2) {
2730
+ async function runSpec(spec, scope, projectRoot, fromTool, normalize) {
2703
2731
  const primary = resolveScopedSources(spec.source, scope);
2704
2732
  const fallback = resolveScopedSources(spec.fallbacks, scope);
2705
2733
  if (primary.length === 0 && fallback.length === 0) return [];
2706
2734
  if (primary.length > 0) {
2707
- const results = await dispatchSpec(spec, primary, projectRoot, fromTool, normalize2);
2735
+ const results = await dispatchSpec(spec, primary, projectRoot, fromTool, normalize);
2708
2736
  if (results.length > 0) return results;
2709
2737
  }
2710
2738
  if (fallback.length > 0) {
2711
- return dispatchSpec(spec, fallback, projectRoot, fromTool, normalize2);
2739
+ return dispatchSpec(spec, fallback, projectRoot, fromTool, normalize);
2712
2740
  }
2713
2741
  return [];
2714
2742
  }
@@ -2721,11 +2749,11 @@ function specsForFeature(importer, feature) {
2721
2749
  async function runDescriptorImport(descriptor31, projectRoot, scope, options) {
2722
2750
  const importer = descriptor31.importer;
2723
2751
  if (!importer) return [];
2724
- const normalize2 = options?.normalize ?? await createImportReferenceNormalizer(descriptor31.id, projectRoot, scope);
2752
+ const normalize = options?.normalize ?? await createImportReferenceNormalizer(descriptor31.id, projectRoot, scope);
2725
2753
  const results = [];
2726
2754
  for (const feature of IMPORT_FEATURE_ORDER) {
2727
2755
  for (const spec of specsForFeature(importer, feature)) {
2728
- results.push(...await runSpec(spec, scope, projectRoot, descriptor31.id, normalize2));
2756
+ results.push(...await runSpec(spec, scope, projectRoot, descriptor31.id, normalize));
2729
2757
  }
2730
2758
  }
2731
2759
  return results;
@@ -2749,10 +2777,10 @@ var init_descriptor_import_runner = __esm({
2749
2777
  async function importFromAider(projectRoot, options = {}) {
2750
2778
  const scope = options.scope ?? "project";
2751
2779
  const results = [];
2752
- const normalize2 = await createImportReferenceNormalizer(AIDER_TARGET, projectRoot, scope);
2753
- results.push(...await runDescriptorImport(descriptor, projectRoot, scope, { normalize: normalize2 }));
2780
+ const normalize = await createImportReferenceNormalizer(AIDER_TARGET, projectRoot, scope);
2781
+ results.push(...await runDescriptorImport(descriptor, projectRoot, scope, { normalize }));
2754
2782
  const skillsDir = scope === "global" ? AIDER_GLOBAL_SKILLS_DIR : AIDER_SKILLS_DIR;
2755
- await importEmbeddedSkills(projectRoot, skillsDir, AIDER_TARGET, results, normalize2);
2783
+ await importEmbeddedSkills(projectRoot, skillsDir, AIDER_TARGET, results, normalize);
2756
2784
  return results;
2757
2785
  }
2758
2786
  var init_importer = __esm({
@@ -3339,12 +3367,12 @@ var init_augment_code = __esm({
3339
3367
  });
3340
3368
 
3341
3369
  // src/targets/claude-code/constants.ts
3342
- var CLAUDE_CODE_TARGET, CLAUDE_ROOT, CLAUDE_LEGACY_ROOT, CLAUDE_RULES_DIR, CLAUDE_COMMANDS_DIR, CLAUDE_AGENTS_DIR, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS, CLAUDE_HOOKS_JSON, CLAUDE_OUTPUT_STYLES_DIR, CLAUDE_IGNORE, CLAUDE_MCP_JSON, CLAUDE_GLOBAL_MCP_JSON, CLAUDE_CANONICAL_RULES_DIR, CLAUDE_CANONICAL_COMMANDS_DIR, CLAUDE_CANONICAL_AGENTS_DIR, CLAUDE_CANONICAL_SKILLS_DIR, CLAUDE_CANONICAL_MCP, CLAUDE_CANONICAL_PERMISSIONS, CLAUDE_CANONICAL_HOOKS, CLAUDE_CANONICAL_IGNORE;
3370
+ var CLAUDE_CODE_TARGET, CLAUDE_ROOT, CLAUDE_NESTED_ROOT, CLAUDE_RULES_DIR, CLAUDE_COMMANDS_DIR, CLAUDE_AGENTS_DIR, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS, CLAUDE_HOOKS_JSON, CLAUDE_OUTPUT_STYLES_DIR, CLAUDE_IGNORE, CLAUDE_MCP_JSON, CLAUDE_GLOBAL_MCP_JSON, CLAUDE_CANONICAL_RULES_DIR, CLAUDE_CANONICAL_COMMANDS_DIR, CLAUDE_CANONICAL_AGENTS_DIR, CLAUDE_CANONICAL_SKILLS_DIR, CLAUDE_CANONICAL_MCP, CLAUDE_CANONICAL_PERMISSIONS, CLAUDE_CANONICAL_HOOKS, CLAUDE_CANONICAL_IGNORE;
3343
3371
  var init_constants7 = __esm({
3344
3372
  "src/targets/claude-code/constants.ts"() {
3345
3373
  CLAUDE_CODE_TARGET = "claude-code";
3346
- CLAUDE_ROOT = ".claude/CLAUDE.md";
3347
- CLAUDE_LEGACY_ROOT = "CLAUDE.md";
3374
+ CLAUDE_ROOT = "CLAUDE.md";
3375
+ CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
3348
3376
  CLAUDE_RULES_DIR = ".claude/rules";
3349
3377
  CLAUDE_COMMANDS_DIR = ".claude/commands";
3350
3378
  CLAUDE_AGENTS_DIR = ".claude/agents";
@@ -4991,10 +5019,10 @@ var init_mcp_import = __esm({
4991
5019
  async function importFromAmp(projectRoot, options = {}) {
4992
5020
  const scope = options.scope ?? "project";
4993
5021
  const results = [];
4994
- const normalize2 = await createImportReferenceNormalizer(AMP_TARGET, projectRoot, scope);
4995
- results.push(...await runDescriptorImport(descriptor3, projectRoot, scope, { normalize: normalize2 }));
5022
+ const normalize = await createImportReferenceNormalizer(AMP_TARGET, projectRoot, scope);
5023
+ results.push(...await runDescriptorImport(descriptor3, projectRoot, scope, { normalize }));
4996
5024
  const skillsDir = scope === "global" ? AMP_GLOBAL_SKILLS_DIR : AMP_SKILLS_DIR;
4997
- await importEmbeddedSkills(projectRoot, skillsDir, AMP_TARGET, results, normalize2);
5025
+ await importEmbeddedSkills(projectRoot, skillsDir, AMP_TARGET, results, normalize);
4998
5026
  const mcpFile = scope === "global" ? AMP_GLOBAL_MCP_FILE : AMP_MCP_FILE;
4999
5027
  await importAmpMcp(projectRoot, mcpFile, results);
5000
5028
  return results;
@@ -5206,7 +5234,8 @@ var init_amp2 = __esm({
5206
5234
  markAsRoot: true
5207
5235
  }
5208
5236
  },
5209
- emitScopedSettings(canonical, _scope) {
5237
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
5238
+ if (!enabledFeatures.has("mcp")) return [];
5210
5239
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
5211
5240
  return [
5212
5241
  {
@@ -5342,7 +5371,7 @@ var init_embedded_rules = __esm({
5342
5371
  init_import_metadata();
5343
5372
  }
5344
5373
  });
5345
- async function importRootRule(projectRoot, results, normalize2, scope) {
5374
+ async function importRootRule(projectRoot, results, normalize, scope) {
5346
5375
  const primary = scope === "global" ? ANTIGRAVITY_GLOBAL_ROOT : ANTIGRAVITY_RULES_ROOT;
5347
5376
  const candidates = scope === "project" ? [primary, ANTIGRAVITY_RULES_ROOT_LEGACY] : [primary];
5348
5377
  for (const rel2 of candidates) {
@@ -5356,10 +5385,10 @@ async function importRootRule(projectRoot, results, normalize2, scope) {
5356
5385
  rulesDir: ANTIGRAVITY_CANONICAL_RULES_DIR,
5357
5386
  sourcePath: srcPath,
5358
5387
  fromTool: ANTIGRAVITY_TARGET,
5359
- normalize: normalize2
5388
+ normalize
5360
5389
  });
5361
5390
  results.push(...split.results);
5362
- const { body } = parseFrontmatter(normalize2(split.rootContent, srcPath, destPath));
5391
+ const { body } = parseFrontmatter(normalize(split.rootContent, srcPath, destPath));
5363
5392
  const output = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
5364
5393
  await mkdirp(join(projectRoot, ANTIGRAVITY_CANONICAL_RULES_DIR));
5365
5394
  await writeFileAtomic(destPath, output);
@@ -5375,15 +5404,15 @@ async function importRootRule(projectRoot, results, normalize2, scope) {
5375
5404
  async function importFromAntigravity(projectRoot, options = {}) {
5376
5405
  const scope = options.scope ?? "project";
5377
5406
  const results = [];
5378
- const normalize2 = await createImportReferenceNormalizer(ANTIGRAVITY_TARGET, projectRoot, scope);
5379
- await importRootRule(projectRoot, results, normalize2, scope);
5380
- results.push(...await runDescriptorImport(descriptor4, projectRoot, scope, { normalize: normalize2 }));
5407
+ const normalize = await createImportReferenceNormalizer(ANTIGRAVITY_TARGET, projectRoot, scope);
5408
+ await importRootRule(projectRoot, results, normalize, scope);
5409
+ results.push(...await runDescriptorImport(descriptor4, projectRoot, scope, { normalize }));
5381
5410
  await importEmbeddedSkills(
5382
5411
  projectRoot,
5383
5412
  scope === "global" ? ANTIGRAVITY_GLOBAL_SKILLS_DIR : ANTIGRAVITY_SKILLS_DIR,
5384
5413
  ANTIGRAVITY_TARGET,
5385
5414
  results,
5386
- normalize2
5415
+ normalize
5387
5416
  );
5388
5417
  return results;
5389
5418
  }
@@ -5799,7 +5828,7 @@ function canonicalRuleMeta(frontmatter, isRoot) {
5799
5828
  }
5800
5829
  return meta;
5801
5830
  }
5802
- async function importRules(projectRoot, results, normalize2, scope) {
5831
+ async function importRules(projectRoot, results, normalize, scope) {
5803
5832
  const rulesDir = scope === "global" ? AUGMENT_CODE_GLOBAL_RULES_DIR : AUGMENT_CODE_RULES_DIR;
5804
5833
  const destDir = join(projectRoot, AUGMENT_CODE_CANONICAL_RULES_DIR);
5805
5834
  results.push(
@@ -5808,7 +5837,7 @@ async function importRules(projectRoot, results, normalize2, scope) {
5808
5837
  destDir,
5809
5838
  extensions: [".md"],
5810
5839
  fromTool: AUGMENT_CODE_TARGET,
5811
- normalize: normalize2,
5840
+ normalize,
5812
5841
  mapEntry: async ({ relativePath, normalizeTo }) => {
5813
5842
  const isRoot = relativePath === "_root.md" || basename(relativePath) === "_root.md";
5814
5843
  const destPath = join(destDir, relativePath);
@@ -5827,7 +5856,7 @@ async function importRules(projectRoot, results, normalize2, scope) {
5827
5856
  })
5828
5857
  );
5829
5858
  }
5830
- async function importCommands(projectRoot, results, normalize2, scope) {
5859
+ async function importCommands(projectRoot, results, normalize, scope) {
5831
5860
  const commandsDir = scope === "global" ? AUGMENT_CODE_GLOBAL_COMMANDS_DIR : AUGMENT_CODE_COMMANDS_DIR;
5832
5861
  const destDir = join(projectRoot, ".agentsmesh/commands");
5833
5862
  results.push(
@@ -5836,7 +5865,7 @@ async function importCommands(projectRoot, results, normalize2, scope) {
5836
5865
  destDir,
5837
5866
  extensions: [".md"],
5838
5867
  fromTool: AUGMENT_CODE_TARGET,
5839
- normalize: normalize2,
5868
+ normalize,
5840
5869
  mapEntry: async ({ relativePath, normalizeTo }) => {
5841
5870
  const name = basename(relativePath, ".md");
5842
5871
  const destPath = join(destDir, `${name}.md`);
@@ -5859,11 +5888,11 @@ async function importCommands(projectRoot, results, normalize2, scope) {
5859
5888
  async function importFromAugmentCode(projectRoot, options = {}) {
5860
5889
  const scope = options.scope ?? "project";
5861
5890
  const results = [];
5862
- const normalize2 = await createImportReferenceNormalizer(AUGMENT_CODE_TARGET, projectRoot, scope);
5863
- await importRules(projectRoot, results, normalize2, scope);
5864
- await importCommands(projectRoot, results, normalize2, scope);
5891
+ const normalize = await createImportReferenceNormalizer(AUGMENT_CODE_TARGET, projectRoot, scope);
5892
+ await importRules(projectRoot, results, normalize, scope);
5893
+ await importCommands(projectRoot, results, normalize, scope);
5865
5894
  const skillsDir = scope === "global" ? AUGMENT_CODE_GLOBAL_SKILLS_DIR : AUGMENT_CODE_SKILLS_DIR;
5866
- await importEmbeddedSkills(projectRoot, skillsDir, AUGMENT_CODE_TARGET, results, normalize2);
5895
+ await importEmbeddedSkills(projectRoot, skillsDir, AUGMENT_CODE_TARGET, results, normalize);
5867
5896
  const settingsFile = scope === "global" ? AUGMENT_CODE_GLOBAL_SETTINGS_FILE : AUGMENT_CODE_SETTINGS_FILE;
5868
5897
  await importAugmentSettings(projectRoot, settingsFile, results);
5869
5898
  if (scope === "project") {
@@ -5958,12 +5987,12 @@ function mergeAugmentSettings(existing, newContent) {
5958
5987
  if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
5959
5988
  return JSON.stringify(base, null, 2);
5960
5989
  }
5961
- function buildSettingsContent(canonical) {
5990
+ function buildSettingsContent(canonical, enabledFeatures) {
5962
5991
  const settings = {};
5963
- if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
5992
+ if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
5964
5993
  settings.mcpServers = canonical.mcp.mcpServers;
5965
5994
  }
5966
- if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
5995
+ if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
5967
5996
  settings.hooks = serializeHooksForSettings(canonical.hooks);
5968
5997
  }
5969
5998
  if (Object.keys(settings).length === 0) return null;
@@ -6092,8 +6121,8 @@ var init_augment_code2 = __esm({
6092
6121
  ],
6093
6122
  layout: globalLayout5
6094
6123
  },
6095
- emitScopedSettings(canonical) {
6096
- const content = buildSettingsContent(canonical);
6124
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
6125
+ const content = buildSettingsContent(canonical, enabledFeatures);
6097
6126
  if (content === null) return [];
6098
6127
  return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
6099
6128
  },
@@ -6481,7 +6510,7 @@ var init_settings_helpers2 = __esm({
6481
6510
  init_constants7();
6482
6511
  }
6483
6512
  });
6484
- async function importClaudeSkills(projectRoot, results, normalize2) {
6513
+ async function importClaudeSkills(projectRoot, results, normalize) {
6485
6514
  const skillsBaseDir = join(projectRoot, CLAUDE_SKILLS_DIR);
6486
6515
  const destBase = join(projectRoot, CLAUDE_CANONICAL_SKILLS_DIR);
6487
6516
  const allFiles = await readDirRecursive(skillsBaseDir);
@@ -6492,7 +6521,7 @@ async function importClaudeSkills(projectRoot, results, normalize2) {
6492
6521
  const destSkillDir = join(destBase, skillName);
6493
6522
  const skillMdContent = await readFileSafe(skillMdPath);
6494
6523
  if (skillMdContent === null) continue;
6495
- const normalizedSkillMd = normalize2(
6524
+ const normalizedSkillMd = normalize(
6496
6525
  skillMdContent,
6497
6526
  skillMdPath,
6498
6527
  join(destSkillDir, "SKILL.md")
@@ -6510,7 +6539,7 @@ async function importClaudeSkills(projectRoot, results, normalize2) {
6510
6539
  const relPath = relative(skillDir, filePath);
6511
6540
  const destPath = join(destSkillDir, relPath);
6512
6541
  await mkdirp(dirname(destPath));
6513
- const normalized = normalize2(fileContent, filePath, destPath);
6542
+ const normalized = normalize(fileContent, filePath, destPath);
6514
6543
  await writeFileAtomic(
6515
6544
  destPath,
6516
6545
  relPath === "SKILL.md" ? await serializeImportedSkillWithFallback(
@@ -6542,9 +6571,9 @@ var init_importer_skills = __esm({
6542
6571
  async function importFromClaudeCode(projectRoot, options = {}) {
6543
6572
  const scope = options.scope ?? "project";
6544
6573
  const results = [];
6545
- const normalize2 = await createImportReferenceNormalizer("claude-code", projectRoot, scope);
6546
- results.push(...await runDescriptorImport(descriptor6, projectRoot, scope, { normalize: normalize2 }));
6547
- await importClaudeSkills(projectRoot, results, normalize2);
6574
+ const normalize = await createImportReferenceNormalizer("claude-code", projectRoot, scope);
6575
+ results.push(...await runDescriptorImport(descriptor6, projectRoot, scope, { normalize }));
6576
+ await importClaudeSkills(projectRoot, results, normalize);
6548
6577
  await importClaudeHooksJson(projectRoot, results);
6549
6578
  await importSettings(projectRoot, results);
6550
6579
  return results;
@@ -6667,7 +6696,10 @@ var init_claude_code2 = __esm({
6667
6696
  skillDir: ".claude/skills",
6668
6697
  managedOutputs: {
6669
6698
  dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
6670
- files: [".claude/CLAUDE.md", ".claude/settings.json", ".claudeignore", ".mcp.json"]
6699
+ // CLAUDE_NESTED_ROOT is the pre-migration project location; listing it here lets
6700
+ // `cleanupStaleGeneratedOutputs` evict a leftover `.claude/CLAUDE.md` once generation
6701
+ // writes the root `CLAUDE.md`, so Claude Code never concatenates both into context.
6702
+ files: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/settings.json", ".claudeignore", ".mcp.json"]
6671
6703
  },
6672
6704
  paths: {
6673
6705
  rulePath(slug, _rule) {
@@ -6682,7 +6714,7 @@ var init_claude_code2 = __esm({
6682
6714
  }
6683
6715
  };
6684
6716
  globalLayout6 = {
6685
- rootInstructionPath: CLAUDE_ROOT,
6717
+ rootInstructionPath: CLAUDE_NESTED_ROOT,
6686
6718
  skillDir: ".claude/skills",
6687
6719
  renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
6688
6720
  managedOutputs: {
@@ -6695,7 +6727,7 @@ var init_claude_code2 = __esm({
6695
6727
  ".agents/skills"
6696
6728
  ],
6697
6729
  files: [
6698
- ".claude/CLAUDE.md",
6730
+ CLAUDE_NESTED_ROOT,
6699
6731
  ".claude/settings.json",
6700
6732
  CLAUDE_GLOBAL_MCP_JSON,
6701
6733
  CLAUDE_HOOKS_JSON,
@@ -6703,6 +6735,7 @@ var init_claude_code2 = __esm({
6703
6735
  ]
6704
6736
  },
6705
6737
  rewriteGeneratedPath(path) {
6738
+ if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
6706
6739
  if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
6707
6740
  return path;
6708
6741
  },
@@ -6766,10 +6799,11 @@ var init_claude_code2 = __esm({
6766
6799
  importer: {
6767
6800
  rules: [
6768
6801
  {
6769
- // Root rule: prefer .claude/CLAUDE.md, fall back to legacy CLAUDE.md (project only).
6802
+ // Root rule: project prefers root CLAUDE.md, falls back to nested .claude/CLAUDE.md;
6803
+ // global reads the nested .claude/CLAUDE.md.
6770
6804
  feature: "rules",
6771
6805
  mode: "singleFile",
6772
- source: { project: [CLAUDE_ROOT, CLAUDE_LEGACY_ROOT], global: [CLAUDE_ROOT] },
6806
+ source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
6773
6807
  canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
6774
6808
  canonicalRootFilename: "_root.md",
6775
6809
  markAsRoot: true
@@ -6815,7 +6849,23 @@ var init_claude_code2 = __esm({
6815
6849
  }
6816
6850
  },
6817
6851
  buildImportPaths: buildClaudeCodeImportPaths,
6818
- detectionPaths: ["CLAUDE.md", ".claude/rules", ".claude/commands"]
6852
+ detectionPaths: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/rules", ".claude/commands"],
6853
+ nativeInstall: {
6854
+ pickPaths: [
6855
+ {
6856
+ prefix: ".claude/commands",
6857
+ feature: "commands",
6858
+ strategy: { kind: "basename", suffix: ".md" }
6859
+ },
6860
+ { prefix: ".claude/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
6861
+ {
6862
+ prefix: ".claude/agents",
6863
+ feature: "agents",
6864
+ strategy: { kind: "basename", suffix: ".md" }
6865
+ },
6866
+ { prefix: ".claude/skills/", feature: "skills", strategy: { kind: "firstSegment" } }
6867
+ ]
6868
+ }
6819
6869
  };
6820
6870
  }
6821
6871
  });
@@ -7004,7 +7054,7 @@ var init_importer_mappers = __esm({
7004
7054
  init_constants8();
7005
7055
  }
7006
7056
  });
7007
- async function importClineRules(projectRoot, results, normalize2) {
7057
+ async function importClineRules(projectRoot, results, normalize) {
7008
7058
  const destRulesDir = join(projectRoot, CLINE_CANONICAL_RULES_DIR);
7009
7059
  const clineRulesPath = join(projectRoot, CLINE_RULES_DIR);
7010
7060
  const clineRulesRaw = join(projectRoot, CLINE_RULES_DIR);
@@ -7020,7 +7070,7 @@ async function importClineRules(projectRoot, results, normalize2) {
7020
7070
  await mkdirp(destRulesDir);
7021
7071
  const destPath = join(destRulesDir, "_root.md");
7022
7072
  const { frontmatter, body } = parseFrontmatter(
7023
- normalize2(flatContent, clineRulesRaw, destPath)
7073
+ normalize(flatContent, clineRulesRaw, destPath)
7024
7074
  );
7025
7075
  const hasRoot = frontmatter.root === true;
7026
7076
  const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
@@ -7046,7 +7096,7 @@ async function importClineRules(projectRoot, results, normalize2) {
7046
7096
  await mkdirp(destRulesDir);
7047
7097
  const destPath = join(destRulesDir, "_root.md");
7048
7098
  const { frontmatter, body } = parseFrontmatter(
7049
- normalize2(agentsMdContent, agentsMdPath, destPath)
7099
+ normalize(agentsMdContent, agentsMdPath, destPath)
7050
7100
  );
7051
7101
  const hasRoot = frontmatter.root === true;
7052
7102
  const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
@@ -7068,7 +7118,7 @@ async function importClineRules(projectRoot, results, normalize2) {
7068
7118
  rootSourcePath = first;
7069
7119
  await mkdirp(destRulesDir);
7070
7120
  const destPath = join(destRulesDir, "_root.md");
7071
- const { frontmatter, body } = parseFrontmatter(normalize2(fc, first, destPath));
7121
+ const { frontmatter, body } = parseFrontmatter(normalize(fc, first, destPath));
7072
7122
  const hasRoot = frontmatter.root === true;
7073
7123
  const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
7074
7124
  const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
@@ -7086,7 +7136,7 @@ async function importClineRules(projectRoot, results, normalize2) {
7086
7136
  rootSourcePath = rootPath;
7087
7137
  await mkdirp(destRulesDir);
7088
7138
  const destPath = join(destRulesDir, "_root.md");
7089
- const { frontmatter, body } = parseFrontmatter(normalize2(rootContent, rootPath, destPath));
7139
+ const { frontmatter, body } = parseFrontmatter(normalize(rootContent, rootPath, destPath));
7090
7140
  const hasRoot = frontmatter.root === true;
7091
7141
  const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
7092
7142
  const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
@@ -7104,7 +7154,7 @@ async function importClineRules(projectRoot, results, normalize2) {
7104
7154
  destDir: destRulesDir,
7105
7155
  extensions: [".md"],
7106
7156
  fromTool: "cline",
7107
- normalize: normalize2,
7157
+ normalize,
7108
7158
  mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
7109
7159
  if (srcPath === rootSourcePath) return null;
7110
7160
  return mapClineRuleFile(relativePath, destRulesDir, normalizeTo);
@@ -7391,12 +7441,12 @@ var init_skill_import_pipeline = __esm({
7391
7441
  });
7392
7442
 
7393
7443
  // src/targets/cline/skills-adapter.ts
7394
- async function importClineSkills(projectRoot, results, normalize2, skillsRelDir = CLINE_SKILLS_DIR) {
7444
+ async function importClineSkills(projectRoot, results, normalize, skillsRelDir = CLINE_SKILLS_DIR) {
7395
7445
  const options = {
7396
7446
  projectRoot,
7397
7447
  destCanonicalSkillsDir: CLINE_CANONICAL_SKILLS_DIR,
7398
7448
  targetName: "cline",
7399
- normalize: normalize2,
7449
+ normalize,
7400
7450
  results
7401
7451
  };
7402
7452
  await importSkillsDirectory([skillsRelDir], options, [
@@ -7450,8 +7500,8 @@ var init_hook_importer = __esm({
7450
7500
  });
7451
7501
  async function importFromCline(projectRoot) {
7452
7502
  const results = [];
7453
- const normalize2 = await createImportReferenceNormalizer(CLINE_TARGET, projectRoot);
7454
- const clineRulesIsFile = await importClineRules(projectRoot, results, normalize2);
7503
+ const normalize = await createImportReferenceNormalizer(CLINE_TARGET, projectRoot);
7504
+ const clineRulesIsFile = await importClineRules(projectRoot, results, normalize);
7455
7505
  const ignorePath = join(projectRoot, CLINE_IGNORE);
7456
7506
  const ignoreContent = await readFileSafe(ignorePath);
7457
7507
  if (ignoreContent !== null && ignoreContent.trim()) {
@@ -7482,12 +7532,12 @@ async function importFromCline(projectRoot) {
7482
7532
  destDir: destCommandsDir,
7483
7533
  extensions: [".md"],
7484
7534
  fromTool: "cline",
7485
- normalize: normalize2,
7535
+ normalize,
7486
7536
  mapEntry: ({ relativePath, normalizeTo }) => mapClineWorkflowFile(relativePath, destCommandsDir, normalizeTo)
7487
7537
  })
7488
7538
  );
7489
7539
  }
7490
- await importClineSkills(projectRoot, results, normalize2);
7540
+ await importClineSkills(projectRoot, results, normalize);
7491
7541
  await importClineHooks(projectRoot, results);
7492
7542
  return results;
7493
7543
  }
@@ -7694,6 +7744,16 @@ var init_cline2 = __esm({
7694
7744
  },
7695
7745
  buildImportPaths: buildClineImportPaths,
7696
7746
  detectionPaths: [".clinerules", ".cline"],
7747
+ nativeInstall: {
7748
+ pickPaths: [
7749
+ { prefix: CLINE_SKILLS_DIR, feature: "skills", strategy: { kind: "skillDir" } },
7750
+ {
7751
+ prefix: CLINE_WORKFLOWS_DIR,
7752
+ feature: "commands",
7753
+ strategy: { kind: "basename", suffix: ".md" }
7754
+ }
7755
+ ]
7756
+ },
7697
7757
  conversionDefaults: { agentsToSkills: true }
7698
7758
  };
7699
7759
  }
@@ -8070,12 +8130,12 @@ var init_mcp_helpers = __esm({
8070
8130
  });
8071
8131
 
8072
8132
  // src/targets/codex-cli/skills-adapter.ts
8073
- async function importSkills(projectRoot, results, normalize2) {
8133
+ async function importSkills(projectRoot, results, normalize) {
8074
8134
  const options = {
8075
8135
  projectRoot,
8076
8136
  destCanonicalSkillsDir: CODEX_CANONICAL_SKILLS_DIR,
8077
8137
  targetName: CODEX_TARGET,
8078
- normalize: normalize2,
8138
+ normalize,
8079
8139
  results
8080
8140
  };
8081
8141
  await importSkillsDirectory([CODEX_SKILLS_DIR, CODEX_SKILLS_FALLBACK_DIR], options, [
@@ -8089,7 +8149,7 @@ var init_skills_adapter2 = __esm({
8089
8149
  init_constants28();
8090
8150
  }
8091
8151
  });
8092
- async function importCodexAgentsFromToml(projectRoot, results, normalize2) {
8152
+ async function importCodexAgentsFromToml(projectRoot, results, normalize) {
8093
8153
  const agentsPath = join(projectRoot, CODEX_AGENTS_DIR);
8094
8154
  const agentsDestDir = join(projectRoot, CODEX_CANONICAL_AGENTS_DIR);
8095
8155
  try {
@@ -8109,7 +8169,7 @@ async function importCodexAgentsFromToml(projectRoot, results, normalize2) {
8109
8169
  const mcpServers = Array.isArray(parsed.mcp_servers) ? parsed.mcp_servers.filter((s) => typeof s === "string") : [];
8110
8170
  await mkdirp(agentsDestDir);
8111
8171
  const destPath = join(agentsDestDir, `${name}.md`);
8112
- const normalizedBody = normalize2(body, srcPath, destPath);
8172
+ const normalizedBody = normalize(body, srcPath, destPath);
8113
8173
  const agent = {
8114
8174
  name,
8115
8175
  description,
@@ -8185,7 +8245,7 @@ var init_codex_rules_embed = __esm({
8185
8245
  init_constants28();
8186
8246
  }
8187
8247
  });
8188
- async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
8248
+ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize) {
8189
8249
  const results = [];
8190
8250
  const codexRulesPath = join(projectRoot, CODEX_RULES_DIR);
8191
8251
  try {
@@ -8196,7 +8256,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
8196
8256
  if (!content) continue;
8197
8257
  const relativePath = relative(codexRulesPath, srcPath).replace(/\\/g, "/");
8198
8258
  const destPath = join(destDir, relativePath);
8199
- const { frontmatter, body } = parseFrontmatter(normalize2(content, srcPath, destPath));
8259
+ const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
8200
8260
  await mkdirp(destDir);
8201
8261
  const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: false };
8202
8262
  const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
@@ -8224,7 +8284,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
8224
8284
  globs: embedded.meta.globs,
8225
8285
  root: false
8226
8286
  },
8227
- normalize2(embedded.body, srcPath, destPath)
8287
+ normalize(embedded.body, srcPath, destPath)
8228
8288
  );
8229
8289
  await writeFileAtomic(destPath, outContent);
8230
8290
  } else {
@@ -8236,7 +8296,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
8236
8296
  globs: [],
8237
8297
  codex_emit: "execution"
8238
8298
  },
8239
- normalize2(raw.trim(), srcPath, destPath)
8299
+ normalize(raw.trim(), srcPath, destPath)
8240
8300
  );
8241
8301
  await writeFileAtomic(destPath, outContent);
8242
8302
  }
@@ -8260,7 +8320,7 @@ var init_import_codex_non_root_rules = __esm({
8260
8320
  init_codex_rules_embed();
8261
8321
  }
8262
8322
  });
8263
- async function importCodexRules(projectRoot, results, normalize2, normalizeWindsurf, layoutScope) {
8323
+ async function importCodexRules(projectRoot, results, normalize, normalizeWindsurf, layoutScope) {
8264
8324
  const codexPath = join(projectRoot, CODEX_MD);
8265
8325
  const agentsPath = join(projectRoot, AGENTS_MD);
8266
8326
  const globalOverridePath = join(projectRoot, CODEX_GLOBAL_AGENTS_OVERRIDE_MD);
@@ -8282,14 +8342,14 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
8282
8342
  rulesDir: CODEX_CANONICAL_RULES_DIR,
8283
8343
  sourcePath,
8284
8344
  fromTool: "codex-cli",
8285
- normalize: normalize2
8345
+ normalize
8286
8346
  });
8287
8347
  results.push(...split.results);
8288
- const normalizedContent = sourcePath === agentsPath || sourcePath === globalAgentsPath || sourcePath === globalOverridePath ? normalize2(
8348
+ const normalizedContent = sourcePath === agentsPath || sourcePath === globalAgentsPath || sourcePath === globalOverridePath ? normalize(
8289
8349
  normalizeWindsurf(split.rootContent, sourcePath, destPath),
8290
8350
  sourcePath,
8291
8351
  destPath
8292
- ) : normalize2(split.rootContent, sourcePath, destPath);
8352
+ ) : normalize(split.rootContent, sourcePath, destPath);
8293
8353
  const { frontmatter, body } = parseFrontmatter(normalizedContent);
8294
8354
  const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: true };
8295
8355
  const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
@@ -8301,8 +8361,8 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
8301
8361
  feature: "rules"
8302
8362
  });
8303
8363
  }
8304
- await importInstructionMirrors(projectRoot, destDir, results, normalize2);
8305
- results.push(...await importCodexNonRootRuleFiles(projectRoot, destDir, normalize2));
8364
+ await importInstructionMirrors(projectRoot, destDir, results, normalize);
8365
+ results.push(...await importCodexNonRootRuleFiles(projectRoot, destDir, normalize));
8306
8366
  if (layoutScope !== "global") {
8307
8367
  results.push(
8308
8368
  ...await importFileDirectory({
@@ -8310,7 +8370,7 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
8310
8370
  destDir,
8311
8371
  extensions: ["AGENTS.md", "AGENTS.override.md"],
8312
8372
  fromTool: "codex-cli",
8313
- normalize: normalize2,
8373
+ normalize,
8314
8374
  mapEntry: async ({ srcPath, normalizeTo }) => {
8315
8375
  const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
8316
8376
  const fileName = basename(srcPath);
@@ -8344,7 +8404,7 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
8344
8404
  );
8345
8405
  }
8346
8406
  }
8347
- async function importInstructionMirrors(projectRoot, destDir, results, normalize2) {
8407
+ async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
8348
8408
  try {
8349
8409
  const files = await readDirRecursive(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
8350
8410
  const instructionFiles = files.filter((file) => file.endsWith(".md"));
@@ -8355,7 +8415,7 @@ async function importInstructionMirrors(projectRoot, destDir, results, normalize
8355
8415
  const content = await readFileSafe(srcPath);
8356
8416
  if (!content) continue;
8357
8417
  const destPath = join(destDir, relativePath);
8358
- const { frontmatter, body } = parseFrontmatter(normalize2(content, srcPath, destPath));
8418
+ const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
8359
8419
  await mkdirp(destDir);
8360
8420
  const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: false };
8361
8421
  const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
@@ -8388,15 +8448,15 @@ var init_importer_rules2 = __esm({
8388
8448
  async function importFromCodex(projectRoot, options) {
8389
8449
  const layoutScope = options?.scope ?? "project";
8390
8450
  const results = [];
8391
- const normalize2 = await createImportReferenceNormalizer(CODEX_TARGET, projectRoot, layoutScope);
8451
+ const normalize = await createImportReferenceNormalizer(CODEX_TARGET, projectRoot, layoutScope);
8392
8452
  const normalizeWindsurf = await createImportReferenceNormalizer(
8393
8453
  "windsurf",
8394
8454
  projectRoot,
8395
8455
  layoutScope
8396
8456
  );
8397
- await importCodexRules(projectRoot, results, normalize2, normalizeWindsurf, layoutScope);
8398
- await importSkills(projectRoot, results, normalize2);
8399
- await importCodexAgentsFromToml(projectRoot, results, normalize2);
8457
+ await importCodexRules(projectRoot, results, normalize, normalizeWindsurf, layoutScope);
8458
+ await importSkills(projectRoot, results, normalize);
8459
+ await importCodexAgentsFromToml(projectRoot, results, normalize);
8400
8460
  await importMcp(projectRoot, results);
8401
8461
  return results;
8402
8462
  }
@@ -8612,6 +8672,11 @@ var init_codex_cli2 = __esm({
8612
8672
  ".codex/agents",
8613
8673
  ".codex/rules"
8614
8674
  ],
8675
+ nativeInstall: {
8676
+ pickPaths: [
8677
+ { prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
8678
+ ]
8679
+ },
8615
8680
  excludeFromStarterInit: true,
8616
8681
  conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
8617
8682
  };
@@ -8769,9 +8834,9 @@ async function importMcp2(projectRoot, results) {
8769
8834
  }
8770
8835
  async function importFromContinue(projectRoot) {
8771
8836
  const results = [];
8772
- const normalize2 = await createImportReferenceNormalizer(CONTINUE_TARGET, projectRoot);
8773
- results.push(...await runDescriptorImport(descriptor9, projectRoot, "project", { normalize: normalize2 }));
8774
- await importEmbeddedSkills(projectRoot, CONTINUE_SKILLS_DIR, CONTINUE_TARGET, results, normalize2);
8837
+ const normalize = await createImportReferenceNormalizer(CONTINUE_TARGET, projectRoot);
8838
+ results.push(...await runDescriptorImport(descriptor9, projectRoot, "project", { normalize }));
8839
+ await importEmbeddedSkills(projectRoot, CONTINUE_SKILLS_DIR, CONTINUE_TARGET, results, normalize);
8775
8840
  await importMcp2(projectRoot, results);
8776
8841
  return results;
8777
8842
  }
@@ -9115,6 +9180,21 @@ var init_continue2 = __esm({
9115
9180
  },
9116
9181
  buildImportPaths: buildContinueImportPaths,
9117
9182
  detectionPaths: [".continue/rules", ".continue/skills", ".continue/mcpServers"],
9183
+ nativeInstall: {
9184
+ pickPaths: [
9185
+ {
9186
+ prefix: ".continue/rules",
9187
+ feature: "rules",
9188
+ strategy: { kind: "basename", suffix: ".md" }
9189
+ },
9190
+ {
9191
+ prefix: ".continue/prompts",
9192
+ feature: "commands",
9193
+ strategy: { kind: "basename", suffix: ".md" }
9194
+ },
9195
+ { prefix: ".continue/skills", feature: "skills", strategy: { kind: "skillDir" } }
9196
+ ]
9197
+ },
9118
9198
  conversionDefaults: { agentsToSkills: true }
9119
9199
  };
9120
9200
  }
@@ -9436,14 +9516,14 @@ var init_hook_parser = __esm({
9436
9516
  init_constants29();
9437
9517
  }
9438
9518
  });
9439
- async function importSkills2(projectRoot, results, normalize2, skillsDirRel = COPILOT_SKILLS_DIR) {
9519
+ async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
9440
9520
  const skillsDir = join(projectRoot, skillsDirRel);
9441
9521
  const directorySkills = await findDirectorySkills(skillsDir);
9442
9522
  const options = {
9443
9523
  projectRoot,
9444
9524
  destCanonicalSkillsDir: COPILOT_CANONICAL_SKILLS_DIR,
9445
9525
  targetName: COPILOT_TARGET,
9446
- normalize: normalize2,
9526
+ normalize,
9447
9527
  results
9448
9528
  };
9449
9529
  for (const [skillName, skillDir] of directorySkills) {
@@ -9461,12 +9541,12 @@ var init_skills_adapter3 = __esm({
9461
9541
  async function importFromCopilot(projectRoot, options = {}) {
9462
9542
  const scope = options.scope ?? "project";
9463
9543
  const results = [];
9464
- const normalize2 = await createImportReferenceNormalizer(COPILOT_TARGET, projectRoot, scope);
9465
- results.push(...await runDescriptorImport(descriptor10, projectRoot, scope, { normalize: normalize2 }));
9544
+ const normalize = await createImportReferenceNormalizer(COPILOT_TARGET, projectRoot, scope);
9545
+ results.push(...await runDescriptorImport(descriptor10, projectRoot, scope, { normalize }));
9466
9546
  await importSkills2(
9467
9547
  projectRoot,
9468
9548
  results,
9469
- normalize2,
9549
+ normalize,
9470
9550
  scope === "global" ? COPILOT_GLOBAL_SKILLS_DIR : COPILOT_SKILLS_DIR
9471
9551
  );
9472
9552
  if (scope === "project") await importHooks(projectRoot, results);
@@ -9482,6 +9562,80 @@ var init_importer10 = __esm({
9482
9562
  init_copilot2();
9483
9563
  }
9484
9564
  });
9565
+ async function skillNamesFromNativeSkillDir(scanRoot) {
9566
+ const files = await readDirRecursive(scanRoot);
9567
+ const names = /* @__PURE__ */ new Set();
9568
+ for (const f of files) {
9569
+ if (basename(f) === "SKILL.md") {
9570
+ names.add(basename(dirname(f)));
9571
+ continue;
9572
+ }
9573
+ const rel2 = relative(scanRoot, f).replace(/\\/g, "/");
9574
+ if (!rel2.includes("/") && f.toLowerCase().endsWith(".md")) {
9575
+ names.add(basename(f, ".md"));
9576
+ }
9577
+ }
9578
+ return [...names].filter(Boolean).sort();
9579
+ }
9580
+ var init_native_skill_scan = __esm({
9581
+ "src/install/native/native-skill-scan.ts"() {
9582
+ init_fs();
9583
+ }
9584
+ });
9585
+ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9586
+ const scan = join(repoRoot, ...posixPath.split("/"));
9587
+ if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
9588
+ const files = await readDirRecursive(scan);
9589
+ const commands = [
9590
+ ...new Set(
9591
+ files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
9592
+ )
9593
+ ].sort();
9594
+ return commands.length ? { commands } : {};
9595
+ }
9596
+ if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
9597
+ const files = await readDirRecursive(scan);
9598
+ const rules = [
9599
+ ...new Set(
9600
+ files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
9601
+ )
9602
+ ].sort();
9603
+ return rules.length ? { rules } : {};
9604
+ }
9605
+ if (posixPath.startsWith(".github/instructions")) {
9606
+ const files = await readDirRecursive(scan);
9607
+ const names = /* @__PURE__ */ new Set();
9608
+ for (const f of files) {
9609
+ const b = basename(f);
9610
+ if (b.toLowerCase().endsWith(".instructions.md"))
9611
+ names.add(b.replace(/\.instructions\.md$/i, ""));
9612
+ else if (b.toLowerCase().endsWith(".md")) names.add(basename(f, ".md"));
9613
+ }
9614
+ const rules = [...names].sort();
9615
+ return rules.length ? { rules } : {};
9616
+ }
9617
+ if (posixPath.startsWith(".github/skills")) {
9618
+ const skills = await skillNamesFromNativeSkillDir(scan);
9619
+ return skills.length ? { skills } : {};
9620
+ }
9621
+ if (posixPath.startsWith(".github/agents")) {
9622
+ const files = await readDirRecursive(scan);
9623
+ const agents = [
9624
+ ...new Set(
9625
+ files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
9626
+ )
9627
+ ].sort();
9628
+ return agents.length ? { agents } : {};
9629
+ }
9630
+ return {};
9631
+ }
9632
+ var init_native_path_pick_infer_copilot = __esm({
9633
+ "src/install/native/native-path-pick-infer-copilot.ts"() {
9634
+ init_fs();
9635
+ init_constants29();
9636
+ init_native_skill_scan();
9637
+ }
9638
+ });
9485
9639
  function pruneUndefined3(record) {
9486
9640
  for (const key of Object.keys(record)) {
9487
9641
  if (record[key] === void 0) delete record[key];
@@ -9771,6 +9925,7 @@ var init_copilot2 = __esm({
9771
9925
  init_generator11();
9772
9926
  init_constants29();
9773
9927
  init_importer10();
9928
+ init_native_path_pick_infer_copilot();
9774
9929
  init_import_mappers4();
9775
9930
  init_linter10();
9776
9931
  init_import_map_builders();
@@ -9985,7 +10140,8 @@ var init_copilot2 = __esm({
9985
10140
  ".github/skills",
9986
10141
  ".github/agents",
9987
10142
  ".github/hooks"
9988
- ]
10143
+ ],
10144
+ nativeInstall: { inferPick: inferCopilotPickFromPath }
9989
10145
  };
9990
10146
  }
9991
10147
  });
@@ -10087,10 +10243,10 @@ var init_generator12 = __esm({
10087
10243
  async function importFromCrush(projectRoot, options = {}) {
10088
10244
  const scope = options.scope ?? "project";
10089
10245
  const results = [];
10090
- const normalize2 = await createImportReferenceNormalizer(CRUSH_TARGET, projectRoot, scope);
10091
- results.push(...await runDescriptorImport(descriptor11, projectRoot, scope, { normalize: normalize2 }));
10246
+ const normalize = await createImportReferenceNormalizer(CRUSH_TARGET, projectRoot, scope);
10247
+ results.push(...await runDescriptorImport(descriptor11, projectRoot, scope, { normalize }));
10092
10248
  const skillsDir = scope === "global" ? CRUSH_GLOBAL_SKILLS_DIR : CRUSH_SKILLS_DIR;
10093
- await importEmbeddedSkills(projectRoot, skillsDir, CRUSH_TARGET, results, normalize2);
10249
+ await importEmbeddedSkills(projectRoot, skillsDir, CRUSH_TARGET, results, normalize);
10094
10250
  await importCrushConfigJson(projectRoot, results);
10095
10251
  return results;
10096
10252
  }
@@ -10775,7 +10931,7 @@ var init_import_root_helpers = __esm({
10775
10931
  init_constants11();
10776
10932
  }
10777
10933
  });
10778
- async function importCursorRules(projectRoot, results, normalize2) {
10934
+ async function importCursorRules(projectRoot, results, normalize) {
10779
10935
  const destDir = join(projectRoot, CURSOR_CANONICAL_RULES_DIR);
10780
10936
  let rootWritten = false;
10781
10937
  const rulesDir = join(projectRoot, CURSOR_RULES_DIR);
@@ -10785,7 +10941,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
10785
10941
  destDir,
10786
10942
  extensions: [".mdc"],
10787
10943
  fromTool: "cursor",
10788
- normalize: normalize2,
10944
+ normalize,
10789
10945
  mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
10790
10946
  if (rootWritten) {
10791
10947
  const raw = await readFileSafe(srcPath);
@@ -10809,7 +10965,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
10809
10965
  results,
10810
10966
  sourcePath: agentsPath,
10811
10967
  content: agentsContent,
10812
- normalize: normalize2
10968
+ normalize
10813
10969
  });
10814
10970
  }
10815
10971
  }
@@ -10822,7 +10978,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
10822
10978
  results,
10823
10979
  sourcePath: cursorRulesPath,
10824
10980
  content: cursorRulesContent,
10825
- normalize: normalize2
10981
+ normalize
10826
10982
  });
10827
10983
  }
10828
10984
  }
@@ -10967,14 +11123,14 @@ var init_settings_helpers3 = __esm({
10967
11123
  init_constants11();
10968
11124
  }
10969
11125
  });
10970
- async function importSkills3(projectRoot, results, normalize2, skillsRelDir = CURSOR_SKILLS_DIR) {
11126
+ async function importSkills3(projectRoot, results, normalize, skillsRelDir = CURSOR_SKILLS_DIR) {
10971
11127
  const skillsDir = join(projectRoot, skillsRelDir);
10972
11128
  const directorySkills = await findDirectorySkills(skillsDir);
10973
11129
  const options = {
10974
11130
  projectRoot,
10975
11131
  destCanonicalSkillsDir: CURSOR_CANONICAL_SKILLS_DIR,
10976
11132
  targetName: "cursor",
10977
- normalize: normalize2,
11133
+ normalize,
10978
11134
  results
10979
11135
  };
10980
11136
  for (const [skillName, skillDir] of directorySkills) {
@@ -11015,8 +11171,8 @@ async function hasGlobalCursorArtifacts(projectRoot) {
11015
11171
  join(projectRoot, CURSOR_COMMANDS_DIR)
11016
11172
  ];
11017
11173
  for (const p of candidates) {
11018
- const stat8 = await readFileSafe(p);
11019
- if (stat8 !== null && stat8.trim() !== "") return true;
11174
+ const stat9 = await readFileSafe(p);
11175
+ if (stat9 !== null && stat9.trim() !== "") return true;
11020
11176
  }
11021
11177
  const skillFiles = await readDirRecursive(join(projectRoot, CURSOR_SKILLS_DIR));
11022
11178
  if (skillFiles.some((f) => f.endsWith(".md"))) return true;
@@ -11026,7 +11182,7 @@ async function hasGlobalCursorArtifacts(projectRoot) {
11026
11182
  if (commandFiles.some((f) => f.endsWith(".md"))) return true;
11027
11183
  return false;
11028
11184
  }
11029
- async function importGlobalCursorRulesFromDir(projectRoot, results, normalize2) {
11185
+ async function importGlobalCursorRulesFromDir(projectRoot, results, normalize) {
11030
11186
  const destDir = join(projectRoot, CURSOR_CANONICAL_RULES_DIR);
11031
11187
  let rootWritten = false;
11032
11188
  const rulesDir = join(projectRoot, CURSOR_RULES_DIR);
@@ -11035,7 +11191,7 @@ async function importGlobalCursorRulesFromDir(projectRoot, results, normalize2)
11035
11191
  destDir,
11036
11192
  extensions: [".mdc"],
11037
11193
  fromTool: CURSOR_TARGET2,
11038
- normalize: normalize2,
11194
+ normalize,
11039
11195
  mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
11040
11196
  if (rootWritten) {
11041
11197
  const raw = await readFileSafe(srcPath);
@@ -11052,7 +11208,7 @@ async function importGlobalCursorRulesFromDir(projectRoot, results, normalize2)
11052
11208
  results.push(...batch);
11053
11209
  return rootWritten;
11054
11210
  }
11055
- async function importGlobalUserRules(projectRoot, results, normalize2) {
11211
+ async function importGlobalUserRules(projectRoot, results, normalize) {
11056
11212
  const srcPath = join(projectRoot, CURSOR_GLOBAL_USER_RULES);
11057
11213
  const raw = await readFileSafe(srcPath);
11058
11214
  if (raw === null || raw.trim() === "") return false;
@@ -11061,10 +11217,10 @@ async function importGlobalUserRules(projectRoot, results, normalize2) {
11061
11217
  results,
11062
11218
  sourcePath: srcPath,
11063
11219
  content: raw.trim(),
11064
- normalize: normalize2
11220
+ normalize
11065
11221
  });
11066
11222
  }
11067
- async function importGlobalDotCursorAgents(projectRoot, results, normalize2) {
11223
+ async function importGlobalDotCursorAgents(projectRoot, results, normalize) {
11068
11224
  const srcPath = join(projectRoot, CURSOR_DOT_CURSOR_AGENTS);
11069
11225
  const raw = await readFileSafe(srcPath);
11070
11226
  if (raw === null || raw.trim() === "") return false;
@@ -11073,7 +11229,7 @@ async function importGlobalDotCursorAgents(projectRoot, results, normalize2) {
11073
11229
  results,
11074
11230
  sourcePath: srcPath,
11075
11231
  content: raw.trim(),
11076
- normalize: normalize2
11232
+ normalize
11077
11233
  });
11078
11234
  }
11079
11235
  async function importGlobalMcp(projectRoot, results) {
@@ -11097,7 +11253,7 @@ async function importGlobalMcp(projectRoot, results) {
11097
11253
  feature: "mcp"
11098
11254
  });
11099
11255
  }
11100
- async function importGlobalAgents(projectRoot, results, normalize2) {
11256
+ async function importGlobalAgents(projectRoot, results, normalize) {
11101
11257
  const agentsDir = join(projectRoot, CURSOR_AGENTS_DIR);
11102
11258
  const destDir = join(projectRoot, CURSOR_CANONICAL_AGENTS_DIR);
11103
11259
  results.push(
@@ -11106,12 +11262,12 @@ async function importGlobalAgents(projectRoot, results, normalize2) {
11106
11262
  destDir,
11107
11263
  extensions: [".md"],
11108
11264
  fromTool: CURSOR_TARGET2,
11109
- normalize: normalize2,
11265
+ normalize,
11110
11266
  mapEntry: ({ relativePath, normalizeTo }) => mapCursorAgentFile(relativePath, destDir, normalizeTo)
11111
11267
  })
11112
11268
  );
11113
11269
  }
11114
- async function importGlobalCommands(projectRoot, results, normalize2) {
11270
+ async function importGlobalCommands(projectRoot, results, normalize) {
11115
11271
  const commandsDir = join(projectRoot, CURSOR_COMMANDS_DIR);
11116
11272
  const destDir = join(projectRoot, CURSOR_CANONICAL_COMMANDS_DIR);
11117
11273
  results.push(
@@ -11120,7 +11276,7 @@ async function importGlobalCommands(projectRoot, results, normalize2) {
11120
11276
  destDir,
11121
11277
  extensions: [".md"],
11122
11278
  fromTool: CURSOR_TARGET2,
11123
- normalize: normalize2,
11279
+ normalize,
11124
11280
  mapEntry: ({ relativePath, normalizeTo }) => mapCursorCommandFile(relativePath, destDir, normalizeTo)
11125
11281
  })
11126
11282
  );
@@ -11142,14 +11298,14 @@ var init_import_global_exports_helpers = __esm({
11142
11298
  async function importFromCursorGlobalExports(projectRoot) {
11143
11299
  if (!await hasGlobalCursorArtifacts(projectRoot)) return [];
11144
11300
  const results = [];
11145
- const normalize2 = await createImportReferenceNormalizer(CURSOR_TARGET2, projectRoot, "global");
11146
- let rootWritten = await importGlobalCursorRulesFromDir(projectRoot, results, normalize2);
11147
- if (!rootWritten) rootWritten = await importGlobalUserRules(projectRoot, results, normalize2);
11148
- if (!rootWritten) await importGlobalDotCursorAgents(projectRoot, results, normalize2);
11301
+ const normalize = await createImportReferenceNormalizer(CURSOR_TARGET2, projectRoot, "global");
11302
+ let rootWritten = await importGlobalCursorRulesFromDir(projectRoot, results, normalize);
11303
+ if (!rootWritten) rootWritten = await importGlobalUserRules(projectRoot, results, normalize);
11304
+ if (!rootWritten) await importGlobalDotCursorAgents(projectRoot, results, normalize);
11149
11305
  await importGlobalMcp(projectRoot, results);
11150
- await importSkills3(projectRoot, results, normalize2, CURSOR_SKILLS_DIR);
11151
- await importGlobalAgents(projectRoot, results, normalize2);
11152
- await importGlobalCommands(projectRoot, results, normalize2);
11306
+ await importSkills3(projectRoot, results, normalize, CURSOR_SKILLS_DIR);
11307
+ await importGlobalAgents(projectRoot, results, normalize);
11308
+ await importGlobalCommands(projectRoot, results, normalize);
11153
11309
  await importSettings2(projectRoot, results);
11154
11310
  await importIgnore(projectRoot, results);
11155
11311
  return results;
@@ -11188,10 +11344,10 @@ async function importFromCursor(projectRoot, options = {}) {
11188
11344
  return importFromCursorGlobalExports(projectRoot);
11189
11345
  }
11190
11346
  const results = [];
11191
- const normalize2 = await createImportReferenceNormalizer("cursor", projectRoot);
11192
- await importCursorRules(projectRoot, results, normalize2);
11193
- results.push(...await runDescriptorImport(descriptor12, projectRoot, "project", { normalize: normalize2 }));
11194
- await importSkills3(projectRoot, results, normalize2);
11347
+ const normalize = await createImportReferenceNormalizer("cursor", projectRoot);
11348
+ await importCursorRules(projectRoot, results, normalize);
11349
+ results.push(...await runDescriptorImport(descriptor12, projectRoot, "project", { normalize }));
11350
+ await importSkills3(projectRoot, results, normalize);
11195
11351
  await importMcp3(projectRoot, results);
11196
11352
  await importSettings2(projectRoot, results);
11197
11353
  await importIgnore(projectRoot, results);
@@ -11498,6 +11654,23 @@ var init_cursor2 = __esm({
11498
11654
  },
11499
11655
  buildImportPaths: buildCursorImportPaths,
11500
11656
  detectionPaths: [".cursor/rules", ".cursor/mcp.json"],
11657
+ nativeInstall: {
11658
+ pickPaths: [
11659
+ { prefix: ".cursor/rules", feature: "rules", strategy: { kind: "basename", suffix: ".mdc" } },
11660
+ {
11661
+ prefix: ".cursor/commands",
11662
+ feature: "commands",
11663
+ strategy: { kind: "basename", suffix: ".md" }
11664
+ },
11665
+ {
11666
+ prefix: ".cursor/agents",
11667
+ feature: "agents",
11668
+ strategy: { kind: "basename", suffix: ".md" }
11669
+ },
11670
+ { prefix: ".cursor/skills", feature: "skills", strategy: { kind: "skillDir" } }
11671
+ ],
11672
+ dialectHints: [{ frontmatterKey: "alwaysApply" }]
11673
+ },
11501
11674
  preservesManualActivation: true
11502
11675
  };
11503
11676
  }
@@ -11549,14 +11722,14 @@ var init_generator15 = __esm({
11549
11722
  async function importFromDeepagentsCli(projectRoot, options = {}) {
11550
11723
  const scope = options.scope ?? "project";
11551
11724
  const results = [];
11552
- const normalize2 = await createImportReferenceNormalizer(
11725
+ const normalize = await createImportReferenceNormalizer(
11553
11726
  DEEPAGENTS_CLI_TARGET,
11554
11727
  projectRoot,
11555
11728
  scope
11556
11729
  );
11557
- results.push(...await runDescriptorImport(descriptor13, projectRoot, scope, { normalize: normalize2 }));
11730
+ results.push(...await runDescriptorImport(descriptor13, projectRoot, scope, { normalize }));
11558
11731
  const skillsDir = scope === "global" ? DEEPAGENTS_CLI_GLOBAL_SKILLS_DIR : DEEPAGENTS_CLI_SKILLS_DIR;
11559
- await importEmbeddedSkills(projectRoot, skillsDir, DEEPAGENTS_CLI_TARGET, results, normalize2);
11732
+ await importEmbeddedSkills(projectRoot, skillsDir, DEEPAGENTS_CLI_TARGET, results, normalize);
11560
11733
  return results;
11561
11734
  }
11562
11735
  var init_importer13 = __esm({
@@ -11883,10 +12056,10 @@ var init_mcp_import2 = __esm({
11883
12056
  async function importFromFactoryDroid(projectRoot, options = {}) {
11884
12057
  const scope = options.scope ?? "project";
11885
12058
  const results = [];
11886
- const normalize2 = await createImportReferenceNormalizer(FACTORY_DROID_TARGET, projectRoot, scope);
11887
- results.push(...await runDescriptorImport(descriptor14, projectRoot, scope, { normalize: normalize2 }));
12059
+ const normalize = await createImportReferenceNormalizer(FACTORY_DROID_TARGET, projectRoot, scope);
12060
+ results.push(...await runDescriptorImport(descriptor14, projectRoot, scope, { normalize }));
11888
12061
  const skillsDir = scope === "global" ? FACTORY_DROID_GLOBAL_SKILLS_DIR : FACTORY_DROID_SKILLS_DIR;
11889
- await importEmbeddedSkills(projectRoot, skillsDir, FACTORY_DROID_TARGET, results, normalize2);
12062
+ await importEmbeddedSkills(projectRoot, skillsDir, FACTORY_DROID_TARGET, results, normalize);
11890
12063
  const mcpFile = scope === "global" ? FACTORY_DROID_GLOBAL_MCP_FILE : FACTORY_DROID_MCP_FILE;
11891
12064
  await importFactoryDroidMcp(projectRoot, mcpFile, results);
11892
12065
  return results;
@@ -12259,18 +12432,18 @@ function mapHookEvent2(event) {
12259
12432
  return null;
12260
12433
  }
12261
12434
  }
12262
- function generateGeminiSettingsFiles(canonical) {
12435
+ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
12263
12436
  const settings = {};
12264
12437
  let hasAnyNativeSettings = false;
12265
- if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
12438
+ if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
12266
12439
  settings.mcpServers = canonical.mcp.mcpServers;
12267
12440
  hasAnyNativeSettings = true;
12268
12441
  }
12269
- if (canonical.agents.length > 0) {
12442
+ if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
12270
12443
  settings.experimental = { enableAgents: true };
12271
12444
  hasAnyNativeSettings = true;
12272
12445
  }
12273
- if (canonical.hooks) {
12446
+ if (enabledFeatures.has("hooks") && canonical.hooks) {
12274
12447
  const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
12275
12448
  const mappedEvent = mapHookEvent2(event);
12276
12449
  if (!mappedEvent || !Array.isArray(entries)) return [];
@@ -12700,7 +12873,7 @@ var init_importer_strip = __esm({
12700
12873
  "src/targets/gemini-cli/importer-strip.ts"() {
12701
12874
  }
12702
12875
  });
12703
- async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
12876
+ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
12704
12877
  const geminiSkillsPath = join(projectRoot, GEMINI_SKILLS_DIR);
12705
12878
  const skillDirs = await readDirRecursive(geminiSkillsPath);
12706
12879
  const skillMdFiles = skillDirs.filter((f) => basename(f) === "SKILL.md");
@@ -12716,7 +12889,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
12716
12889
  const agentPath = join(agentsDir, `${projectedAgent.name}.md`);
12717
12890
  await writeFileAtomic(
12718
12891
  agentPath,
12719
- serializeImportedAgent(projectedAgent, normalize2(rawParsed.body, srcPath, agentPath))
12892
+ serializeImportedAgent(projectedAgent, normalize(rawParsed.body, srcPath, agentPath))
12720
12893
  );
12721
12894
  results.push({
12722
12895
  fromTool: "gemini-cli",
@@ -12727,7 +12900,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
12727
12900
  continue;
12728
12901
  }
12729
12902
  const destPath = join(projectRoot, GEMINI_CANONICAL_SKILLS_DIR, skillName, "SKILL.md");
12730
- const normalized = normalize2(content, srcPath, destPath);
12903
+ const normalized = normalize(content, srcPath, destPath);
12731
12904
  const skillDir = join(projectRoot, GEMINI_CANONICAL_SKILLS_DIR, skillName);
12732
12905
  await mkdirp(skillDir);
12733
12906
  const { frontmatter, body } = parseFrontmatter(normalized);
@@ -12749,7 +12922,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
12749
12922
  const relPath = relative(dirname(srcPath), absPath).replace(/\\/g, "/");
12750
12923
  const destSupportPath = join(skillDir, relPath);
12751
12924
  await mkdirp(dirname(destSupportPath));
12752
- await writeFileAtomic(destSupportPath, normalize2(supportContent, absPath, destSupportPath));
12925
+ await writeFileAtomic(destSupportPath, normalize(supportContent, absPath, destSupportPath));
12753
12926
  results.push({
12754
12927
  fromTool: "gemini-cli",
12755
12928
  fromPath: absPath,
@@ -12771,7 +12944,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
12771
12944
  const agentsDir = join(projectRoot, GEMINI_CANONICAL_AGENTS_DIR);
12772
12945
  await mkdirp(agentsDir);
12773
12946
  const destPath = join(agentsDir, relativeMdPath);
12774
- const normalizedBody = normalize2(body, srcPath, destPath);
12947
+ const normalizedBody = normalize(body, srcPath, destPath);
12775
12948
  await writeFileAtomic(
12776
12949
  destPath,
12777
12950
  await serializeImportedAgentWithFallback(
@@ -12805,7 +12978,7 @@ var init_importer_skills_agents = __esm({
12805
12978
  init_constants30();
12806
12979
  }
12807
12980
  });
12808
- async function importRootRule2(projectRoot, results, normalize2) {
12981
+ async function importRootRule2(projectRoot, results, normalize) {
12809
12982
  const normalizeCodex = await createImportReferenceNormalizer("codex-cli", projectRoot);
12810
12983
  const rulesDir = join(projectRoot, GEMINI_CANONICAL_RULES_DIR);
12811
12984
  const compatAgentsRootPath = join(projectRoot, GEMINI_COMPAT_AGENTS);
@@ -12836,10 +13009,10 @@ async function importRootRule2(projectRoot, results, normalize2) {
12836
13009
  rulesDir: GEMINI_CANONICAL_RULES_DIR,
12837
13010
  sourcePath: rootSourcePath,
12838
13011
  fromTool: GEMINI_TARGET,
12839
- normalize: normalize2
13012
+ normalize
12840
13013
  });
12841
13014
  results.push(...split.results);
12842
- const compatNormalized = normalize2(split.rootContent, rootSourcePath, destPath);
13015
+ const compatNormalized = normalize(split.rootContent, rootSourcePath, destPath);
12843
13016
  const normalizedRoot = stripProjectRootCanonicalPrefix(
12844
13017
  compatNormalized.replace(/\.agents\/skills\//g, ".agentsmesh/skills/").replace(/\.agents\\skills\\/g, ".agentsmesh/skills/"),
12845
13018
  projectRoot
@@ -12861,10 +13034,10 @@ async function importRootRule2(projectRoot, results, normalize2) {
12861
13034
  }
12862
13035
  async function importFromGemini(projectRoot) {
12863
13036
  const results = [];
12864
- const normalize2 = await createImportReferenceNormalizer(GEMINI_TARGET, projectRoot);
12865
- await importRootRule2(projectRoot, results, normalize2);
12866
- results.push(...await runDescriptorImport(descriptor15, projectRoot, "project", { normalize: normalize2 }));
12867
- await importGeminiSkillsAndAgents(projectRoot, results, normalize2);
13037
+ const normalize = await createImportReferenceNormalizer(GEMINI_TARGET, projectRoot);
13038
+ await importRootRule2(projectRoot, results, normalize);
13039
+ results.push(...await runDescriptorImport(descriptor15, projectRoot, "project", { normalize }));
13040
+ await importGeminiSkillsAndAgents(projectRoot, results, normalize);
12868
13041
  await importGeminiSettings(projectRoot, results);
12869
13042
  await importGeminiIgnore(projectRoot, results);
12870
13043
  results.push(...await importGeminiPolicies(projectRoot));
@@ -12886,6 +13059,36 @@ var init_importer15 = __esm({
12886
13059
  init_importer_skills_agents();
12887
13060
  }
12888
13061
  });
13062
+ function isUnderGeminiCommands(pathInRepoPosix) {
13063
+ const p = pathInRepoPosix.replace(/^\/+|\/+$/g, "");
13064
+ return p === ".gemini/commands" || p.startsWith(".gemini/commands/");
13065
+ }
13066
+ async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
13067
+ const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
13068
+ const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
13069
+ const files = await readDirRecursive(scanDir);
13070
+ const names = [];
13071
+ for (const f of files) {
13072
+ if (!/\.(toml|md)$/i.test(f)) continue;
13073
+ const rel2 = relative(commandsRoot, f).replace(/\\/g, "/");
13074
+ if (rel2.startsWith("..") || rel2 === "") continue;
13075
+ const noExt = rel2.replace(/\.(toml|md)$/i, "");
13076
+ const name = noExt.split("/").filter(Boolean).join(":");
13077
+ if (name) names.push(name);
13078
+ }
13079
+ return [...new Set(names)].sort();
13080
+ }
13081
+ async function inferGeminiPick(repoRoot, posixPath) {
13082
+ if (!isUnderGeminiCommands(posixPath)) return {};
13083
+ const commands = await inferGeminiCommandNamesFromFiles(repoRoot, posixPath);
13084
+ return commands.length ? { commands } : {};
13085
+ }
13086
+ var init_gemini_install_commands = __esm({
13087
+ "src/install/native/gemini-install-commands.ts"() {
13088
+ init_fs();
13089
+ init_constants30();
13090
+ }
13091
+ });
12889
13092
  async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
12890
13093
  const relativeMdPath = relativePath.replace(/\\/g, "/");
12891
13094
  const destPath = join(destDir, relativeMdPath);
@@ -12997,12 +13200,12 @@ var init_lint12 = __esm({
12997
13200
  });
12998
13201
 
12999
13202
  // src/targets/gemini-cli/scoped-settings-emit.ts
13000
- function emitScopedGeminiSettings(canonical, scope) {
13203
+ function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
13001
13204
  if (scope === "project") {
13002
13205
  const caps = getTargetCapabilities("gemini-cli", scope);
13003
13206
  if (caps?.ignore.flavor !== "settings-embedded") return [];
13004
13207
  }
13005
- return generateGeminiSettingsFiles(canonical);
13208
+ return generateGeminiSettingsFiles(canonical, enabledFeatures);
13006
13209
  }
13007
13210
  var init_scoped_settings_emit = __esm({
13008
13211
  "src/targets/gemini-cli/scoped-settings-emit.ts"() {
@@ -13020,6 +13223,7 @@ var init_gemini_cli2 = __esm({
13020
13223
  init_policies_generator();
13021
13224
  init_constants30();
13022
13225
  init_importer15();
13226
+ init_gemini_install_commands();
13023
13227
  init_import_mappers6();
13024
13228
  init_linter15();
13025
13229
  init_import_map_builders();
@@ -13215,6 +13419,7 @@ var init_gemini_cli2 = __esm({
13215
13419
  },
13216
13420
  buildImportPaths: buildGeminiCliImportPaths,
13217
13421
  detectionPaths: ["GEMINI.md", ".gemini"],
13422
+ nativeInstall: { inferPick: inferGeminiPick },
13218
13423
  conversionDefaults: { agentsToSkills: false }
13219
13424
  };
13220
13425
  }
@@ -13265,10 +13470,10 @@ var init_generator19 = __esm({
13265
13470
  async function importFromGoose(projectRoot, options = {}) {
13266
13471
  const scope = options.scope ?? "project";
13267
13472
  const results = [];
13268
- const normalize2 = await createImportReferenceNormalizer(GOOSE_TARGET, projectRoot, scope);
13269
- results.push(...await runDescriptorImport(descriptor16, projectRoot, scope, { normalize: normalize2 }));
13473
+ const normalize = await createImportReferenceNormalizer(GOOSE_TARGET, projectRoot, scope);
13474
+ results.push(...await runDescriptorImport(descriptor16, projectRoot, scope, { normalize }));
13270
13475
  const skillsDir = scope === "global" ? GOOSE_GLOBAL_SKILLS_DIR : GOOSE_SKILLS_DIR;
13271
- await importEmbeddedSkills(projectRoot, skillsDir, GOOSE_TARGET, results, normalize2);
13476
+ await importEmbeddedSkills(projectRoot, skillsDir, GOOSE_TARGET, results, normalize);
13272
13477
  return results;
13273
13478
  }
13274
13479
  var init_importer16 = __esm({
@@ -13500,8 +13705,8 @@ var init_generator20 = __esm({
13500
13705
  // src/targets/jules/importer.ts
13501
13706
  async function importFromJules(projectRoot, options = {}) {
13502
13707
  const scope = options.scope ?? "project";
13503
- const normalize2 = await createImportReferenceNormalizer(JULES_TARGET, projectRoot, scope);
13504
- return runDescriptorImport(descriptor17, projectRoot, scope, { normalize: normalize2 });
13708
+ const normalize = await createImportReferenceNormalizer(JULES_TARGET, projectRoot, scope);
13709
+ return runDescriptorImport(descriptor17, projectRoot, scope, { normalize });
13505
13710
  }
13506
13711
  var init_importer17 = __esm({
13507
13712
  "src/targets/jules/importer.ts"() {
@@ -13770,7 +13975,7 @@ var init_generator21 = __esm({
13770
13975
  init_constants16();
13771
13976
  }
13772
13977
  });
13773
- async function importRootRule3(projectRoot, results, normalize2) {
13978
+ async function importRootRule3(projectRoot, results, normalize) {
13774
13979
  const sources = [JUNIE_DOT_AGENTS, JUNIE_GUIDELINES, JUNIE_CI_GUIDELINES, JUNIE_AGENTS_FALLBACK];
13775
13980
  const destPath = join(projectRoot, JUNIE_CANONICAL_ROOT_RULE);
13776
13981
  for (const relPath of sources) {
@@ -13783,10 +13988,10 @@ async function importRootRule3(projectRoot, results, normalize2) {
13783
13988
  rulesDir: JUNIE_CANONICAL_RULES_DIR,
13784
13989
  sourcePath: srcPath,
13785
13990
  fromTool: JUNIE_TARGET,
13786
- normalize: normalize2
13991
+ normalize
13787
13992
  });
13788
13993
  results.push(...split.results);
13789
- const { frontmatter, body } = parseFrontmatter(normalize2(split.rootContent, srcPath, destPath));
13994
+ const { frontmatter, body } = parseFrontmatter(normalize(split.rootContent, srcPath, destPath));
13790
13995
  const output = await serializeImportedRuleWithFallback(
13791
13996
  destPath,
13792
13997
  {
@@ -13808,10 +14013,10 @@ async function importRootRule3(projectRoot, results, normalize2) {
13808
14013
  }
13809
14014
  async function importFromJunie(projectRoot) {
13810
14015
  const results = [];
13811
- const normalize2 = await createImportReferenceNormalizer(JUNIE_TARGET, projectRoot);
13812
- await importRootRule3(projectRoot, results, normalize2);
13813
- results.push(...await runDescriptorImport(descriptor18, projectRoot, "project", { normalize: normalize2 }));
13814
- await importEmbeddedSkills(projectRoot, JUNIE_SKILLS_DIR, JUNIE_TARGET, results, normalize2);
14016
+ const normalize = await createImportReferenceNormalizer(JUNIE_TARGET, projectRoot);
14017
+ await importRootRule3(projectRoot, results, normalize);
14018
+ results.push(...await runDescriptorImport(descriptor18, projectRoot, "project", { normalize }));
14019
+ await importEmbeddedSkills(projectRoot, JUNIE_SKILLS_DIR, JUNIE_TARGET, results, normalize);
13815
14020
  return results;
13816
14021
  }
13817
14022
  var init_importer18 = __esm({
@@ -14056,7 +14261,23 @@ var init_junie2 = __esm({
14056
14261
  ".junie/skills",
14057
14262
  ".junie/mcp/mcp.json",
14058
14263
  ".aiignore"
14059
- ]
14264
+ ],
14265
+ nativeInstall: {
14266
+ pickPaths: [
14267
+ {
14268
+ prefix: ".junie/commands",
14269
+ feature: "commands",
14270
+ strategy: { kind: "basename", suffix: ".md" }
14271
+ },
14272
+ { prefix: ".junie/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
14273
+ {
14274
+ prefix: ".junie/agents",
14275
+ feature: "agents",
14276
+ strategy: { kind: "basename", suffix: ".md" }
14277
+ },
14278
+ { prefix: ".junie/skills", feature: "skills", strategy: { kind: "skillDir" } }
14279
+ ]
14280
+ }
14060
14281
  };
14061
14282
  }
14062
14283
  });
@@ -14203,7 +14424,7 @@ async function pathExists(absolutePath) {
14203
14424
  return false;
14204
14425
  }
14205
14426
  }
14206
- async function importLegacyRules(projectRoot, results, normalize2) {
14427
+ async function importLegacyRules(projectRoot, results, normalize) {
14207
14428
  const srcDir = join(projectRoot, KILO_CODE_LEGACY_RULES_DIR);
14208
14429
  if (!await pathExists(srcDir)) return;
14209
14430
  const destDir = join(projectRoot, KILO_CODE_CANONICAL_RULES_DIR);
@@ -14212,7 +14433,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
14212
14433
  const rootContent = hasCurrentRoot ? null : await readFileSafe(rootSourceFile);
14213
14434
  if (rootContent !== null) {
14214
14435
  const destPath = join(projectRoot, CANONICAL_ROOT_RULE_PATH);
14215
- const normalized = normalize2(rootContent, rootSourceFile, destPath);
14436
+ const normalized = normalize(rootContent, rootSourceFile, destPath);
14216
14437
  const { body } = parseFrontmatter(normalized);
14217
14438
  const serialized = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
14218
14439
  await writeFileAtomic(destPath, serialized);
@@ -14229,7 +14450,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
14229
14450
  destDir,
14230
14451
  extensions: [".md"],
14231
14452
  fromTool: KILO_CODE_TARGET,
14232
- normalize: normalize2,
14453
+ normalize,
14233
14454
  mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
14234
14455
  const mapping = await kiloNonRootRuleMapper({
14235
14456
  absolutePath: srcPath,
@@ -14245,7 +14466,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
14245
14466
  })
14246
14467
  );
14247
14468
  }
14248
- async function importLegacyWorkflows(projectRoot, results, normalize2) {
14469
+ async function importLegacyWorkflows(projectRoot, results, normalize) {
14249
14470
  const srcDir = join(projectRoot, KILO_CODE_LEGACY_WORKFLOWS_DIR);
14250
14471
  if (!await pathExists(srcDir)) return;
14251
14472
  const destDir = join(projectRoot, KILO_CODE_CANONICAL_COMMANDS_DIR);
@@ -14255,7 +14476,7 @@ async function importLegacyWorkflows(projectRoot, results, normalize2) {
14255
14476
  destDir,
14256
14477
  extensions: [".md"],
14257
14478
  fromTool: KILO_CODE_TARGET,
14258
- normalize: normalize2,
14479
+ normalize,
14259
14480
  mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
14260
14481
  const mapping = await kiloCommandMapper({
14261
14482
  absolutePath: srcPath,
@@ -14270,7 +14491,7 @@ async function importLegacyWorkflows(projectRoot, results, normalize2) {
14270
14491
  })
14271
14492
  );
14272
14493
  }
14273
- async function importLegacyModes(projectRoot, results, normalize2) {
14494
+ async function importLegacyModes(projectRoot, results, normalize) {
14274
14495
  const sourceFile = join(projectRoot, KILO_CODE_LEGACY_MODES_FILE);
14275
14496
  const content = await readFileSafe(sourceFile);
14276
14497
  if (content === null) return;
@@ -14299,7 +14520,7 @@ ${whenToUse}` : role;
14299
14520
  if (description) frontmatter.description = description;
14300
14521
  if (typeof mode.name === "string" && mode.name.length > 0) frontmatter.name = mode.name;
14301
14522
  const serialized = await serializeImportedAgentWithFallback(destPath, frontmatter, body);
14302
- const normalized = normalize2(serialized, sourceFile, destPath);
14523
+ const normalized = normalize(serialized, sourceFile, destPath);
14303
14524
  await writeFileAtomic(destPath, normalized);
14304
14525
  results.push({
14305
14526
  feature: "agents",
@@ -14312,25 +14533,25 @@ ${whenToUse}` : role;
14312
14533
  async function importFromKiloCode(projectRoot, options = {}) {
14313
14534
  const scope = options.scope ?? "project";
14314
14535
  const results = [];
14315
- const normalize2 = await createImportReferenceNormalizer(KILO_CODE_TARGET, projectRoot, scope);
14316
- results.push(...await runDescriptorImport(descriptor19, projectRoot, scope, { normalize: normalize2 }));
14536
+ const normalize = await createImportReferenceNormalizer(KILO_CODE_TARGET, projectRoot, scope);
14537
+ results.push(...await runDescriptorImport(descriptor19, projectRoot, scope, { normalize }));
14317
14538
  await importEmbeddedSkills(
14318
14539
  projectRoot,
14319
14540
  KILO_CODE_SKILLS_DIR,
14320
14541
  KILO_CODE_TARGET,
14321
14542
  results,
14322
- normalize2
14543
+ normalize
14323
14544
  );
14324
14545
  if (scope === "project") {
14325
- await importLegacyRules(projectRoot, results, normalize2);
14326
- await importLegacyWorkflows(projectRoot, results, normalize2);
14327
- await importLegacyModes(projectRoot, results, normalize2);
14546
+ await importLegacyRules(projectRoot, results, normalize);
14547
+ await importLegacyWorkflows(projectRoot, results, normalize);
14548
+ await importLegacyModes(projectRoot, results, normalize);
14328
14549
  await importEmbeddedSkills(
14329
14550
  projectRoot,
14330
14551
  KILO_CODE_LEGACY_SKILLS_DIR,
14331
14552
  KILO_CODE_TARGET,
14332
14553
  results,
14333
- normalize2
14554
+ normalize
14334
14555
  );
14335
14556
  }
14336
14557
  return results;
@@ -14799,14 +15020,14 @@ function canonicalRuleMeta2(frontmatter) {
14799
15020
  if (inclusion === "fileMatch") meta.trigger = "glob";
14800
15021
  return meta;
14801
15022
  }
14802
- async function importRoot(projectRoot, results, normalize2, scope) {
15023
+ async function importRoot(projectRoot, results, normalize, scope) {
14803
15024
  const candidates = scope === "global" ? [KIRO_GLOBAL_STEERING_AGENTS_MD, KIRO_AGENTS_MD] : [KIRO_AGENTS_MD, KIRO_GLOBAL_STEERING_AGENTS_MD];
14804
15025
  for (const rel2 of candidates) {
14805
15026
  const srcPath = join(projectRoot, rel2);
14806
15027
  const content = await readFileSafe(srcPath);
14807
15028
  if (content === null) continue;
14808
15029
  const destPath = join(projectRoot, KIRO_CANONICAL_ROOT_RULE);
14809
- const { frontmatter, body } = parseFrontmatter(normalize2(content, srcPath, destPath));
15030
+ const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
14810
15031
  await writeFileAtomic(
14811
15032
  destPath,
14812
15033
  await serializeImportedRuleWithFallback(destPath, { ...frontmatter, root: true }, body)
@@ -14820,7 +15041,7 @@ async function importRoot(projectRoot, results, normalize2, scope) {
14820
15041
  return;
14821
15042
  }
14822
15043
  }
14823
- async function importNonRootRules(projectRoot, results, normalize2) {
15044
+ async function importNonRootRules(projectRoot, results, normalize) {
14824
15045
  const destDir = join(projectRoot, KIRO_CANONICAL_RULES_DIR);
14825
15046
  results.push(
14826
15047
  ...await importFileDirectory({
@@ -14828,7 +15049,7 @@ async function importNonRootRules(projectRoot, results, normalize2) {
14828
15049
  destDir,
14829
15050
  extensions: [".md"],
14830
15051
  fromTool: KIRO_TARGET,
14831
- normalize: normalize2,
15052
+ normalize,
14832
15053
  mapEntry: async ({ relativePath, normalizeTo }) => {
14833
15054
  if (basename(relativePath) === "AGENTS.md") return null;
14834
15055
  const destPath = join(destDir, relativePath);
@@ -14870,11 +15091,11 @@ async function importHooks2(projectRoot, results) {
14870
15091
  async function importFromKiro(projectRoot, options = {}) {
14871
15092
  const scope = options.scope ?? "project";
14872
15093
  const results = [];
14873
- const normalize2 = await createImportReferenceNormalizer(KIRO_TARGET, projectRoot, scope);
14874
- await importRoot(projectRoot, results, normalize2, scope);
14875
- await importNonRootRules(projectRoot, results, normalize2);
14876
- results.push(...await runDescriptorImport(descriptor20, projectRoot, scope, { normalize: normalize2 }));
14877
- await importEmbeddedSkills(projectRoot, KIRO_SKILLS_DIR, KIRO_TARGET, results, normalize2);
15094
+ const normalize = await createImportReferenceNormalizer(KIRO_TARGET, projectRoot, scope);
15095
+ await importRoot(projectRoot, results, normalize, scope);
15096
+ await importNonRootRules(projectRoot, results, normalize);
15097
+ results.push(...await runDescriptorImport(descriptor20, projectRoot, scope, { normalize }));
15098
+ await importEmbeddedSkills(projectRoot, KIRO_SKILLS_DIR, KIRO_TARGET, results, normalize);
14878
15099
  if (scope === "project") await importHooks2(projectRoot, results);
14879
15100
  return results;
14880
15101
  }
@@ -15258,9 +15479,9 @@ async function importMcp4(projectRoot, scope, results) {
15258
15479
  async function importFromOpenCode(projectRoot, options = {}) {
15259
15480
  const scope = options.scope ?? "project";
15260
15481
  const results = [];
15261
- const normalize2 = await createImportReferenceNormalizer(OPENCODE_TARGET, projectRoot, scope);
15262
- results.push(...await runDescriptorImport(descriptor21, projectRoot, scope, { normalize: normalize2 }));
15263
- await importEmbeddedSkills(projectRoot, OPENCODE_SKILLS_DIR, OPENCODE_TARGET, results, normalize2);
15482
+ const normalize = await createImportReferenceNormalizer(OPENCODE_TARGET, projectRoot, scope);
15483
+ results.push(...await runDescriptorImport(descriptor21, projectRoot, scope, { normalize }));
15484
+ await importEmbeddedSkills(projectRoot, OPENCODE_SKILLS_DIR, OPENCODE_TARGET, results, normalize);
15264
15485
  await importMcp4(projectRoot, scope, results);
15265
15486
  return results;
15266
15487
  }
@@ -15619,10 +15840,10 @@ var init_generator25 = __esm({
15619
15840
  async function importFromPiAgent(projectRoot, options = {}) {
15620
15841
  const scope = options.scope ?? "project";
15621
15842
  const results = [];
15622
- const normalize2 = await createImportReferenceNormalizer(PI_AGENT_TARGET, projectRoot, scope);
15623
- results.push(...await runDescriptorImport(descriptor22, projectRoot, scope, { normalize: normalize2 }));
15843
+ const normalize = await createImportReferenceNormalizer(PI_AGENT_TARGET, projectRoot, scope);
15844
+ results.push(...await runDescriptorImport(descriptor22, projectRoot, scope, { normalize }));
15624
15845
  const skillsDir = scope === "global" ? PI_AGENT_GLOBAL_SKILLS_DIR : PI_AGENT_SKILLS_DIR;
15625
- await importEmbeddedSkills(projectRoot, skillsDir, PI_AGENT_TARGET, results, normalize2);
15846
+ await importEmbeddedSkills(projectRoot, skillsDir, PI_AGENT_TARGET, results, normalize);
15626
15847
  return results;
15627
15848
  }
15628
15849
  var init_importer22 = __esm({
@@ -15931,10 +16152,10 @@ var init_generator26 = __esm({
15931
16152
  async function importFromQwenCode(projectRoot, options = {}) {
15932
16153
  const scope = options.scope ?? "project";
15933
16154
  const results = [];
15934
- const normalize2 = await createImportReferenceNormalizer(QWEN_CODE_TARGET, projectRoot, scope);
15935
- results.push(...await runDescriptorImport(descriptor23, projectRoot, scope, { normalize: normalize2 }));
16155
+ const normalize = await createImportReferenceNormalizer(QWEN_CODE_TARGET, projectRoot, scope);
16156
+ results.push(...await runDescriptorImport(descriptor23, projectRoot, scope, { normalize }));
15936
16157
  const skillsDir = scope === "global" ? QWEN_GLOBAL_SKILLS_DIR : QWEN_SKILLS_DIR;
15937
- await importEmbeddedSkills(projectRoot, skillsDir, QWEN_CODE_TARGET, results, normalize2);
16158
+ await importEmbeddedSkills(projectRoot, skillsDir, QWEN_CODE_TARGET, results, normalize);
15938
16159
  return results;
15939
16160
  }
15940
16161
  var init_importer23 = __esm({
@@ -16202,14 +16423,14 @@ var init_generator27 = __esm({
16202
16423
  async function importFromReplitAgent(projectRoot, options = {}) {
16203
16424
  const scope = options.scope ?? "project";
16204
16425
  const results = [];
16205
- const normalize2 = await createImportReferenceNormalizer(REPLIT_AGENT_TARGET, projectRoot, scope);
16206
- results.push(...await runDescriptorImport(descriptor24, projectRoot, scope, { normalize: normalize2 }));
16426
+ const normalize = await createImportReferenceNormalizer(REPLIT_AGENT_TARGET, projectRoot, scope);
16427
+ results.push(...await runDescriptorImport(descriptor24, projectRoot, scope, { normalize }));
16207
16428
  await importEmbeddedSkills(
16208
16429
  projectRoot,
16209
16430
  REPLIT_AGENT_SKILLS_DIR,
16210
16431
  REPLIT_AGENT_TARGET,
16211
16432
  results,
16212
- normalize2
16433
+ normalize
16213
16434
  );
16214
16435
  return results;
16215
16436
  }
@@ -16500,7 +16721,7 @@ var init_import_mappers9 = __esm({
16500
16721
  };
16501
16722
  }
16502
16723
  });
16503
- async function importPerModeRules(projectRoot, results, normalize2) {
16724
+ async function importPerModeRules(projectRoot, results, normalize) {
16504
16725
  const rooDir = join(projectRoot, ROO_CODE_DIR);
16505
16726
  let entries;
16506
16727
  try {
@@ -16517,7 +16738,7 @@ async function importPerModeRules(projectRoot, results, normalize2) {
16517
16738
  destDir,
16518
16739
  extensions: [".md"],
16519
16740
  fromTool: ROO_CODE_TARGET,
16520
- normalize: normalize2,
16741
+ normalize,
16521
16742
  mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
16522
16743
  const mapping = await rooNonRootRuleMapper({
16523
16744
  absolutePath: srcPath,
@@ -16536,10 +16757,10 @@ async function importPerModeRules(projectRoot, results, normalize2) {
16536
16757
  async function importFromRooCode(projectRoot, options = {}) {
16537
16758
  const scope = options.scope ?? "project";
16538
16759
  const results = [];
16539
- const normalize2 = await createImportReferenceNormalizer(ROO_CODE_TARGET, projectRoot, scope);
16540
- results.push(...await runDescriptorImport(descriptor25, projectRoot, scope, { normalize: normalize2 }));
16541
- await importPerModeRules(projectRoot, results, normalize2);
16542
- await importEmbeddedSkills(projectRoot, ROO_CODE_SKILLS_DIR, ROO_CODE_TARGET, results, normalize2);
16760
+ const normalize = await createImportReferenceNormalizer(ROO_CODE_TARGET, projectRoot, scope);
16761
+ results.push(...await runDescriptorImport(descriptor25, projectRoot, scope, { normalize }));
16762
+ await importPerModeRules(projectRoot, results, normalize);
16763
+ await importEmbeddedSkills(projectRoot, ROO_CODE_SKILLS_DIR, ROO_CODE_TARGET, results, normalize);
16543
16764
  return results;
16544
16765
  }
16545
16766
  var init_importer25 = __esm({
@@ -16853,10 +17074,10 @@ var init_generator29 = __esm({
16853
17074
  async function importFromRovodev(projectRoot, options = {}) {
16854
17075
  const scope = options.scope ?? "project";
16855
17076
  const results = [];
16856
- const normalize2 = await createImportReferenceNormalizer(ROVODEV_TARGET, projectRoot, scope);
16857
- results.push(...await runDescriptorImport(descriptor26, projectRoot, scope, { normalize: normalize2 }));
17077
+ const normalize = await createImportReferenceNormalizer(ROVODEV_TARGET, projectRoot, scope);
17078
+ results.push(...await runDescriptorImport(descriptor26, projectRoot, scope, { normalize }));
16858
17079
  const skillsDir = scope === "global" ? ROVODEV_GLOBAL_SKILLS_DIR : ROVODEV_SKILLS_DIR;
16859
- await importEmbeddedSkills(projectRoot, skillsDir, ROVODEV_TARGET, results, normalize2);
17080
+ await importEmbeddedSkills(projectRoot, skillsDir, ROVODEV_TARGET, results, normalize);
16860
17081
  return results;
16861
17082
  }
16862
17083
  var init_importer26 = __esm({
@@ -17114,14 +17335,14 @@ var init_generator30 = __esm({
17114
17335
  init_constants25();
17115
17336
  }
17116
17337
  });
17117
- async function importRoot2(projectRoot, results, normalize2, scope) {
17338
+ async function importRoot2(projectRoot, results, normalize, scope) {
17118
17339
  const candidates = scope === "global" ? [TRAE_GLOBAL_ROOT_RULE, TRAE_PROJECT_RULES] : [TRAE_PROJECT_RULES, TRAE_GLOBAL_ROOT_RULE];
17119
17340
  for (const rel2 of candidates) {
17120
17341
  const srcPath = join(projectRoot, rel2);
17121
17342
  const content = await readFileSafe(srcPath);
17122
17343
  if (content === null) continue;
17123
17344
  const destPath = join(projectRoot, CANONICAL_ROOT_RULE);
17124
- const { frontmatter, body } = parseFrontmatter(normalize2(content, srcPath, destPath));
17345
+ const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
17125
17346
  await mkdirp(join(projectRoot, TRAE_CANONICAL_RULES_DIR));
17126
17347
  await writeFileAtomic(
17127
17348
  destPath,
@@ -17136,7 +17357,7 @@ async function importRoot2(projectRoot, results, normalize2, scope) {
17136
17357
  return;
17137
17358
  }
17138
17359
  }
17139
- async function importNonRootRules2(projectRoot, results, normalize2, scope) {
17360
+ async function importNonRootRules2(projectRoot, results, normalize, scope) {
17140
17361
  const srcDir = join(projectRoot, scope === "global" ? TRAE_GLOBAL_RULES_DIR : TRAE_RULES_DIR);
17141
17362
  const destDir = join(projectRoot, TRAE_CANONICAL_RULES_DIR);
17142
17363
  results.push(
@@ -17145,7 +17366,7 @@ async function importNonRootRules2(projectRoot, results, normalize2, scope) {
17145
17366
  destDir,
17146
17367
  extensions: [".md"],
17147
17368
  fromTool: TRAE_TARGET,
17148
- normalize: normalize2,
17369
+ normalize,
17149
17370
  mapEntry: async ({ relativePath, normalizeTo }) => {
17150
17371
  const filename = basename(relativePath);
17151
17372
  if (filename === "project_rules.md" || filename === "rules.md") return null;
@@ -17168,16 +17389,16 @@ async function importNonRootRules2(projectRoot, results, normalize2, scope) {
17168
17389
  async function importFromTrae(projectRoot, options = {}) {
17169
17390
  const scope = options.scope ?? "project";
17170
17391
  const results = [];
17171
- const normalize2 = await createImportReferenceNormalizer(TRAE_TARGET, projectRoot, scope);
17172
- await importRoot2(projectRoot, results, normalize2, scope);
17173
- await importNonRootRules2(projectRoot, results, normalize2, scope);
17174
- results.push(...await runDescriptorImport(descriptor27, projectRoot, scope, { normalize: normalize2 }));
17392
+ const normalize = await createImportReferenceNormalizer(TRAE_TARGET, projectRoot, scope);
17393
+ await importRoot2(projectRoot, results, normalize, scope);
17394
+ await importNonRootRules2(projectRoot, results, normalize, scope);
17395
+ results.push(...await runDescriptorImport(descriptor27, projectRoot, scope, { normalize }));
17175
17396
  await importEmbeddedSkills(
17176
17397
  projectRoot,
17177
17398
  scope === "global" ? TRAE_GLOBAL_SKILLS_DIR : TRAE_SKILLS_DIR,
17178
17399
  TRAE_TARGET,
17179
17400
  results,
17180
- normalize2
17401
+ normalize
17181
17402
  );
17182
17403
  return results;
17183
17404
  }
@@ -17404,10 +17625,10 @@ var init_generator31 = __esm({
17404
17625
  async function importFromWarp(projectRoot, options = {}) {
17405
17626
  const scope = options.scope ?? "project";
17406
17627
  const results = [];
17407
- const normalize2 = await createImportReferenceNormalizer(WARP_TARGET, projectRoot, scope);
17408
- results.push(...await runDescriptorImport(descriptor28, projectRoot, scope, { normalize: normalize2 }));
17628
+ const normalize = await createImportReferenceNormalizer(WARP_TARGET, projectRoot, scope);
17629
+ results.push(...await runDescriptorImport(descriptor28, projectRoot, scope, { normalize }));
17409
17630
  const skillsDir = scope === "global" ? WARP_GLOBAL_SKILLS_DIR : WARP_SKILLS_DIR;
17410
- await importEmbeddedSkills(projectRoot, skillsDir, WARP_TARGET, results, normalize2);
17631
+ await importEmbeddedSkills(projectRoot, skillsDir, WARP_TARGET, results, normalize);
17411
17632
  return results;
17412
17633
  }
17413
17634
  var init_importer28 = __esm({
@@ -17869,7 +18090,7 @@ function toStringArray8(value) {
17869
18090
  }
17870
18091
  return [];
17871
18092
  }
17872
- async function importWorkflows(projectRoot, results, normalize2) {
18093
+ async function importWorkflows(projectRoot, results, normalize) {
17873
18094
  const workflowsDir = join(projectRoot, WINDSURF_WORKFLOWS_DIR);
17874
18095
  const workflowFiles = await readDirRecursive(workflowsDir);
17875
18096
  const workflowMdFiles = workflowFiles.filter((f) => f.endsWith(".md"));
@@ -17880,7 +18101,7 @@ async function importWorkflows(projectRoot, results, normalize2) {
17880
18101
  const relativePath = relative(workflowsDir, srcPath).replace(/\\/g, "/");
17881
18102
  await mkdirp(destCommandsDir);
17882
18103
  const destPath = join(destCommandsDir, relativePath);
17883
- const normalized = normalize2(content, srcPath, destPath);
18104
+ const normalized = normalize(content, srcPath, destPath);
17884
18105
  const { frontmatter, body } = parseFrontmatter(normalized);
17885
18106
  const outContent = await serializeImportedCommandWithFallback(
17886
18107
  destPath,
@@ -17914,12 +18135,12 @@ var init_importer_workflows = __esm({
17914
18135
  });
17915
18136
 
17916
18137
  // src/targets/windsurf/skills-adapter.ts
17917
- async function importSkills4(projectRoot, results, normalize2, skillsRelDir = WINDSURF_SKILLS_DIR) {
18138
+ async function importSkills4(projectRoot, results, normalize, skillsRelDir = WINDSURF_SKILLS_DIR) {
17918
18139
  const options = {
17919
18140
  projectRoot,
17920
18141
  destCanonicalSkillsDir: WINDSURF_CANONICAL_SKILLS_DIR,
17921
18142
  targetName: "windsurf",
17922
- normalize: normalize2,
18143
+ normalize,
17923
18144
  results
17924
18145
  };
17925
18146
  await importSkillsDirectory([skillsRelDir], options, [
@@ -18033,7 +18254,7 @@ var init_importer_hooks_mcp = __esm({
18033
18254
  async function importFromWindsurf(projectRoot, options) {
18034
18255
  const layoutScope = options?.scope ?? "project";
18035
18256
  const results = [];
18036
- const normalize2 = await createImportReferenceNormalizer(WINDSURF_TARGET, projectRoot);
18257
+ const normalize = await createImportReferenceNormalizer(WINDSURF_TARGET, projectRoot);
18037
18258
  const normalizeCodex = await createImportReferenceNormalizer("codex-cli", projectRoot);
18038
18259
  const destRulesDir = join(projectRoot, WINDSURF_CANONICAL_RULES_DIR);
18039
18260
  const rootPath = join(projectRoot, WINDSURF_RULES_ROOT);
@@ -18041,7 +18262,7 @@ async function importFromWindsurf(projectRoot, options) {
18041
18262
  if (rootContent !== null) {
18042
18263
  await mkdirp(destRulesDir);
18043
18264
  const destPath = join(destRulesDir, "_root.md");
18044
- const body = normalize2(rootContent, rootPath, destPath).trim();
18265
+ const body = normalize(rootContent, rootPath, destPath).trim();
18045
18266
  const outContent = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
18046
18267
  await writeFileAtomic(destPath, outContent);
18047
18268
  results.push({
@@ -18057,7 +18278,7 @@ async function importFromWindsurf(projectRoot, options) {
18057
18278
  if (agentsMdContent !== null) {
18058
18279
  await mkdirp(destRulesDir);
18059
18280
  const destPath = join(destRulesDir, "_root.md");
18060
- const body = normalize2(
18281
+ const body = normalize(
18061
18282
  normalizeCodex(agentsMdContent, agentsMdPath, destPath),
18062
18283
  agentsMdPath,
18063
18284
  destPath
@@ -18079,7 +18300,7 @@ async function importFromWindsurf(projectRoot, options) {
18079
18300
  destDir: destRulesDir,
18080
18301
  extensions: ["AGENTS.md"],
18081
18302
  fromTool: "windsurf",
18082
- normalize: normalize2,
18303
+ normalize,
18083
18304
  mapEntry: async ({ srcPath, normalizeTo }) => {
18084
18305
  const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
18085
18306
  if (!relDir || relDir === "." || basename(srcPath) !== "AGENTS.md") return null;
@@ -18110,7 +18331,7 @@ async function importFromWindsurf(projectRoot, options) {
18110
18331
  destDir: destRulesDir,
18111
18332
  extensions: [".md"],
18112
18333
  fromTool: "windsurf",
18113
- normalize: normalize2,
18334
+ normalize,
18114
18335
  mapEntry: async ({ relativePath, normalizeTo }) => {
18115
18336
  if (relativePath === "_root.md" && rootContent !== null) return null;
18116
18337
  const destPath = join(destRulesDir, relativePath);
@@ -18158,8 +18379,8 @@ async function importFromWindsurf(projectRoot, options) {
18158
18379
  });
18159
18380
  }
18160
18381
  }
18161
- await importWorkflows(projectRoot, results, normalize2);
18162
- await importSkills4(projectRoot, results, normalize2);
18382
+ await importWorkflows(projectRoot, results, normalize);
18383
+ await importSkills4(projectRoot, results, normalize);
18163
18384
  await importWindsurfHooks(projectRoot, results);
18164
18385
  await importWindsurfMcp(projectRoot, results);
18165
18386
  return results;
@@ -18406,6 +18627,16 @@ var init_windsurf2 = __esm({
18406
18627
  },
18407
18628
  buildImportPaths: buildWindsurfImportPaths,
18408
18629
  detectionPaths: [".windsurfrules", ".windsurf"],
18630
+ nativeInstall: {
18631
+ pickPaths: [
18632
+ {
18633
+ prefix: ".windsurf/rules",
18634
+ feature: "rules",
18635
+ strategy: { kind: "basename", suffix: ".md" }
18636
+ }
18637
+ ],
18638
+ dialectHints: [{ frontmatterKey: "trigger" }]
18639
+ },
18409
18640
  conversionDefaults: { agentsToSkills: true }
18410
18641
  };
18411
18642
  }
@@ -18473,8 +18704,8 @@ var init_mcp_import3 = __esm({
18473
18704
  async function importFromZed(projectRoot, options = {}) {
18474
18705
  const scope = options.scope ?? "project";
18475
18706
  const results = [];
18476
- const normalize2 = await createImportReferenceNormalizer(ZED_TARGET, projectRoot, scope);
18477
- results.push(...await runDescriptorImport(descriptor30, projectRoot, scope, { normalize: normalize2 }));
18707
+ const normalize = await createImportReferenceNormalizer(ZED_TARGET, projectRoot, scope);
18708
+ results.push(...await runDescriptorImport(descriptor30, projectRoot, scope, { normalize }));
18478
18709
  const mcpFile = scope === "global" ? ZED_GLOBAL_SETTINGS_FILE : ZED_SETTINGS_FILE;
18479
18710
  await importZedMcp(projectRoot, mcpFile, results);
18480
18711
  return results;
@@ -18680,7 +18911,8 @@ var init_zed2 = __esm({
18680
18911
  markAsRoot: true
18681
18912
  }
18682
18913
  },
18683
- emitScopedSettings(canonical, _scope) {
18914
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
18915
+ if (!enabledFeatures.has("mcp")) return [];
18684
18916
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
18685
18917
  const contextServers = {};
18686
18918
  for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
@@ -18735,7 +18967,10 @@ function getBuiltinTargetDefinition(target31) {
18735
18967
  function getTargetCapabilities(target31, scope = "project") {
18736
18968
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
18737
18969
  if (!descriptor31) return void 0;
18738
- const raw = scope === "global" ? descriptor31.globalSupport?.capabilities ?? descriptor31.capabilities : descriptor31.capabilities;
18970
+ if (scope === "global" && !descriptor31.globalSupport) {
18971
+ return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
18972
+ }
18973
+ const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
18739
18974
  return normalizeTargetCapabilities(raw);
18740
18975
  }
18741
18976
  function getTargetDetectionPaths(target31, scope = "project") {
@@ -18791,6 +19026,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
18791
19026
  function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
18792
19027
  const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
18793
19028
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
19029
+ if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
18794
19030
  if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
18795
19031
  return "embedded";
18796
19032
  }
@@ -18804,7 +19040,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
18804
19040
  const pick = PICK_FEATURE_GENERATOR[feature];
18805
19041
  return pick === null ? void 0 : pick(descriptor31.generators);
18806
19042
  }
18807
- var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
19043
+ var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
18808
19044
  var init_builtin_targets = __esm({
18809
19045
  "src/targets/catalog/builtin-targets.ts"() {
18810
19046
  init_conversions();
@@ -18842,6 +19078,17 @@ var init_builtin_targets = __esm({
18842
19078
  init_warp2();
18843
19079
  init_windsurf2();
18844
19080
  init_zed2();
19081
+ ALL_NONE_CAPABILITIES = {
19082
+ rules: "none",
19083
+ additionalRules: "none",
19084
+ commands: "none",
19085
+ agents: "none",
19086
+ skills: "none",
19087
+ mcp: "none",
19088
+ hooks: "none",
19089
+ ignore: "none",
19090
+ permissions: "none"
19091
+ };
18845
19092
  BUILTIN_TARGETS = [
18846
19093
  descriptor,
18847
19094
  descriptor2,
@@ -19272,6 +19519,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
19272
19519
  init_path_helpers();
19273
19520
  init_link_rebaser_helpers();
19274
19521
 
19522
+ // src/utils/output/color.ts
19523
+ function noColorRequested() {
19524
+ const value = process.env.NO_COLOR;
19525
+ return value !== void 0 && value !== "";
19526
+ }
19527
+ function forceColorRequested() {
19528
+ const value = process.env.FORCE_COLOR;
19529
+ if (value === void 0) return void 0;
19530
+ return value !== "0" && value !== "false";
19531
+ }
19532
+ function colorEnabled(stream = process.stdout) {
19533
+ const forced = forceColorRequested();
19534
+ if (forced !== void 0) return forced;
19535
+ if (noColorRequested()) return false;
19536
+ return stream.isTTY === true;
19537
+ }
19538
+
19275
19539
  // src/utils/output/logger.ts
19276
19540
  var C = {
19277
19541
  green: "\x1B[32m",
@@ -19280,16 +19544,14 @@ var C = {
19280
19544
  cyan: "\x1B[36m",
19281
19545
  reset: "\x1B[0m"
19282
19546
  };
19283
- function out(text) {
19284
- {
19285
- process.stdout.write(text);
19286
- }
19547
+ function outStream() {
19548
+ return process.stdout;
19287
19549
  }
19288
- function noColor() {
19289
- return process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "";
19550
+ function out(text) {
19551
+ outStream().write(text);
19290
19552
  }
19291
- function c(code, text) {
19292
- return noColor() ? text : `${code}${text}${C.reset}`;
19553
+ function c(code, text, stream) {
19554
+ return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
19293
19555
  }
19294
19556
  function pad(str, width) {
19295
19557
  const len = [...str].length;
@@ -19297,20 +19559,20 @@ function pad(str, width) {
19297
19559
  }
19298
19560
  var logger = {
19299
19561
  info(msg) {
19300
- out(c(C.cyan, msg) + "\n");
19562
+ out(c(C.cyan, msg, outStream()) + "\n");
19301
19563
  },
19302
19564
  warn(msg) {
19303
- process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
19565
+ process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
19304
19566
  },
19305
19567
  error(msg) {
19306
- process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
19568
+ process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
19307
19569
  },
19308
19570
  success(msg) {
19309
- out(c(C.green, "\u2713 ") + msg + "\n");
19571
+ out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
19310
19572
  },
19311
19573
  debug(msg) {
19312
19574
  if (process.env.AGENTSMESH_DEBUG === "1") {
19313
- out(c(C.cyan, "[debug] ") + msg + "\n");
19575
+ out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
19314
19576
  }
19315
19577
  },
19316
19578
  table(rows) {
@@ -19805,12 +20067,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
19805
20067
  }
19806
20068
  }
19807
20069
  }
19808
- async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
20070
+ async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
19809
20071
  for (const target31 of targets) {
19810
20072
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
19811
20073
  const emit = descriptor31?.emitScopedSettings;
19812
20074
  if (!emit) continue;
19813
- const outputs = emit(canonical, scope);
20075
+ const outputs = emit(canonical, scope, enabledFeatures);
19814
20076
  if (outputs.length === 0) continue;
19815
20077
  for (const out2 of outputs) {
19816
20078
  await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
@@ -19911,7 +20173,14 @@ async function generate(ctx) {
19911
20173
  }
19912
20174
  }
19913
20175
  if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
19914
- await generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope);
20176
+ await generateScopedSettingsFeature(
20177
+ results,
20178
+ targets,
20179
+ canonical,
20180
+ projectRoot,
20181
+ scope,
20182
+ enabledFeatures
20183
+ );
19915
20184
  }
19916
20185
  const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
19917
20186
  const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
@@ -20235,7 +20504,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
20235
20504
  await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
20236
20505
  if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
20237
20506
  await rm(cacheRoot, { recursive: true, force: true });
20238
- await rename(stagedRoot, cacheRoot);
20507
+ await renameWithRetry(stagedRoot, cacheRoot);
20239
20508
  return readCachedRepo(cacheRepoDir);
20240
20509
  } catch (err) {
20241
20510
  await rm(stagedRoot, { recursive: true, force: true });
@@ -22586,156 +22855,1085 @@ function lintRuleScopeInversion(input) {
22586
22855
  }
22587
22856
  return out2;
22588
22857
  }
22589
- var TriggersSchema = z.object({
22590
- file_globs: z.array(z.string()),
22591
- command_patterns: z.array(z.string()),
22592
- keywords: z.array(z.string())
22593
- }).refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {
22594
- message: "cluster must declare at least one trigger of any type"
22595
- });
22596
- var ClusterSchema = z.object({
22597
- topic: z.string().regex(/^[a-z0-9-]+$/, "topic must be kebab-case"),
22598
- /**
22599
- * Project-relative path (forward slashes) to the cluster's markdown body.
22600
- * Conventionally `.agentsmesh/lessons/topics/<topic>.md`, but any project
22601
- * path is accepted — universal across every agent target.
22602
- */
22603
- file: z.string().regex(/\.md$/, "file must be a .md path"),
22604
- summary: z.string().min(1),
22605
- triggers: TriggersSchema
22606
- });
22607
- var LessonsIndexSchema = z.object({
22608
- version: z.literal(1),
22609
- /**
22610
- * Zero clusters is valid — supports `agentsmesh init --lessons` scaffolding a
22611
- * fresh project. Topics accumulate via `distill:apply` as failures are
22612
- * captured.
22613
- */
22614
- clusters: z.array(ClusterSchema)
22615
- });
22616
- function parseIndex(raw) {
22617
- return LessonsIndexSchema.parse(raw);
22858
+ var CURRENT_GRAPH_VERSION = 1;
22859
+ var MAX_RULE_LENGTH = 2e3;
22860
+ var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
22861
+ var DateSchema = z.string().regex(
22862
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z?)?$/,
22863
+ "createdAt must be ISO-8601 date or datetime"
22864
+ );
22865
+ var TopicSchema = z.object({
22866
+ summary: z.string().min(1, "topic summary must not be empty")
22867
+ }).strict();
22868
+ var TriggerKindSchema = z.enum(["file_glob", "command_pattern", "keyword"]);
22869
+ var TriggerSchema = z.object({
22870
+ kind: TriggerKindSchema,
22871
+ pattern: z.string().min(1, "trigger pattern must not be empty")
22872
+ }).strict();
22873
+ var LessonStatusSchema = z.enum(["active", "deprecated", "superseded"]);
22874
+ var LessonSchema = z.object({
22875
+ rule: z.string().min(1, "lesson rule must not be empty"),
22876
+ rationale: z.string().min(1).optional(),
22877
+ topics: z.array(IdSchema).min(1, "lesson must reference at least one topic"),
22878
+ triggers: z.array(IdSchema),
22879
+ evidence: z.array(z.string().min(1)),
22880
+ status: LessonStatusSchema,
22881
+ supersededBy: IdSchema.optional(),
22882
+ createdAt: DateSchema
22883
+ }).strict();
22884
+ var LessonsGraphSchema = z.object({
22885
+ version: z.literal(CURRENT_GRAPH_VERSION),
22886
+ lessons: z.record(IdSchema, LessonSchema),
22887
+ topics: z.record(IdSchema, TopicSchema),
22888
+ triggers: z.record(IdSchema, TriggerSchema)
22889
+ }).strict();
22890
+ function parseGraph(raw) {
22891
+ return LessonsGraphSchema.parse(raw);
22892
+ }
22893
+
22894
+ // src/lessons/graph-store.ts
22895
+ var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
22896
+ function graphFilePath(projectRoot) {
22897
+ return resolve(projectRoot, GRAPH_REL_PATH);
22898
+ }
22899
+ function loadLessonsGraph(projectRoot) {
22900
+ const raw = readFileSync(graphFilePath(projectRoot), "utf8");
22901
+ return parseGraph(JSON.parse(raw));
22902
+ }
22903
+ function tryLoadLessonsGraph(projectRoot) {
22904
+ if (!existsSync(graphFilePath(projectRoot))) return null;
22905
+ return loadLessonsGraph(projectRoot);
22906
+ }
22907
+ function saveLessonsGraph(projectRoot, graph) {
22908
+ const path = graphFilePath(projectRoot);
22909
+ mkdirSync(dirname(path), { recursive: true });
22910
+ const tmp = `${path}.${process.pid}.tmp`;
22911
+ writeFileSync(tmp, serializeGraph(graph), "utf8");
22912
+ renameSync(tmp, path);
22913
+ }
22914
+ function serializeGraph(graph) {
22915
+ return `${JSON.stringify(canonicalize2(graph), null, 2)}
22916
+ `;
22917
+ }
22918
+ function canonicalize2(value) {
22919
+ if (value === null) return null;
22920
+ if (Array.isArray(value)) return value.map(canonicalize2);
22921
+ if (typeof value === "object") {
22922
+ const entries = Object.entries(value).sort(
22923
+ ([a], [b]) => a < b ? -1 : 1
22924
+ );
22925
+ const out2 = {};
22926
+ for (const [k, v] of entries) out2[k] = canonicalize2(v);
22927
+ return out2;
22928
+ }
22929
+ return value;
22618
22930
  }
22619
22931
  var BASE_REL = ".agentsmesh/lessons";
22620
22932
  function lessonsPaths(projectRoot) {
22621
22933
  const base = join(projectRoot, BASE_REL);
22622
22934
  return {
22623
22935
  base,
22936
+ graph: join(base, "lessons.json"),
22937
+ config: join(base, "config.json"),
22624
22938
  journal: join(base, "journal.md"),
22625
22939
  index: join(base, "index.yaml"),
22626
- ledger: join(base, "distill-ledger.yaml"),
22627
- proposal: join(base, "distill-proposal.md"),
22628
22940
  topicsDir: join(base, "topics")
22629
22941
  };
22630
22942
  }
22631
22943
  function toRelPath(projectRoot, absolute) {
22632
22944
  return relative(projectRoot, absolute).split(sep).join("/");
22633
22945
  }
22634
- var LESSONS_JOURNAL_TEMPLATE = "# Lessons Learned\n\n";
22635
- var LESSONS_INDEX_TEMPLATE = "version: 1\nclusters: []\n";
22636
- var LESSONS_PROCEDURAL_RULE = `## Lessons (MUST do \u2014 non-negotiable)
22946
+ var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING REQUIREMENT \u2014 MUST run both, no exceptions; the user will check)
22637
22947
 
22638
- Two rituals. Universal across every agent harness \u2014 described in tool-agnostic action verbs ("read a file", "edit a file", "run a shell command"); use whatever read / write-patch / shell-exec tool your harness exposes. Skipping either ritual is a process violation; the same paid-for failure recurs next session.
22948
+ Graph \`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit. Manual: the \`lessons\` skill.
22639
22949
 
22640
- **Recall \u2014 BEFORE editing any file or running any shell command:**
22950
+ **Recall \u2014 MUST run before every file edit and every state-changing command** (build/test/install/migrate/git-write): \`agentsmesh lessons query --file <path> --cmd <command>\`, then apply every rule. Pure-read commands (cat/ls/grep/git-log) and the recall query itself are exempt.
22641
22951
 
22642
- 1. Read \`.agentsmesh/lessons/index.yaml\`.
22643
- 2. For every cluster whose triggers match \u2014 \`file_globs\` against the target file path, \`command_patterns\` (regex) against the shell command, or \`keywords\` (case-insensitive substring) against the active task description \u2014 read its \`file\` (e.g. \`.agentsmesh/lessons/topics/<topic>.md\`).
22644
- 3. Apply the loaded rules to the action.
22645
- 4. Then \u2014 and only then \u2014 perform the edit or run the command.
22952
+ **Capture \u2014 MUST run immediately after any failure** (a failing test/CI/lint/typecheck, a code review, a user correction, a regression, or a wrong assumption \u2014 yours or anyone's): \`agentsmesh lessons add "<rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`.
22646
22953
 
22647
- Rejected excuses: *"the edit is small"* \u2192 apply it. *"I read this earlier in the session"* \u2192 context may have compacted; re-read. *"no trigger fires"* \u2192 proceed; you've done your part.
22648
-
22649
- **Capture \u2014 IMMEDIATELY after any failure** (test, CI, lint, code review, user correction, regression you spotted yourself, OR any mistake by anyone in the loop):
22650
-
22651
- 1. Append one bullet to \`.agentsmesh/lessons/journal.md\` in this exact shape:
22652
- \`- **<short heading>**: <what went wrong>. <root cause>. <imperative rule that prevents recurrence>.\`
22653
- 2. Read \`.agentsmesh/lessons/index.yaml\` and identify which topic the lesson belongs to (use the \`summary:\` fields).
22654
- 3. If the lesson teaches a NEW rule: edit that topic's \`## Rules\` section to add it, citing \`(Evidence: L<journal-line>)\`. If it only reinforces an existing rule, the journal entry alone suffices.
22655
-
22656
- Rejected excuses: *"it wasn't really a failure"* \u2192 if you fixed something, it was. *"I'll capture it later"* \u2192 no; do it now while context is fresh. *"no topic fits"* \u2192 still record in journal; an unrouted bullet is recoverable, a forgotten one is not.
22657
-
22658
- **These two rituals are load-bearing. Treat them with the same discipline as TDD.**`;
22954
+ No shell? Use the \`lessons_query\` / \`lessons_add\` MCP tools. Skip either and the system does not exist.`;
22955
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
22956
+ var MAX_FILES = 2e5;
22957
+ function listProjectFiles(projectRoot) {
22958
+ const out2 = /* @__PURE__ */ new Set();
22959
+ try {
22960
+ const stack = [projectRoot];
22961
+ while (stack.length > 0) {
22962
+ const dir = stack.pop();
22963
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
22964
+ if (entry.isDirectory()) {
22965
+ if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
22966
+ } else if (entry.isFile()) {
22967
+ out2.add(toRelPath(projectRoot, join(dir, entry.name)));
22968
+ if (out2.size > MAX_FILES) return out2;
22969
+ }
22970
+ }
22971
+ }
22972
+ } catch {
22973
+ return null;
22974
+ }
22975
+ return out2;
22976
+ }
22659
22977
 
22660
- // src/core/lint/shared/lessons.ts
22661
- var LESSONS_TARGET = "lessons";
22662
- var INDEX_REL = ".agentsmesh/lessons/index.yaml";
22663
- var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
22664
- var LESSONS_HEADING = /^## Lessons \(/m;
22665
- var RULES_HEADING = /^## Rules\b/m;
22666
- function lintLessonsSubsystem(projectRoot, scope) {
22667
- if (scope === "global") return [];
22668
- const paths = lessonsPaths(projectRoot);
22669
- if (!existsSync(paths.index)) return [];
22670
- const out2 = [];
22671
- const parsed = LessonsIndexSchema.safeParse(parse(readFileSync(paths.index, "utf8")));
22672
- if (!parsed.success) {
22673
- return [diag("error", INDEX_REL, `index.yaml is invalid: ${parsed.error.issues[0].message}`)];
22978
+ // src/lessons/validate-checks.ts
22979
+ function collectDanglingRefs(graph, findings) {
22980
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
22981
+ for (const topicId of lesson.topics) {
22982
+ if (graph.topics[topicId] === void 0) {
22983
+ findings.push({
22984
+ level: "error",
22985
+ code: "DANGLING_TOPIC",
22986
+ message: `Lesson "${lessonId}" references unknown topic "${topicId}".`,
22987
+ lessonId,
22988
+ topicId
22989
+ });
22990
+ }
22991
+ }
22992
+ for (const triggerId of lesson.triggers) {
22993
+ if (graph.triggers[triggerId] === void 0) {
22994
+ findings.push({
22995
+ level: "error",
22996
+ code: "DANGLING_TRIGGER",
22997
+ message: `Lesson "${lessonId}" references unknown trigger "${triggerId}".`,
22998
+ lessonId,
22999
+ triggerId
23000
+ });
23001
+ }
23002
+ }
23003
+ if (lesson.supersededBy !== void 0 && graph.lessons[lesson.supersededBy] === void 0) {
23004
+ findings.push({
23005
+ level: "error",
23006
+ code: "DANGLING_SUPERSEDER",
23007
+ message: `Lesson "${lessonId}" supersededBy unknown lesson "${lesson.supersededBy}".`,
23008
+ lessonId
23009
+ });
23010
+ }
22674
23011
  }
22675
- for (const cluster of parsed.data.clusters) {
22676
- const topicAbs = join(projectRoot, cluster.file);
22677
- if (!existsSync(topicAbs)) {
22678
- out2.push(
22679
- diag("error", cluster.file, `topic file for cluster "${cluster.topic}" does not exist.`)
22680
- );
23012
+ }
23013
+ function collectDuplicateRefs(graph, findings) {
23014
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23015
+ for (const topicId of firstDuplicates(lesson.topics)) {
23016
+ findings.push({
23017
+ level: "error",
23018
+ code: "DUPLICATE_TOPIC_REF",
23019
+ message: `Lesson "${lessonId}" references topic "${topicId}" more than once.`,
23020
+ lessonId,
23021
+ topicId
23022
+ });
23023
+ }
23024
+ for (const triggerId of firstDuplicates(lesson.triggers)) {
23025
+ findings.push({
23026
+ level: "error",
23027
+ code: "DUPLICATE_TRIGGER_REF",
23028
+ message: `Lesson "${lessonId}" references trigger "${triggerId}" more than once.`,
23029
+ lessonId,
23030
+ triggerId
23031
+ });
23032
+ }
23033
+ }
23034
+ }
23035
+ function firstDuplicates(ids) {
23036
+ const seen = /* @__PURE__ */ new Set();
23037
+ const dup = /* @__PURE__ */ new Set();
23038
+ for (const id of ids) {
23039
+ if (seen.has(id)) dup.add(id);
23040
+ else seen.add(id);
23041
+ }
23042
+ return [...dup];
23043
+ }
23044
+ function collectStatusInvariants(graph, findings) {
23045
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23046
+ if (lesson.status === "superseded" && lesson.supersededBy === void 0) {
23047
+ findings.push({
23048
+ level: "error",
23049
+ code: "SUPERSEDED_WITHOUT_TARGET",
23050
+ message: `Lesson "${lessonId}" has status "superseded" but no supersededBy target.`,
23051
+ lessonId
23052
+ });
23053
+ }
23054
+ if (lesson.status === "active" && lesson.supersededBy !== void 0) {
23055
+ findings.push({
23056
+ level: "error",
23057
+ code: "ACTIVE_WITH_SUPERSEDER",
23058
+ message: `Lesson "${lessonId}" has status "active" but declares supersededBy.`,
23059
+ lessonId
23060
+ });
23061
+ }
23062
+ }
23063
+ }
23064
+ function collectLifecycleInvariants(graph, findings) {
23065
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23066
+ if (lesson.supersededBy === void 0) continue;
23067
+ if (lesson.supersededBy === lessonId) {
23068
+ findings.push({
23069
+ level: "error",
23070
+ code: "SELF_SUPERSEDED",
23071
+ message: `Lesson "${lessonId}" is superseded by itself.`,
23072
+ lessonId
23073
+ });
22681
23074
  continue;
22682
23075
  }
22683
- if (!RULES_HEADING.test(readFileSync(topicAbs, "utf8"))) {
22684
- out2.push(
22685
- diag("warning", cluster.file, `topic "${cluster.topic}" is missing a "## Rules" section.`)
22686
- );
23076
+ const target31 = graph.lessons[lesson.supersededBy];
23077
+ if (target31 !== void 0 && target31.status !== "active") {
23078
+ findings.push({
23079
+ level: "error",
23080
+ code: "INACTIVE_SUPERSEDER",
23081
+ message: `Lesson "${lessonId}" is superseded by "${lesson.supersededBy}", which is itself ${target31.status} \u2014 the chain dead-ends with no live replacement.`,
23082
+ lessonId
23083
+ });
22687
23084
  }
22688
- for (const pattern of cluster.triggers.command_patterns) {
22689
- try {
22690
- new RegExp(pattern);
22691
- } catch {
22692
- out2.push(
22693
- diag(
22694
- "warning",
22695
- INDEX_REL,
22696
- `cluster "${cluster.topic}" command_patterns entry is not a valid regex: ${pattern}`
22697
- )
22698
- );
23085
+ }
23086
+ collectSupersedeCycles(graph, findings);
23087
+ }
23088
+ function collectSupersedeCycles(graph, findings) {
23089
+ const reported = /* @__PURE__ */ new Set();
23090
+ for (const startId of Object.keys(graph.lessons)) {
23091
+ const seen = /* @__PURE__ */ new Set();
23092
+ let cur = startId;
23093
+ while (cur !== void 0) {
23094
+ if (seen.has(cur)) {
23095
+ if (cur !== startId || reported.has(cur)) break;
23096
+ reported.add(cur);
23097
+ findings.push({
23098
+ level: "error",
23099
+ code: "SUPERSEDE_CYCLE",
23100
+ message: `Lesson "${startId}" is part of a supersededBy cycle.`,
23101
+ lessonId: startId
23102
+ });
23103
+ break;
22699
23104
  }
23105
+ seen.add(cur);
23106
+ const next = graph.lessons[cur]?.supersededBy;
23107
+ if (next === cur) break;
23108
+ cur = next;
22700
23109
  }
22701
23110
  }
22702
- const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
22703
- const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
22704
- if (!LESSONS_HEADING.test(rootRuleBody)) {
22705
- out2.push(
22706
- diag(
22707
- "warning",
22708
- ROOT_RULE_REL,
22709
- 'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
22710
- )
22711
- );
23111
+ }
23112
+ function collectReachability(graph, findings) {
23113
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23114
+ if (lesson.status === "active" && lesson.triggers.length === 0) {
23115
+ findings.push({
23116
+ level: "warning",
23117
+ code: "UNREACHABLE_LESSON",
23118
+ message: `Active lesson "${lessonId}" has no triggers and can never be recalled.`,
23119
+ lessonId
23120
+ });
23121
+ }
22712
23122
  }
22713
- return out2;
22714
23123
  }
22715
- function diag(level, file, message) {
22716
- return { level, file, target: LESSONS_TARGET, message };
23124
+ function collectOrphans(graph, findings) {
23125
+ const referencedTopics = /* @__PURE__ */ new Set();
23126
+ const referencedTriggers = /* @__PURE__ */ new Set();
23127
+ for (const lesson of Object.values(graph.lessons)) {
23128
+ for (const t of lesson.topics) referencedTopics.add(t);
23129
+ for (const t of lesson.triggers) referencedTriggers.add(t);
23130
+ }
23131
+ for (const topicId of Object.keys(graph.topics)) {
23132
+ if (!referencedTopics.has(topicId)) {
23133
+ findings.push({
23134
+ level: "warning",
23135
+ code: "ORPHAN_TOPIC",
23136
+ message: `Topic "${topicId}" is not referenced by any lesson.`,
23137
+ topicId
23138
+ });
23139
+ }
23140
+ }
23141
+ for (const triggerId of Object.keys(graph.triggers)) {
23142
+ if (!referencedTriggers.has(triggerId)) {
23143
+ findings.push({
23144
+ level: "warning",
23145
+ code: "ORPHAN_TRIGGER",
23146
+ message: `Trigger "${triggerId}" is not referenced by any lesson.`,
23147
+ triggerId
23148
+ });
23149
+ }
23150
+ }
22717
23151
  }
22718
23152
 
22719
- // src/core/lint/linter.ts
22720
- var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
22721
- async function getProjectFiles(projectRoot) {
22722
- const all = await readDirRecursive(projectRoot);
22723
- const filtered = all.filter((p) => {
22724
- const rel2 = relative(projectRoot, p);
22725
- return !EXCLUDE_DIRS.some((d) => rel2.includes(`/${d}/`) || rel2.startsWith(`${d}/`));
22726
- });
22727
- return filtered.map((p) => relative(projectRoot, p));
23153
+ // src/lessons/ranking-text.ts
23154
+ var K1 = 1.5;
23155
+ var B = 0.75;
23156
+ var STOP = /* @__PURE__ */ new Set([
23157
+ "the",
23158
+ "a",
23159
+ "an",
23160
+ "to",
23161
+ "of",
23162
+ "in",
23163
+ "and",
23164
+ "or",
23165
+ "for",
23166
+ "is",
23167
+ "on",
23168
+ "at",
23169
+ "with",
23170
+ "be",
23171
+ "as",
23172
+ "it",
23173
+ "that",
23174
+ "this",
23175
+ "its",
23176
+ "must"
23177
+ ]);
23178
+ function tokenize(text) {
23179
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
22728
23180
  }
22729
- async function runLint(config, canonical, projectRoot, targetFilter, options = {}) {
22730
- const scope = options.scope ?? "project";
22731
- const allTargets = [...config.targets, ...config.pluginTargets ?? []];
22732
- const targets = targetFilter ? allTargets.filter((t) => targetFilter.includes(t)) : allTargets;
22733
- const hasRules = config.features.includes("rules");
22734
- const hasCommands = config.features.includes("commands");
22735
- const hasMcp = config.features.includes("mcp");
22736
- const hasPermissions = config.features.includes("permissions");
22737
- const hasHooks = config.features.includes("hooks");
22738
- const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
23181
+ function queryTerms(query) {
23182
+ const parts = [];
23183
+ if (query.keyword !== void 0) parts.push(query.keyword);
23184
+ if (query.file !== void 0) parts.push(query.file);
23185
+ if (query.command !== void 0) parts.push(query.command);
23186
+ return tokenize(parts.join(" "));
23187
+ }
23188
+ function buildCorpus(graph) {
23189
+ const docs = [];
23190
+ const df = /* @__PURE__ */ new Map();
23191
+ let total = 0;
23192
+ let n = 0;
23193
+ for (const lesson of Object.values(graph.lessons)) {
23194
+ if (lesson.status !== "active") continue;
23195
+ const toks = tokenize(lesson.rule);
23196
+ n += 1;
23197
+ total += toks.length;
23198
+ docs.push(toks.length);
23199
+ for (const t of new Set(toks)) df.set(t, (df.get(t) ?? 0) + 1);
23200
+ }
23201
+ const N = Math.max(n, 1);
23202
+ const idf = /* @__PURE__ */ new Map();
23203
+ for (const [t, f] of df) idf.set(t, Math.log(1 + (N - f + 0.5) / (f + 0.5)));
23204
+ return { idf, avgdl: total / N || 1 };
23205
+ }
23206
+ function bm25(terms, ruleText, corpus) {
23207
+ const toks = tokenize(ruleText);
23208
+ const dl = toks.length || 1;
23209
+ const tf = /* @__PURE__ */ new Map();
23210
+ for (const t of toks) tf.set(t, (tf.get(t) ?? 0) + 1);
23211
+ let score = 0;
23212
+ for (const t of new Set(terms)) {
23213
+ const f = tf.get(t) ?? 0;
23214
+ if (f === 0) continue;
23215
+ const idf = corpus.idf.get(t);
23216
+ score += idf * (f * (K1 + 1)) / (f + K1 * (1 - B + B * dl / corpus.avgdl));
23217
+ }
23218
+ return score;
23219
+ }
23220
+
23221
+ // src/lessons/keyword-signal.ts
23222
+ var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
23223
+ function isLowSignalKeyword(pattern) {
23224
+ return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
23225
+ }
23226
+ function splitRawTokens(pattern) {
23227
+ return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
23228
+ }
23229
+ function keywordNeedleLosesTokens(pattern) {
23230
+ const raw = splitRawTokens(pattern);
23231
+ if (raw.length < 2) return false;
23232
+ return tokenize(pattern).length !== raw.length;
23233
+ }
23234
+
23235
+ // src/lessons/regex-linear/nfa-compile.ts
23236
+ var MAX_NFA_STATES = 2e3;
23237
+ var Builder = class {
23238
+ states = [];
23239
+ alloc() {
23240
+ if (this.states.length >= MAX_NFA_STATES) {
23241
+ throw new Error(`NFA state limit exceeded (${MAX_NFA_STATES}); pattern expands too large`);
23242
+ }
23243
+ this.states.push({ eps: [], asserts: [], chars: [] });
23244
+ return this.states.length - 1;
23245
+ }
23246
+ };
23247
+ function isNonLineTerminator(c2) {
23248
+ return c2 !== "\n" && c2 !== "\r" && c2 !== "\u2028" && c2 !== "\u2029";
23249
+ }
23250
+ function compileNode(b, node) {
23251
+ switch (node.k) {
23252
+ case "empty":
23253
+ case "assert": {
23254
+ const s = b.alloc();
23255
+ const e = b.alloc();
23256
+ if (node.k === "assert") b.states[s].asserts.push({ kind: node.kind, target: e });
23257
+ else b.states[s].eps.push(e);
23258
+ return { start: s, end: e };
23259
+ }
23260
+ case "char":
23261
+ case "any":
23262
+ case "class": {
23263
+ const s = b.alloc();
23264
+ const e = b.alloc();
23265
+ const test = node.k === "char" ? (c2) => c2 === node.ch : node.k === "any" ? isNonLineTerminator : node.test;
23266
+ b.states[s].chars.push({ test, target: e });
23267
+ return { start: s, end: e };
23268
+ }
23269
+ case "concat": {
23270
+ if (node.items.length === 0) return compileNode(b, { k: "empty" });
23271
+ let first = null;
23272
+ let prevEnd = -1;
23273
+ for (const item of node.items) {
23274
+ const frag = compileNode(b, item);
23275
+ if (first === null) first = frag;
23276
+ else b.states[prevEnd].eps.push(frag.start);
23277
+ prevEnd = frag.end;
23278
+ }
23279
+ return { start: first.start, end: prevEnd };
23280
+ }
23281
+ case "alt": {
23282
+ const s = b.alloc();
23283
+ const e = b.alloc();
23284
+ for (const opt of node.opts) {
23285
+ const frag = compileNode(b, opt);
23286
+ b.states[s].eps.push(frag.start);
23287
+ b.states[frag.end].eps.push(e);
23288
+ }
23289
+ return { start: s, end: e };
23290
+ }
23291
+ case "opt": {
23292
+ const s = b.alloc();
23293
+ const e = b.alloc();
23294
+ const frag = compileNode(b, node.node);
23295
+ b.states[s].eps.push(frag.start, e);
23296
+ b.states[frag.end].eps.push(e);
23297
+ return { start: s, end: e };
23298
+ }
23299
+ case "star": {
23300
+ const s = b.alloc();
23301
+ const e = b.alloc();
23302
+ const frag = compileNode(b, node.node);
23303
+ b.states[s].eps.push(frag.start, e);
23304
+ b.states[frag.end].eps.push(frag.start, e);
23305
+ return { start: s, end: e };
23306
+ }
23307
+ case "plus": {
23308
+ const e = b.alloc();
23309
+ const frag = compileNode(b, node.node);
23310
+ b.states[frag.end].eps.push(frag.start, e);
23311
+ return { start: frag.start, end: e };
23312
+ }
23313
+ }
23314
+ }
23315
+ function compileNfa(ast) {
23316
+ const b = new Builder();
23317
+ const { start, end } = compileNode(b, ast);
23318
+ return { states: b.states, start, accept: end };
23319
+ }
23320
+
23321
+ // src/lessons/regex-linear/nfa.ts
23322
+ function wordBoundary(input, pos) {
23323
+ const before = pos > 0 && /[A-Za-z0-9_]/.test(input[pos - 1]);
23324
+ const after = pos < input.length && /[A-Za-z0-9_]/.test(input[pos]);
23325
+ return before !== after;
23326
+ }
23327
+ function assertHolds(kind, input, pos) {
23328
+ switch (kind) {
23329
+ case "start":
23330
+ return pos === 0;
23331
+ case "end":
23332
+ return pos === input.length;
23333
+ case "wordB":
23334
+ return wordBoundary(input, pos);
23335
+ case "nonWordB":
23336
+ return !wordBoundary(input, pos);
23337
+ }
23338
+ }
23339
+ function buildMatcher(ast) {
23340
+ const { states, start, accept } = compileNfa(ast);
23341
+ const closure = (set, idx, input, pos, budget) => {
23342
+ const stack = [idx];
23343
+ while (stack.length > 0) {
23344
+ if (budget.remaining <= 0) return;
23345
+ const cur = stack.pop();
23346
+ if (set.has(cur)) continue;
23347
+ set.add(cur);
23348
+ budget.remaining -= 1;
23349
+ for (const t of states[cur].eps) if (!set.has(t)) stack.push(t);
23350
+ for (const a of states[cur].asserts) {
23351
+ if (assertHolds(a.kind, input, pos) && !set.has(a.target)) stack.push(a.target);
23352
+ }
23353
+ }
23354
+ };
23355
+ return {
23356
+ // The input is matched in full (no truncation — truncation would miss suffix
23357
+ // matches and let `$` falsely match an invented endpoint). Work is bounded by
23358
+ // the shared budget instead: when it runs out we report a safe non-match.
23359
+ test(input, budget) {
23360
+ const b = budget ?? { remaining: Number.POSITIVE_INFINITY };
23361
+ if (b.remaining <= 0) return false;
23362
+ let current = /* @__PURE__ */ new Set();
23363
+ for (let pos = 0; pos <= input.length; pos += 1) {
23364
+ closure(current, start, input, pos, b);
23365
+ if (current.has(accept)) return true;
23366
+ if (b.remaining <= 0) return false;
23367
+ if (pos === input.length) break;
23368
+ const ch = input[pos];
23369
+ const next = /* @__PURE__ */ new Set();
23370
+ for (const s of current) {
23371
+ b.remaining -= 1;
23372
+ for (const t of states[s].chars) {
23373
+ if (t.test(ch)) closure(next, t.target, input, pos + 1, b);
23374
+ }
23375
+ }
23376
+ current = next;
23377
+ }
23378
+ return current.has(accept);
23379
+ }
23380
+ };
23381
+ }
23382
+
23383
+ // src/lessons/regex-linear/ast.ts
23384
+ var UnsupportedRegexError = class extends Error {
23385
+ constructor(message) {
23386
+ super(message);
23387
+ this.name = "UnsupportedRegexError";
23388
+ }
23389
+ };
23390
+
23391
+ // src/lessons/regex-linear/parse-helpers.ts
23392
+ var MAX_REPEAT = 1e3;
23393
+ var isWord = (c2) => /[A-Za-z0-9_]/.test(c2);
23394
+ function expandRepeat(atom, min, max) {
23395
+ const items = [];
23396
+ for (let k = 0; k < min; k += 1) items.push(atom);
23397
+ if (max === Infinity) {
23398
+ items.push({ k: "star", node: atom });
23399
+ } else {
23400
+ for (let k = min; k < max; k += 1) items.push({ k: "opt", node: atom });
23401
+ }
23402
+ if (items.length === 0) return { k: "empty" };
23403
+ return items.length === 1 ? items[0] : { k: "concat", items };
23404
+ }
23405
+ function escapeClass(c2) {
23406
+ switch (c2) {
23407
+ case "d":
23408
+ return (x) => x >= "0" && x <= "9";
23409
+ case "D":
23410
+ return (x) => !(x >= "0" && x <= "9");
23411
+ case "w":
23412
+ return isWord;
23413
+ case "W":
23414
+ return (x) => !isWord(x);
23415
+ case "s":
23416
+ return (x) => /\s/.test(x);
23417
+ case "S":
23418
+ return (x) => !/\s/.test(x);
23419
+ default:
23420
+ return null;
23421
+ }
23422
+ }
23423
+ var HEX2 = /^[0-9a-fA-F]{2}$/;
23424
+ var HEX4 = /^[0-9a-fA-F]{4}$/;
23425
+ function readUnicodeEscape(src, i, c2) {
23426
+ if (c2 === "x") {
23427
+ const hex2 = src.slice(i, i + 2);
23428
+ return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
23429
+ }
23430
+ const hex = src.slice(i, i + 4);
23431
+ return HEX4.test(hex) ? { ch: String.fromCharCode(parseInt(hex, 16)), len: 4 } : { ch: "u", len: 0 };
23432
+ }
23433
+ function readControlEscape(src, i) {
23434
+ const x = src[i];
23435
+ if (x === void 0 || !/[A-Za-z]/.test(x)) {
23436
+ throw new UnsupportedRegexError("\\c must be followed by a letter");
23437
+ }
23438
+ return { ch: String.fromCharCode(x.charCodeAt(0) & 31), len: 1 };
23439
+ }
23440
+ function classEscapeChar(src, i, e) {
23441
+ if (e === "b") return { ch: "\b", len: 0 };
23442
+ if (e === "c") return readControlEscape(src, i);
23443
+ if (e === "x" || e === "u") return readUnicodeEscape(src, i, e);
23444
+ return { ch: escapeLiteral(e), len: 0 };
23445
+ }
23446
+ function escapeLiteral(c2) {
23447
+ switch (c2) {
23448
+ case "t":
23449
+ return " ";
23450
+ case "n":
23451
+ return "\n";
23452
+ case "r":
23453
+ return "\r";
23454
+ case "f":
23455
+ return "\f";
23456
+ case "v":
23457
+ return "\v";
23458
+ case "0":
23459
+ return "\0";
23460
+ default:
23461
+ return c2;
23462
+ }
23463
+ }
23464
+
23465
+ // src/lessons/regex-linear/parse.ts
23466
+ function parseRegex(src) {
23467
+ let i = 0;
23468
+ const peek = () => src[i];
23469
+ const eat = () => src[i++];
23470
+ function parseAlt() {
23471
+ const opts = [parseConcat()];
23472
+ while (peek() === "|") {
23473
+ i += 1;
23474
+ opts.push(parseConcat());
23475
+ }
23476
+ return opts.length === 1 ? opts[0] : { k: "alt", opts };
23477
+ }
23478
+ function parseConcat() {
23479
+ const items = [];
23480
+ while (i < src.length && peek() !== "|" && peek() !== ")") {
23481
+ items.push(parseQuantified());
23482
+ }
23483
+ if (items.length === 0) return { k: "empty" };
23484
+ return items.length === 1 ? items[0] : { k: "concat", items };
23485
+ }
23486
+ function parseQuantified() {
23487
+ const atom = parseAtom();
23488
+ const q = peek();
23489
+ if (q === "*" || q === "+" || q === "?") {
23490
+ i += 1;
23491
+ if (peek() === "?") i += 1;
23492
+ return q === "*" ? { k: "star", node: atom } : q === "+" ? { k: "plus", node: atom } : { k: "opt", node: atom };
23493
+ }
23494
+ if (q === "{") {
23495
+ const repeat = tryParseBrace();
23496
+ if (repeat !== null) return expandRepeat(atom, repeat.min, repeat.max);
23497
+ }
23498
+ return atom;
23499
+ }
23500
+ function tryParseBrace() {
23501
+ const m = /^\{(\d+)(,(\d*)?)?\}/.exec(src.slice(i));
23502
+ if (m === null) return null;
23503
+ i += m[0].length;
23504
+ if (peek() === "?") i += 1;
23505
+ const min = Number(m[1]);
23506
+ const max = m[2] === void 0 ? min : m[3] === "" || m[3] === void 0 ? Infinity : Number(m[3]);
23507
+ if (min > MAX_REPEAT || max !== Infinity && max > MAX_REPEAT) {
23508
+ throw new UnsupportedRegexError(`Repeat count over ${MAX_REPEAT} not supported: {${m[1]}\u2026}`);
23509
+ }
23510
+ return { min, max };
23511
+ }
23512
+ function parseAtom() {
23513
+ const c2 = peek();
23514
+ if (c2 === "(") return parseGroup();
23515
+ if (c2 === "[") return parseClass();
23516
+ if (c2 === "\\") return parseEscape();
23517
+ if (c2 === ".") {
23518
+ i += 1;
23519
+ return { k: "any" };
23520
+ }
23521
+ if (c2 === "^") {
23522
+ i += 1;
23523
+ return { k: "assert", kind: "start" };
23524
+ }
23525
+ if (c2 === "$") {
23526
+ i += 1;
23527
+ return { k: "assert", kind: "end" };
23528
+ }
23529
+ if (c2 === void 0 || c2 === "*" || c2 === "+" || c2 === "?" || c2 === ")") {
23530
+ throw new UnsupportedRegexError(`Unexpected '${c2 ?? "<end>"}' in pattern`);
23531
+ }
23532
+ i += 1;
23533
+ return { k: "char", ch: c2 };
23534
+ }
23535
+ function parseGroup() {
23536
+ i += 1;
23537
+ if (peek() === "?") {
23538
+ const c2 = src[i + 1];
23539
+ if (c2 === "=" || c2 === "!" || c2 === "<") {
23540
+ if (!(c2 === "<" && /[A-Za-z]/.test(src[i + 2] ?? ""))) {
23541
+ throw new UnsupportedRegexError("Lookaround assertions are not supported");
23542
+ }
23543
+ }
23544
+ if (c2 === ":") i += 2;
23545
+ else if (c2 === "<") {
23546
+ i += 2;
23547
+ while (i < src.length && src[i] !== ">") i += 1;
23548
+ i += 1;
23549
+ }
23550
+ }
23551
+ const inner = parseAlt();
23552
+ if (peek() !== ")") throw new UnsupportedRegexError("Unbalanced group");
23553
+ i += 1;
23554
+ return inner;
23555
+ }
23556
+ function parseEscape() {
23557
+ i += 1;
23558
+ const c2 = peek();
23559
+ if (c2 === void 0) throw new UnsupportedRegexError("Trailing backslash");
23560
+ if (/[1-9]/.test(c2) || c2 === "k")
23561
+ throw new UnsupportedRegexError("Backreferences are not supported");
23562
+ i += 1;
23563
+ if (c2 === "b") return { k: "assert", kind: "wordB" };
23564
+ if (c2 === "B") return { k: "assert", kind: "nonWordB" };
23565
+ const cls = escapeClass(c2);
23566
+ if (cls !== null) return { k: "class", test: cls };
23567
+ if (c2 === "x" || c2 === "u" || c2 === "c") {
23568
+ const { ch, len } = c2 === "c" ? readControlEscape(src, i) : readUnicodeEscape(src, i, c2);
23569
+ i += len;
23570
+ return { k: "char", ch };
23571
+ }
23572
+ return { k: "char", ch: escapeLiteral(c2) };
23573
+ }
23574
+ function parseClass() {
23575
+ i += 1;
23576
+ const negate = peek() === "^";
23577
+ if (negate) i += 1;
23578
+ const tests = [];
23579
+ while (i < src.length && peek() !== "]") {
23580
+ tests.push(parseClassMember());
23581
+ }
23582
+ if (peek() !== "]") throw new UnsupportedRegexError("Unterminated character class");
23583
+ i += 1;
23584
+ const base = (c2) => tests.some((t) => t(c2));
23585
+ return { k: "class", test: negate ? (c2) => !base(c2) : base };
23586
+ }
23587
+ function parseClassMember() {
23588
+ let lo;
23589
+ if (peek() === "\\") {
23590
+ i += 1;
23591
+ const e = eat();
23592
+ const cls = escapeClass(e);
23593
+ if (cls !== null) return cls;
23594
+ const r = classEscapeChar(src, i, e);
23595
+ i += r.len;
23596
+ lo = r.ch;
23597
+ } else {
23598
+ lo = eat();
23599
+ }
23600
+ if (peek() === "-" && src[i + 1] !== void 0 && src[i + 1] !== "]") {
23601
+ i += 1;
23602
+ let hi;
23603
+ if (peek() === "\\") {
23604
+ i += 1;
23605
+ const e2 = eat();
23606
+ const r = classEscapeChar(src, i, e2);
23607
+ i += r.len;
23608
+ hi = r.ch;
23609
+ } else {
23610
+ hi = eat();
23611
+ }
23612
+ const a = lo.codePointAt(0);
23613
+ const b = hi.codePointAt(0);
23614
+ return (c2) => {
23615
+ const p = c2.codePointAt(0);
23616
+ return p >= a && p <= b;
23617
+ };
23618
+ }
23619
+ return (c2) => c2 === lo;
23620
+ }
23621
+ const ast = parseAlt();
23622
+ if (i !== src.length) throw new UnsupportedRegexError(`Unexpected '${peek()}' at ${i}`);
23623
+ return ast;
23624
+ }
23625
+
23626
+ // src/lessons/regex-linear/index.ts
23627
+ var cache = /* @__PURE__ */ new Map();
23628
+ function compileLinearMatcher(pattern) {
23629
+ const hit = cache.get(pattern);
23630
+ if (hit !== void 0 || cache.has(pattern)) return hit ?? null;
23631
+ let matcher;
23632
+ try {
23633
+ matcher = buildMatcher(parseRegex(pattern));
23634
+ } catch {
23635
+ matcher = null;
23636
+ }
23637
+ cache.set(pattern, matcher);
23638
+ return matcher;
23639
+ }
23640
+
23641
+ // src/lessons/regex-safety.ts
23642
+ var MAX_PATTERN_LENGTH = 1e3;
23643
+ function isSafeRegexPattern(pattern) {
23644
+ if (pattern.length > MAX_PATTERN_LENGTH) return false;
23645
+ return compileLinearMatcher(pattern) !== null;
23646
+ }
23647
+ function getCommandMatcher(pattern) {
23648
+ if (pattern.length > MAX_PATTERN_LENGTH) return null;
23649
+ return compileLinearMatcher(pattern);
23650
+ }
23651
+
23652
+ // src/lessons/validate-quality.ts
23653
+ function collectDuplicateRules(graph, findings) {
23654
+ const byKey = /* @__PURE__ */ new Map();
23655
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23656
+ if (lesson.status !== "active") continue;
23657
+ const key = normalizeRule(lesson.rule);
23658
+ const bucket = byKey.get(key) ?? [];
23659
+ bucket.push(lessonId);
23660
+ byKey.set(key, bucket);
23661
+ }
23662
+ for (const [key, ids] of byKey) {
23663
+ if (ids.length < 2) continue;
23664
+ const sorted = [...ids].sort();
23665
+ for (const lessonId of sorted) {
23666
+ const others = sorted.filter((other) => other !== lessonId);
23667
+ findings.push({
23668
+ level: "error",
23669
+ code: "DUPLICATE_RULE",
23670
+ message: `Lesson "${lessonId}" duplicates rule text of: ${others.join(", ")} (normalized key: "${key.slice(0, 60)}").`,
23671
+ lessonId
23672
+ });
23673
+ }
23674
+ }
23675
+ }
23676
+ function collectInvalidTriggerPatterns(graph, findings) {
23677
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23678
+ if (trigger.kind !== "command_pattern") continue;
23679
+ try {
23680
+ new RegExp(trigger.pattern);
23681
+ } catch (err) {
23682
+ findings.push({
23683
+ level: "error",
23684
+ code: "INVALID_TRIGGER_PATTERN",
23685
+ message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${err instanceof Error ? err.message : String(err)}.`,
23686
+ triggerId
23687
+ });
23688
+ continue;
23689
+ }
23690
+ if (!isSafeRegexPattern(trigger.pattern)) {
23691
+ findings.push({
23692
+ level: "error",
23693
+ code: "UNSAFE_TRIGGER_PATTERN",
23694
+ 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.`,
23695
+ triggerId
23696
+ });
23697
+ }
23698
+ }
23699
+ }
23700
+ function collectBackslashGlobPatterns(graph, findings) {
23701
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23702
+ if (trigger.kind !== "file_glob") continue;
23703
+ if (!trigger.pattern.includes("\\")) continue;
23704
+ findings.push({
23705
+ level: "error",
23706
+ code: "BACKSLASH_GLOB_PATTERN",
23707
+ message: `Trigger "${triggerId}" has a file_glob pattern with a backslash (${trigger.pattern}); recall normalizes paths to forward slashes, so it never fires. Replace \\ with /.`,
23708
+ triggerId
23709
+ });
23710
+ }
23711
+ }
23712
+ function collectDuplicateTriggers(graph, findings) {
23713
+ const byKey = /* @__PURE__ */ new Map();
23714
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23715
+ const key = `${trigger.kind}|${trigger.pattern}`;
23716
+ const bucket = byKey.get(key) ?? [];
23717
+ bucket.push(triggerId);
23718
+ byKey.set(key, bucket);
23719
+ }
23720
+ for (const [key, ids] of byKey) {
23721
+ if (ids.length < 2) continue;
23722
+ const sorted = [...ids].sort();
23723
+ for (const triggerId of sorted) {
23724
+ const others = sorted.filter((other) => other !== triggerId);
23725
+ findings.push({
23726
+ level: "error",
23727
+ code: "DUPLICATE_TRIGGER",
23728
+ message: `Trigger "${triggerId}" duplicates (kind, pattern) of: ${others.join(", ")} (key: "${key}").`,
23729
+ triggerId
23730
+ });
23731
+ }
23732
+ }
23733
+ }
23734
+ var HIGH_FANOUT_THRESHOLD = 10;
23735
+ function collectFanout(graph, findings) {
23736
+ const fanout = /* @__PURE__ */ new Map();
23737
+ for (const lesson of Object.values(graph.lessons)) {
23738
+ if (lesson.status !== "active") continue;
23739
+ for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
23740
+ }
23741
+ let over = 0;
23742
+ let max = 0;
23743
+ for (const n of fanout.values()) {
23744
+ if (n > HIGH_FANOUT_THRESHOLD) over += 1;
23745
+ if (n > max) max = n;
23746
+ }
23747
+ if (over > 0) {
23748
+ findings.push({
23749
+ level: "warning",
23750
+ code: "HIGH_FANOUT_TRIGGERS",
23751
+ message: `${over} trigger(s) each match more than ${HIGH_FANOUT_THRESHOLD} active lessons (max ${max}); recall returns the ranked top by default \u2014 consider per-lesson trigger refinement to improve precision.`
23752
+ });
23753
+ }
23754
+ }
23755
+ function collectLowSignalKeywords(graph, findings) {
23756
+ const activeTriggerIds2 = /* @__PURE__ */ new Set();
23757
+ for (const lesson of Object.values(graph.lessons)) {
23758
+ if (lesson.status !== "active") continue;
23759
+ for (const t of lesson.triggers) activeTriggerIds2.add(t);
23760
+ }
23761
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23762
+ if (trigger.kind !== "keyword") continue;
23763
+ if (!activeTriggerIds2.has(triggerId)) continue;
23764
+ if (!isLowSignalKeyword(trigger.pattern)) continue;
23765
+ findings.push({
23766
+ level: "warning",
23767
+ code: "LOW_SIGNAL_KEYWORD",
23768
+ message: `Keyword trigger "${triggerId}" carries more than ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens (${trigger.pattern}); recall matches a keyword only as a substring of --keyword or a contiguous token-run in the file/command, so it rarely fires \u2014 use a short distinctive phrase.`,
23769
+ triggerId
23770
+ });
23771
+ }
23772
+ }
23773
+ function collectStopwordKeywords(graph, findings) {
23774
+ const activeTriggerIds2 = /* @__PURE__ */ new Set();
23775
+ for (const lesson of Object.values(graph.lessons)) {
23776
+ if (lesson.status !== "active") continue;
23777
+ for (const t of lesson.triggers) activeTriggerIds2.add(t);
23778
+ }
23779
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23780
+ if (trigger.kind !== "keyword") continue;
23781
+ if (!activeTriggerIds2.has(triggerId)) continue;
23782
+ if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
23783
+ continue;
23784
+ }
23785
+ findings.push({
23786
+ level: "warning",
23787
+ code: "STOPWORD_KEYWORD",
23788
+ message: `Keyword trigger "${triggerId}" (${trigger.pattern}) loses tokens to stopword filtering, so its needle can never appear as a contiguous run on the mandatory --file/--cmd recall path \u2014 drop the stopwords (e.g. "state art" instead of "state of the art"), or detach it with \`lessons untrigger\`.`,
23789
+ triggerId
23790
+ });
23791
+ }
23792
+ }
23793
+ function normalizeRule(rule) {
23794
+ return rule.trim().replace(/\s+/g, " ").toLowerCase();
23795
+ }
23796
+ function activeTriggerIds(graph) {
23797
+ const ids = /* @__PURE__ */ new Set();
23798
+ for (const lesson of Object.values(graph.lessons)) {
23799
+ if (lesson.status !== "active") continue;
23800
+ for (const t of lesson.triggers) ids.add(t);
23801
+ }
23802
+ return ids;
23803
+ }
23804
+ function deadFileGlobIds(graph, knownPaths) {
23805
+ const active = activeTriggerIds(graph);
23806
+ const paths = [...knownPaths];
23807
+ const dead = /* @__PURE__ */ new Set();
23808
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23809
+ if (trigger.kind !== "file_glob") continue;
23810
+ if (!active.has(triggerId)) continue;
23811
+ const isMatch = picomatch(trigger.pattern, { dot: true });
23812
+ if (!paths.some((p) => isMatch(p))) dead.add(triggerId);
23813
+ }
23814
+ return dead;
23815
+ }
23816
+ function collectDeadFileGlobs(graph, findings, knownPaths) {
23817
+ for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
23818
+ findings.push({
23819
+ level: "warning",
23820
+ code: "DEAD_FILE_GLOB",
23821
+ 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\`.`,
23822
+ triggerId
23823
+ });
23824
+ }
23825
+ }
23826
+ var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
23827
+ function collectRunnerAnchoredPatterns(graph, findings) {
23828
+ const active = activeTriggerIds(graph);
23829
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23830
+ if (trigger.kind !== "command_pattern") continue;
23831
+ if (!active.has(triggerId)) continue;
23832
+ if (!RUNNER_ANCHOR.test(trigger.pattern)) continue;
23833
+ findings.push({
23834
+ level: "warning",
23835
+ code: "RUNNER_ANCHORED_PATTERN",
23836
+ message: `command_pattern trigger "${triggerId}" (${trigger.pattern}) is anchored to one runner \u2014 it won't fire for the same task via another runner (e.g. \`npx\` vs \`pnpm\`). Drop the \`^<runner>\` anchor and key on the task (e.g. \`\\bvitest\\b\`).`,
23837
+ triggerId
23838
+ });
23839
+ }
23840
+ }
23841
+
23842
+ // src/lessons/validate.ts
23843
+ function validateLessonsGraph(graph, options = {}) {
23844
+ const findings = [];
23845
+ const schemaResult = LessonsGraphSchema.safeParse(graph);
23846
+ if (!schemaResult.success) {
23847
+ findings.push({
23848
+ level: "error",
23849
+ code: "SCHEMA_INVALID",
23850
+ message: schemaResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
23851
+ });
23852
+ return { ok: false, findings };
23853
+ }
23854
+ collectDanglingRefs(graph, findings);
23855
+ collectDuplicateRefs(graph, findings);
23856
+ collectStatusInvariants(graph, findings);
23857
+ collectLifecycleInvariants(graph, findings);
23858
+ collectDuplicateRules(graph, findings);
23859
+ collectReachability(graph, findings);
23860
+ collectInvalidTriggerPatterns(graph, findings);
23861
+ collectBackslashGlobPatterns(graph, findings);
23862
+ collectDuplicateTriggers(graph, findings);
23863
+ collectOrphans(graph, findings);
23864
+ collectFanout(graph, findings);
23865
+ collectLowSignalKeywords(graph, findings);
23866
+ collectStopwordKeywords(graph, findings);
23867
+ collectRunnerAnchoredPatterns(graph, findings);
23868
+ if (options.knownPaths !== void 0) collectDeadFileGlobs(graph, findings, options.knownPaths);
23869
+ const ok = findings.every((f) => f.level !== "error");
23870
+ return { ok, findings };
23871
+ }
23872
+
23873
+ // src/core/lint/shared/lessons.ts
23874
+ var LESSONS_TARGET = "lessons";
23875
+ var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
23876
+ var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
23877
+ var LESSONS_HEADING = /^## Lessons \(/m;
23878
+ function lintLessonsSubsystem(projectRoot, scope) {
23879
+ if (scope === "global") return [];
23880
+ const paths = lessonsPaths(projectRoot);
23881
+ if (!existsSync(paths.graph)) return [];
23882
+ const out2 = [];
23883
+ let graph;
23884
+ try {
23885
+ graph = loadLessonsGraph(projectRoot);
23886
+ } catch (err) {
23887
+ return [
23888
+ diag(
23889
+ "error",
23890
+ GRAPH_REL,
23891
+ `lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
23892
+ )
23893
+ ];
23894
+ }
23895
+ const knownPaths = listProjectFiles(projectRoot) ?? void 0;
23896
+ const report = validateLessonsGraph(graph, { knownPaths });
23897
+ for (const finding of report.findings) {
23898
+ out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
23899
+ }
23900
+ const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
23901
+ const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
23902
+ if (!LESSONS_HEADING.test(rootRuleBody)) {
23903
+ out2.push(
23904
+ diag(
23905
+ "warning",
23906
+ ROOT_RULE_REL,
23907
+ 'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
23908
+ )
23909
+ );
23910
+ }
23911
+ return out2;
23912
+ }
23913
+ function diag(level, file, message) {
23914
+ return { level, file, target: LESSONS_TARGET, message };
23915
+ }
23916
+
23917
+ // src/core/lint/linter.ts
23918
+ var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
23919
+ async function getProjectFiles(projectRoot) {
23920
+ const all = await readDirRecursive(projectRoot);
23921
+ const filtered = all.filter((p) => {
23922
+ const rel2 = relative(projectRoot, p);
23923
+ return !EXCLUDE_DIRS.some((d) => rel2.includes(`/${d}/`) || rel2.startsWith(`${d}/`));
23924
+ });
23925
+ return filtered.map((p) => relative(projectRoot, p));
23926
+ }
23927
+ async function runLint(config, canonical, projectRoot, targetFilter, options = {}) {
23928
+ const scope = options.scope ?? "project";
23929
+ const allTargets = [...config.targets, ...config.pluginTargets ?? []];
23930
+ const targets = targetFilter ? allTargets.filter((t) => targetFilter.includes(t)) : allTargets;
23931
+ const hasRules = config.features.includes("rules");
23932
+ const hasCommands = config.features.includes("commands");
23933
+ const hasMcp = config.features.includes("mcp");
23934
+ const hasPermissions = config.features.includes("permissions");
23935
+ const hasHooks = config.features.includes("hooks");
23936
+ const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
22739
23937
  const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
22740
23938
  for (const target31 of targets) {
22741
23939
  const fullDesc = getDescriptor(target31);
@@ -23138,203 +24336,1190 @@ function copyTargetDescriptor(descriptor31) {
23138
24336
  function getTargetCatalog() {
23139
24337
  return Object.freeze(BUILTIN_TARGETS.map(copyTargetDescriptor));
23140
24338
  }
23141
- function normalize(bullet) {
23142
- return bullet.split("\n").map((line) => line.replace(/\s+$/, "")).join("\n").trim().replace(/^[-*]\s+/, "");
23143
- }
23144
- function hashBullet(bullet) {
23145
- return createHash("sha256").update(normalize(bullet)).digest("hex").slice(0, 16);
23146
- }
23147
-
23148
- // src/lessons/bullet-parser.ts
23149
- function parseBullets(markdown) {
23150
- const bullets = [];
23151
- let current = null;
23152
- let lineNumber = 0;
23153
- for (const line of markdown.split("\n")) {
23154
- lineNumber += 1;
23155
- if (/^[-*]\s+/.test(line)) {
23156
- if (current !== null) bullets.push(current);
23157
- current = { text: line, lineNumber };
23158
- } else if (current !== null) {
23159
- if (line.length === 0) {
23160
- bullets.push(current);
23161
- current = null;
23162
- } else if (/^\s+\S/.test(line)) {
23163
- current.text += `
23164
- ${line}`;
23165
- } else {
23166
- bullets.push(current);
23167
- current = null;
23168
- }
24339
+ function normalizeRule2(rule) {
24340
+ return rule.trim().replace(/\s+/g, " ").toLowerCase();
24341
+ }
24342
+ function union(base, extra) {
24343
+ const out2 = [...base];
24344
+ for (const item of extra) if (!out2.includes(item)) out2.push(item);
24345
+ return out2;
24346
+ }
24347
+ function mergeTriggers(graph, spec) {
24348
+ const requested = [
24349
+ // Normalize `\` → `/` so a Windows-shaped glob matches: recall relativizes
24350
+ // every `--file` to forward slashes (normalizeRecallFile), so a backslash
24351
+ // pattern stored raw would silently never fire. Normalizing here also lets
24352
+ // a backslash pattern dedupe against the forward-slash node it equals.
24353
+ ...(spec.files ?? []).map(
24354
+ (p) => ({ kind: "file_glob", pattern: p.replaceAll("\\", "/") })
24355
+ ),
24356
+ ...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
24357
+ ...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
24358
+ ];
24359
+ const reverseLookup = /* @__PURE__ */ new Map();
24360
+ for (const [id, trigger] of Object.entries(graph.triggers)) {
24361
+ reverseLookup.set(triggerKey(trigger), id);
24362
+ }
24363
+ const triggerIds = [];
24364
+ const newTriggerIds = [];
24365
+ for (const spec2 of requested) {
24366
+ const key = triggerKey(spec2);
24367
+ const existing = reverseLookup.get(key);
24368
+ if (existing !== void 0) {
24369
+ if (!triggerIds.includes(existing)) triggerIds.push(existing);
24370
+ continue;
24371
+ }
24372
+ const id = makeTriggerId(spec2);
24373
+ graph.triggers[id] = { kind: spec2.kind, pattern: spec2.pattern };
24374
+ reverseLookup.set(key, id);
24375
+ triggerIds.push(id);
24376
+ newTriggerIds.push(id);
24377
+ }
24378
+ return { triggerIds, newTriggerIds };
24379
+ }
24380
+ function triggerKey(t) {
24381
+ return `${t.kind}|${t.pattern}`;
24382
+ }
24383
+ var TRIGGER_PREFIX = {
24384
+ file_glob: "glob",
24385
+ command_pattern: "cmd",
24386
+ keyword: "kw"
24387
+ };
24388
+ function makeTriggerId(spec) {
24389
+ const hash = createHash("sha1").update(triggerKey(spec)).digest("hex").slice(0, 8);
24390
+ return `t-${TRIGGER_PREFIX[spec.kind]}-${hash}`;
24391
+ }
24392
+ function makeLessonId(graph, topic, ruleKey) {
24393
+ const slug = ruleToSlug(ruleKey);
24394
+ const base = slug.length > 0 ? `${topic}-${slug}` : `${topic}-${createHash("sha1").update(ruleKey).digest("hex").slice(0, 8)}`;
24395
+ let candidate = base;
24396
+ let i = 2;
24397
+ while (graph.lessons[candidate] !== void 0) {
24398
+ candidate = `${base}-${i}`;
24399
+ i += 1;
24400
+ }
24401
+ return candidate;
24402
+ }
24403
+ function ruleToSlug(rule) {
24404
+ const words = rule.replace(/[^a-z0-9 ]+/g, " ").split(/\s+/).filter((w) => w.length > 0).slice(0, 5);
24405
+ return words.join("-").slice(0, 40).replace(/-+$/, "");
24406
+ }
24407
+ function todayIso() {
24408
+ const now = /* @__PURE__ */ new Date();
24409
+ const y = now.getUTCFullYear();
24410
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
24411
+ const d = String(now.getUTCDate()).padStart(2, "0");
24412
+ return `${y}-${m}-${d}`;
24413
+ }
24414
+
24415
+ // src/lessons/add-errors.ts
24416
+ var UnknownTopicError = class extends Error {
24417
+ constructor(topic) {
24418
+ super(`Unknown topic: ${topic}. Pass allowNewTopic + topicSummary to create it.`);
24419
+ this.topic = topic;
24420
+ this.name = "UnknownTopicError";
24421
+ }
24422
+ code = "UNKNOWN_TOPIC";
24423
+ };
24424
+ var RuleTooLongError = class extends Error {
24425
+ constructor(length, max) {
24426
+ super(
24427
+ `Lesson rule is ${length} characters (max ${max}). A rule should be one imperative sentence \u2014 trim it to the essential instruction, or split it into separate lessons.`
24428
+ );
24429
+ this.length = length;
24430
+ this.max = max;
24431
+ this.name = "RuleTooLongError";
24432
+ }
24433
+ code = "OVERSIZED_RULE";
24434
+ };
24435
+ var NoTriggerError = class extends Error {
24436
+ code = "NO_TRIGGER";
24437
+ constructor() {
24438
+ super(
24439
+ "A lesson needs at least one trigger to be recallable. Pass --trigger-file <glob> (preferred), --trigger-cmd <regex>, or --trigger-kw <text>."
24440
+ );
24441
+ this.name = "NoTriggerError";
24442
+ }
24443
+ };
24444
+ var UnrecallableLessonError = class extends Error {
24445
+ constructor(deadTriggers) {
24446
+ super(
24447
+ "This capture would create a lesson with no effective trigger \u2014 every trigger is dead on the mandatory --file/--cmd recall path, so the lesson could never be recalled there:\n" + deadTriggers.map((t) => ` \u2022 ${t.kind} "${t.pattern}" \u2014 ${t.reason}`).join("\n") + '\nFix: add a precise --trigger-file <glob> (preferred) or a valid --trigger-cmd <regex>; for a keyword, drop the stopwords (e.g. "state art" not "state of the art").'
24448
+ );
24449
+ this.deadTriggers = deadTriggers;
24450
+ this.name = "UnrecallableLessonError";
24451
+ }
24452
+ code = "UNRECALLABLE_LESSON";
24453
+ };
24454
+
24455
+ // src/lessons/capture-guardrails.ts
24456
+ var NEAR_DUPLICATE_THRESHOLD = 0.6;
24457
+ var MAX_RECOMMENDED_TRIGGERS = 8;
24458
+ function isBroadGlob(pattern) {
24459
+ const p = pattern.trim();
24460
+ if (p === "*" || p === "**") return true;
24461
+ if (!p.includes("**")) return false;
24462
+ const basename69 = p.slice(p.lastIndexOf("/") + 1);
24463
+ return basename69.startsWith("*");
24464
+ }
24465
+ function inspectCapturedLesson(graph, lessonId, knownPaths) {
24466
+ const lesson = graph.lessons[lessonId];
24467
+ if (lesson === void 0) return [];
24468
+ const warnings = [];
24469
+ if (lesson.triggers.length > MAX_RECOMMENDED_TRIGGERS) {
24470
+ warnings.push({
24471
+ code: "OVERSIZED_LESSON_TRIGGERS",
24472
+ message: `Lesson "${lessonId}" has ${lesson.triggers.length} triggers (recommended \u2264 ${MAX_RECOMMENDED_TRIGGERS}); broad trigger sets fire on too many edits and dilute recall \u2014 prefer a few specific triggers.`
24473
+ });
24474
+ }
24475
+ const triggers = lesson.triggers.map((id) => graph.triggers[id]).filter((t) => t !== void 0);
24476
+ const broad = triggers.filter((t) => t.kind === "file_glob" && isBroadGlob(t.pattern)).map((t) => t.pattern);
24477
+ if (broad.length > 0) {
24478
+ warnings.push({
24479
+ code: "BROAD_GLOB_TRIGGER",
24480
+ message: `Lesson "${lessonId}" has broad file glob(s) (${broad.join(", ")}) that match large swaths of the tree; prefer a path specific to the lesson.`
24481
+ });
24482
+ }
24483
+ if (triggers.length > 0 && triggers.every((t) => t.kind === "keyword")) {
24484
+ warnings.push({
24485
+ code: "KEYWORD_ONLY_LESSON",
24486
+ message: `Lesson "${lessonId}" has only keyword triggers; mandatory --file/--cmd recall surfaces these only when the keyword appears as a path/command token, so it fires less reliably \u2014 add a file_glob or command_pattern trigger for precise recall.`
24487
+ });
24488
+ }
24489
+ const lowSignal = triggers.filter((t) => t.kind === "keyword" && isLowSignalKeyword(t.pattern)).map((t) => t.pattern);
24490
+ if (lowSignal.length > 0) {
24491
+ warnings.push({
24492
+ code: "LOW_SIGNAL_KEYWORD",
24493
+ message: `Lesson "${lessonId}" has long keyword trigger(s) (${lowSignal.join(", ")}); recall matches a keyword only as a substring of --keyword or a contiguous token-run in the file/command, so a pattern past ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens rarely fires \u2014 use a short distinctive phrase.`
24494
+ });
24495
+ }
24496
+ const stopworded = triggers.filter((t) => t.kind === "keyword" && keywordNeedleLosesTokens(t.pattern)).map((t) => t.pattern);
24497
+ if (stopworded.length > 0) {
24498
+ warnings.push({
24499
+ code: "STOPWORD_KEYWORD",
24500
+ message: `Lesson "${lessonId}" has keyword trigger(s) containing stopwords/short words (${stopworded.join(", ")}); recall filters them from the pattern but NOT from the file/command text, so the phrase can never match contiguously on the --file/--cmd path \u2014 drop the stopwords (e.g. "state art" instead of "state of the art").`
24501
+ });
24502
+ }
24503
+ if (knownPaths !== void 0) {
24504
+ const dead = deadFileGlobIds(graph, knownPaths);
24505
+ const deadHere = lesson.triggers.filter((id) => dead.has(id)).map((id) => graph.triggers[id]?.pattern).filter((p) => p !== void 0);
24506
+ if (deadHere.length > 0) {
24507
+ warnings.push({
24508
+ code: "DEAD_GLOB",
24509
+ message: `Lesson "${lessonId}" has file_glob trigger(s) (${deadHere.join(", ")}) that match no file in the working tree \u2014 likely a rename. Re-point them at the current path, or the lesson is unreachable via those globs.`
24510
+ });
24511
+ }
24512
+ }
24513
+ return warnings;
24514
+ }
24515
+ function nearDuplicateWarning(graph, lessonId) {
24516
+ const subject = graph.lessons[lessonId];
24517
+ if (subject === void 0) return null;
24518
+ const subjectTokens = new Set(tokenize(subject.rule));
24519
+ if (subjectTokens.size === 0) return null;
24520
+ let best = null;
24521
+ for (const [id, other] of Object.entries(graph.lessons)) {
24522
+ if (id === lessonId || other.status !== "active") continue;
24523
+ const otherTokens = new Set(tokenize(other.rule));
24524
+ if (otherTokens.size === 0) continue;
24525
+ const score = jaccard(subjectTokens, otherTokens);
24526
+ if (score >= NEAR_DUPLICATE_THRESHOLD && (best === null || score > best.score)) {
24527
+ best = { id, score };
24528
+ }
24529
+ }
24530
+ if (best === null) return null;
24531
+ return {
24532
+ code: "NEAR_DUPLICATE_LESSON",
24533
+ message: `Lesson "${lessonId}" closely resembles active lesson "${best.id}" (~${Math.round(best.score * 100)}% token overlap); consider updating "${best.id}" instead of adding a paraphrase (recall would surface both).`
24534
+ };
24535
+ }
24536
+ function jaccard(a, b) {
24537
+ let intersection = 0;
24538
+ for (const t of a) if (b.has(t)) intersection += 1;
24539
+ return intersection / (a.size + b.size - intersection);
24540
+ }
24541
+ var LegacyTriggersSchema = z.object({
24542
+ file_globs: z.array(z.string()),
24543
+ command_patterns: z.array(z.string()),
24544
+ keywords: z.array(z.string())
24545
+ }).refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {
24546
+ message: "cluster must declare at least one trigger of any type"
24547
+ });
24548
+ var LegacyClusterSchema = z.object({
24549
+ topic: z.string().regex(/^[a-z0-9-]+$/),
24550
+ file: z.string().regex(/\.md$/),
24551
+ summary: z.string().min(1),
24552
+ triggers: LegacyTriggersSchema
24553
+ });
24554
+ var LegacyIndexSchema = z.object({
24555
+ version: z.literal(1),
24556
+ clusters: z.array(LegacyClusterSchema)
24557
+ });
24558
+ function collectClusterTriggerIds(cluster, triggersById, triggerIdByKey) {
24559
+ const specs = [
24560
+ ...cluster.triggers.file_globs.map((p) => ({ kind: "file_glob", pattern: p })),
24561
+ ...cluster.triggers.command_patterns.map(
24562
+ (p) => ({ kind: "command_pattern", pattern: p })
24563
+ ),
24564
+ ...cluster.triggers.keywords.map((p) => ({ kind: "keyword", pattern: p }))
24565
+ ];
24566
+ const ids = [];
24567
+ for (const spec of specs) {
24568
+ const key = `${spec.kind}|${spec.pattern}`;
24569
+ let id = triggerIdByKey.get(key);
24570
+ if (id === void 0) {
24571
+ id = makeTriggerId2(spec);
24572
+ triggerIdByKey.set(key, id);
24573
+ triggersById.set(id, { kind: spec.kind, pattern: spec.pattern });
24574
+ }
24575
+ if (!ids.includes(id)) ids.push(id);
24576
+ }
24577
+ return ids;
24578
+ }
24579
+ var TRIGGER_PREFIX2 = {
24580
+ file_glob: "glob",
24581
+ command_pattern: "cmd",
24582
+ keyword: "kw"
24583
+ };
24584
+ function makeTriggerId2(spec) {
24585
+ const hash = createHash("sha1").update(`${spec.kind}|${spec.pattern}`).digest("hex").slice(0, 8);
24586
+ return `t-${TRIGGER_PREFIX2[spec.kind]}-${hash}`;
24587
+ }
24588
+ var RULE_HEADING_RE = /^##\s+Rules\b.*$/i;
24589
+ var NEXT_HEADING_RE = /^##\s+/;
24590
+ var RULE_LINE_RE = /^(\d+)\.\s+(.+?)\s*$/;
24591
+ var EVIDENCE_TAIL_RE = /\s*\(Evidence:?\s+([^)]+)\)\s*$/;
24592
+ var EVIDENCE_REF_RE = /L\d+/g;
24593
+ function parseRulesSection(markdown) {
24594
+ const lines = markdown.split(/\r?\n/);
24595
+ let inRules = false;
24596
+ const rules = [];
24597
+ for (const line of lines) {
24598
+ if (!inRules) {
24599
+ if (RULE_HEADING_RE.test(line)) inRules = true;
24600
+ continue;
24601
+ }
24602
+ if (NEXT_HEADING_RE.test(line)) break;
24603
+ const m = RULE_LINE_RE.exec(line);
24604
+ if (m === null) continue;
24605
+ const ruleIndex = Number(m[1]);
24606
+ let body = m[2];
24607
+ const evidence = [];
24608
+ let tail = EVIDENCE_TAIL_RE.exec(body);
24609
+ while (tail !== null) {
24610
+ const refs = tail[1];
24611
+ const matches = refs.match(EVIDENCE_REF_RE);
24612
+ if (matches !== null) evidence.unshift(...matches);
24613
+ body = body.slice(0, tail.index).trimEnd();
24614
+ tail = EVIDENCE_TAIL_RE.exec(body);
24615
+ }
24616
+ rules.push({ index: ruleIndex, body, evidence });
24617
+ }
24618
+ return rules;
24619
+ }
24620
+ var LEGACY_ARTIFACT_REL = [
24621
+ "index.yaml",
24622
+ "journal.md",
24623
+ "journal.legacy.md",
24624
+ "topics",
24625
+ "distill-ledger.yaml",
24626
+ "distill-proposal.md"
24627
+ ];
24628
+ function deleteLegacyArtifacts(baseDir) {
24629
+ const deleted = [];
24630
+ for (const rel2 of LEGACY_ARTIFACT_REL) {
24631
+ const abs = join(baseDir, rel2);
24632
+ if (!existsSync(abs)) continue;
24633
+ rmSync(abs, { recursive: true, force: true });
24634
+ deleted.push(abs);
24635
+ }
24636
+ return deleted;
24637
+ }
24638
+
24639
+ // src/lessons/import-legacy-merge.ts
24640
+ async function mergeLegacy(projectRoot, paths, specs, summaryByTopic, options) {
24641
+ let addedLessons = 0;
24642
+ const addedTriggers = /* @__PURE__ */ new Set();
24643
+ const touchedTopics = /* @__PURE__ */ new Set();
24644
+ await mutateLessonsGraphLocked(projectRoot, (g) => {
24645
+ addedLessons = 0;
24646
+ addedTriggers.clear();
24647
+ touchedTopics.clear();
24648
+ for (const spec of specs) {
24649
+ const result = addLessonInto(g, spec, {
24650
+ allowNewTopic: true,
24651
+ topicSummary: summaryByTopic.get(spec.topic),
24652
+ // Legacy lessons may predate the ≥1-trigger requirement; recover them
24653
+ // as-is rather than dropping historical knowledge.
24654
+ allowNoTrigger: true
24655
+ });
24656
+ if (result.isNewLesson) addedLessons += 1;
24657
+ for (const t of result.newTriggerIds) addedTriggers.add(t);
24658
+ touchedTopics.add(spec.topic);
24659
+ }
24660
+ });
24661
+ const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths.base);
24662
+ return {
24663
+ wroteGraphPath: paths.graph,
24664
+ deletedPaths,
24665
+ topicCount: touchedTopics.size,
24666
+ lessonCount: addedLessons,
24667
+ triggerCount: addedTriggers.size
24668
+ };
24669
+ }
24670
+
24671
+ // src/lessons/import-legacy.ts
24672
+ var LessonsGraphExistsError = class extends Error {
24673
+ code = "LESSONS_GRAPH_EXISTS";
24674
+ constructor() {
24675
+ super("importLegacyLessons: a non-empty lessons.json already exists; pass force to overwrite.");
24676
+ this.name = "LessonsGraphExistsError";
24677
+ }
24678
+ };
24679
+ async function importLegacyLessons(projectRoot, options) {
24680
+ const paths = lessonsPaths(projectRoot);
24681
+ const indexRaw = readFileSync(paths.index, "utf8");
24682
+ const index = LegacyIndexSchema.parse(parse(indexRaw));
24683
+ const topics = {};
24684
+ const triggersById = /* @__PURE__ */ new Map();
24685
+ const triggerIdByKey = /* @__PURE__ */ new Map();
24686
+ const lessons = {};
24687
+ const specs = [];
24688
+ const summaryByTopic = /* @__PURE__ */ new Map();
24689
+ for (const cluster of index.clusters) {
24690
+ topics[cluster.topic] = { summary: cluster.summary };
24691
+ summaryByTopic.set(cluster.topic, cluster.summary);
24692
+ const clusterTriggerIds = collectClusterTriggerIds(cluster, triggersById, triggerIdByKey);
24693
+ const topicFile = join(projectRoot, cluster.file);
24694
+ if (!existsSync(topicFile)) {
24695
+ throw new Error(
24696
+ `importLegacyLessons: declared topic file is missing: ${cluster.file}. Refusing to migrate (legacy artifacts left intact).`
24697
+ );
24698
+ }
24699
+ const topicMarkdown = readFileSync(topicFile, "utf8");
24700
+ for (const { index: ruleIndex, body, evidence } of parseRulesSection(topicMarkdown)) {
24701
+ const lessonEvidence = [
24702
+ `legacy:${cluster.file}#rule-${ruleIndex}`,
24703
+ ...evidence.map((e) => `legacy:${e}`)
24704
+ ];
24705
+ lessons[`${cluster.topic}-rule-${ruleIndex}`] = {
24706
+ rule: body,
24707
+ topics: [cluster.topic],
24708
+ triggers: clusterTriggerIds,
24709
+ evidence: lessonEvidence,
24710
+ status: "active",
24711
+ createdAt: options.migratedAt
24712
+ };
24713
+ specs.push({
24714
+ rule: body,
24715
+ topic: cluster.topic,
24716
+ triggers: {
24717
+ files: cluster.triggers.file_globs,
24718
+ commands: cluster.triggers.command_patterns,
24719
+ keywords: cluster.triggers.keywords
24720
+ },
24721
+ evidence: lessonEvidence,
24722
+ createdAt: options.migratedAt
24723
+ });
24724
+ }
24725
+ }
24726
+ if (options.merge === true)
24727
+ return mergeLegacy(projectRoot, paths, specs, summaryByTopic, options);
24728
+ const triggers = Object.fromEntries(triggersById.entries());
24729
+ await mutateLessonsGraphLocked(projectRoot, (g) => {
24730
+ const populated = Object.keys(g.lessons).length > 0 || Object.keys(g.topics).length > 0 || Object.keys(g.triggers).length > 0;
24731
+ if (options.force !== true && populated) {
24732
+ throw new LessonsGraphExistsError();
24733
+ }
24734
+ g.version = 1;
24735
+ g.lessons = lessons;
24736
+ g.topics = topics;
24737
+ g.triggers = triggers;
24738
+ });
24739
+ const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths.base);
24740
+ return {
24741
+ wroteGraphPath: paths.graph,
24742
+ deletedPaths,
24743
+ topicCount: Object.keys(topics).length,
24744
+ lessonCount: Object.keys(lessons).length,
24745
+ triggerCount: triggersById.size
24746
+ };
24747
+ }
24748
+
24749
+ // src/lessons/auto-migrate.ts
24750
+ function todayIso2() {
24751
+ const now = /* @__PURE__ */ new Date();
24752
+ const y = now.getUTCFullYear();
24753
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
24754
+ const d = String(now.getUTCDate()).padStart(2, "0");
24755
+ return `${y}-${m}-${d}`;
24756
+ }
24757
+ async function maybeAutoMigrateLessons(projectRoot) {
24758
+ if (existsSync(graphFilePath(projectRoot))) return false;
24759
+ const paths = lessonsPaths(projectRoot);
24760
+ if (!existsSync(paths.index)) return false;
24761
+ try {
24762
+ await importLegacyLessons(projectRoot, { migratedAt: todayIso2() });
24763
+ return true;
24764
+ } catch (err) {
24765
+ if (err instanceof LessonsGraphExistsError) return false;
24766
+ throw err;
24767
+ }
24768
+ }
24769
+
24770
+ // src/utils/filesystem/process-lock.ts
24771
+ init_errors();
24772
+ var DEFAULT_STALE_MS = 6e4;
24773
+ var DEFAULT_RETRIES = 30;
24774
+ var DEFAULT_RETRY_DELAY_MS = 200;
24775
+ var YOUNG_LOCK_GRACE_MS = 2e3;
24776
+ async function acquireProcessLock(lockPath, opts = {}) {
24777
+ const retries = opts.retries ?? DEFAULT_RETRIES;
24778
+ const delay = opts.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
24779
+ const stale = opts.staleMs ?? DEFAULT_STALE_MS;
24780
+ await mkdir(dirname(lockPath), { recursive: true });
24781
+ let attempt = 0;
24782
+ while (true) {
24783
+ const acquired = await tryAcquire(lockPath);
24784
+ if (acquired) return acquired;
24785
+ const existing = await inspectLock(lockPath);
24786
+ if (existing !== "young" && isStale(existing, stale)) {
24787
+ await rm(lockPath, { recursive: true, force: true }).catch(() => {
24788
+ });
24789
+ continue;
24790
+ }
24791
+ if (attempt >= retries) {
24792
+ const holder = existing === "young" ? null : existing;
24793
+ throw new LockAcquisitionError(lockPath, describeHolder(holder), { label: opts.label });
23169
24794
  }
24795
+ attempt++;
24796
+ await sleep2(delay);
23170
24797
  }
23171
- if (current !== null) bullets.push(current);
23172
- return bullets;
23173
24798
  }
23174
- function fileMatch(globs, path) {
23175
- return globs.some((g) => picomatch(g, { dot: true })(path));
24799
+ async function tryAcquire(lockPath) {
24800
+ try {
24801
+ await mkdir(lockPath, { recursive: false });
24802
+ } catch (err) {
24803
+ if (err.code === "EEXIST") return null;
24804
+ throw err;
24805
+ }
24806
+ const metadataPath = join(lockPath, "holder.json");
24807
+ const metadata = {
24808
+ pid: process.pid,
24809
+ started: Date.now(),
24810
+ hostname: getHostname()
24811
+ };
24812
+ await writeFile(metadataPath, JSON.stringify(metadata), "utf-8");
24813
+ let released = false;
24814
+ const cleanup = () => {
24815
+ if (released) return;
24816
+ released = true;
24817
+ try {
24818
+ rmSync(lockPath, { recursive: true, force: true });
24819
+ } catch {
24820
+ }
24821
+ };
24822
+ const signalHandler = (signal) => {
24823
+ cleanup();
24824
+ process.kill(process.pid, signal);
24825
+ };
24826
+ process.once("SIGINT", signalHandler);
24827
+ process.once("SIGTERM", signalHandler);
24828
+ process.once("exit", cleanup);
24829
+ return async () => {
24830
+ if (released) return;
24831
+ released = true;
24832
+ process.off("SIGINT", signalHandler);
24833
+ process.off("SIGTERM", signalHandler);
24834
+ process.off("exit", cleanup);
24835
+ await rm(lockPath, { recursive: true, force: true }).catch(() => {
24836
+ });
24837
+ };
23176
24838
  }
23177
- function cmdMatch(patterns, cmd) {
23178
- return patterns.some((p) => {
24839
+ async function inspectLock(lockPath) {
24840
+ try {
24841
+ const raw = await readFile(join(lockPath, "holder.json"), "utf-8");
24842
+ const parsed = JSON.parse(raw);
24843
+ if (!isLockMetadata(parsed)) return null;
24844
+ return parsed;
24845
+ } catch {
23179
24846
  try {
23180
- return new RegExp(p).test(cmd);
24847
+ const info = await stat(lockPath);
24848
+ const ageMs = Date.now() - info.mtimeMs;
24849
+ if (ageMs < YOUNG_LOCK_GRACE_MS) return "young";
23181
24850
  } catch {
23182
- return false;
23183
24851
  }
23184
- });
24852
+ return null;
24853
+ }
24854
+ }
24855
+ function isStale(meta, staleMs) {
24856
+ if (!meta) return true;
24857
+ const age = Date.now() - meta.started;
24858
+ if (age > staleMs) return true;
24859
+ if (meta.hostname && meta.hostname !== getHostname()) return false;
24860
+ return !isProcessAlive(meta.pid);
23185
24861
  }
23186
- function kwMatch(keywords, text) {
23187
- const lower = text.toLowerCase();
23188
- return keywords.some((k) => lower.includes(k.toLowerCase()));
23189
- }
23190
- function matchTriggers(clusters, event) {
23191
- return clusters.filter((c2) => {
23192
- const t = c2.triggers;
23193
- switch (event.kind) {
23194
- case "edit":
23195
- case "write":
23196
- return fileMatch(t.file_globs, event.filePath);
23197
- case "bash":
23198
- return cmdMatch(t.command_patterns, event.command);
23199
- case "task":
23200
- return kwMatch(t.keywords, event.text);
24862
+ function isProcessAlive(pid) {
24863
+ if (!Number.isInteger(pid) || pid <= 0) return false;
24864
+ try {
24865
+ process.kill(pid, 0);
24866
+ return true;
24867
+ } catch (err) {
24868
+ return err.code === "EPERM";
24869
+ }
24870
+ }
24871
+ function describeHolder(meta) {
24872
+ if (!meta) return "unknown (unreadable lock metadata)";
24873
+ const host = meta.hostname ? `${meta.hostname}:` : "";
24874
+ return `${host}pid ${meta.pid} (running ${Date.now() - meta.started}ms)`;
24875
+ }
24876
+ function isLockMetadata(value) {
24877
+ if (typeof value !== "object" || value === null) return false;
24878
+ const v = value;
24879
+ return typeof v.pid === "number" && typeof v.started === "number";
24880
+ }
24881
+ function getHostname() {
24882
+ return hostname();
24883
+ }
24884
+ function sleep2(ms) {
24885
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
24886
+ }
24887
+
24888
+ // src/lessons/lessons-lock.ts
24889
+ var LESSONS_LOCK_FILENAME = ".lessons.lock";
24890
+ function lessonsLockPath(projectRoot) {
24891
+ return resolve(projectRoot, ".agentsmesh/lessons", LESSONS_LOCK_FILENAME);
24892
+ }
24893
+ async function acquireLessonsLock(projectRoot, opts = {}) {
24894
+ const lockPath = lessonsLockPath(projectRoot);
24895
+ await mkdir(dirname(lockPath), { recursive: true });
24896
+ return acquireProcessLock(lockPath, { ...opts, label: "lessons lock" });
24897
+ }
24898
+
24899
+ // src/lessons/mutate.ts
24900
+ function emptyGraph() {
24901
+ return { version: 1, lessons: {}, topics: {}, triggers: {} };
24902
+ }
24903
+ async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
24904
+ const release = await acquireLessonsLock(projectRoot, { retries: options.retries });
24905
+ try {
24906
+ const graph = tryLoadLessonsGraph(projectRoot) ?? emptyGraph();
24907
+ const result = await mutator(graph);
24908
+ const report = validateLessonsGraph(graph);
24909
+ if (!report.ok) {
24910
+ const errors = report.findings.filter((f) => f.level === "error").map((f) => `${f.code}: ${f.message}`).join("; ");
24911
+ throw new Error(`mutateLessonsGraph: refusing to write an invalid graph \u2014 ${errors}`);
24912
+ }
24913
+ saveLessonsGraph(projectRoot, graph);
24914
+ return result;
24915
+ } finally {
24916
+ await release();
24917
+ }
24918
+ }
24919
+ async function mutateLessonsGraph(projectRoot, mutator, options = {}) {
24920
+ await maybeAutoMigrateLessons(projectRoot);
24921
+ return mutateLessonsGraphLocked(projectRoot, mutator, options);
24922
+ }
24923
+
24924
+ // src/lessons/trigger-effectiveness.ts
24925
+ function ineffectiveTriggers(graph, triggerIds) {
24926
+ const out2 = [];
24927
+ for (const id of triggerIds) {
24928
+ const trigger = graph.triggers[id];
24929
+ if (trigger === void 0) continue;
24930
+ const reason = ineffectiveReason(trigger.kind, trigger.pattern);
24931
+ if (reason !== null) out2.push({ id, kind: trigger.kind, pattern: trigger.pattern, reason });
24932
+ }
24933
+ return out2;
24934
+ }
24935
+ function ineffectiveReason(kind, pattern) {
24936
+ if (kind === "keyword") {
24937
+ if (tokenize(pattern).length === 0) {
24938
+ return "keyword has no matchable token after stopword filtering \u2014 it cannot fire on the mandatory --file/--cmd recall path";
24939
+ }
24940
+ if (keywordNeedleLosesTokens(pattern)) {
24941
+ return "keyword contains stopwords/short words, so its needle can never appear as a contiguous run on the mandatory --file/--cmd recall path";
24942
+ }
24943
+ return null;
24944
+ }
24945
+ if (kind === "command_pattern") {
24946
+ let valid = true;
24947
+ try {
24948
+ new RegExp(pattern);
24949
+ } catch {
24950
+ valid = false;
24951
+ }
24952
+ if (!valid) {
24953
+ return "invalid regex \u2014 recall compiles it with new RegExp and swallows the throw as a non-match, so it never fires";
23201
24954
  }
24955
+ if (!isSafeRegexPattern(pattern)) {
24956
+ return "regex is outside the provably-linear engine \u2014 recall skips it (ReDoS guard), so it never fires";
24957
+ }
24958
+ return null;
24959
+ }
24960
+ return null;
24961
+ }
24962
+ function blockingDeadTriggers(graph, triggerIds) {
24963
+ return ineffectiveTriggers(graph, triggerIds).filter((t) => t.kind !== "command_pattern");
24964
+ }
24965
+
24966
+ // src/lessons/add.ts
24967
+ function countInputTriggers(triggers) {
24968
+ return (triggers.files?.length ?? 0) + (triggers.commands?.length ?? 0) + (triggers.keywords?.length ?? 0);
24969
+ }
24970
+ async function addLesson(projectRoot, input, options = {}) {
24971
+ return mutateLessonsGraph(projectRoot, (graph) => addLessonInto(graph, input, options), {
24972
+ retries: options.retries
23202
24973
  });
23203
24974
  }
23204
- var LedgerSchema = z.object({
23205
- version: z.literal(1),
23206
- assignments: z.record(z.string(), z.string())
23207
- });
23208
- function loadLedger(path) {
23209
- if (!existsSync(path)) return { version: 1, assignments: {} };
23210
- return LedgerSchema.parse(parse(readFileSync(path, "utf8")));
23211
- }
23212
- function saveLedger(path, ledger) {
23213
- writeFileSync(path, stringify(ledger), "utf8");
23214
- }
23215
-
23216
- // src/lessons/scoring.ts
23217
- function scoreBullet(bullet, clusters) {
23218
- const lower = bullet.toLowerCase();
23219
- return clusters.map((cluster) => {
23220
- const t = cluster.triggers;
23221
- const kwHits = t.keywords.filter((k) => lower.includes(k.toLowerCase())).length;
23222
- const pathHits = t.file_globs.filter((g) => {
23223
- const stem = g.replace(/[*{}[\]?!]/g, "").replace(/\/+/g, "/").trim();
23224
- return stem.length > 2 && lower.includes(stem.toLowerCase());
23225
- }).length;
23226
- const cmdHits = t.command_patterns.filter((p) => {
23227
- try {
23228
- return new RegExp(p, "i").test(bullet);
23229
- } catch {
23230
- return false;
23231
- }
23232
- }).length;
23233
- return { cluster, score: kwHits * 2 + pathHits + cmdHits };
23234
- }).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
23235
- }
23236
- function loadLessonsIndex(projectRoot) {
23237
- const raw = readFileSync(lessonsPaths(projectRoot).index, "utf8");
23238
- return parseIndex(parse(raw));
23239
- }
23240
- function readTriggeredLessons(projectRoot, event) {
23241
- const index = loadLessonsIndex(projectRoot);
23242
- const contentByPath = /* @__PURE__ */ new Map();
23243
- return matchTriggers(index.clusters, normalizeToolEvent(projectRoot, event)).map(
23244
- (cluster) => {
23245
- const filePath = resolveProjectFile(projectRoot, cluster.file);
23246
- let content = contentByPath.get(cluster.file);
23247
- if (content === void 0) {
23248
- content = readFileSync(filePath, "utf8");
23249
- contentByPath.set(cluster.file, content);
24975
+ function addLessonInto(graph, input, options) {
24976
+ const ruleKey = normalizeRule2(input.rule);
24977
+ const trimmedRule = input.rule.trim();
24978
+ if (trimmedRule.length > MAX_RULE_LENGTH) {
24979
+ throw new RuleTooLongError(trimmedRule.length, MAX_RULE_LENGTH);
24980
+ }
24981
+ const existingId = findExistingLessonByRule(graph, ruleKey);
24982
+ const isNewTopic = graph.topics[input.topic] === void 0;
24983
+ if (isNewTopic) {
24984
+ if (options.allowNewTopic !== true) throw new UnknownTopicError(input.topic);
24985
+ if (options.topicSummary === void 0 || options.topicSummary.length === 0) {
24986
+ throw new Error(`addLesson: new topic "${input.topic}" requires topicSummary.`);
24987
+ }
24988
+ graph.topics[input.topic] = { summary: options.topicSummary };
24989
+ }
24990
+ if (options.allowNoTrigger !== true) {
24991
+ const existingTriggers = existingId !== null ? graph.lessons[existingId]?.triggers.length ?? 0 : 0;
24992
+ if (countInputTriggers(input.triggers) === 0 && existingTriggers === 0) {
24993
+ throw new NoTriggerError();
24994
+ }
24995
+ }
24996
+ const { triggerIds, newTriggerIds } = mergeTriggers(graph, input.triggers);
24997
+ if (options.allowNoTrigger !== true) {
24998
+ const resultingTriggers = existingId !== null ? union(graph.lessons[existingId].triggers, triggerIds) : triggerIds;
24999
+ const blockingDead = blockingDeadTriggers(graph, resultingTriggers);
25000
+ if (resultingTriggers.length > 0 && blockingDead.length === resultingTriggers.length) {
25001
+ throw new UnrecallableLessonError(blockingDead);
25002
+ }
25003
+ }
25004
+ if (existingId !== null) {
25005
+ const existing = graph.lessons[existingId];
25006
+ graph.lessons[existingId] = {
25007
+ ...existing,
25008
+ topics: union(existing.topics, [input.topic]),
25009
+ triggers: union(existing.triggers, triggerIds),
25010
+ evidence: union(existing.evidence, input.evidence ?? []),
25011
+ ...existing.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {}
25012
+ };
25013
+ return {
25014
+ id: existingId,
25015
+ isNewLesson: false,
25016
+ isNewTopic,
25017
+ newTriggerIds,
25018
+ // Near-duplicate detection is meaningless on an upsert (the lesson IS the
25019
+ // match), so only DEAD_GLOB/hygiene warnings apply here.
25020
+ warnings: inspectCapturedLesson(graph, existingId, options.knownPaths)
25021
+ };
25022
+ }
25023
+ const id = makeLessonId(graph, input.topic, ruleKey);
25024
+ graph.lessons[id] = {
25025
+ rule: trimmedRule,
25026
+ topics: [input.topic],
25027
+ triggers: triggerIds,
25028
+ evidence: input.evidence === void 0 ? [] : [...input.evidence],
25029
+ status: "active",
25030
+ createdAt: input.createdAt ?? todayIso(),
25031
+ ...input.rationale === void 0 ? {} : { rationale: input.rationale }
25032
+ };
25033
+ const warnings = inspectCapturedLesson(graph, id, options.knownPaths);
25034
+ const nearDup = nearDuplicateWarning(graph, id);
25035
+ return {
25036
+ id,
25037
+ isNewLesson: true,
25038
+ isNewTopic,
25039
+ newTriggerIds,
25040
+ warnings: nearDup === null ? warnings : [...warnings, nearDup]
25041
+ };
25042
+ }
25043
+ function findExistingLessonByRule(graph, ruleKey) {
25044
+ for (const [id, lesson] of Object.entries(graph.lessons)) {
25045
+ if (lesson.status !== "active") continue;
25046
+ if (normalizeRule2(lesson.rule) === ruleKey) return id;
25047
+ }
25048
+ return null;
25049
+ }
25050
+
25051
+ // src/lessons/ranking-signals.ts
25052
+ function buildFanout(graph) {
25053
+ const fanout = /* @__PURE__ */ new Map();
25054
+ for (const lesson of Object.values(graph.lessons)) {
25055
+ if (lesson.status !== "active") continue;
25056
+ for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
25057
+ }
25058
+ return fanout;
25059
+ }
25060
+ function buildTopicCoherence(matches) {
25061
+ const topicCount = /* @__PURE__ */ new Map();
25062
+ for (const { lesson } of matches) {
25063
+ for (const t of lesson.topics) topicCount.set(t, (topicCount.get(t) ?? 0) + 1);
25064
+ }
25065
+ const coherence = /* @__PURE__ */ new Map();
25066
+ for (const { id, lesson } of matches) {
25067
+ let best = 0;
25068
+ for (const t of lesson.topics) best = Math.max(best, topicCount.get(t));
25069
+ coherence.set(id, best);
25070
+ }
25071
+ return coherence;
25072
+ }
25073
+ function recallLogPath(projectRoot) {
25074
+ return join(lessonsPaths(projectRoot).base, "recall-log.jsonl");
25075
+ }
25076
+
25077
+ // src/lessons/capture-telemetry.ts
25078
+ function captureLogPath(projectRoot) {
25079
+ return join(lessonsPaths(projectRoot).base, "capture-log.jsonl");
25080
+ }
25081
+
25082
+ // src/lessons/keyword-match.ts
25083
+ function splitTokens(text) {
25084
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
25085
+ }
25086
+ function deriveHaystackTokens(query) {
25087
+ const parts = [];
25088
+ if (query.file !== void 0) parts.push(query.file);
25089
+ if (query.command !== void 0) parts.push(query.command);
25090
+ return parts.length === 0 ? [] : splitTokens(parts.join(" "));
25091
+ }
25092
+ function containsRun(needle, hay) {
25093
+ if (needle.length === 0) return false;
25094
+ for (let i = 0; i + needle.length <= hay.length; i += 1) {
25095
+ let hit = true;
25096
+ for (let j = 0; j < needle.length; j += 1) {
25097
+ if (hay[i + j] !== needle[j]) {
25098
+ hit = false;
25099
+ break;
23250
25100
  }
23251
- return {
23252
- cluster,
23253
- relativePath: cluster.file,
23254
- filePath,
23255
- content
23256
- };
23257
25101
  }
25102
+ if (hit) return true;
25103
+ }
25104
+ return false;
25105
+ }
25106
+ function keywordMatches(pattern, query) {
25107
+ if (query.keyword !== void 0 && query.keyword.toLowerCase().includes(pattern.toLowerCase())) {
25108
+ return true;
25109
+ }
25110
+ return containsRun(tokenize(pattern), deriveHaystackTokens(query));
25111
+ }
25112
+
25113
+ // src/lessons/query.ts
25114
+ var COMMAND_MATCH_BUDGET = 5e6;
25115
+ function queryLessons(graph, query) {
25116
+ if (query.file === void 0 && query.command === void 0 && query.keyword === void 0) {
25117
+ return [];
25118
+ }
25119
+ const matchedTriggerIds = collectMatchedTriggerIds(graph, query);
25120
+ const matched = [];
25121
+ for (const [id, lesson] of Object.entries(graph.lessons)) {
25122
+ if (lesson.status !== "active") continue;
25123
+ if (lesson.triggers.some((t) => matchedTriggerIds.has(t))) {
25124
+ matched.push({ id, lesson });
25125
+ }
25126
+ }
25127
+ matched.sort((a, b) => a.id < b.id ? -1 : 1);
25128
+ return matched;
25129
+ }
25130
+ function collectMatchedTriggersByKind(graph, query) {
25131
+ const byKind = {
25132
+ file_glob: /* @__PURE__ */ new Set(),
25133
+ command_pattern: /* @__PURE__ */ new Set(),
25134
+ keyword: /* @__PURE__ */ new Set()
25135
+ };
25136
+ const budget = { remaining: COMMAND_MATCH_BUDGET };
25137
+ for (const [id, trigger] of Object.entries(graph.triggers)) {
25138
+ if (triggerMatches(trigger, query, budget)) byKind[trigger.kind].add(id);
25139
+ }
25140
+ return byKind;
25141
+ }
25142
+ function collectMatchedTriggerIds(graph, query) {
25143
+ const { file_glob, command_pattern, keyword } = collectMatchedTriggersByKind(graph, query);
25144
+ return /* @__PURE__ */ new Set([...file_glob, ...command_pattern, ...keyword]);
25145
+ }
25146
+ function triggerMatches(trigger, query, budget) {
25147
+ switch (trigger.kind) {
25148
+ case "file_glob":
25149
+ if (query.file === void 0) return false;
25150
+ return picomatch(trigger.pattern, { dot: true })(query.file);
25151
+ case "command_pattern": {
25152
+ if (query.command === void 0) return false;
25153
+ const matcher = getCommandMatcher(trigger.pattern);
25154
+ return matcher !== null && matcher.test(query.command, budget);
25155
+ }
25156
+ case "keyword":
25157
+ return keywordMatches(trigger.pattern, query);
25158
+ }
25159
+ }
25160
+
25161
+ // src/lessons/ranking.ts
25162
+ var DEFAULT_RECALL_LIMIT = 10;
25163
+ var DEFAULT_RECALL_MAX_TOKENS = 400;
25164
+ var RRF_K = 60;
25165
+ var SPECIFICITY_WEIGHT = 3;
25166
+ var TOPIC_COHERENCE_WEIGHT = 2;
25167
+ var BM25_WEIGHT = 1;
25168
+ function rankMap(items) {
25169
+ const sorted = [...items].sort(
25170
+ (a, b) => b.value !== a.value ? b.value - a.value : a.id < b.id ? -1 : 1
23258
25171
  );
25172
+ const ranks = /* @__PURE__ */ new Map();
25173
+ let prevValue = null;
25174
+ let prevRank = 0;
25175
+ sorted.forEach((item, i) => {
25176
+ const rank = prevValue !== null && item.value === prevValue ? prevRank : i + 1;
25177
+ ranks.set(item.id, rank);
25178
+ prevValue = item.value;
25179
+ prevRank = rank;
25180
+ });
25181
+ return ranks;
25182
+ }
25183
+ function estTokens(rule) {
25184
+ return Math.ceil(rule.length / 4);
25185
+ }
25186
+ function rankLessons(graph, query, matches, options = {}) {
25187
+ if (matches.length === 0) return [];
25188
+ const terms = queryTerms(query);
25189
+ const corpus = buildCorpus(graph);
25190
+ const fanout = buildFanout(graph);
25191
+ const coherence = buildTopicCoherence(matches);
25192
+ const matchedTriggerIds = collectMatchedTriggerIds(graph, query);
25193
+ const scored = matches.map(({ id, lesson }) => {
25194
+ const hitTriggers = lesson.triggers.filter((t) => matchedTriggerIds.has(t));
25195
+ let specificity = 0;
25196
+ for (const t of hitTriggers) specificity = Math.max(specificity, 1 / fanout.get(t));
25197
+ return {
25198
+ id,
25199
+ lesson,
25200
+ bm25: bm25(terms, lesson.rule, corpus),
25201
+ specificity,
25202
+ // `id` is a matched lesson, and buildTopicCoherence keys every matched id.
25203
+ topicCoherence: coherence.get(id),
25204
+ matchedTriggers: hitTriggers
25205
+ };
25206
+ });
25207
+ const bm25Ranks = rankMap(scored.map((s) => ({ id: s.id, value: s.bm25 })));
25208
+ const specRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.specificity })));
25209
+ const topicRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.topicCoherence })));
25210
+ const ranked = scored.map((s) => ({
25211
+ id: s.id,
25212
+ lesson: s.lesson,
25213
+ score: SPECIFICITY_WEIGHT / (RRF_K + specRanks.get(s.id)) + TOPIC_COHERENCE_WEIGHT / (RRF_K + topicRanks.get(s.id)) + BM25_WEIGHT / (RRF_K + bm25Ranks.get(s.id)),
25214
+ reason: {
25215
+ matchedTriggers: s.matchedTriggers,
25216
+ bm25: s.bm25,
25217
+ specificity: s.specificity,
25218
+ topicCoherence: s.topicCoherence
25219
+ }
25220
+ })).sort((a, b) => {
25221
+ if (b.score !== a.score) return b.score - a.score;
25222
+ const ca = a.lesson.createdAt;
25223
+ const cb = b.lesson.createdAt;
25224
+ if (ca !== cb) return ca < cb ? 1 : -1;
25225
+ return a.id < b.id ? -1 : 1;
25226
+ });
25227
+ return applyCaps(ranked, options);
25228
+ }
25229
+ function applyCaps(ranked, options) {
25230
+ let out2 = ranked;
25231
+ if (options.limit !== void 0 && options.limit >= 0) out2 = out2.slice(0, options.limit);
25232
+ if (options.maxTokens !== void 0 && out2.length > 0) {
25233
+ const budgeted = [out2[0]];
25234
+ let used = estTokens(out2[0].lesson.rule);
25235
+ for (const row of out2.slice(1)) {
25236
+ const cost = estTokens(row.lesson.rule);
25237
+ if (used + cost > options.maxTokens) break;
25238
+ used += cost;
25239
+ budgeted.push(row);
25240
+ }
25241
+ out2 = budgeted;
25242
+ }
25243
+ return out2;
23259
25244
  }
23260
- function formatLessonBullet(input) {
23261
- const heading = compact(input.heading);
23262
- if (heading.length === 0) throw new Error("Lesson heading must not be empty.");
23263
- return [
23264
- `- **${heading}**:`,
23265
- sentence(input.whatWentWrong),
23266
- sentence(input.rootCause),
23267
- sentence(input.rule)
23268
- ].join(" ");
23269
- }
23270
- function appendLessonToJournal(projectRoot, input) {
23271
- const journalPath = lessonsPaths(projectRoot).journal;
23272
- const bullet = formatLessonBullet(input);
23273
- const current = existsSync(journalPath) ? readFileSync(journalPath, "utf8") : "";
23274
- const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
23275
- const lineNumber = nextLineNumber(current);
23276
- mkdirSync(dirname(journalPath), { recursive: true });
23277
- appendFileSync(journalPath, `${prefix}${bullet}
23278
- `, "utf8");
23279
- return { journalPath, bullet, lineNumber };
25245
+ function defaultLessonsConfig() {
25246
+ return {
25247
+ recallLimit: DEFAULT_RECALL_LIMIT,
25248
+ recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
25249
+ autoPrune: false
25250
+ };
23280
25251
  }
23281
- function resolveProjectFile(projectRoot, relPath) {
23282
- if (isAbsolute(relPath) || /^[A-Za-z]:[\\/]/.test(relPath)) {
23283
- throw new Error(`Lessons file must be project-relative: ${relPath}`);
25252
+
25253
+ // src/lessons/merge.ts
25254
+ async function mergeLessons(projectRoot, loserId, keeperId, options = {}) {
25255
+ return mutateLessonsGraph(projectRoot, (graph) => mergeInto(graph, loserId, keeperId), {
25256
+ retries: options.retries
25257
+ });
25258
+ }
25259
+ function mergeInto(graph, loserId, keeperId) {
25260
+ if (loserId === keeperId) {
25261
+ throw new Error(`mergeLessons: cannot merge lesson "${loserId}" into itself.`);
23284
25262
  }
23285
- const root = resolve(projectRoot);
23286
- const filePath = resolve(root, relPath);
23287
- const backToRoot = relative(root, filePath);
23288
- if (backToRoot === "" || backToRoot.startsWith("..") || isAbsolute(backToRoot)) {
23289
- throw new Error(`Lessons file escapes the project root: ${relPath}`);
25263
+ const loser = graph.lessons[loserId];
25264
+ if (loser === void 0) throw new Error(`mergeLessons: unknown lesson "${loserId}".`);
25265
+ const keeper = graph.lessons[keeperId];
25266
+ if (keeper === void 0) throw new Error(`mergeLessons: unknown lesson "${keeperId}".`);
25267
+ if (keeper.status !== "active") {
25268
+ throw new Error(`mergeLessons: keeper "${keeperId}" is not active (status: ${keeper.status}).`);
23290
25269
  }
23291
- return filePath;
25270
+ if (loser.status !== "active") {
25271
+ throw new Error(
25272
+ `mergeLessons: loser "${loserId}" is already ${loser.status}; nothing to merge.`
25273
+ );
25274
+ }
25275
+ graph.lessons[keeperId] = {
25276
+ ...keeper,
25277
+ triggers: union2(keeper.triggers, loser.triggers),
25278
+ topics: union2(keeper.topics, loser.topics),
25279
+ evidence: union2(keeper.evidence, loser.evidence)
25280
+ };
25281
+ graph.lessons[loserId] = { ...loser, status: "superseded", supersededBy: keeperId };
25282
+ return { loserId, keeperId };
23292
25283
  }
23293
- function normalizeToolEvent(projectRoot, event) {
23294
- if (event.kind !== "edit" && event.kind !== "write") return event;
23295
- return { ...event, filePath: normalizeEventPath(projectRoot, event.filePath) };
25284
+ function union2(base, extra) {
25285
+ const out2 = [...base];
25286
+ for (const item of extra) {
25287
+ if (!out2.includes(item)) out2.push(item);
25288
+ }
25289
+ return out2;
23296
25290
  }
23297
- function normalizeEventPath(projectRoot, filePath) {
23298
- const normalized = filePath.replaceAll("\\", "/");
23299
- const root = resolve(projectRoot).replaceAll("\\", "/");
23300
- if (normalized === root) return ".";
23301
- if (normalized.startsWith(`${root}/`)) return normalized.slice(root.length + 1);
23302
- return normalized.replace(/^\.\//, "");
25291
+
25292
+ // src/lessons/strip-markers.ts
25293
+ var LINE_REFS = String.raw`L\d+(?:\s*,\s*L\d+)*`;
25294
+ var LINE_REF_PATTERNS = [
25295
+ new RegExp(String.raw`\s*\bSee\s+${LINE_REFS}\.?`, "g"),
25296
+ // " See L128." / " See L140, L149"
25297
+ new RegExp(String.raw`\s*\((?:${LINE_REFS})\)\.?`, "g"),
25298
+ // " (L174)" / " (L92, L163)"
25299
+ new RegExp(String.raw`\s*\[(?:${LINE_REFS})\]\.?`, "g")
25300
+ // " [L161, L208]"
25301
+ ];
25302
+ var ALSO_RELEVANT_PATTERN = /\s*\(also relevant[^)]*\)\s*/g;
25303
+ function stripLegacyMarkers(rule) {
25304
+ let out2 = rule;
25305
+ for (const pattern of LINE_REF_PATTERNS) out2 = out2.replace(pattern, "");
25306
+ out2 = out2.replace(ALSO_RELEVANT_PATTERN, " ");
25307
+ return out2.trim();
25308
+ }
25309
+ function applyStrip(graph) {
25310
+ const changedIds = [];
25311
+ for (const [id, lesson] of Object.entries(graph.lessons)) {
25312
+ const stripped = stripLegacyMarkers(lesson.rule);
25313
+ if (stripped === lesson.rule || stripped.length === 0) continue;
25314
+ changedIds.push(id);
25315
+ graph.lessons[id] = { ...lesson, rule: stripped };
25316
+ }
25317
+ return changedIds.sort();
25318
+ }
25319
+ async function stripMarkersInGraph(projectRoot, options = {}) {
25320
+ await maybeAutoMigrateLessons(projectRoot);
25321
+ const existing = tryLoadLessonsGraph(projectRoot);
25322
+ if (existing === null) return { changedIds: [], changedCount: 0 };
25323
+ if (options.dryRun === true) {
25324
+ const changedIds2 = applyStrip(existing);
25325
+ return { changedIds: changedIds2, changedCount: changedIds2.length };
25326
+ }
25327
+ let changedIds = [];
25328
+ await mutateLessonsGraph(projectRoot, (graph) => {
25329
+ changedIds = applyStrip(graph);
25330
+ });
25331
+ return { changedIds, changedCount: changedIds.length };
25332
+ }
25333
+ var RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
25334
+ var RECALL_HOOK_MATCHER = "Edit|Write|Bash";
25335
+ function injectRecallHook(projectRoot) {
25336
+ const path = join(projectRoot, ".agentsmesh", "hooks.yaml");
25337
+ if (!existsSync(path)) return false;
25338
+ const doc = parseDocument(readFileSync(path, "utf8"));
25339
+ const existing = doc.get("PostToolUse");
25340
+ const post = existing instanceof YAMLSeq ? existing : new YAMLSeq();
25341
+ const present = post.items.some(
25342
+ (item) => item instanceof YAMLMap && item.get("command") === RECALL_HOOK_COMMAND
25343
+ );
25344
+ if (present) return false;
25345
+ post.add(
25346
+ doc.createNode({ matcher: RECALL_HOOK_MATCHER, type: "command", command: RECALL_HOOK_COMMAND })
25347
+ );
25348
+ doc.set("PostToolUse", post);
25349
+ writeFileSync(path, String(doc), "utf8");
25350
+ return true;
23303
25351
  }
23304
- function compact(value) {
23305
- return value.replace(/\s+/g, " ").trim();
25352
+
25353
+ // src/utils/filesystem/gitignore.ts
25354
+ init_fs();
25355
+ async function ensureGitignoreEntries(projectRoot, entries) {
25356
+ const gitignorePath = join(projectRoot, ".gitignore");
25357
+ const current = await readFileSafe(gitignorePath) ?? "";
25358
+ const existing = new Set(
25359
+ current.split("\n").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("#"))
25360
+ );
25361
+ const toAdd = entries.filter((e) => !isCoveredByExisting(e, existing));
25362
+ if (toAdd.length === 0) return false;
25363
+ const suffix = current.endsWith("\n") || current === "" ? "" : "\n";
25364
+ await writeFileAtomic(gitignorePath, current + suffix + toAdd.join("\n") + "\n");
25365
+ return true;
23306
25366
  }
23307
- function sentence(value) {
23308
- const compacted = compact(value);
23309
- if (compacted.length === 0) throw new Error("Lesson sentence must not be empty.");
23310
- return /[.!?]$/.test(compacted) ? compacted : `${compacted}.`;
25367
+ function isCoveredByExisting(candidate, existing) {
25368
+ if (existing.has(candidate)) return true;
25369
+ let parent = candidate.replace(/\/$/, "");
25370
+ while (parent.includes("/")) {
25371
+ parent = parent.slice(0, parent.lastIndexOf("/"));
25372
+ if (parent === "") break;
25373
+ if (existing.has(parent) || existing.has(`${parent}/`) || existing.has(`${parent}/**`)) {
25374
+ return true;
25375
+ }
25376
+ }
25377
+ return false;
25378
+ }
25379
+
25380
+ // src/targets/projection/lessons-paragraph.ts
25381
+ init_managed_blocks();
25382
+ var LESSONS_PARAGRAPH_BLOCK = `${LESSONS_CONTRACT_START}
25383
+ ${LESSONS_PROCEDURAL_RULE}
25384
+ ${LESSONS_CONTRACT_END}`;
25385
+ function appendLessonsParagraph(content) {
25386
+ const withoutPrior = stripLessonsParagraph(content);
25387
+ return insertAtBodyTop(withoutPrior, LESSONS_PARAGRAPH_BLOCK);
23311
25388
  }
23312
- function nextLineNumber(current) {
23313
- if (current.length === 0) return 1;
23314
- const lineCount = current.split("\n").length;
23315
- return current.endsWith("\n") ? lineCount : lineCount + 1;
25389
+ function stripLessonsParagraph(content) {
25390
+ const withoutBlock = stripManagedBlock(content, LESSONS_CONTRACT_START, LESSONS_CONTRACT_END);
25391
+ return stripRawProceduralRule(withoutBlock).trim();
23316
25392
  }
23317
- function scaffoldLessons(projectRoot) {
25393
+ function stripRawProceduralRule(content) {
25394
+ return content.replace(`
25395
+
25396
+ ${LESSONS_PROCEDURAL_RULE}`, "").replace(LESSONS_PROCEDURAL_RULE, "");
25397
+ }
25398
+
25399
+ // src/lessons/skill.ts
25400
+ init_markdown();
25401
+ var LESSONS_SKILL_NAME = "lessons";
25402
+ var LESSONS_SKILL_DESCRIPTION = "Full operating manual for the agentsmesh lessons system (recall + capture). Consult when running any `agentsmesh lessons` subcommand (query, add, topics, show, deprecate, merge, untrigger, strip-markers, journal, validate, stats, prune, import-md), choosing a topic or trigger flags, using the lessons MCP tools, or when unsure how to phrase or capture a lesson.";
25403
+ var LESSONS_SKILL_BODY = `# Lessons \u2014 operating manual
25404
+
25405
+ Two commands: **Recall** before you act, **Capture** after any failure. The graph
25406
+ \`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit.
25407
+
25408
+ ## Recall \u2014 before each file edit and each state-changing command
25409
+
25410
+ \`agentsmesh lessons query --file <path> --cmd <command>\` (add \`--keyword <text>\` to
25411
+ match by task), then apply every rule returned. Scope is MUTATING actions: file edits
25412
+ and state-changing commands (build/test/install/migrate/git-write). Pure-read commands
25413
+ (cat/ls/grep/git-log; read-only) and the recall query itself are **exempt** \u2014 no
25414
+ infinite regress. A predicate-less query is rejected; **keyword-only recall is the
25415
+ anti-pattern** \u2014 most lessons are keyed to a \`file_glob\`/\`command_pattern\` and won't
25416
+ surface (the CLI warns). Excuses ("small edit", "I already know this", "later") all
25417
+ mean: query first \u2014 skipping recall on a mutating action is a process violation, and
25418
+ the user will check.
25419
+
25420
+ ## Capture \u2014 immediately after any failure
25421
+
25422
+ Any failure counts, not just red tests: a failing test/CI/lint/typecheck, a code
25423
+ review, a user correction, a regression, or a wrong assumption \u2014 yours or anyone's.
25424
+
25425
+ \`agentsmesh lessons add "<imperative rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`
25426
+
25427
+ - **At least one _effective_ trigger is required.** A capture is rejected
25428
+ (\`UNRECALLABLE_LESSON\`) when EVERY trigger is dead on the mandatory \`--file\`/\`--cmd\`
25429
+ recall path \u2014 a stopword-only keyword ("state of the art"), or an invalid/ReDoS
25430
+ command regex \u2014 because the lesson could never be recalled there. Prefer
25431
+ \`--trigger-file\`: the most reliable trigger, it fires on \`--file\` recall. A keyword
25432
+ alone is discouraged (\`KEYWORD_ONLY_LESSON\`); paraphrasing an existing rule warns
25433
+ (\`NEAR_DUPLICATE_LESSON\` \u2014 update that lesson instead).
25434
+ - **One imperative sentence.** A rule over 2000 chars is rejected (\`OVERSIZED_RULE\`) \u2014
25435
+ trim it or split into separate lessons; don't paste a log/diff.
25436
+ - Widen with \`--trigger-cmd <regex>\` / \`--trigger-kw <text>\`. New area:
25437
+ \`--new-topic --topic-summary "<line>"\` (list ids with \`agentsmesh lessons topics\`).
25438
+
25439
+ ## No shell? \u2014 MCP tools
25440
+
25441
+ \`lessons_query\`, \`lessons_add\`, \`lessons_topics\`, \`lessons_show\` (inspect a topic),
25442
+ \`lessons_deprecate\` (retire). validate / prune / merge / import-md are CLI-only.
25443
+
25444
+ ## Other subcommands
25445
+
25446
+ \`agentsmesh lessons <cmd>\`: \`show\` \xB7 \`deprecate\` (\`--superseded-by\`) \xB7 \`merge\` \xB7
25447
+ \`untrigger\` \xB7 \`strip-markers\` \xB7 \`prune\` (\`--apply\`; trims over-cap triggers, GCs
25448
+ orphan triggers/topics) \xB7 \`journal\` \xB7 \`validate\` \xB7 \`stats\` \xB7 \`import-md\`. Full
25449
+ help: \`agentsmesh lessons --help\`.
25450
+
25451
+ ## Config (\`.agentsmesh/lessons/config.json\`)
25452
+
25453
+ \`recallLimit\` / \`recallMaxTokens\` (canonical recall caps; per-call overrides
25454
+ \`--top\` / \`--max-tokens\`). \`recallMaxTokens\` is approximate \u2014 \`rule.length / 4\`,
25455
+ not a real tokenizer. \`autoPrune: true\` (default off) auto-GCs structural cruft
25456
+ after each capture \u2014 orphan triggers/topics + non-stranding dead globs, the safe
25457
+ half of \`prune\`; never trims/strands an active lesson, git-reversible.
25458
+
25459
+ ## Dedup (opt-in)
25460
+
25461
+ Set \`--session <id>\` (or \`AGENTSMESH_SESSION_ID\`) and lessons already delivered this
25462
+ session are suppressed, so each recall carries only what is new (\`--no-dedup\` opts
25463
+ out). With no session id, recall is fully stateless \u2014 unchanged.`;
25464
+ var LESSONS_SKILL_FILE = serializeFrontmatter(
25465
+ { name: LESSONS_SKILL_NAME, description: LESSONS_SKILL_DESCRIPTION },
25466
+ LESSONS_SKILL_BODY
25467
+ );
25468
+
25469
+ // src/lessons/init.ts
25470
+ async function scaffoldLessons(projectRoot) {
23318
25471
  const paths = lessonsPaths(projectRoot);
23319
25472
  const created = [];
25473
+ const updated = [];
23320
25474
  const skipped = [];
23321
- mkdirSync(paths.topicsDir, { recursive: true });
23322
- for (const [path, template] of [
23323
- [paths.journal, LESSONS_JOURNAL_TEMPLATE],
23324
- [paths.index, LESSONS_INDEX_TEMPLATE]
23325
- ]) {
23326
- if (existsSync(path)) {
23327
- skipped.push(path);
23328
- } else {
23329
- mkdirSync(dirname(path), { recursive: true });
23330
- writeFileSync(path, template, "utf8");
23331
- created.push(path);
23332
- }
25475
+ mkdirSync(paths.base, { recursive: true });
25476
+ await maybeAutoMigrateLessons(projectRoot);
25477
+ if (existsSync(paths.graph)) {
25478
+ skipped.push(paths.graph);
25479
+ } else {
25480
+ await mutateLessonsGraphLocked(projectRoot, () => {
25481
+ });
25482
+ created.push(paths.graph);
25483
+ }
25484
+ seedLessonsConfig(projectRoot, created, skipped);
25485
+ seedLessonsSkill(projectRoot, created, updated, skipped);
25486
+ const rootRuleUpdated = injectProceduralBlock(projectRoot);
25487
+ const recallHookInjected = injectRecallHook(projectRoot);
25488
+ const gitignoreUpdated = await ensureGitignoreEntries(projectRoot, [
25489
+ toRelPath(projectRoot, recallLogPath(projectRoot)),
25490
+ toRelPath(projectRoot, captureLogPath(projectRoot))
25491
+ ]);
25492
+ return { created, updated, skipped, rootRuleUpdated, gitignoreUpdated, recallHookInjected };
25493
+ }
25494
+ function seedLessonsSkill(projectRoot, created, updated, skipped) {
25495
+ const skillPath = join(projectRoot, ".agentsmesh/skills", LESSONS_SKILL_NAME, "SKILL.md");
25496
+ const desired = `${LESSONS_SKILL_FILE}
25497
+ `;
25498
+ if (!existsSync(skillPath)) {
25499
+ mkdirSync(dirname(skillPath), { recursive: true });
25500
+ writeFileSync(skillPath, desired, "utf8");
25501
+ created.push(skillPath);
25502
+ return;
25503
+ }
25504
+ if (readFileSync(skillPath, "utf8") === desired) {
25505
+ skipped.push(skillPath);
25506
+ return;
25507
+ }
25508
+ writeFileSync(skillPath, desired, "utf8");
25509
+ updated.push(skillPath);
25510
+ }
25511
+ function seedLessonsConfig(projectRoot, created, skipped) {
25512
+ const configPath = lessonsPaths(projectRoot).config;
25513
+ if (existsSync(configPath)) {
25514
+ skipped.push(configPath);
25515
+ return;
23333
25516
  }
23334
- const rootRuleUpdated = appendProceduralRule(projectRoot);
23335
- return { created, skipped, rootRuleUpdated };
25517
+ mkdirSync(dirname(configPath), { recursive: true });
25518
+ writeFileSync(configPath, `${JSON.stringify(defaultLessonsConfig(), null, 2)}
25519
+ `, "utf8");
25520
+ created.push(configPath);
23336
25521
  }
23337
- function appendProceduralRule(projectRoot) {
25522
+ function injectProceduralBlock(projectRoot) {
23338
25523
  const rootRule = join(projectRoot, ".agentsmesh/rules/_root.md");
23339
25524
  if (!existsSync(rootRule)) {
23340
25525
  mkdirSync(dirname(rootRule), { recursive: true });
@@ -23343,23 +25528,21 @@ root: true
23343
25528
  description: ""
23344
25529
  ---
23345
25530
 
23346
- # Operational Guidelines
25531
+ ${LESSONS_PARAGRAPH_BLOCK}
23347
25532
 
23348
- ${LESSONS_PROCEDURAL_RULE}
25533
+ # Operational Guidelines
23349
25534
  `;
23350
25535
  writeFileSync(rootRule, seeded, "utf8");
23351
25536
  return true;
23352
25537
  }
23353
25538
  const current = readFileSync(rootRule, "utf8");
23354
- if (/^## Lessons \(/m.test(current)) return false;
23355
- const next = current.endsWith("\n") ? current : `${current}
25539
+ const desired = `${appendLessonsParagraph(current)}
23356
25540
  `;
23357
- writeFileSync(rootRule, `${next}
23358
- ${LESSONS_PROCEDURAL_RULE}
23359
- `, "utf8");
25541
+ if (desired === current) return false;
25542
+ writeFileSync(rootRule, desired, "utf8");
23360
25543
  return true;
23361
25544
  }
23362
25545
 
23363
- export { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, FileSystemError, GenerationError, ImportError, LESSONS_INDEX_TEMPLATE, LESSONS_JOURNAL_TEMPLATE, LESSONS_PROCEDURAL_RULE, LessonsIndexSchema, LockAcquisitionError, RemoteFetchError, TargetNotFoundError, appendLessonToJournal, check, computeDiff2 as computeDiff, diff, formatDiffSummary, formatLessonBullet, generate, getAllDescriptors, getDescriptor, getTargetCatalog, hashBullet, importFrom, lessonsPaths, lint, loadCanonical, loadCanonicalFiles, loadConfig2 as loadConfig, loadConfigFromDirectory, loadLedger, loadLessonsIndex, loadProjectContext, matchTriggers, parseBullets, parseIndex, readTriggeredLessons, registerTargetDescriptor, resolveOutputCollisions, saveLedger, scaffoldLessons, scoreBullet, toRelPath };
25546
+ export { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, DEFAULT_RECALL_LIMIT, FileSystemError, GenerationError, ImportError, LESSONS_PROCEDURAL_RULE, LessonsGraphSchema, LockAcquisitionError, RemoteFetchError, TargetNotFoundError, UnknownTopicError, acquireLessonsLock, addLesson, check, computeDiff2 as computeDiff, diff, formatDiffSummary, generate, getAllDescriptors, getDescriptor, getTargetCatalog, graphFilePath, importFrom, importLegacyLessons, lessonsPaths, lint, loadCanonical, loadCanonicalFiles, loadConfig2 as loadConfig, loadConfigFromDirectory, loadLessonsGraph, loadProjectContext, mergeLessons, mutateLessonsGraph, parseGraph, queryLessons, rankLessons, registerTargetDescriptor, resolveOutputCollisions, scaffoldLessons, serializeGraph, stripLegacyMarkers, stripMarkersInGraph, toRelPath, tryLoadLessonsGraph, validateLessonsGraph };
23364
25547
  //# sourceMappingURL=index.js.map
23365
25548
  //# sourceMappingURL=index.js.map