agentsmesh 0.41.0 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.js CHANGED
@@ -1496,25 +1496,6 @@ var init_no_outputs = __esm({
1496
1496
  function escapeRegExp(value) {
1497
1497
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1498
1498
  }
1499
- function managedBlockPattern(start, end) {
1500
- return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
1501
- }
1502
- function stripManagedBlock(content, start, end) {
1503
- return content.replace(managedBlockPattern(start, end), "").trim();
1504
- }
1505
- function splitFrontmatterPrefix(content) {
1506
- const split = splitFrontmatter(content);
1507
- return split === null ? { prefix: "", body: content.trim() } : { prefix: split.prefix, body: split.body };
1508
- }
1509
- function insertAtBodyTop(content, block) {
1510
- const { prefix, body } = splitFrontmatterPrefix(content);
1511
- const placed = body ? `${block}
1512
-
1513
- ${body}` : block;
1514
- return prefix ? `${prefix}
1515
-
1516
- ${placed}` : placed;
1517
- }
1518
1499
  function ruleSource(source) {
1519
1500
  const normalized = source.replace(/\\/g, "/");
1520
1501
  const meshIndex = normalized.lastIndexOf(".agentsmesh/");
@@ -1522,36 +1503,22 @@ function ruleSource(source) {
1522
1503
  if (normalized.startsWith("rules/")) return normalized;
1523
1504
  return join("rules", basename(normalized)).replace(/\\/g, "/");
1524
1505
  }
1525
- function markerForRule(rule) {
1526
- return {
1506
+ function renderEmbeddedRule(rule) {
1507
+ const marker = {
1527
1508
  source: ruleSource(rule.source),
1528
1509
  description: rule.description,
1529
1510
  globs: rule.globs,
1530
1511
  targets: rule.targets
1531
1512
  };
1532
- }
1533
- function embeddedRuleStart(rule) {
1534
- return `${EMBEDDED_RULE_START_PREFIX}${JSON.stringify(markerForRule(rule))}${EMBEDDED_RULE_START_SUFFIX}`;
1535
- }
1536
- function renderRule(rule) {
1537
- const parts = [embeddedRuleStart(rule)];
1513
+ const parts = [`${START_PREFIX}${JSON.stringify(marker)}${START_SUFFIX}`];
1538
1514
  if (rule.description.trim()) {
1539
1515
  parts.push(`## ${rule.description.trim()}`, "");
1540
1516
  }
1541
1517
  parts.push(rule.body.trim(), EMBEDDED_RULE_END);
1542
1518
  return parts.filter((part) => part.length > 0).join("\n");
1543
1519
  }
1544
- function renderEmbeddedRulesBlock(rules) {
1545
- if (rules.length === 0) return "";
1546
- return [EMBEDDED_RULES_START, ...rules.map(renderRule), EMBEDDED_RULES_END].join("\n");
1547
- }
1548
- function appendEmbeddedRulesBlock(content, rules) {
1549
- const block = renderEmbeddedRulesBlock(rules);
1550
- const withoutExisting = stripManagedBlock(content, EMBEDDED_RULES_START, EMBEDDED_RULES_END);
1551
- if (!block) return withoutExisting;
1552
- return withoutExisting ? `${withoutExisting}
1553
-
1554
- ${block}` : block;
1520
+ function renderEmbeddedRuleEntries(rules) {
1521
+ return rules.map(renderEmbeddedRule).join("\n\n");
1555
1522
  }
1556
1523
  function toStringArray2(value) {
1557
1524
  return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
@@ -1578,26 +1545,67 @@ function stripGeneratedHeading(body, description) {
1578
1545
  const heading = `## ${description.trim()}`;
1579
1546
  return trimmed.startsWith(heading) ? trimmed.slice(heading.length).trim() : trimmed;
1580
1547
  }
1548
+ function takeEmbeddedRuleEntries(text) {
1549
+ const rules = [];
1550
+ const entry = new RegExp(
1551
+ `${escapeRegExp(START_PREFIX)}([\\s\\S]*?)${escapeRegExp(START_SUFFIX)}([\\s\\S]*?)${escapeRegExp(EMBEDDED_RULE_END)}`,
1552
+ "g"
1553
+ );
1554
+ const rest = text.replace(entry, (whole, markerText, body) => {
1555
+ const marker = parseMarker(markerText);
1556
+ if (!marker) return whole;
1557
+ rules.push({ ...marker, body: stripGeneratedHeading(body, marker.description) });
1558
+ return "";
1559
+ });
1560
+ return { rest: rest.trim(), rules };
1561
+ }
1562
+ var EMBEDDED_RULE_END, START_PREFIX, START_SUFFIX;
1563
+ var init_embedded_rule_entries = __esm({
1564
+ "src/targets/projection/embedded-rule-entries.ts"() {
1565
+ EMBEDDED_RULE_END = "<!-- agentsmesh:embedded-rule:end -->";
1566
+ START_PREFIX = "<!-- agentsmesh:embedded-rule:start ";
1567
+ START_SUFFIX = " -->";
1568
+ }
1569
+ });
1570
+
1571
+ // src/targets/projection/managed-blocks.ts
1572
+ function managedBlockPattern(start, end) {
1573
+ return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
1574
+ }
1575
+ function stripManagedBlock(content, start, end) {
1576
+ return content.replace(managedBlockPattern(start, end), "").trim();
1577
+ }
1578
+ function splitFrontmatterPrefix(content) {
1579
+ const split = splitFrontmatter(content);
1580
+ return split === null ? { prefix: "", body: content.trim() } : { prefix: split.prefix, body: split.body };
1581
+ }
1582
+ function insertAtBodyTop(content, block) {
1583
+ const { prefix, body } = splitFrontmatterPrefix(content);
1584
+ const placed = body ? `${block}
1585
+
1586
+ ${body}` : block;
1587
+ return prefix ? `${prefix}
1588
+
1589
+ ${placed}` : placed;
1590
+ }
1591
+ function renderEmbeddedRulesBlock(rules) {
1592
+ if (rules.length === 0) return "";
1593
+ return [EMBEDDED_RULES_START, ...rules.map(renderEmbeddedRule), EMBEDDED_RULES_END].join("\n");
1594
+ }
1595
+ function appendEmbeddedRulesBlock(content, rules) {
1596
+ const block = renderEmbeddedRulesBlock(rules);
1597
+ const withoutExisting = stripManagedBlock(content, EMBEDDED_RULES_START, EMBEDDED_RULES_END);
1598
+ if (!block) return withoutExisting;
1599
+ return withoutExisting ? `${withoutExisting}
1600
+
1601
+ ${block}` : block;
1602
+ }
1581
1603
  function extractEmbeddedRules(content) {
1582
1604
  const rules = [];
1583
1605
  const outerPattern = managedBlockPattern(EMBEDDED_RULES_START, EMBEDDED_RULES_END);
1584
1606
  const rootContent = content.replace(outerPattern, (block) => {
1585
- const inner = block.replace(EMBEDDED_RULES_START, "").replace(EMBEDDED_RULES_END, "").trim();
1586
- const entryPattern = new RegExp(
1587
- `${escapeRegExp(EMBEDDED_RULE_START_PREFIX)}([\\s\\S]*?)${escapeRegExp(EMBEDDED_RULE_START_SUFFIX)}([\\s\\S]*?)${escapeRegExp(EMBEDDED_RULE_END)}`,
1588
- "g"
1589
- );
1590
- for (const match of inner.matchAll(entryPattern)) {
1591
- const markerText = match[1];
1592
- const body = match[2];
1593
- if (markerText === void 0 || body === void 0) continue;
1594
- const marker = parseMarker(markerText);
1595
- if (!marker) continue;
1596
- rules.push({
1597
- ...marker,
1598
- body: stripGeneratedHeading(body, marker.description)
1599
- });
1600
- }
1607
+ const inner = block.replace(EMBEDDED_RULES_START, "").replace(EMBEDDED_RULES_END, "");
1608
+ rules.push(...takeEmbeddedRuleEntries(inner).rules);
1601
1609
  return "";
1602
1610
  });
1603
1611
  return { rootContent: rootContent.trim(), rules };
@@ -1610,17 +1618,16 @@ function embeddedRootRule(canonical, target34, rootFile) {
1610
1618
  const content = appendEmbeddedRulesBlock(rootBody, nonRootRules);
1611
1619
  return content ? [{ path: rootFile, content }] : [];
1612
1620
  }
1613
- var ROOT_CONTRACT_START, ROOT_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
1621
+ var ROOT_CONTRACT_START, ROOT_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END;
1614
1622
  var init_managed_blocks = __esm({
1615
1623
  "src/targets/projection/managed-blocks.ts"() {
1616
1624
  init_markdown();
1625
+ init_embedded_rule_entries();
1626
+ init_embedded_rule_entries();
1617
1627
  ROOT_CONTRACT_START = "<!-- agentsmesh:root-generation-contract:start -->";
1618
1628
  ROOT_CONTRACT_END = "<!-- agentsmesh:root-generation-contract:end -->";
1619
1629
  EMBEDDED_RULES_START = "<!-- agentsmesh:embedded-rules:start -->";
1620
1630
  EMBEDDED_RULES_END = "<!-- agentsmesh:embedded-rules:end -->";
1621
- EMBEDDED_RULE_END = "<!-- agentsmesh:embedded-rule:end -->";
1622
- EMBEDDED_RULE_START_PREFIX = "<!-- agentsmesh:embedded-rule:start ";
1623
- EMBEDDED_RULE_START_SUFFIX = " -->";
1624
1631
  }
1625
1632
  });
1626
1633
 
@@ -2912,6 +2919,8 @@ function rewriteFileLinks(input) {
2912
2919
  const lineNumSuffix = lineNumMatch ? lineNumMatch[0] : "";
2913
2920
  if (!rawCandidate) return match;
2914
2921
  const tokenContext = getTokenContext(fullContent, offset, offset + rawCandidate.length);
2922
+ if (input.markdownLinksOnly === true && tokenContext.role !== "markdown-link-dest")
2923
+ return match;
2915
2924
  const candidate = decodeLinkPath(rawCandidate, tokenContext.role);
2916
2925
  if (tokenContext.role !== "markdown-link-dest" && WINDOWS_ABSOLUTE_PATH.test(candidate)) {
2917
2926
  return match;
@@ -3086,13 +3095,12 @@ var init_import_rewriter = __esm({
3086
3095
  });
3087
3096
  async function writeMcpWithMerge(projectRoot, canonicalPath, imported) {
3088
3097
  const destPath = join(projectRoot, canonicalPath);
3089
- const existing = await readExistingServers(destPath);
3098
+ const existing = parseMcpServers(await readFileSafe(destPath));
3090
3099
  const merged = { ...existing, ...imported };
3091
3100
  await mkdirp(dirname(destPath));
3092
3101
  await writeFileAtomic(destPath, JSON.stringify({ mcpServers: merged }, null, 2));
3093
3102
  }
3094
- async function readExistingServers(path) {
3095
- const content = await readFileSafe(path);
3103
+ function parseMcpServers(content) {
3096
3104
  if (content === null) return {};
3097
3105
  let parsed;
3098
3106
  try {
@@ -7130,12 +7138,14 @@ function canonicalRulePath(source) {
7130
7138
  }
7131
7139
  async function splitEmbeddedRulesToCanonical(input) {
7132
7140
  const extracted = extractEmbeddedRules(input.content);
7141
+ const results = await writeEmbeddedRules(extracted.rules, input);
7142
+ return { rootContent: extracted.rootContent, results };
7143
+ }
7144
+ async function writeEmbeddedRules(rules, input) {
7133
7145
  const results = [];
7134
- if (extracted.rules.length === 0) {
7135
- return { rootContent: extracted.rootContent, results };
7136
- }
7146
+ if (rules.length === 0) return results;
7137
7147
  await mkdirp(join(input.projectRoot, input.rulesDir));
7138
- for (const rule of extracted.rules) {
7148
+ for (const rule of rules) {
7139
7149
  const canonicalSource = canonicalRulePath(rule.source);
7140
7150
  if (canonicalSource === null || canonicalSource === "rules/_root.md") continue;
7141
7151
  const destPath = join(input.projectRoot, ".agentsmesh", canonicalSource);
@@ -7145,6 +7155,7 @@ async function splitEmbeddedRulesToCanonical(input) {
7145
7155
  destPath,
7146
7156
  {
7147
7157
  ...frontmatter,
7158
+ ...input.frontmatter,
7148
7159
  root: false,
7149
7160
  description: rule.description || void 0,
7150
7161
  globs: rule.globs.length > 0 ? rule.globs : void 0,
@@ -7160,13 +7171,19 @@ async function splitEmbeddedRulesToCanonical(input) {
7160
7171
  feature: "rules"
7161
7172
  });
7162
7173
  }
7163
- return { rootContent: extracted.rootContent, results };
7174
+ return results;
7175
+ }
7176
+ async function splitNestedAgentsFile(input, into) {
7177
+ const { rest, rules } = takeEmbeddedRuleEntries(input.content);
7178
+ into.push(...await writeEmbeddedRules(rules, input));
7179
+ return rest.length > 0 ? rest : null;
7164
7180
  }
7165
7181
  var init_embedded_rules = __esm({
7166
7182
  "src/targets/import/embedded-rules.ts"() {
7167
7183
  init_fs();
7168
7184
  init_markdown();
7169
7185
  init_managed_blocks();
7186
+ init_embedded_rule_entries();
7170
7187
  init_import_metadata();
7171
7188
  }
7172
7189
  });
@@ -8594,7 +8611,7 @@ function generateRules6(canonical) {
8594
8611
  const slug = basename(rule.source, ".md");
8595
8612
  const frontmatter = {};
8596
8613
  if (rule.description) frontmatter.description = rule.description;
8597
- if (rule.globs.length > 0) frontmatter.globs = rule.globs;
8614
+ if (rule.globs.length > 0) frontmatter.paths = rule.globs;
8598
8615
  const content = serializeFrontmatter(frontmatter, rule.body.trim() || "");
8599
8616
  outputs.push({ path: `${CLAUDE_RULES_DIR}/${slug}.md`, content });
8600
8617
  }
@@ -8976,12 +8993,14 @@ var init_import_mappers2 = __esm({
8976
8993
  }) => {
8977
8994
  const destPath = join(destDir, relativePath);
8978
8995
  const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
8996
+ const { paths: paths2, ...rest } = frontmatter;
8997
+ const scoped = paths2 === void 0 ? {} : { globs: toToolsArray(paths2) };
8979
8998
  return {
8980
8999
  destPath,
8981
9000
  toPath: `${AB_RULES}/${relativePath}`,
8982
9001
  content: await serializeImportedRuleWithFallback(
8983
9002
  destPath,
8984
- { ...frontmatter, root: false },
9003
+ { ...rest, ...scoped, root: false },
8985
9004
  body
8986
9005
  )
8987
9006
  };
@@ -10486,7 +10505,7 @@ function generateRules8(canonical) {
10486
10505
  const rootBody = canonical.rules.find((rule) => rule.root)?.body.trim() ?? "";
10487
10506
  if (rootBody) outputs.push({ path: CODEBUFF_ROOT_FILE, content: rootBody });
10488
10507
  for (const [path, rules] of groupByNestedPath(eligibleRules(canonical))) {
10489
- const content = rules.map((rule) => rule.body.trim()).filter((body) => body.length > 0).join("\n\n");
10508
+ const content = renderEmbeddedRuleEntries(rules.filter((rule) => rule.body.trim().length > 0));
10490
10509
  if (content) outputs.push({ path, content });
10491
10510
  }
10492
10511
  return outputs;
@@ -10514,6 +10533,7 @@ var init_generator8 = __esm({
10514
10533
  init_no_outputs();
10515
10534
  init_embedded_skill();
10516
10535
  init_managed_blocks();
10536
+ init_embedded_rule_entries();
10517
10537
  init_command_skill();
10518
10538
  init_nested_rules();
10519
10539
  init_mcp_format2();
@@ -10563,33 +10583,47 @@ function isVendored(relDir) {
10563
10583
  }
10564
10584
  async function importNestedRules(projectRoot, normalize) {
10565
10585
  const destDir = join(projectRoot, AB_RULES);
10566
- return importFileDirectory({
10586
+ const embedded = [];
10587
+ const results = await importFileDirectory({
10567
10588
  srcDir: projectRoot,
10568
10589
  destDir,
10569
10590
  extensions: [CODEBUFF_ROOT_FILE],
10570
10591
  fromTool: CODEBUFF_TARGET,
10571
10592
  normalize,
10572
- mapEntry: ({ srcPath, normalizeTo }) => {
10593
+ mapEntry: async ({ srcPath, content, normalizeTo }) => {
10573
10594
  if (basename(srcPath) !== CODEBUFF_ROOT_FILE) return null;
10574
10595
  const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
10575
10596
  if (!relDir || relDir === ".") return null;
10576
10597
  if (!shouldImportScopedAgentsRule(relDir)) return null;
10577
10598
  if (isVendored(relDir)) return null;
10599
+ const ownText = await splitNestedAgentsFile(
10600
+ {
10601
+ content,
10602
+ projectRoot,
10603
+ rulesDir: AB_RULES,
10604
+ sourcePath: srcPath,
10605
+ fromTool: CODEBUFF_TARGET,
10606
+ normalize
10607
+ },
10608
+ embedded
10609
+ );
10610
+ if (ownText === null) return null;
10578
10611
  const ruleName2 = relDir.replace(/\//g, "-");
10579
10612
  const destPath = join(destDir, `${ruleName2}.md`);
10580
- const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
10581
- return serializeImportedRuleWithFallback(
10582
- destPath,
10583
- { ...frontmatter, root: false, globs: [`${relDir}/**`] },
10584
- body
10585
- ).then((content) => ({
10613
+ const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath, ownText));
10614
+ return {
10586
10615
  destPath,
10587
10616
  toPath: `${AB_RULES}/${ruleName2}.md`,
10588
10617
  feature: "rules",
10589
- content
10590
- }));
10618
+ content: await serializeImportedRuleWithFallback(
10619
+ destPath,
10620
+ { ...frontmatter, root: false, globs: [`${relDir}/**`] },
10621
+ body
10622
+ )
10623
+ };
10591
10624
  }
10592
10625
  });
10626
+ return [...results, ...embedded];
10593
10627
  }
10594
10628
  async function importCodebuffRules(projectRoot, scope, normalize) {
10595
10629
  const results = await importRootRule2(projectRoot, scope, normalize);
@@ -11022,7 +11056,7 @@ function generateRules9(canonical) {
11022
11056
  }
11023
11057
  const nested = advisory.filter((rule) => !isRootEmbedded(rule));
11024
11058
  for (const [path, rules] of groupByNestedPath2(nested)) {
11025
- const content = rules.map((rule) => rule.body.trim()).filter((body) => body.length > 0).join("\n\n");
11059
+ const content = renderEmbeddedRuleEntries(rules.filter((rule) => rule.body.trim().length > 0));
11026
11060
  outputs.push({ path, content });
11027
11061
  }
11028
11062
  return outputs;
@@ -11035,6 +11069,7 @@ function renderCodexGlobalInstructions(canonical) {
11035
11069
  var init_rules = __esm({
11036
11070
  "src/targets/codex-cli/generator/rules.ts"() {
11037
11071
  init_managed_blocks();
11072
+ init_embedded_rule_entries();
11038
11073
  init_constants10();
11039
11074
  init_codex_rule_paths();
11040
11075
  }
@@ -11664,6 +11699,7 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
11664
11699
  await importInstructionMirrors(projectRoot, destDir, results, normalize);
11665
11700
  results.push(...await importCodexNonRootRuleFiles(projectRoot, destDir, normalize));
11666
11701
  if (layoutScope !== "global") {
11702
+ const embedded = [];
11667
11703
  results.push(
11668
11704
  ...await importFileDirectory({
11669
11705
  srcDir: projectRoot,
@@ -11671,7 +11707,7 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
11671
11707
  extensions: ["AGENTS.md", "AGENTS.override.md"],
11672
11708
  fromTool: "codex-cli",
11673
11709
  normalize,
11674
- mapEntry: async ({ srcPath, normalizeTo }) => {
11710
+ mapEntry: async ({ srcPath, content: content2, normalizeTo }) => {
11675
11711
  const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
11676
11712
  const fileName = basename(srcPath);
11677
11713
  const isOverride = fileName === "AGENTS.override.md";
@@ -11682,26 +11718,36 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
11682
11718
  await removePathIfExists(join(destDir, `${ruleName2}.md`));
11683
11719
  return null;
11684
11720
  }
11721
+ const variant = isOverride ? { codex_instruction: "override" } : {};
11722
+ const ownText = await splitNestedAgentsFile(
11723
+ {
11724
+ content: content2,
11725
+ projectRoot,
11726
+ rulesDir: AB_RULES,
11727
+ sourcePath: srcPath,
11728
+ fromTool: "codex-cli",
11729
+ normalize,
11730
+ frontmatter: variant
11731
+ },
11732
+ embedded
11733
+ );
11734
+ if (ownText === null) return null;
11685
11735
  const destPath = join(destDir, `${ruleName2}.md`);
11686
- const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
11736
+ const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath, ownText));
11687
11737
  return {
11688
11738
  destPath,
11689
11739
  toPath: `${AB_RULES}/${ruleName2}.md`,
11690
11740
  feature: "rules",
11691
11741
  content: await serializeImportedRuleWithFallback(
11692
11742
  destPath,
11693
- {
11694
- ...frontmatter,
11695
- root: false,
11696
- globs: [`${relDir}/**`],
11697
- ...isOverride ? { codex_instruction: "override" } : {}
11698
- },
11743
+ { ...frontmatter, root: false, globs: [`${relDir}/**`], ...variant },
11699
11744
  body
11700
11745
  )
11701
11746
  };
11702
11747
  }
11703
11748
  })
11704
11749
  );
11750
+ results.push(...embedded);
11705
11751
  }
11706
11752
  }
11707
11753
  async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
@@ -18325,7 +18371,7 @@ function parseExtensions(content) {
18325
18371
  }
18326
18372
  return out2;
18327
18373
  }
18328
- async function readExistingServers2(destPath) {
18374
+ async function readExistingServers(destPath) {
18329
18375
  const content = await readFileSafe(destPath);
18330
18376
  if (content === null) return {};
18331
18377
  let parsed;
@@ -18348,7 +18394,7 @@ async function gooseMcpMap(ctx) {
18348
18394
  const imported = ctx.relativePath.endsWith(".mcp.json") ? parsePluginMcpJson(ctx.content) : parseExtensions(ctx.content);
18349
18395
  if (Object.keys(imported).length === 0) return null;
18350
18396
  const destPath = join(ctx.destDir, "mcp.json");
18351
- const existing = await readExistingServers2(destPath);
18397
+ const existing = await readExistingServers(destPath);
18352
18398
  const merged = { ...existing, ...imported };
18353
18399
  return {
18354
18400
  destPath,
@@ -26903,12 +26949,6 @@ function ruleSlug3(source) {
26903
26949
  const name = basename(source, ".md");
26904
26950
  return name === "_root" ? "root" : name;
26905
26951
  }
26906
- function directoryScopedRuleDir(globs) {
26907
- if (globs.length === 0) return null;
26908
- const dirs = globs.map((glob) => glob.split("/")[0] ?? "").filter((segment) => /^[A-Za-z0-9._-]+$/.test(segment));
26909
- if (dirs.length !== globs.length) return null;
26910
- return dirs.every((dir) => dir === dirs[0]) ? dirs[0] : null;
26911
- }
26912
26952
  function generateRules32(canonical) {
26913
26953
  const outputs = [];
26914
26954
  const root = canonical.rules.find((r) => r.root);
@@ -26925,21 +26965,14 @@ function generateRules32(canonical) {
26925
26965
  const frontmatter = {
26926
26966
  description: rule.description || void 0,
26927
26967
  trigger: normalizedTrigger,
26928
- glob: rule.globs.length === 1 ? rule.globs[0] : void 0,
26929
- globs: rule.globs.length > 1 ? rule.globs : void 0
26968
+ // Windsurf reads `globs` as one comma-joined string; it ignores `glob`.
26969
+ globs: rule.globs.length > 0 ? rule.globs.join(",") : void 0
26930
26970
  };
26931
26971
  Object.keys(frontmatter).forEach((k) => {
26932
26972
  if (frontmatter[k] === void 0) delete frontmatter[k];
26933
26973
  });
26934
26974
  const content = Object.keys(frontmatter).length > 0 ? serializeFrontmatter(frontmatter, rule.body.trim() || "") : rule.body.trim() || "";
26935
26975
  outputs.push({ path: `${WINDSURF_RULES_DIR}/${slug}.md`, content });
26936
- const dir = directoryScopedRuleDir(rule.globs);
26937
- if (dir) {
26938
- if (dir !== slug) {
26939
- outputs.push({ path: `${WINDSURF_RULES_DIR}/${dir}.md`, content });
26940
- }
26941
- outputs.push({ path: `${dir}/AGENTS.md`, content: rule.body.trim() || "" });
26942
- }
26943
26976
  }
26944
26977
  return outputs;
26945
26978
  }
@@ -27139,6 +27172,185 @@ var init_generator36 = __esm({
27139
27172
  init_generator35();
27140
27173
  }
27141
27174
  });
27175
+
27176
+ // src/utils/output/color.ts
27177
+ function noColorRequested() {
27178
+ const value = process.env.NO_COLOR;
27179
+ return value !== void 0 && value !== "";
27180
+ }
27181
+ function forceColorRequested() {
27182
+ const value = process.env.FORCE_COLOR;
27183
+ if (value === void 0) return void 0;
27184
+ return value !== "0" && value !== "false";
27185
+ }
27186
+ function colorEnabled(stream = process.stdout) {
27187
+ const forced = forceColorRequested();
27188
+ if (forced !== void 0) return forced;
27189
+ if (noColorRequested()) return false;
27190
+ return stream.isTTY === true;
27191
+ }
27192
+ var init_color = __esm({
27193
+ "src/utils/output/color.ts"() {
27194
+ }
27195
+ });
27196
+
27197
+ // src/utils/output/logger.ts
27198
+ function outStream() {
27199
+ return stdoutRedirectedToStderr ? process.stderr : process.stdout;
27200
+ }
27201
+ function out(text) {
27202
+ outStream().write(text);
27203
+ }
27204
+ function c(code, text, stream) {
27205
+ return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
27206
+ }
27207
+ var C, muted, stdoutRedirectedToStderr, logger;
27208
+ var init_logger = __esm({
27209
+ "src/utils/output/logger.ts"() {
27210
+ init_color();
27211
+ C = {
27212
+ green: "\x1B[32m",
27213
+ red: "\x1B[31m",
27214
+ yellow: "\x1B[33m",
27215
+ cyan: "\x1B[36m",
27216
+ reset: "\x1B[0m"
27217
+ };
27218
+ muted = false;
27219
+ stdoutRedirectedToStderr = false;
27220
+ logger = {
27221
+ info(msg) {
27222
+ if (muted) return;
27223
+ out(c(C.cyan, msg, outStream()) + "\n");
27224
+ },
27225
+ warn(msg) {
27226
+ if (muted) return;
27227
+ process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
27228
+ },
27229
+ error(msg) {
27230
+ if (muted) return;
27231
+ process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
27232
+ },
27233
+ success(msg) {
27234
+ if (muted) return;
27235
+ out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
27236
+ },
27237
+ debug(msg) {
27238
+ if (muted) return;
27239
+ if (process.env.AGENTSMESH_DEBUG === "1") {
27240
+ out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
27241
+ }
27242
+ }
27243
+ };
27244
+ }
27245
+ });
27246
+
27247
+ // src/targets/windsurf/rule-globs.ts
27248
+ function splitTopLevelCommas(value) {
27249
+ const parts = [];
27250
+ let depth = 0;
27251
+ let current = "";
27252
+ for (const char of value) {
27253
+ if (char === "{") depth++;
27254
+ else if (char === "}") depth = Math.max(0, depth - 1);
27255
+ if (char === "," && depth === 0) {
27256
+ parts.push(current);
27257
+ current = "";
27258
+ } else {
27259
+ current += char;
27260
+ }
27261
+ }
27262
+ parts.push(current);
27263
+ return parts.map((part) => part.trim()).filter(Boolean);
27264
+ }
27265
+ function parseWindsurfGlobs(value) {
27266
+ if (typeof value === "string") return splitTopLevelCommas(value);
27267
+ return Array.isArray(value) ? toToolsArray(value) : [];
27268
+ }
27269
+ function quoteWindsurfGlobValues(content) {
27270
+ const lines = content.split("\n");
27271
+ if (lines[0]?.trim() !== "---") return content;
27272
+ for (let i = 1; i < lines.length; i++) {
27273
+ if (lines[i].trim() === "---") break;
27274
+ const match = UNQUOTED_GLOBS_LINE.exec(lines[i]);
27275
+ if (match !== null) lines[i] = `${match[1]}${JSON.stringify(match[2])}${match[3]}`;
27276
+ }
27277
+ return lines.join("\n");
27278
+ }
27279
+ var UNQUOTED_GLOBS_LINE;
27280
+ var init_rule_globs = __esm({
27281
+ "src/targets/windsurf/rule-globs.ts"() {
27282
+ init_shared_import_helpers();
27283
+ UNQUOTED_GLOBS_LINE = /^(\s*globs?\s*:[ \t]*)([^\s"'[{|>#][^\r\n]*?)[ \t]*(\r?)$/;
27284
+ }
27285
+ });
27286
+ async function windsurfRuleBodies(projectRoot) {
27287
+ const files = await readDirRecursiveNoSymlinks(join(projectRoot, WINDSURF_RULES_DIR));
27288
+ const bodies = /* @__PURE__ */ new Set();
27289
+ for (const file of files.filter((path) => path.endsWith(".md"))) {
27290
+ const content = await readFileSafe(file);
27291
+ if (content !== null) bodies.add(bodyKey(splitFrontmatter(content)?.body ?? content));
27292
+ }
27293
+ return bodies;
27294
+ }
27295
+ async function importWindsurfNestedAgents(projectRoot, normalize) {
27296
+ const destRulesDir = join(projectRoot, AB_RULES);
27297
+ const embedded = [];
27298
+ const ruleBodies = await windsurfRuleBodies(projectRoot);
27299
+ const results = await importFileDirectory({
27300
+ srcDir: projectRoot,
27301
+ destDir: destRulesDir,
27302
+ extensions: ["AGENTS.md"],
27303
+ fromTool: "windsurf",
27304
+ normalize,
27305
+ mapEntry: async ({ srcPath, content, normalizeTo }) => {
27306
+ const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
27307
+ if (!relDir || relDir === "." || basename(srcPath) !== "AGENTS.md") return null;
27308
+ const ruleName2 = relDir.replace(/\//g, "-");
27309
+ if (!shouldImportScopedAgentsRule(relDir)) {
27310
+ await removePathIfExists(join(destRulesDir, `${ruleName2}.md`));
27311
+ return null;
27312
+ }
27313
+ const ownText = await splitNestedAgentsFile(
27314
+ {
27315
+ content,
27316
+ projectRoot,
27317
+ rulesDir: AB_RULES,
27318
+ sourcePath: srcPath,
27319
+ fromTool: "windsurf",
27320
+ normalize
27321
+ },
27322
+ embedded
27323
+ );
27324
+ if (ownText === null || ruleBodies.has(bodyKey(ownText))) return null;
27325
+ const destPath = join(destRulesDir, `${ruleName2}.md`);
27326
+ return {
27327
+ destPath,
27328
+ toPath: `${AB_RULES}/${ruleName2}.md`,
27329
+ feature: "rules",
27330
+ content: await serializeImportedRuleWithFallback(
27331
+ destPath,
27332
+ { root: false, globs: [`${relDir}/**`] },
27333
+ normalizeTo(destPath, ownText)
27334
+ )
27335
+ };
27336
+ }
27337
+ });
27338
+ return [...results, ...embedded];
27339
+ }
27340
+ var bodyKey;
27341
+ var init_import_nested_agents = __esm({
27342
+ "src/targets/windsurf/import-nested-agents.ts"() {
27343
+ init_canonical_paths();
27344
+ init_embedded_rules();
27345
+ init_import_metadata();
27346
+ init_import_orchestrator();
27347
+ init_scoped_agents_import();
27348
+ init_fs();
27349
+ init_markdown();
27350
+ init_constants34();
27351
+ bodyKey = (text) => text.replace(/\r\n?/g, "\n").trim();
27352
+ }
27353
+ });
27142
27354
  function toStringArray3(value) {
27143
27355
  if (Array.isArray(value)) {
27144
27356
  return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
@@ -27407,35 +27619,7 @@ async function importFromWindsurf(projectRoot, options) {
27407
27619
  }
27408
27620
  }
27409
27621
  if (layoutScope !== "global") {
27410
- results.push(
27411
- ...await importFileDirectory({
27412
- srcDir: projectRoot,
27413
- destDir: destRulesDir,
27414
- extensions: ["AGENTS.md"],
27415
- fromTool: "windsurf",
27416
- normalize,
27417
- mapEntry: async ({ srcPath, normalizeTo }) => {
27418
- const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
27419
- if (!relDir || relDir === "." || basename(srcPath) !== "AGENTS.md") return null;
27420
- const ruleName2 = relDir.replace(/\//g, "-");
27421
- if (!shouldImportScopedAgentsRule(relDir)) {
27422
- await removePathIfExists(join(destRulesDir, `${ruleName2}.md`));
27423
- return null;
27424
- }
27425
- const destPath = join(destRulesDir, `${ruleName2}.md`);
27426
- return {
27427
- destPath,
27428
- toPath: `${AB_RULES}/${ruleName2}.md`,
27429
- feature: "rules",
27430
- content: await serializeImportedRuleWithFallback(
27431
- destPath,
27432
- { root: false, globs: [`${relDir}/**`] },
27433
- normalizeTo(destPath)
27434
- )
27435
- };
27436
- }
27437
- })
27438
- );
27622
+ results.push(...await importWindsurfNestedAgents(projectRoot, normalize));
27439
27623
  }
27440
27624
  const rulesDir = join(projectRoot, WINDSURF_RULES_DIR);
27441
27625
  results.push(
@@ -27445,15 +27629,22 @@ async function importFromWindsurf(projectRoot, options) {
27445
27629
  extensions: [".md"],
27446
27630
  fromTool: "windsurf",
27447
27631
  normalize,
27448
- mapEntry: async ({ relativePath, normalizeTo }) => {
27632
+ mapEntry: async ({ relativePath, content, normalizeTo }) => {
27449
27633
  if (relativePath === "_root.md" && rootContent !== null) return null;
27450
27634
  const destPath = join(destRulesDir, relativePath);
27451
- const { frontmatter, body } = parseFrontmatter(normalizeTo(destPath));
27452
- const normalizedFrontmatter = { ...frontmatter };
27453
- if (typeof normalizedFrontmatter.glob === "string" && normalizedFrontmatter.glob.trim()) {
27454
- normalizedFrontmatter.globs = [normalizedFrontmatter.glob];
27455
- delete normalizedFrontmatter.glob;
27635
+ const sourceLabel = `${WINDSURF_RULES_DIR}/${relativePath}`;
27636
+ const parsed = tryParseFrontmatter(
27637
+ normalizeTo(destPath, quoteWindsurfGlobValues(content)),
27638
+ sourceLabel
27639
+ );
27640
+ if (!parsed.ok) {
27641
+ logger.warn(`Skipping ${sourceLabel}: ${parsed.error.message}`);
27642
+ return null;
27456
27643
  }
27644
+ const { frontmatter, body } = parsed.value;
27645
+ const { glob, ...normalizedFrontmatter } = frontmatter;
27646
+ const globs = parseWindsurfGlobs(frontmatter.globs ?? glob);
27647
+ if (globs.length > 0) normalizedFrontmatter.globs = globs;
27457
27648
  return {
27458
27649
  destPath,
27459
27650
  toPath: `${AB_RULES}/${relativePath}`,
@@ -27504,9 +27695,11 @@ var init_importer32 = __esm({
27504
27695
  init_import_rewriter();
27505
27696
  init_fs();
27506
27697
  init_markdown();
27698
+ init_logger();
27699
+ init_rule_globs();
27507
27700
  init_import_metadata();
27508
27701
  init_import_orchestrator();
27509
- init_scoped_agents_import();
27702
+ init_import_nested_agents();
27510
27703
  init_constants34();
27511
27704
  init_importer_workflows();
27512
27705
  init_skills_adapter5();
@@ -27611,12 +27804,6 @@ var init_lint31 = __esm({
27611
27804
  });
27612
27805
 
27613
27806
  // src/targets/windsurf/index.ts
27614
- function directoryScopedRuleDir2(globs) {
27615
- if (globs.length === 0) return null;
27616
- const dirs = globs.map((glob) => glob.split("/")[0] ?? "").filter((segment) => /^[A-Za-z0-9._-]+$/.test(segment));
27617
- if (dirs.length !== globs.length) return null;
27618
- return dirs.every((dir) => dir === dirs[0]) ? dirs[0] : null;
27619
- }
27620
27807
  var target32, project24, globalLayout30, globalCapabilities25, descriptor32;
27621
27808
  var init_windsurf2 = __esm({
27622
27809
  "src/targets/windsurf/index.ts"() {
@@ -27648,9 +27835,7 @@ var init_windsurf2 = __esm({
27648
27835
  project24 = {
27649
27836
  rootInstructionPath: WINDSURF_AGENTS_MD,
27650
27837
  extraRuleOutputPaths(rule) {
27651
- if (rule.root) return [WINDSURF_AGENTS_MD];
27652
- const dir = directoryScopedRuleDir2(rule.globs);
27653
- return dir !== null ? [`${dir}/AGENTS.md`] : [];
27838
+ return rule.root ? [WINDSURF_AGENTS_MD] : [];
27654
27839
  },
27655
27840
  skillDir: WINDSURF_SKILLS_DIR,
27656
27841
  managedOutputs: {
@@ -29259,7 +29444,6 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
29259
29444
  const artifactCache = /* @__PURE__ */ new Map();
29260
29445
  const sourceCache = /* @__PURE__ */ new Map();
29261
29446
  return results.map((result2) => {
29262
- if (skipPaths?.has(result2.path)) return result2;
29263
29447
  const smKey = sourceMapCacheKey(result2.target, activeTargets);
29264
29448
  const sourceMap = sourceCache.get(smKey) ?? (() => {
29265
29449
  const built = buildOutputSourceMap(result2.target, canonical, config, scope, activeTargets);
@@ -29268,9 +29452,10 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
29268
29452
  })();
29269
29453
  const sourceFile = sourceMap.get(result2.path);
29270
29454
  if (!sourceFile) return result2;
29455
+ const shared = skipPaths?.has(result2.path) === true;
29271
29456
  const artifactMapTarget = artifactMapTargetForResult(result2, scope, activeTargets);
29272
29457
  const cacheKey = artifactCacheKey(result2, scope, activeTargets);
29273
- const artifactMap = artifactCache.get(cacheKey) ?? (() => {
29458
+ const artifactMap = shared ? /* @__PURE__ */ new Map() : artifactCache.get(cacheKey) ?? (() => {
29274
29459
  const built = buildArtifactPathMap(
29275
29460
  artifactMapTarget,
29276
29461
  canonical,
@@ -29291,6 +29476,7 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
29291
29476
  pathExists: (absolutePath) => plannedPaths.has(absolutePath) || existsSync(absolutePath),
29292
29477
  explicitCurrentDirLinks: true,
29293
29478
  rewriteBarePathTokens: true,
29479
+ markdownLinksOnly: shared,
29294
29480
  scope,
29295
29481
  pathIsDirectory: (absolutePath) => {
29296
29482
  try {
@@ -29308,62 +29494,7 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
29308
29494
  init_path_helpers();
29309
29495
  init_link_rebaser_helpers();
29310
29496
  init_protected_ranges();
29311
-
29312
- // src/utils/output/color.ts
29313
- function noColorRequested() {
29314
- const value = process.env.NO_COLOR;
29315
- return value !== void 0 && value !== "";
29316
- }
29317
- function forceColorRequested() {
29318
- const value = process.env.FORCE_COLOR;
29319
- if (value === void 0) return void 0;
29320
- return value !== "0" && value !== "false";
29321
- }
29322
- function colorEnabled(stream = process.stdout) {
29323
- const forced = forceColorRequested();
29324
- if (forced !== void 0) return forced;
29325
- if (noColorRequested()) return false;
29326
- return stream.isTTY === true;
29327
- }
29328
-
29329
- // src/utils/output/logger.ts
29330
- var C = {
29331
- green: "\x1B[32m",
29332
- red: "\x1B[31m",
29333
- yellow: "\x1B[33m",
29334
- cyan: "\x1B[36m",
29335
- reset: "\x1B[0m"
29336
- };
29337
- function outStream() {
29338
- return process.stdout;
29339
- }
29340
- function out(text) {
29341
- outStream().write(text);
29342
- }
29343
- function c(code, text, stream) {
29344
- return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
29345
- }
29346
- var logger = {
29347
- info(msg) {
29348
- out(c(C.cyan, msg, outStream()) + "\n");
29349
- },
29350
- warn(msg) {
29351
- process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
29352
- },
29353
- error(msg) {
29354
- process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
29355
- },
29356
- success(msg) {
29357
- out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
29358
- },
29359
- debug(msg) {
29360
- if (process.env.AGENTSMESH_DEBUG === "1") {
29361
- out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
29362
- }
29363
- }
29364
- };
29365
-
29366
- // src/core/reference/validate-generated-markdown-links.ts
29497
+ init_logger();
29367
29498
  var INLINE_MD_LINK = /!?\[[^\]]*\]\(([^)]+)\)/g;
29368
29499
  var REF_LINK_DEF = /^\s*\[(?!\^)[^\]\n]+\]:\s*(?:<([^>\n]*)>|(\S+))/gm;
29369
29500
  function isMarkdownLikeOutput(relativePath) {
@@ -29595,6 +29726,7 @@ init_fs_text_encoding();
29595
29726
 
29596
29727
  // src/core/generate/collision-agents.ts
29597
29728
  init_target_ids();
29729
+ init_logger();
29598
29730
  var AGENTS_SUFFIX = "AGENTS.md";
29599
29731
  var OPTIONAL_AGENTS_BLOCKS = [
29600
29732
  /<!-- agentsmesh:embedded-rules:start -->[\s\S]*?<!-- agentsmesh:embedded-rules:end -->\n*/g
@@ -29955,6 +30087,7 @@ init_errors();
29955
30087
 
29956
30088
  // src/config/core/loader.ts
29957
30089
  init_fs();
30090
+ init_logger();
29958
30091
  init_errors();
29959
30092
 
29960
30093
  // src/config/core/schema.ts
@@ -30782,6 +30915,7 @@ async function resolveExtendPaths(config, configDir, options = {}) {
30782
30915
  }
30783
30916
 
30784
30917
  // src/canonical/features/empty-file.ts
30918
+ init_logger();
30785
30919
  function isEmptyCanonicalFile(content, path) {
30786
30920
  if (content.trim() !== "") return false;
30787
30921
  logger.warn(`Skipping empty canonical file ${path.replaceAll("\\", "/")}`);
@@ -30883,6 +31017,9 @@ function assertNoBasenameCollisions(feature, paths2, stripExt) {
30883
31017
  seen.set(key, { path: p, slug });
30884
31018
  }
30885
31019
  }
31020
+
31021
+ // src/canonical/features/unrecognized-files-warning.ts
31022
+ init_logger();
30886
31023
  var ALTERNATE_RESOURCE_FORMATS = /* @__PURE__ */ new Set([".toml", ".yaml", ".yml", ".json"]);
30887
31024
  function warnIfUnrecognizedResourceFormats(featureLabel, dir, allFiles, parsedFiles, opts = {}) {
30888
31025
  if (allFiles.length === 0) return;
@@ -31091,6 +31228,14 @@ async function readContent(path) {
31091
31228
  return c2 ?? "";
31092
31229
  }
31093
31230
  var SKILL_FILE = "SKILL.md";
31231
+ async function readSkillFile(skillPath) {
31232
+ try {
31233
+ if ((await lstat(skillPath)).isSymbolicLink()) return null;
31234
+ } catch {
31235
+ return null;
31236
+ }
31237
+ return readFileSafe(skillPath);
31238
+ }
31094
31239
  var DOC_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".rst", ".txt"]);
31095
31240
  function isMarkdownLikeDoc(name) {
31096
31241
  const dot = name.lastIndexOf(".");
@@ -31120,7 +31265,7 @@ async function listSupportingFiles(skillDir) {
31120
31265
  }
31121
31266
  async function parseSkillDirectory(skillDir, opts = {}) {
31122
31267
  const skillPath = join(skillDir, SKILL_FILE);
31123
- const content = await readFileSafe(skillPath);
31268
+ const content = await readSkillFile(skillPath);
31124
31269
  if (!content) return null;
31125
31270
  const parsed = parseOrSkipFrontmatter(content, skillPath, opts.onParseError);
31126
31271
  if (!parsed) return null;
@@ -31151,7 +31296,7 @@ async function parseSkills(skillsDir, opts = {}) {
31151
31296
  assertCanonicalName("skill", ent.name);
31152
31297
  const skillDir = join(skillsDir, ent.name);
31153
31298
  const skillPath = join(skillDir, SKILL_FILE);
31154
- const content = await readFileSafe(skillPath);
31299
+ const content = await readSkillFile(skillPath);
31155
31300
  if (!content) continue;
31156
31301
  const parsed = parseOrSkipFrontmatter(content, skillPath, opts.onParseError);
31157
31302
  if (!parsed) continue;
@@ -31181,6 +31326,9 @@ function ensureStringArray(val) {
31181
31326
  async function parsePermissions(permissionsPath, onParseError) {
31182
31327
  const content = await readFileSafe(permissionsPath);
31183
31328
  if (content === null) return null;
31329
+ return parsePermissionsContent(content, permissionsPath, onParseError);
31330
+ }
31331
+ function parsePermissionsContent(content, permissionsPath, onParseError) {
31184
31332
  if (!content.trim()) return { allow: [], deny: [], ask: [] };
31185
31333
  let parsed;
31186
31334
  try {
@@ -31313,6 +31461,20 @@ function hookEvents(a, b) {
31313
31461
  function hookKey(entry) {
31314
31462
  return JSON.stringify([entry.type ?? "command", entry.matcher, entry.command]);
31315
31463
  }
31464
+ function settleRootRule(merged, local, packs) {
31465
+ const roots = merged.filter((rule) => rule.root);
31466
+ const root = roots.find((rule) => local.includes(rule)) ?? roots.find((rule) => packs.includes(rule)) ?? roots[0];
31467
+ const demoted = roots.filter((rule) => rule !== root);
31468
+ return {
31469
+ rules: merged.map((rule) => demoted.includes(rule) ? { ...rule, root: false } : rule),
31470
+ root,
31471
+ demoted
31472
+ };
31473
+ }
31474
+ function demotedRootMessage(rule, root, baseDir) {
31475
+ const shown = (r) => relative(baseDir, r.source).replaceAll("\\", "/");
31476
+ return `[agentsmesh] Rule "${shown(rule)}" also says root: true, but "${shown(root)}" is the root rule (the project's own root wins over installed packs, and packs over extends), so it is used as a normal rule.`;
31477
+ }
31316
31478
 
31317
31479
  // src/config/resolve/native-format-detector.ts
31318
31480
  init_fs();
@@ -31358,6 +31520,7 @@ var KNOWN_NATIVE_PATHS = BUILTIN_TARGETS.map(
31358
31520
 
31359
31521
  // src/canonical/extends/extend-load.ts
31360
31522
  init_fs();
31523
+ init_logger();
31361
31524
 
31362
31525
  // src/canonical/extends/native-extends-importer.ts
31363
31526
  init_registry();
@@ -31984,6 +32147,9 @@ Expected one of: .agentsmesh/, ${KNOWN_NATIVE_PATHS.join(", ")}.`
31984
32147
  throw wrapped;
31985
32148
  }
31986
32149
  }
32150
+
32151
+ // src/canonical/extends/extend-pick.ts
32152
+ init_logger();
31987
32153
  function applyExtendPick(canonical, features, pick, extendName) {
31988
32154
  if (!pick) return canonical;
31989
32155
  let next = { ...canonical };
@@ -32176,6 +32342,7 @@ async function loadPacksCanonical(abDir) {
32176
32342
  }
32177
32343
 
32178
32344
  // src/canonical/extends/extends.ts
32345
+ init_logger();
32179
32346
  var FEATURE_TO_KEYS = {
32180
32347
  rules: ["rules"],
32181
32348
  commands: ["commands"],
@@ -32224,6 +32391,15 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
32224
32391
  merged = mergeCanonicalFiles(merged, packsCanonical, { hooks: "combine" });
32225
32392
  const localCanonical = await loadCanonicalFiles(canonicalDir);
32226
32393
  merged = mergeCanonicalFiles(merged, localCanonical);
32394
+ const { rules, root, demoted } = settleRootRule(
32395
+ merged.rules,
32396
+ localCanonical.rules,
32397
+ packsCanonical.rules
32398
+ );
32399
+ if (root !== void 0) {
32400
+ for (const rule of demoted) logger.warn(demotedRootMessage(rule, root, configDir));
32401
+ }
32402
+ merged = { ...merged, rules };
32227
32403
  merged = { ...merged, hooks: combineHooks(merged.hooks, packsCanonical.hooks) };
32228
32404
  return { canonical: merged, resolvedExtends };
32229
32405
  }
@@ -32231,6 +32407,7 @@ async function loadCanonicalWithExtends(config, configDir, options = {}, canonic
32231
32407
  // src/plugins/load-plugin.ts
32232
32408
  init_target_descriptor_schema();
32233
32409
  init_registry();
32410
+ init_logger();
32234
32411
  function resolveNpmSpecifier(source, projectRoot) {
32235
32412
  const pkgDir = join(projectRoot, "node_modules", source);
32236
32413
  const pkgJsonPath = join(pkgDir, "package.json");
@@ -32547,15 +32724,44 @@ var LessonsGraphSchema = z.object({
32547
32724
  function parseGraph(raw) {
32548
32725
  return LessonsGraphSchema.parse(raw);
32549
32726
  }
32550
-
32551
- // src/lessons/graph-store.ts
32552
- var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
32553
- function graphFilePath(projectRoot) {
32554
- return resolve(projectRoot, LESSONS_GRAPH_PATH);
32727
+ async function canonicalizePath(path) {
32728
+ try {
32729
+ return await realpath(path);
32730
+ } catch (error) {
32731
+ if (error.code !== "ENOENT") throw error;
32732
+ const parent = dirname(path);
32733
+ if (parent === path) return resolve(path);
32734
+ return join(await canonicalizePath(parent), basename(path));
32735
+ }
32555
32736
  }
32556
- function loadLessonsGraph(projectRoot) {
32557
- const raw = readFileSync(graphFilePath(projectRoot), "utf8");
32558
- return parseGraph(JSON.parse(stripBom(raw)));
32737
+ function isPathInside(target34, root) {
32738
+ return target34 === root || target34.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
32739
+ }
32740
+ var display = (path) => path.replaceAll("\\", "/");
32741
+ async function assertPathInsideRoot(root, target34) {
32742
+ const rootAbs = resolve(root);
32743
+ const targetAbs = resolve(target34);
32744
+ if (!isPathInside(targetAbs, rootAbs)) {
32745
+ throw new Error(`Unsafe filesystem path: ${display(target34)} is outside ${display(rootAbs)}`);
32746
+ }
32747
+ let realTarget;
32748
+ let realRoot;
32749
+ try {
32750
+ [realTarget, realRoot] = await Promise.all([
32751
+ canonicalizePath(targetAbs),
32752
+ canonicalizePath(rootAbs)
32753
+ ]);
32754
+ } catch (cause) {
32755
+ const detail = cause instanceof Error ? cause.message : String(cause);
32756
+ throw new Error(
32757
+ `Unsafe filesystem path: ${display(target34)} could not be resolved (${detail})`,
32758
+ { cause }
32759
+ );
32760
+ }
32761
+ if (isPathInside(realTarget, realRoot)) return;
32762
+ throw new Error(
32763
+ `Unsafe filesystem path: ${display(target34)} resolves to ${display(realTarget)} outside ${display(realRoot)}`
32764
+ );
32559
32765
  }
32560
32766
  var BASE_REL = ".agentsmesh/lessons";
32561
32767
  function lessonsPaths(projectRoot) {
@@ -32572,6 +32778,16 @@ function lessonsPaths(projectRoot) {
32572
32778
  function toRelPath(projectRoot, absolute) {
32573
32779
  return relative(projectRoot, absolute).split(sep).join("/");
32574
32780
  }
32781
+
32782
+ // src/lessons/graph-store.ts
32783
+ var LESSONS_GRAPH_PATH = ".agentsmesh/lessons/lessons.json";
32784
+ function graphFilePath(projectRoot) {
32785
+ return resolve(projectRoot, LESSONS_GRAPH_PATH);
32786
+ }
32787
+ function loadLessonsGraph(projectRoot) {
32788
+ const raw = readFileSync(graphFilePath(projectRoot), "utf8");
32789
+ return parseGraph(JSON.parse(stripBom(raw)));
32790
+ }
32575
32791
  var MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
32576
32792
  function runGit2(cwd, args, timeoutMs) {
32577
32793
  const r = spawnSync("git", [...args], {
@@ -34148,6 +34364,13 @@ async function hashFileForManifest(path) {
34148
34364
  }
34149
34365
  }
34150
34366
 
34367
+ // src/config/core/lock-stale-targets.ts
34368
+ function parseStaleTargets(raw) {
34369
+ if (!Array.isArray(raw)) return void 0;
34370
+ const targets = raw.filter((target34) => typeof target34 === "string");
34371
+ return targets.length > 0 ? targets : void 0;
34372
+ }
34373
+
34151
34374
  // src/config/core/lock.ts
34152
34375
  var LOCK_FILENAME = ".lock";
34153
34376
  var CANONICAL_PATTERNS = [
@@ -34189,7 +34412,8 @@ async function readLock(abDir) {
34189
34412
  extends: raw.extends && typeof raw.extends === "object" ? raw.extends : {},
34190
34413
  packs: raw.packs && typeof raw.packs === "object" ? raw.packs : {},
34191
34414
  // undefined (not {}) when absent → old-format lock; skips output check.
34192
- outputs: raw.outputs && typeof raw.outputs === "object" ? raw.outputs : void 0
34415
+ outputs: raw.outputs && typeof raw.outputs === "object" ? raw.outputs : void 0,
34416
+ staleTargets: parseStaleTargets(raw.stale_targets)
34193
34417
  };
34194
34418
  } catch {
34195
34419
  return null;
@@ -34258,47 +34482,6 @@ async function diffOutputChecksums(rootBase, lockOutputs) {
34258
34482
 
34259
34483
  // src/core/generate/stale-cleanup.ts
34260
34484
  init_fs();
34261
- async function canonicalizePath(path) {
34262
- try {
34263
- return await realpath(path);
34264
- } catch (error) {
34265
- if (error.code !== "ENOENT") throw error;
34266
- const parent = dirname(path);
34267
- if (parent === path) return resolve(path);
34268
- return join(await canonicalizePath(parent), basename(path));
34269
- }
34270
- }
34271
- function isPathInside(target34, root) {
34272
- return target34 === root || target34.startsWith(root.endsWith(sep) ? root : `${root}${sep}`);
34273
- }
34274
- var display = (path) => path.replaceAll("\\", "/");
34275
- async function assertPathInsideRoot(root, target34) {
34276
- const rootAbs = resolve(root);
34277
- const targetAbs = resolve(target34);
34278
- if (!isPathInside(targetAbs, rootAbs)) {
34279
- throw new Error(`Unsafe filesystem path: ${display(target34)} is outside ${display(rootAbs)}`);
34280
- }
34281
- let realTarget;
34282
- let realRoot;
34283
- try {
34284
- [realTarget, realRoot] = await Promise.all([
34285
- canonicalizePath(targetAbs),
34286
- canonicalizePath(rootAbs)
34287
- ]);
34288
- } catch (cause) {
34289
- const detail = cause instanceof Error ? cause.message : String(cause);
34290
- throw new Error(
34291
- `Unsafe filesystem path: ${display(target34)} could not be resolved (${detail})`,
34292
- { cause }
34293
- );
34294
- }
34295
- if (isPathInside(realTarget, realRoot)) return;
34296
- throw new Error(
34297
- `Unsafe filesystem path: ${display(target34)} resolves to ${display(realTarget)} outside ${display(realRoot)}`
34298
- );
34299
- }
34300
-
34301
- // src/core/generate/stale-cleanup.ts
34302
34485
  init_builtin_targets();
34303
34486
  init_registry();
34304
34487
  init_builtin_targets();
@@ -34413,6 +34596,7 @@ async function checkLockSync(opts) {
34413
34596
  outputsModified: [],
34414
34597
  outputsRemoved: [],
34415
34598
  outputsStale: [],
34599
+ staleTargets: [],
34416
34600
  outputsUntracked: [],
34417
34601
  outputsChecked: false
34418
34602
  };
@@ -34466,8 +34650,10 @@ async function checkLockSync(opts) {
34466
34650
  generatedOutputs: Object.keys(lock.outputs),
34467
34651
  scope
34468
34652
  }) : [];
34653
+ const configured = [...config.targets, ...config.pluginTargets ?? []];
34654
+ const staleTargets = (lock.staleTargets ?? []).filter((target34) => configured.includes(target34));
34469
34655
  const canonicalDrift = modified.length > 0 || added.length > 0 || removed.length > 0 || extendsModified.length > 0;
34470
- const outputDrift = outputsModified.length > 0 || outputsRemoved.length > 0 || outputsStale.length > 0;
34656
+ const outputDrift = outputsModified.length > 0 || outputsRemoved.length > 0 || outputsStale.length > 0 || staleTargets.length > 0;
34471
34657
  const inSync = !canonicalDrift && !outputDrift;
34472
34658
  return {
34473
34659
  inSync,
@@ -34483,6 +34669,7 @@ async function checkLockSync(opts) {
34483
34669
  outputsModified,
34484
34670
  outputsRemoved,
34485
34671
  outputsStale,
34672
+ staleTargets,
34486
34673
  outputsUntracked,
34487
34674
  outputsChecked
34488
34675
  };
@@ -34491,6 +34678,62 @@ async function checkLockSync(opts) {
34491
34678
  // src/public/engine.ts
34492
34679
  init_registry();
34493
34680
  init_target_ids();
34681
+ init_canonical_paths();
34682
+ init_fs();
34683
+ init_mcp_merge();
34684
+ var PERMISSION_LISTS = ["allow", "deny", "ask"];
34685
+ async function keepPermissions(path, before) {
34686
+ const after = await readFileSafe(path) ?? "";
34687
+ const skipBroken = () => void 0;
34688
+ const earlier = parsePermissionsContent(before, path, skipBroken);
34689
+ const now = parsePermissionsContent(after, path, skipBroken);
34690
+ if (earlier === null || now === null) return;
34691
+ const doc = parseDocument(after);
34692
+ let changed = false;
34693
+ for (const list of PERMISSION_LISTS) {
34694
+ const old = earlier[list];
34695
+ const cur = now[list];
34696
+ if (old.every((entry) => cur.includes(entry))) continue;
34697
+ doc.set(list, [...old, ...cur.filter((entry) => !old.includes(entry))]);
34698
+ changed = true;
34699
+ }
34700
+ if (changed) await writeFileAtomic(path, doc.toString());
34701
+ }
34702
+ async function keepIgnorePatterns(path, before) {
34703
+ const lines = (text) => text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
34704
+ const earlier = lines(before);
34705
+ const now = lines(await readFileSafe(path) ?? "");
34706
+ if (earlier.every((line) => now.includes(line))) return;
34707
+ const added = now.filter((line) => !earlier.includes(line));
34708
+ await writeFileAtomic(path, `${[before.trimEnd(), ...added].join("\n")}
34709
+ `);
34710
+ }
34711
+ async function keepMcpServers(path, before) {
34712
+ const earlier = parseMcpServers(before);
34713
+ const now = parseMcpServers(await readFileSafe(path));
34714
+ if (Object.keys(earlier).every((name) => Object.hasOwn(now, name))) return;
34715
+ await writeFileAtomic(path, JSON.stringify({ mcpServers: { ...earlier, ...now } }, null, 2));
34716
+ }
34717
+ var KEEPERS = [
34718
+ [AB_PERMISSIONS, keepPermissions],
34719
+ [AB_IGNORE, keepIgnorePatterns],
34720
+ [AB_MCP, keepMcpServers]
34721
+ ];
34722
+ async function runTargetImport(descriptor34, rootBase, scope) {
34723
+ const snapshots = await Promise.all(
34724
+ KEEPERS.map(async ([rel2, keep]) => {
34725
+ const path = join(rootBase, rel2);
34726
+ return { path, keep, before: await readFileSafe(path) };
34727
+ })
34728
+ );
34729
+ const results = await descriptor34.generators.importFrom(rootBase, { scope });
34730
+ for (const { path, keep, before } of snapshots) {
34731
+ if (before !== null) await keep(path, before);
34732
+ }
34733
+ return results;
34734
+ }
34735
+
34736
+ // src/public/engine.ts
34494
34737
  async function importFrom(target34, opts) {
34495
34738
  const descriptor34 = getDescriptor(target34);
34496
34739
  if (!descriptor34) {
@@ -34498,7 +34741,7 @@ async function importFrom(target34, opts) {
34498
34741
  supported: [...TARGET_IDS, ...getAllDescriptors().map((d) => d.id)]
34499
34742
  });
34500
34743
  }
34501
- return descriptor34.generators.importFrom(opts.root, { scope: opts.scope ?? "project" });
34744
+ return runTargetImport(descriptor34, opts.root, opts.scope ?? "project");
34502
34745
  }
34503
34746
  async function loadConfig2(projectRoot) {
34504
34747
  return loadConfigFromDir(projectRoot);