agentsmesh 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { stringify, parse } from 'yaml';
3
3
  import { basename, join, dirname, relative, win32, posix, sep, resolve, extname } from 'path';
4
- import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, realpath, access, mkdtemp, cp } from 'fs/promises';
4
+ import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, access, realpath, mkdtemp, cp } from 'fs/promises';
5
5
  import { setTimeout } from 'timers/promises';
6
6
  import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
7
7
  import { parse as parse$1 } from 'smol-toml';
@@ -1379,7 +1379,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
1379
1379
  toPath: `${AB_SKILLS}/${entry.name}/SKILL.md`,
1380
1380
  feature: "skills"
1381
1381
  });
1382
- const sourceFiles = await readDirRecursive(sourceSkillDir);
1382
+ const sourceFiles = await readDirRecursiveNoSymlinks(sourceSkillDir);
1383
1383
  for (const sourcePath of sourceFiles) {
1384
1384
  if (sourcePath === sourceSkillFile) continue;
1385
1385
  const relativePath = relative(sourceSkillDir, sourcePath).replace(/\\/g, "/");
@@ -2327,7 +2327,7 @@ function matchesExtension(path, extensions) {
2327
2327
  return extensions.some((extension) => path.endsWith(extension));
2328
2328
  }
2329
2329
  async function importFileDirectory(opts) {
2330
- const files = await readDirRecursive(opts.srcDir);
2330
+ const files = await readDirRecursiveNoSymlinks(opts.srcDir);
2331
2331
  const matchedFiles = files.filter((path) => matchesExtension(path, opts.extensions));
2332
2332
  const results = [];
2333
2333
  for (const srcPath of matchedFiles) {
@@ -6746,7 +6746,7 @@ var init_settings_helpers2 = __esm({
6746
6746
  async function importClaudeSkills(projectRoot, results, normalize) {
6747
6747
  const skillsBaseDir = join(projectRoot, CLAUDE_SKILLS_DIR);
6748
6748
  const destBase = join(projectRoot, CLAUDE_CANONICAL_SKILLS_DIR);
6749
- const allFiles = await readDirRecursive(skillsBaseDir);
6749
+ const allFiles = await readDirRecursiveNoSymlinks(skillsBaseDir);
6750
6750
  const skillMdFiles = allFiles.filter((f) => f.endsWith("SKILL.md"));
6751
6751
  for (const skillMdPath of skillMdFiles) {
6752
6752
  const skillDir = dirname(skillMdPath);
@@ -6765,7 +6765,7 @@ async function importClaudeSkills(projectRoot, results, normalize) {
6765
6765
  `);
6766
6766
  continue;
6767
6767
  }
6768
- const skillFiles = await readDirRecursive(skillDir);
6768
+ const skillFiles = await readDirRecursiveNoSymlinks(skillDir);
6769
6769
  for (const filePath of skillFiles) {
6770
6770
  const fileContent = await readFileSafe(filePath);
6771
6771
  if (fileContent === null) continue;
@@ -7390,7 +7390,7 @@ async function importClineRules(projectRoot, results, normalize) {
7390
7390
  feature: "rules"
7391
7391
  });
7392
7392
  } else {
7393
- const ruleFiles = await readDirRecursive(clineRulesPath);
7393
+ const ruleFiles = await readDirRecursiveNoSymlinks(clineRulesPath);
7394
7394
  const mdFiles = ruleFiles.filter((f) => f.endsWith(".md") && !f.includes("/workflows/")).sort();
7395
7395
  const first = mdFiles[0];
7396
7396
  if (first) {
@@ -7454,27 +7454,40 @@ var init_importer_rules = __esm({
7454
7454
  init_constants8();
7455
7455
  }
7456
7456
  });
7457
+ function toStringRecord2(raw) {
7458
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
7459
+ return Object.fromEntries(
7460
+ Object.entries(raw).filter((entry) => typeof entry[1] === "string")
7461
+ );
7462
+ }
7457
7463
  function mapClineServerToCanonical(raw) {
7458
7464
  if (!raw || typeof raw !== "object") return null;
7459
7465
  const obj = raw;
7460
- const command = typeof obj.command === "string" ? obj.command : "";
7461
- if (!command) return null;
7462
- const type = typeof obj.type === "string" ? obj.type : typeof obj.transportType === "string" ? obj.transportType : "stdio";
7463
- const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
7464
- const envRaw = obj.env;
7465
- const env = envRaw !== null && typeof envRaw === "object" && !Array.isArray(envRaw) ? Object.fromEntries(
7466
- Object.entries(envRaw).filter(
7467
- (entry) => typeof entry[1] === "string"
7468
- )
7469
- ) : {};
7466
+ const transport = typeof obj.type === "string" ? obj.type : void 0;
7467
+ const transportType = typeof obj.transportType === "string" ? obj.transportType : void 0;
7468
+ const env = toStringRecord2(obj.env);
7470
7469
  const description = typeof obj.description === "string" ? obj.description : void 0;
7471
- return {
7472
- ...description !== void 0 && { description },
7473
- type,
7474
- command,
7475
- args,
7476
- env
7477
- };
7470
+ const command = typeof obj.command === "string" ? obj.command : "";
7471
+ if (command) {
7472
+ const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
7473
+ return {
7474
+ ...description !== void 0 && { description },
7475
+ type: transport ?? transportType ?? "stdio",
7476
+ command,
7477
+ args,
7478
+ env
7479
+ };
7480
+ }
7481
+ if (typeof obj.url === "string") {
7482
+ return {
7483
+ ...description !== void 0 && { description },
7484
+ type: transport ?? transportType ?? "http",
7485
+ url: obj.url,
7486
+ headers: toStringRecord2(obj.headers),
7487
+ env
7488
+ };
7489
+ }
7490
+ return null;
7478
7491
  }
7479
7492
  async function importClineMcp(projectRoot, results) {
7480
7493
  const candidatePaths = [CLINE_MCP_SETTINGS, CLINE_MCP_SETTINGS_LEGACY].map(
@@ -7548,7 +7561,7 @@ var init_reserved = __esm({
7548
7561
  }
7549
7562
  });
7550
7563
  async function readNativeSkill(skillDir) {
7551
- const allFiles = await readDirRecursive(skillDir).catch(() => []);
7564
+ const allFiles = await readDirRecursiveNoSymlinks(skillDir).catch(() => []);
7552
7565
  const entries = [];
7553
7566
  for (const absPath of allFiles) {
7554
7567
  const relPath = relative(skillDir, absPath).replace(/\\/g, "/");
@@ -7614,7 +7627,7 @@ async function importFlatSkill(skillName, srcPath, content, options) {
7614
7627
  async function findDirectorySkills(skillsDir) {
7615
7628
  const skills = /* @__PURE__ */ new Map();
7616
7629
  try {
7617
- const allFiles = await readDirRecursive(skillsDir);
7630
+ const allFiles = await readDirRecursiveNoSymlinks(skillsDir);
7618
7631
  const skillMdFiles = allFiles.filter((f) => basename(f) === "SKILL.md");
7619
7632
  for (const skillMdPath of skillMdFiles) {
7620
7633
  const skillDir = dirname(skillMdPath);
@@ -7745,7 +7758,7 @@ function extractMeta(content, key) {
7745
7758
  return match?.[1]?.trim() ?? null;
7746
7759
  }
7747
7760
  async function loadHooksFromDir(dir, hooks) {
7748
- const files = await readDirRecursive(dir).catch(() => []);
7761
+ const files = await readDirRecursiveNoSymlinks(dir).catch(() => []);
7749
7762
  const shFiles = files.filter((f) => basename(f).endsWith(".sh") && dirname(f) === dir);
7750
7763
  for (const srcPath of shFiles) {
7751
7764
  const content = await readFileSafe(srcPath);
@@ -8480,7 +8493,7 @@ async function importCodexAgentsFromToml(projectRoot, results, normalize) {
8480
8493
  const agentsPath = join(projectRoot, CODEX_AGENTS_DIR);
8481
8494
  const agentsDestDir = join(projectRoot, CODEX_CANONICAL_AGENTS_DIR);
8482
8495
  try {
8483
- const agentFiles = await readDirRecursive(agentsPath);
8496
+ const agentFiles = await readDirRecursiveNoSymlinks(agentsPath);
8484
8497
  const tomlFiles = agentFiles.filter((f) => f.endsWith(".toml"));
8485
8498
  for (const srcPath of tomlFiles) {
8486
8499
  const content = await readFileSafe(srcPath);
@@ -8576,7 +8589,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize) {
8576
8589
  const results = [];
8577
8590
  const codexRulesPath = join(projectRoot, CODEX_RULES_DIR);
8578
8591
  try {
8579
- const ruleFiles = await readDirRecursive(codexRulesPath);
8592
+ const ruleFiles = await readDirRecursiveNoSymlinks(codexRulesPath);
8580
8593
  const mdFiles = ruleFiles.filter((f) => f.endsWith(".md"));
8581
8594
  for (const srcPath of mdFiles) {
8582
8595
  const content = await readFileSafe(srcPath);
@@ -8733,7 +8746,7 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
8733
8746
  }
8734
8747
  async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
8735
8748
  try {
8736
- const files = await readDirRecursive(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
8749
+ const files = await readDirRecursiveNoSymlinks(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
8737
8750
  const instructionFiles = files.filter((file) => file.endsWith(".md"));
8738
8751
  const instructionsRoot = join(projectRoot, CODEX_INSTRUCTIONS_DIR);
8739
8752
  for (const srcPath of instructionFiles) {
@@ -9179,19 +9192,31 @@ function readMcpServers(content, extension) {
9179
9192
  for (const [name, value] of Object.entries(rawServers)) {
9180
9193
  if (!value || typeof value !== "object" || Array.isArray(value)) continue;
9181
9194
  const server = value;
9182
- if (typeof server.command !== "string") continue;
9183
- servers[name] = {
9184
- type: typeof server.type === "string" ? server.type : "stdio",
9185
- command: server.command,
9186
- args: toStringArray5(server.args),
9187
- env: toStringRecord(server.env),
9188
- description: typeof server.description === "string" ? server.description : void 0
9189
- };
9195
+ const description = typeof server.description === "string" ? server.description : void 0;
9196
+ if (typeof server.command === "string") {
9197
+ servers[name] = {
9198
+ type: typeof server.type === "string" ? server.type : "stdio",
9199
+ command: server.command,
9200
+ args: toStringArray5(server.args),
9201
+ env: toStringRecord(server.env),
9202
+ description
9203
+ };
9204
+ continue;
9205
+ }
9206
+ if (typeof server.url === "string") {
9207
+ servers[name] = {
9208
+ type: typeof server.type === "string" ? server.type : "http",
9209
+ url: server.url,
9210
+ headers: toStringRecord(server.headers),
9211
+ env: toStringRecord(server.env),
9212
+ description
9213
+ };
9214
+ }
9190
9215
  }
9191
9216
  return servers;
9192
9217
  }
9193
9218
  async function importMcp2(projectRoot, results) {
9194
- const files = (await readDirRecursive(join(projectRoot, CONTINUE_MCP_DIR))).filter(
9219
+ const files = (await readDirRecursiveNoSymlinks(join(projectRoot, CONTINUE_MCP_DIR))).filter(
9195
9220
  (file) => [".json", ".yaml", ".yml"].includes(extname(file))
9196
9221
  );
9197
9222
  const merged = {};
@@ -9851,7 +9876,7 @@ function extractWrapperCommand(content) {
9851
9876
  }
9852
9877
  async function importHooks(projectRoot, results) {
9853
9878
  const hooksDir = join(projectRoot, COPILOT_HOOKS_DIR);
9854
- const allFiles = await readDirRecursive(hooksDir).catch(() => []);
9879
+ const allFiles = await readDirRecursiveNoSymlinks(hooksDir).catch(() => []);
9855
9880
  const jsonFiles = allFiles.filter((file) => file.endsWith(".json"));
9856
9881
  const hooks = {};
9857
9882
  for (const srcPath of jsonFiles) {
@@ -9887,7 +9912,7 @@ async function importHooks(projectRoot, results) {
9887
9912
  }
9888
9913
  }
9889
9914
  const legacyDir = join(projectRoot, COPILOT_LEGACY_HOOKS_DIR);
9890
- const legacyFiles = await readDirRecursive(legacyDir).catch(() => []);
9915
+ const legacyFiles = await readDirRecursiveNoSymlinks(legacyDir).catch(() => []);
9891
9916
  const shFiles = legacyFiles.filter(
9892
9917
  (file) => dirname(file) === legacyDir && /^[^-]+-\d+\.sh$/i.test(basename(file))
9893
9918
  );
@@ -9964,7 +9989,7 @@ var init_importer10 = __esm({
9964
9989
  }
9965
9990
  });
9966
9991
  async function skillNamesFromNativeSkillDir(scanRoot) {
9967
- const files = await readDirRecursive(scanRoot);
9992
+ const files = await readDirRecursiveNoSymlinks(scanRoot);
9968
9993
  const names = /* @__PURE__ */ new Set();
9969
9994
  for (const f of files) {
9970
9995
  if (basename(f) === "SKILL.md") {
@@ -9986,7 +10011,7 @@ var init_native_skill_scan = __esm({
9986
10011
  async function inferCopilotPickFromPath(repoRoot, posixPath) {
9987
10012
  const scan = join(repoRoot, ...posixPath.split("/"));
9988
10013
  if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
9989
- const files = await readDirRecursive(scan);
10014
+ const files = await readDirRecursiveNoSymlinks(scan);
9990
10015
  const commands = [
9991
10016
  ...new Set(
9992
10017
  files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
@@ -9995,7 +10020,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9995
10020
  return commands.length ? { commands } : {};
9996
10021
  }
9997
10022
  if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
9998
- const files = await readDirRecursive(scan);
10023
+ const files = await readDirRecursiveNoSymlinks(scan);
9999
10024
  const rules = [
10000
10025
  ...new Set(
10001
10026
  files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
@@ -10004,7 +10029,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
10004
10029
  return rules.length ? { rules } : {};
10005
10030
  }
10006
10031
  if (posixPath.startsWith(".github/instructions")) {
10007
- const files = await readDirRecursive(scan);
10032
+ const files = await readDirRecursiveNoSymlinks(scan);
10008
10033
  const names = /* @__PURE__ */ new Set();
10009
10034
  for (const f of files) {
10010
10035
  const b = basename(f);
@@ -10020,7 +10045,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
10020
10045
  return skills.length ? { skills } : {};
10021
10046
  }
10022
10047
  if (posixPath.startsWith(".github/agents")) {
10023
- const files = await readDirRecursive(scan);
10048
+ const files = await readDirRecursiveNoSymlinks(scan);
10024
10049
  const agents = [
10025
10050
  ...new Set(
10026
10051
  files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
@@ -11569,7 +11594,7 @@ async function importSkills3(projectRoot, results, normalize, skillsRelDir = CUR
11569
11594
  for (const [skillName, skillDir] of directorySkills) {
11570
11595
  await importDirectorySkill(skillName, skillDir, options);
11571
11596
  }
11572
- const allFiles = await readDirRecursive(skillsDir).catch(() => []);
11597
+ const allFiles = await readDirRecursiveNoSymlinks(skillsDir).catch(() => []);
11573
11598
  const mdFiles = allFiles.filter((f) => f.endsWith(".md"));
11574
11599
  const handledPaths = new Set(
11575
11600
  Array.from(directorySkills.values()).flatMap(
@@ -11607,11 +11632,11 @@ async function hasGlobalCursorArtifacts(projectRoot) {
11607
11632
  const stat8 = await readFileSafe(p);
11608
11633
  if (stat8 !== null && stat8.trim() !== "") return true;
11609
11634
  }
11610
- const skillFiles = await readDirRecursive(join(projectRoot, CURSOR_SKILLS_DIR));
11635
+ const skillFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_SKILLS_DIR));
11611
11636
  if (skillFiles.some((f) => f.endsWith(".md"))) return true;
11612
- const agentFiles = await readDirRecursive(join(projectRoot, CURSOR_AGENTS_DIR));
11637
+ const agentFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_AGENTS_DIR));
11613
11638
  if (agentFiles.some((f) => f.endsWith(".md"))) return true;
11614
- const commandFiles = await readDirRecursive(join(projectRoot, CURSOR_COMMANDS_DIR));
11639
+ const commandFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_COMMANDS_DIR));
11615
11640
  if (commandFiles.some((f) => f.endsWith(".md"))) return true;
11616
11641
  return false;
11617
11642
  }
@@ -13199,7 +13224,7 @@ async function importGeminiPolicies(projectRoot) {
13199
13224
  const policiesDir = join(projectRoot, GEMINI_POLICIES_DIR);
13200
13225
  let policyFiles;
13201
13226
  try {
13202
- policyFiles = await readDirRecursive(policiesDir);
13227
+ policyFiles = await readDirRecursiveNoSymlinks(policiesDir);
13203
13228
  } catch {
13204
13229
  return results;
13205
13230
  }
@@ -13298,7 +13323,7 @@ var init_importer_strip = __esm({
13298
13323
  });
13299
13324
  async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13300
13325
  const geminiSkillsPath = join(projectRoot, GEMINI_SKILLS_DIR);
13301
- const skillDirs = await readDirRecursive(geminiSkillsPath);
13326
+ const skillDirs = await readDirRecursiveNoSymlinks(geminiSkillsPath);
13302
13327
  const skillMdFiles = skillDirs.filter((f) => basename(f) === "SKILL.md");
13303
13328
  for (const srcPath of skillMdFiles) {
13304
13329
  const content = await readFileSafe(srcPath);
@@ -13337,7 +13362,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13337
13362
  toPath: `${GEMINI_CANONICAL_SKILLS_DIR}/${skillName}/SKILL.md`,
13338
13363
  feature: "skills"
13339
13364
  });
13340
- const allSkillFiles = await readDirRecursive(dirname(srcPath));
13365
+ const allSkillFiles = await readDirRecursiveNoSymlinks(dirname(srcPath));
13341
13366
  for (const absPath of allSkillFiles) {
13342
13367
  if (absPath === srcPath) continue;
13343
13368
  const supportContent = await readFileSafe(absPath);
@@ -13356,7 +13381,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
13356
13381
  }
13357
13382
  const geminiAgentsPath = join(projectRoot, GEMINI_AGENTS_DIR);
13358
13383
  try {
13359
- const agentFiles = await readDirRecursive(geminiAgentsPath);
13384
+ const agentFiles = await readDirRecursiveNoSymlinks(geminiAgentsPath);
13360
13385
  const agentMdFiles = agentFiles.filter((f) => f.endsWith(".md"));
13361
13386
  for (const srcPath of agentMdFiles) {
13362
13387
  const content = await readFileSafe(srcPath);
@@ -13489,7 +13514,7 @@ function isUnderGeminiCommands(pathInRepoPosix) {
13489
13514
  async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
13490
13515
  const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
13491
13516
  const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
13492
- const files = await readDirRecursive(scanDir);
13517
+ const files = await readDirRecursiveNoSymlinks(scanDir);
13493
13518
  const names = [];
13494
13519
  for (const f of files) {
13495
13520
  if (!/\.(toml|md)$/i.test(f)) continue;
@@ -15701,7 +15726,7 @@ async function importNonRootRules(projectRoot, results, normalize) {
15701
15726
  }
15702
15727
  async function importHooks2(projectRoot, results) {
15703
15728
  const hooks = {};
15704
- for (const absPath of await readDirRecursive(join(projectRoot, KIRO_HOOKS_DIR))) {
15729
+ for (const absPath of await readDirRecursiveNoSymlinks(join(projectRoot, KIRO_HOOKS_DIR))) {
15705
15730
  if (!absPath.endsWith(".kiro.hook")) continue;
15706
15731
  const parsed = parseKiroHookFile(await readFileSafe(absPath) ?? "");
15707
15732
  if (!parsed) continue;
@@ -16053,7 +16078,7 @@ var init_generator24 = __esm({
16053
16078
  init_constants19();
16054
16079
  }
16055
16080
  });
16056
- function toStringRecord2(value) {
16081
+ function toStringRecord3(value) {
16057
16082
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
16058
16083
  const out2 = {};
16059
16084
  for (const [k, v] of Object.entries(value)) {
@@ -16079,8 +16104,8 @@ function parseOpenCodeMcp(content) {
16079
16104
  out2[name] = {
16080
16105
  type: "url",
16081
16106
  url: entry.url,
16082
- headers: toStringRecord2(entry.headers),
16083
- env: toStringRecord2(entry.environment),
16107
+ headers: toStringRecord3(entry.headers),
16108
+ env: toStringRecord3(entry.environment),
16084
16109
  ...typeof entry.description === "string" ? { description: entry.description } : {}
16085
16110
  };
16086
16111
  continue;
@@ -16094,7 +16119,7 @@ function parseOpenCodeMcp(content) {
16094
16119
  type: "stdio",
16095
16120
  command,
16096
16121
  args,
16097
- env: toStringRecord2(entry.environment),
16122
+ env: toStringRecord3(entry.environment),
16098
16123
  ...typeof entry.description === "string" ? { description: entry.description } : {}
16099
16124
  };
16100
16125
  }
@@ -18916,7 +18941,7 @@ function toStringArray8(value) {
18916
18941
  }
18917
18942
  async function importWorkflows(projectRoot, results, normalize) {
18918
18943
  const workflowsDir = join(projectRoot, WINDSURF_WORKFLOWS_DIR);
18919
- const workflowFiles = await readDirRecursive(workflowsDir);
18944
+ const workflowFiles = await readDirRecursiveNoSymlinks(workflowsDir);
18920
18945
  const workflowMdFiles = workflowFiles.filter((f) => f.endsWith(".md"));
18921
18946
  const destCommandsDir = join(projectRoot, WINDSURF_CANONICAL_COMMANDS_DIR);
18922
18947
  for (const srcPath of workflowMdFiles) {
@@ -20758,8 +20783,22 @@ function resolveOutputCollisions(results) {
20758
20783
  }
20759
20784
  deduped[existingIdx] = mergeDuplicateMetadata(existing, result);
20760
20785
  }
20786
+ assertNoCaseOnlyPathCollisions(deduped);
20761
20787
  return deduped;
20762
20788
  }
20789
+ function assertNoCaseOnlyPathCollisions(results) {
20790
+ const byLower = /* @__PURE__ */ new Map();
20791
+ for (const result of results) {
20792
+ const key = result.path.toLowerCase();
20793
+ const prior = byLower.get(key);
20794
+ if (prior !== void 0 && prior.path !== result.path) {
20795
+ throw new Error(
20796
+ `Case-only path collision: "${prior.path}" (${prior.target}) and "${result.path}" (${result.target}) resolve to the same file on case-insensitive filesystems (Windows/macOS). Rename one canonical source.`
20797
+ );
20798
+ }
20799
+ byLower.set(key, result);
20800
+ }
20801
+ }
20763
20802
  function refreshResultStatus(result) {
20764
20803
  const status = result.currentContent === void 0 ? "created" : result.currentContent !== result.content ? "updated" : "unchanged";
20765
20804
  return result.status === status ? result : { ...result, status };
@@ -21314,6 +21353,100 @@ function redactUrlSecrets(message) {
21314
21353
  }
21315
21354
  );
21316
21355
  }
21356
+ function gitProtocolOptIns() {
21357
+ const on = (v) => v === "1" || v === "true";
21358
+ return {
21359
+ http: on(process.env.AGENTSMESH_ALLOW_INSECURE_GIT),
21360
+ file: on(process.env.AGENTSMESH_ALLOW_LOCAL_GIT)
21361
+ };
21362
+ }
21363
+ function isAllowedGitProtocol(url) {
21364
+ let parsed;
21365
+ try {
21366
+ parsed = new URL(url);
21367
+ } catch {
21368
+ return false;
21369
+ }
21370
+ const { http, file } = gitProtocolOptIns();
21371
+ const allowed = ["https:", "ssh:"];
21372
+ if (http) allowed.push("http:");
21373
+ if (file) allowed.push("file:");
21374
+ return allowed.includes(parsed.protocol);
21375
+ }
21376
+ function gitAllowProtocolEnv() {
21377
+ const { http, file } = gitProtocolOptIns();
21378
+ const protos = ["https", "ssh"];
21379
+ if (http) protos.push("http");
21380
+ if (file) protos.push("file");
21381
+ return protos.join(":");
21382
+ }
21383
+ function assertAllowedGitUrl(url) {
21384
+ if (isAllowedGitProtocol(url)) return;
21385
+ throw new Error(
21386
+ `agentsmesh refuses a git remote with a disallowed transport: "${redactUrlSecrets(url)}". Allowed: https, ssh. Set AGENTSMESH_ALLOW_INSECURE_GIT=1 to permit http, AGENTSMESH_ALLOW_LOCAL_GIT=1 to permit file.`
21387
+ );
21388
+ }
21389
+ function splitSourceRef(source, prefix, defaultRef) {
21390
+ if (!source.startsWith(prefix)) return null;
21391
+ const rest = source.slice(prefix.length).trim();
21392
+ if (!rest) return null;
21393
+ const refIdx = rest.lastIndexOf("@");
21394
+ if (refIdx < 0) return [rest, defaultRef];
21395
+ const slug = rest.slice(0, refIdx).trim();
21396
+ const ref = rest.slice(refIdx + 1).trim();
21397
+ if (!slug || !ref) return null;
21398
+ return [slug, ref];
21399
+ }
21400
+ function parseGithubSource(source) {
21401
+ const parts = splitSourceRef(source, "github:", "latest");
21402
+ if (!parts) return null;
21403
+ const [slug, tag] = parts;
21404
+ const slash = slug.indexOf("/");
21405
+ if (slash < 0) return null;
21406
+ const org = slug.slice(0, slash).trim();
21407
+ const repo = slug.slice(slash + 1).trim();
21408
+ if (!org || !repo || !tag) return null;
21409
+ return { org, repo, tag };
21410
+ }
21411
+ function parseGitlabSource(source) {
21412
+ const parts = splitSourceRef(source, "gitlab:");
21413
+ if (!parts) return null;
21414
+ const [slug, ref] = parts;
21415
+ const slash = slug.lastIndexOf("/");
21416
+ if (slash < 0) return null;
21417
+ const namespace = slug.slice(0, slash).trim();
21418
+ const project31 = slug.slice(slash + 1).trim();
21419
+ if (!namespace || !project31) return null;
21420
+ return {
21421
+ namespace,
21422
+ project: project31,
21423
+ ref,
21424
+ cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
21425
+ };
21426
+ }
21427
+ function parseGitSource(source) {
21428
+ if (!source.startsWith("git+")) return null;
21429
+ const rest = source.slice(4).trim();
21430
+ if (!rest) return null;
21431
+ const hashIdx = rest.lastIndexOf("#");
21432
+ const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
21433
+ const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
21434
+ if (!url || hashIdx >= 0 && !ref) return null;
21435
+ if (!isAllowedGitProtocol(url)) return null;
21436
+ return { url, ref };
21437
+ }
21438
+ function parseRemoteSource(source) {
21439
+ const github = parseGithubSource(source);
21440
+ if (github) return { kind: "github", ...github };
21441
+ const gitlab = parseGitlabSource(source);
21442
+ if (gitlab) return { kind: "gitlab", ...gitlab };
21443
+ const git = parseGitSource(source);
21444
+ if (git) return { kind: "git", ...git };
21445
+ return null;
21446
+ }
21447
+ function isSupportedRemoteSource(source) {
21448
+ return parseRemoteSource(source) !== null;
21449
+ }
21317
21450
 
21318
21451
  // src/config/remote/git-remote.ts
21319
21452
  var execFileAsync = promisify(execFile);
@@ -21384,7 +21517,8 @@ function resolveCloneUrl(parsed) {
21384
21517
  }
21385
21518
  async function cloneRepo(cloneUrl, repoDir) {
21386
21519
  ensureNotFlag(cloneUrl, "clone-url");
21387
- await runGit(["clone", cloneUrl, repoDir]);
21520
+ assertAllowedGitUrl(cloneUrl);
21521
+ await runGit(["clone", "-c", "core.symlinks=false", cloneUrl, repoDir]);
21388
21522
  }
21389
21523
  async function checkoutRef(repoDir, ref) {
21390
21524
  ensureNotFlag(ref, "ref");
@@ -21398,7 +21532,10 @@ async function runGit(args, cwd) {
21398
21532
  cwd,
21399
21533
  env: {
21400
21534
  ...process.env,
21401
- GIT_TERMINAL_PROMPT: "0"
21535
+ GIT_TERMINAL_PROMPT: "0",
21536
+ // Constrain transports git may use (incl. mid-clone redirects/insteadOf)
21537
+ // to the same allowlist the clone URL itself passed.
21538
+ GIT_ALLOW_PROTOCOL: gitAllowProtocolEnv()
21402
21539
  }
21403
21540
  });
21404
21541
  return stdout.trim();
@@ -21586,80 +21723,6 @@ async function fetchGithubDefaultBranch(parsed, extendName, options, cacheDir, b
21586
21723
  }
21587
21724
  throw lastError instanceof Error ? lastError : new Error("Failed to clone GitHub default branch");
21588
21725
  }
21589
- function splitSourceRef(source, prefix, defaultRef) {
21590
- if (!source.startsWith(prefix)) return null;
21591
- const rest = source.slice(prefix.length).trim();
21592
- if (!rest) return null;
21593
- const refIdx = rest.lastIndexOf("@");
21594
- if (refIdx < 0) return [rest, defaultRef];
21595
- const slug = rest.slice(0, refIdx).trim();
21596
- const ref = rest.slice(refIdx + 1).trim();
21597
- if (!slug || !ref) return null;
21598
- return [slug, ref];
21599
- }
21600
- function parseGithubSource(source) {
21601
- const parts = splitSourceRef(source, "github:", "latest");
21602
- if (!parts) return null;
21603
- const [slug, tag] = parts;
21604
- const slash = slug.indexOf("/");
21605
- if (slash < 0) return null;
21606
- const org = slug.slice(0, slash).trim();
21607
- const repo = slug.slice(slash + 1).trim();
21608
- if (!org || !repo || !tag) return null;
21609
- return { org, repo, tag };
21610
- }
21611
- function parseGitlabSource(source) {
21612
- const parts = splitSourceRef(source, "gitlab:");
21613
- if (!parts) return null;
21614
- const [slug, ref] = parts;
21615
- const slash = slug.lastIndexOf("/");
21616
- if (slash < 0) return null;
21617
- const namespace = slug.slice(0, slash).trim();
21618
- const project31 = slug.slice(slash + 1).trim();
21619
- if (!namespace || !project31) return null;
21620
- return {
21621
- namespace,
21622
- project: project31,
21623
- ref,
21624
- cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
21625
- };
21626
- }
21627
- function parseGitSource(source) {
21628
- if (!source.startsWith("git+")) return null;
21629
- const rest = source.slice(4).trim();
21630
- if (!rest) return null;
21631
- const hashIdx = rest.lastIndexOf("#");
21632
- const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
21633
- const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
21634
- if (!url || hashIdx >= 0 && !ref) return null;
21635
- let parsedUrl;
21636
- try {
21637
- parsedUrl = new URL(url);
21638
- } catch {
21639
- return null;
21640
- }
21641
- const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
21642
- const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
21643
- const allowed = ["https:", "ssh:"];
21644
- if (allowInsecure) allowed.push("http:");
21645
- if (allowLocalGit) allowed.push("file:");
21646
- if (!allowed.includes(parsedUrl.protocol)) {
21647
- return null;
21648
- }
21649
- return { url, ref };
21650
- }
21651
- function parseRemoteSource(source) {
21652
- const github = parseGithubSource(source);
21653
- if (github) return { kind: "github", ...github };
21654
- const gitlab = parseGitlabSource(source);
21655
- if (gitlab) return { kind: "gitlab", ...gitlab };
21656
- const git = parseGitSource(source);
21657
- if (git) return { kind: "git", ...git };
21658
- return null;
21659
- }
21660
- function isSupportedRemoteSource(source) {
21661
- return parseRemoteSource(source) !== null;
21662
- }
21663
21726
  async function sweepStaleCache(cacheDir, maxAgeMs) {
21664
21727
  const dir = cacheDir ?? getCacheDir();
21665
21728
  const threshold = Number(process.env.AGENTSMESH_CACHE_MAX_AGE_DAYS ?? 30) * 864e5;
@@ -21893,15 +21956,17 @@ function assertNoBasenameCollisions(feature, paths, stripExt) {
21893
21956
  const idx = Math.max(fwdIdx, bckIdx);
21894
21957
  const base = idx === -1 ? p : p.slice(idx + 1);
21895
21958
  const slug = base.endsWith(stripExt) ? base.slice(0, -stripExt.length) : base;
21896
- const prior = seen.get(slug);
21897
- if (prior !== void 0 && prior !== p) {
21959
+ const key = slug.toLowerCase();
21960
+ const prior = seen.get(key);
21961
+ if (prior !== void 0 && prior.path !== p) {
21962
+ const detail = prior.slug === slug ? `"${slug}"` : `"${prior.slug}" vs "${slug}" (case-insensitive)`;
21898
21963
  throw new CanonicalNameError(
21899
21964
  feature,
21900
21965
  slug,
21901
- `canonical ${feature} files collide on slug "${slug}": ${prior} vs ${p}. Rename one.`
21966
+ `canonical ${feature} files collide on slug ${detail}: ${prior.path} vs ${p}. Rename one.`
21902
21967
  );
21903
21968
  }
21904
- seen.set(slug, p);
21969
+ seen.set(key, { path: p, slug });
21905
21970
  }
21906
21971
  }
21907
21972
  var ALTERNATE_RESOURCE_FORMATS = /* @__PURE__ */ new Set([".toml", ".yaml", ".yml", ".json"]);
@@ -21935,7 +22000,7 @@ function toStrArray(v) {
21935
22000
  return [];
21936
22001
  }
21937
22002
  async function parseRules(rulesDir, opts = {}) {
21938
- const files = await readDirRecursive(rulesDir);
22003
+ const files = await readDirRecursiveNoSymlinks(rulesDir);
21939
22004
  const mdFiles = files.filter((f) => {
21940
22005
  if (!f.endsWith(".md")) return false;
21941
22006
  const name = basename(f, ".md");
@@ -21944,6 +22009,7 @@ async function parseRules(rulesDir, opts = {}) {
21944
22009
  warnIfUnrecognizedResourceFormats("rules", rulesDir, files, mdFiles, {
21945
22010
  handledByOtherReader: opts.handledByOtherReader
21946
22011
  });
22012
+ assertNoBasenameCollisions("rule", mdFiles, ".md");
21947
22013
  const rules = [];
21948
22014
  for (const path of mdFiles) {
21949
22015
  const content = await readFileSafe(path);
@@ -21992,7 +22058,7 @@ function toToolsArray2(v) {
21992
22058
  return [];
21993
22059
  }
21994
22060
  async function parseCommands(commandsDir, opts = {}) {
21995
- const files = await readDirRecursive(commandsDir);
22061
+ const files = await readDirRecursiveNoSymlinks(commandsDir);
21996
22062
  const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
21997
22063
  warnIfUnrecognizedResourceFormats("commands", commandsDir, files, mdFiles, {
21998
22064
  handledByOtherReader: opts.handledByOtherReader
@@ -22052,7 +22118,7 @@ function toHooks2(v) {
22052
22118
  return {};
22053
22119
  }
22054
22120
  async function parseAgents(agentsDir, opts = {}) {
22055
- const files = await readDirRecursive(agentsDir);
22121
+ const files = await readDirRecursiveNoSymlinks(agentsDir);
22056
22122
  const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
22057
22123
  warnIfUnrecognizedResourceFormats("agents", agentsDir, files, mdFiles, {
22058
22124
  handledByOtherReader: opts.handledByOtherReader
@@ -22636,7 +22702,7 @@ async function importEntities(kind, dir, opts) {
22636
22702
  }
22637
22703
  async function readToolNativeEntities(srcDir, targetId, kind, parseOpts = {}) {
22638
22704
  const specs = directorySpecsFor(getDescriptor(targetId)?.importer, kind);
22639
- const allFiles = await readDirRecursive(srcDir);
22705
+ const allFiles = await readDirRecursiveNoSymlinks(srcDir);
22640
22706
  const nonMdExtensions = /* @__PURE__ */ new Set();
22641
22707
  for (const spec of specs) {
22642
22708
  for (const ext of spec.extensions) {
@@ -22993,7 +23059,7 @@ async function stageSingleFile(sourcePath, destinationDir, acceptMdc) {
22993
23059
  async function stageMarkdownCollection(sourceRoot, destinationDir, acceptMdc) {
22994
23060
  const info = await stat(sourceRoot);
22995
23061
  if (info.isFile()) return stageSingleFile(sourceRoot, destinationDir, acceptMdc);
22996
- const files = (await readDirRecursive(sourceRoot)).filter(
23062
+ const files = (await readDirRecursiveNoSymlinks(sourceRoot)).filter(
22997
23063
  (file) => isAcceptedFile(file, acceptMdc) && !isBoilerplate(basename(file))
22998
23064
  );
22999
23065
  if (files.length === 0) {
@@ -23040,7 +23106,7 @@ async function stagePreferredSkills(sourceRoot, destinationDir, preferredSkillNa
23040
23106
  }
23041
23107
  const wanted = new Set(preferredSkillNames);
23042
23108
  const matches = /* @__PURE__ */ new Map();
23043
- for (const file of await readDirRecursive(sourceRoot)) {
23109
+ for (const file of await readDirRecursiveNoSymlinks(sourceRoot)) {
23044
23110
  if (!file.endsWith("/SKILL.md") && !file.endsWith("\\SKILL.md")) continue;
23045
23111
  const skillDir = dirname(file);
23046
23112
  const skillName = basename(skillDir);
@@ -23088,7 +23154,7 @@ async function stageSkills(sourceRoot, destinationDir, options = {}) {
23088
23154
  return;
23089
23155
  }
23090
23156
  await mkdirp(destinationDir);
23091
- const entries = await readDirRecursive(sourceRoot);
23157
+ const entries = await readDirRecursiveNoSymlinks(sourceRoot);
23092
23158
  const roots = /* @__PURE__ */ new Set();
23093
23159
  for (const file of entries.filter(
23094
23160
  (entry) => entry.endsWith("/SKILL.md") || entry.endsWith("\\SKILL.md")