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/CHANGELOG.md +45 -0
- package/README.md +104 -456
- package/dist/canonical.js +199 -147
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +249 -201
- package/dist/engine.js +213 -147
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +286 -207
- package/dist/index.js.map +1 -1
- package/dist/{init-YKxF2zpQ.d.ts → init-B0aI-g8W.d.ts} +1 -1
- package/dist/lessons.d.ts +2 -2
- package/dist/lessons.js +73 -60
- package/dist/lessons.js.map +1 -1
- package/dist/targets.js +111 -59
- package/dist/targets.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { stringify, parse, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
|
|
3
3
|
import { join, resolve, relative, sep, dirname, basename, win32, posix, extname } from 'path';
|
|
4
|
-
import { mkdir, access, readdir, rm, readFile,
|
|
4
|
+
import { mkdir, access, readdir, rm, readFile, writeFile, stat, lstat, unlink, rename, chmod, realpath, mkdtemp, cp } from 'fs/promises';
|
|
5
5
|
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
6
6
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, constants, rmSync, renameSync, readdirSync, realpathSync, statSync } from 'fs';
|
|
7
7
|
import { parse as parse$1 } from 'smol-toml';
|
|
@@ -1381,7 +1381,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
|
|
|
1381
1381
|
toPath: `${AB_SKILLS}/${entry.name}/SKILL.md`,
|
|
1382
1382
|
feature: "skills"
|
|
1383
1383
|
});
|
|
1384
|
-
const sourceFiles = await
|
|
1384
|
+
const sourceFiles = await readDirRecursiveNoSymlinks(sourceSkillDir);
|
|
1385
1385
|
for (const sourcePath of sourceFiles) {
|
|
1386
1386
|
if (sourcePath === sourceSkillFile) continue;
|
|
1387
1387
|
const relativePath = relative(sourceSkillDir, sourcePath).replace(/\\/g, "/");
|
|
@@ -2329,7 +2329,7 @@ function matchesExtension(path, extensions) {
|
|
|
2329
2329
|
return extensions.some((extension) => path.endsWith(extension));
|
|
2330
2330
|
}
|
|
2331
2331
|
async function importFileDirectory(opts) {
|
|
2332
|
-
const files = await
|
|
2332
|
+
const files = await readDirRecursiveNoSymlinks(opts.srcDir);
|
|
2333
2333
|
const matchedFiles = files.filter((path) => matchesExtension(path, opts.extensions));
|
|
2334
2334
|
const results = [];
|
|
2335
2335
|
for (const srcPath of matchedFiles) {
|
|
@@ -6748,7 +6748,7 @@ var init_settings_helpers2 = __esm({
|
|
|
6748
6748
|
async function importClaudeSkills(projectRoot, results, normalize) {
|
|
6749
6749
|
const skillsBaseDir = join(projectRoot, CLAUDE_SKILLS_DIR);
|
|
6750
6750
|
const destBase = join(projectRoot, CLAUDE_CANONICAL_SKILLS_DIR);
|
|
6751
|
-
const allFiles = await
|
|
6751
|
+
const allFiles = await readDirRecursiveNoSymlinks(skillsBaseDir);
|
|
6752
6752
|
const skillMdFiles = allFiles.filter((f) => f.endsWith("SKILL.md"));
|
|
6753
6753
|
for (const skillMdPath of skillMdFiles) {
|
|
6754
6754
|
const skillDir = dirname(skillMdPath);
|
|
@@ -6767,7 +6767,7 @@ async function importClaudeSkills(projectRoot, results, normalize) {
|
|
|
6767
6767
|
`);
|
|
6768
6768
|
continue;
|
|
6769
6769
|
}
|
|
6770
|
-
const skillFiles = await
|
|
6770
|
+
const skillFiles = await readDirRecursiveNoSymlinks(skillDir);
|
|
6771
6771
|
for (const filePath of skillFiles) {
|
|
6772
6772
|
const fileContent = await readFileSafe(filePath);
|
|
6773
6773
|
if (fileContent === null) continue;
|
|
@@ -7392,7 +7392,7 @@ async function importClineRules(projectRoot, results, normalize) {
|
|
|
7392
7392
|
feature: "rules"
|
|
7393
7393
|
});
|
|
7394
7394
|
} else {
|
|
7395
|
-
const ruleFiles = await
|
|
7395
|
+
const ruleFiles = await readDirRecursiveNoSymlinks(clineRulesPath);
|
|
7396
7396
|
const mdFiles = ruleFiles.filter((f) => f.endsWith(".md") && !f.includes("/workflows/")).sort();
|
|
7397
7397
|
const first = mdFiles[0];
|
|
7398
7398
|
if (first) {
|
|
@@ -7456,27 +7456,40 @@ var init_importer_rules = __esm({
|
|
|
7456
7456
|
init_constants8();
|
|
7457
7457
|
}
|
|
7458
7458
|
});
|
|
7459
|
+
function toStringRecord2(raw) {
|
|
7460
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
7461
|
+
return Object.fromEntries(
|
|
7462
|
+
Object.entries(raw).filter((entry) => typeof entry[1] === "string")
|
|
7463
|
+
);
|
|
7464
|
+
}
|
|
7459
7465
|
function mapClineServerToCanonical(raw) {
|
|
7460
7466
|
if (!raw || typeof raw !== "object") return null;
|
|
7461
7467
|
const obj = raw;
|
|
7462
|
-
const
|
|
7463
|
-
|
|
7464
|
-
const
|
|
7465
|
-
const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
|
|
7466
|
-
const envRaw = obj.env;
|
|
7467
|
-
const env = envRaw !== null && typeof envRaw === "object" && !Array.isArray(envRaw) ? Object.fromEntries(
|
|
7468
|
-
Object.entries(envRaw).filter(
|
|
7469
|
-
(entry) => typeof entry[1] === "string"
|
|
7470
|
-
)
|
|
7471
|
-
) : {};
|
|
7468
|
+
const transport = typeof obj.type === "string" ? obj.type : void 0;
|
|
7469
|
+
const transportType = typeof obj.transportType === "string" ? obj.transportType : void 0;
|
|
7470
|
+
const env = toStringRecord2(obj.env);
|
|
7472
7471
|
const description = typeof obj.description === "string" ? obj.description : void 0;
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7472
|
+
const command = typeof obj.command === "string" ? obj.command : "";
|
|
7473
|
+
if (command) {
|
|
7474
|
+
const args = Array.isArray(obj.args) ? obj.args.filter((x) => typeof x === "string") : [];
|
|
7475
|
+
return {
|
|
7476
|
+
...description !== void 0 && { description },
|
|
7477
|
+
type: transport ?? transportType ?? "stdio",
|
|
7478
|
+
command,
|
|
7479
|
+
args,
|
|
7480
|
+
env
|
|
7481
|
+
};
|
|
7482
|
+
}
|
|
7483
|
+
if (typeof obj.url === "string") {
|
|
7484
|
+
return {
|
|
7485
|
+
...description !== void 0 && { description },
|
|
7486
|
+
type: transport ?? transportType ?? "http",
|
|
7487
|
+
url: obj.url,
|
|
7488
|
+
headers: toStringRecord2(obj.headers),
|
|
7489
|
+
env
|
|
7490
|
+
};
|
|
7491
|
+
}
|
|
7492
|
+
return null;
|
|
7480
7493
|
}
|
|
7481
7494
|
async function importClineMcp(projectRoot, results) {
|
|
7482
7495
|
const candidatePaths = [CLINE_MCP_SETTINGS, CLINE_MCP_SETTINGS_LEGACY].map(
|
|
@@ -7550,7 +7563,7 @@ var init_reserved = __esm({
|
|
|
7550
7563
|
}
|
|
7551
7564
|
});
|
|
7552
7565
|
async function readNativeSkill(skillDir) {
|
|
7553
|
-
const allFiles = await
|
|
7566
|
+
const allFiles = await readDirRecursiveNoSymlinks(skillDir).catch(() => []);
|
|
7554
7567
|
const entries = [];
|
|
7555
7568
|
for (const absPath of allFiles) {
|
|
7556
7569
|
const relPath = relative(skillDir, absPath).replace(/\\/g, "/");
|
|
@@ -7616,7 +7629,7 @@ async function importFlatSkill(skillName, srcPath, content, options) {
|
|
|
7616
7629
|
async function findDirectorySkills(skillsDir) {
|
|
7617
7630
|
const skills = /* @__PURE__ */ new Map();
|
|
7618
7631
|
try {
|
|
7619
|
-
const allFiles = await
|
|
7632
|
+
const allFiles = await readDirRecursiveNoSymlinks(skillsDir);
|
|
7620
7633
|
const skillMdFiles = allFiles.filter((f) => basename(f) === "SKILL.md");
|
|
7621
7634
|
for (const skillMdPath of skillMdFiles) {
|
|
7622
7635
|
const skillDir = dirname(skillMdPath);
|
|
@@ -7747,7 +7760,7 @@ function extractMeta(content, key) {
|
|
|
7747
7760
|
return match?.[1]?.trim() ?? null;
|
|
7748
7761
|
}
|
|
7749
7762
|
async function loadHooksFromDir(dir, hooks) {
|
|
7750
|
-
const files = await
|
|
7763
|
+
const files = await readDirRecursiveNoSymlinks(dir).catch(() => []);
|
|
7751
7764
|
const shFiles = files.filter((f) => basename(f).endsWith(".sh") && dirname(f) === dir);
|
|
7752
7765
|
for (const srcPath of shFiles) {
|
|
7753
7766
|
const content = await readFileSafe(srcPath);
|
|
@@ -8482,7 +8495,7 @@ async function importCodexAgentsFromToml(projectRoot, results, normalize) {
|
|
|
8482
8495
|
const agentsPath = join(projectRoot, CODEX_AGENTS_DIR);
|
|
8483
8496
|
const agentsDestDir = join(projectRoot, CODEX_CANONICAL_AGENTS_DIR);
|
|
8484
8497
|
try {
|
|
8485
|
-
const agentFiles = await
|
|
8498
|
+
const agentFiles = await readDirRecursiveNoSymlinks(agentsPath);
|
|
8486
8499
|
const tomlFiles = agentFiles.filter((f) => f.endsWith(".toml"));
|
|
8487
8500
|
for (const srcPath of tomlFiles) {
|
|
8488
8501
|
const content = await readFileSafe(srcPath);
|
|
@@ -8578,7 +8591,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize) {
|
|
|
8578
8591
|
const results = [];
|
|
8579
8592
|
const codexRulesPath = join(projectRoot, CODEX_RULES_DIR);
|
|
8580
8593
|
try {
|
|
8581
|
-
const ruleFiles = await
|
|
8594
|
+
const ruleFiles = await readDirRecursiveNoSymlinks(codexRulesPath);
|
|
8582
8595
|
const mdFiles = ruleFiles.filter((f) => f.endsWith(".md"));
|
|
8583
8596
|
for (const srcPath of mdFiles) {
|
|
8584
8597
|
const content = await readFileSafe(srcPath);
|
|
@@ -8735,7 +8748,7 @@ async function importCodexRules(projectRoot, results, normalize, normalizeWindsu
|
|
|
8735
8748
|
}
|
|
8736
8749
|
async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
|
|
8737
8750
|
try {
|
|
8738
|
-
const files = await
|
|
8751
|
+
const files = await readDirRecursiveNoSymlinks(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
|
|
8739
8752
|
const instructionFiles = files.filter((file) => file.endsWith(".md"));
|
|
8740
8753
|
const instructionsRoot = join(projectRoot, CODEX_INSTRUCTIONS_DIR);
|
|
8741
8754
|
for (const srcPath of instructionFiles) {
|
|
@@ -9181,19 +9194,31 @@ function readMcpServers(content, extension) {
|
|
|
9181
9194
|
for (const [name, value] of Object.entries(rawServers)) {
|
|
9182
9195
|
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
9183
9196
|
const server = value;
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9197
|
+
const description = typeof server.description === "string" ? server.description : void 0;
|
|
9198
|
+
if (typeof server.command === "string") {
|
|
9199
|
+
servers[name] = {
|
|
9200
|
+
type: typeof server.type === "string" ? server.type : "stdio",
|
|
9201
|
+
command: server.command,
|
|
9202
|
+
args: toStringArray5(server.args),
|
|
9203
|
+
env: toStringRecord(server.env),
|
|
9204
|
+
description
|
|
9205
|
+
};
|
|
9206
|
+
continue;
|
|
9207
|
+
}
|
|
9208
|
+
if (typeof server.url === "string") {
|
|
9209
|
+
servers[name] = {
|
|
9210
|
+
type: typeof server.type === "string" ? server.type : "http",
|
|
9211
|
+
url: server.url,
|
|
9212
|
+
headers: toStringRecord(server.headers),
|
|
9213
|
+
env: toStringRecord(server.env),
|
|
9214
|
+
description
|
|
9215
|
+
};
|
|
9216
|
+
}
|
|
9192
9217
|
}
|
|
9193
9218
|
return servers;
|
|
9194
9219
|
}
|
|
9195
9220
|
async function importMcp2(projectRoot, results) {
|
|
9196
|
-
const files = (await
|
|
9221
|
+
const files = (await readDirRecursiveNoSymlinks(join(projectRoot, CONTINUE_MCP_DIR))).filter(
|
|
9197
9222
|
(file) => [".json", ".yaml", ".yml"].includes(extname(file))
|
|
9198
9223
|
);
|
|
9199
9224
|
const merged = {};
|
|
@@ -9853,7 +9878,7 @@ function extractWrapperCommand(content) {
|
|
|
9853
9878
|
}
|
|
9854
9879
|
async function importHooks(projectRoot, results) {
|
|
9855
9880
|
const hooksDir = join(projectRoot, COPILOT_HOOKS_DIR);
|
|
9856
|
-
const allFiles = await
|
|
9881
|
+
const allFiles = await readDirRecursiveNoSymlinks(hooksDir).catch(() => []);
|
|
9857
9882
|
const jsonFiles = allFiles.filter((file) => file.endsWith(".json"));
|
|
9858
9883
|
const hooks = {};
|
|
9859
9884
|
for (const srcPath of jsonFiles) {
|
|
@@ -9889,7 +9914,7 @@ async function importHooks(projectRoot, results) {
|
|
|
9889
9914
|
}
|
|
9890
9915
|
}
|
|
9891
9916
|
const legacyDir = join(projectRoot, COPILOT_LEGACY_HOOKS_DIR);
|
|
9892
|
-
const legacyFiles = await
|
|
9917
|
+
const legacyFiles = await readDirRecursiveNoSymlinks(legacyDir).catch(() => []);
|
|
9893
9918
|
const shFiles = legacyFiles.filter(
|
|
9894
9919
|
(file) => dirname(file) === legacyDir && /^[^-]+-\d+\.sh$/i.test(basename(file))
|
|
9895
9920
|
);
|
|
@@ -9966,7 +9991,7 @@ var init_importer10 = __esm({
|
|
|
9966
9991
|
}
|
|
9967
9992
|
});
|
|
9968
9993
|
async function skillNamesFromNativeSkillDir(scanRoot) {
|
|
9969
|
-
const files = await
|
|
9994
|
+
const files = await readDirRecursiveNoSymlinks(scanRoot);
|
|
9970
9995
|
const names = /* @__PURE__ */ new Set();
|
|
9971
9996
|
for (const f of files) {
|
|
9972
9997
|
if (basename(f) === "SKILL.md") {
|
|
@@ -9988,7 +10013,7 @@ var init_native_skill_scan = __esm({
|
|
|
9988
10013
|
async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
9989
10014
|
const scan = join(repoRoot, ...posixPath.split("/"));
|
|
9990
10015
|
if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
|
|
9991
|
-
const files = await
|
|
10016
|
+
const files = await readDirRecursiveNoSymlinks(scan);
|
|
9992
10017
|
const commands = [
|
|
9993
10018
|
...new Set(
|
|
9994
10019
|
files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
|
|
@@ -9997,7 +10022,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
|
9997
10022
|
return commands.length ? { commands } : {};
|
|
9998
10023
|
}
|
|
9999
10024
|
if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
|
|
10000
|
-
const files = await
|
|
10025
|
+
const files = await readDirRecursiveNoSymlinks(scan);
|
|
10001
10026
|
const rules = [
|
|
10002
10027
|
...new Set(
|
|
10003
10028
|
files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
|
|
@@ -10006,7 +10031,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
|
10006
10031
|
return rules.length ? { rules } : {};
|
|
10007
10032
|
}
|
|
10008
10033
|
if (posixPath.startsWith(".github/instructions")) {
|
|
10009
|
-
const files = await
|
|
10034
|
+
const files = await readDirRecursiveNoSymlinks(scan);
|
|
10010
10035
|
const names = /* @__PURE__ */ new Set();
|
|
10011
10036
|
for (const f of files) {
|
|
10012
10037
|
const b = basename(f);
|
|
@@ -10022,7 +10047,7 @@ async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
|
10022
10047
|
return skills.length ? { skills } : {};
|
|
10023
10048
|
}
|
|
10024
10049
|
if (posixPath.startsWith(".github/agents")) {
|
|
10025
|
-
const files = await
|
|
10050
|
+
const files = await readDirRecursiveNoSymlinks(scan);
|
|
10026
10051
|
const agents = [
|
|
10027
10052
|
...new Set(
|
|
10028
10053
|
files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
|
|
@@ -11571,7 +11596,7 @@ async function importSkills3(projectRoot, results, normalize, skillsRelDir = CUR
|
|
|
11571
11596
|
for (const [skillName, skillDir] of directorySkills) {
|
|
11572
11597
|
await importDirectorySkill(skillName, skillDir, options);
|
|
11573
11598
|
}
|
|
11574
|
-
const allFiles = await
|
|
11599
|
+
const allFiles = await readDirRecursiveNoSymlinks(skillsDir).catch(() => []);
|
|
11575
11600
|
const mdFiles = allFiles.filter((f) => f.endsWith(".md"));
|
|
11576
11601
|
const handledPaths = new Set(
|
|
11577
11602
|
Array.from(directorySkills.values()).flatMap(
|
|
@@ -11609,11 +11634,11 @@ async function hasGlobalCursorArtifacts(projectRoot) {
|
|
|
11609
11634
|
const stat9 = await readFileSafe(p);
|
|
11610
11635
|
if (stat9 !== null && stat9.trim() !== "") return true;
|
|
11611
11636
|
}
|
|
11612
|
-
const skillFiles = await
|
|
11637
|
+
const skillFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_SKILLS_DIR));
|
|
11613
11638
|
if (skillFiles.some((f) => f.endsWith(".md"))) return true;
|
|
11614
|
-
const agentFiles = await
|
|
11639
|
+
const agentFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_AGENTS_DIR));
|
|
11615
11640
|
if (agentFiles.some((f) => f.endsWith(".md"))) return true;
|
|
11616
|
-
const commandFiles = await
|
|
11641
|
+
const commandFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_COMMANDS_DIR));
|
|
11617
11642
|
if (commandFiles.some((f) => f.endsWith(".md"))) return true;
|
|
11618
11643
|
return false;
|
|
11619
11644
|
}
|
|
@@ -13201,7 +13226,7 @@ async function importGeminiPolicies(projectRoot) {
|
|
|
13201
13226
|
const policiesDir = join(projectRoot, GEMINI_POLICIES_DIR);
|
|
13202
13227
|
let policyFiles;
|
|
13203
13228
|
try {
|
|
13204
|
-
policyFiles = await
|
|
13229
|
+
policyFiles = await readDirRecursiveNoSymlinks(policiesDir);
|
|
13205
13230
|
} catch {
|
|
13206
13231
|
return results;
|
|
13207
13232
|
}
|
|
@@ -13300,7 +13325,7 @@ var init_importer_strip = __esm({
|
|
|
13300
13325
|
});
|
|
13301
13326
|
async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
|
|
13302
13327
|
const geminiSkillsPath = join(projectRoot, GEMINI_SKILLS_DIR);
|
|
13303
|
-
const skillDirs = await
|
|
13328
|
+
const skillDirs = await readDirRecursiveNoSymlinks(geminiSkillsPath);
|
|
13304
13329
|
const skillMdFiles = skillDirs.filter((f) => basename(f) === "SKILL.md");
|
|
13305
13330
|
for (const srcPath of skillMdFiles) {
|
|
13306
13331
|
const content = await readFileSafe(srcPath);
|
|
@@ -13339,7 +13364,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
|
|
|
13339
13364
|
toPath: `${GEMINI_CANONICAL_SKILLS_DIR}/${skillName}/SKILL.md`,
|
|
13340
13365
|
feature: "skills"
|
|
13341
13366
|
});
|
|
13342
|
-
const allSkillFiles = await
|
|
13367
|
+
const allSkillFiles = await readDirRecursiveNoSymlinks(dirname(srcPath));
|
|
13343
13368
|
for (const absPath of allSkillFiles) {
|
|
13344
13369
|
if (absPath === srcPath) continue;
|
|
13345
13370
|
const supportContent = await readFileSafe(absPath);
|
|
@@ -13358,7 +13383,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
|
|
|
13358
13383
|
}
|
|
13359
13384
|
const geminiAgentsPath = join(projectRoot, GEMINI_AGENTS_DIR);
|
|
13360
13385
|
try {
|
|
13361
|
-
const agentFiles = await
|
|
13386
|
+
const agentFiles = await readDirRecursiveNoSymlinks(geminiAgentsPath);
|
|
13362
13387
|
const agentMdFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
13363
13388
|
for (const srcPath of agentMdFiles) {
|
|
13364
13389
|
const content = await readFileSafe(srcPath);
|
|
@@ -13491,7 +13516,7 @@ function isUnderGeminiCommands(pathInRepoPosix) {
|
|
|
13491
13516
|
async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
|
|
13492
13517
|
const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
|
|
13493
13518
|
const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
|
|
13494
|
-
const files = await
|
|
13519
|
+
const files = await readDirRecursiveNoSymlinks(scanDir);
|
|
13495
13520
|
const names = [];
|
|
13496
13521
|
for (const f of files) {
|
|
13497
13522
|
if (!/\.(toml|md)$/i.test(f)) continue;
|
|
@@ -15703,7 +15728,7 @@ async function importNonRootRules(projectRoot, results, normalize) {
|
|
|
15703
15728
|
}
|
|
15704
15729
|
async function importHooks2(projectRoot, results) {
|
|
15705
15730
|
const hooks = {};
|
|
15706
|
-
for (const absPath of await
|
|
15731
|
+
for (const absPath of await readDirRecursiveNoSymlinks(join(projectRoot, KIRO_HOOKS_DIR))) {
|
|
15707
15732
|
if (!absPath.endsWith(".kiro.hook")) continue;
|
|
15708
15733
|
const parsed = parseKiroHookFile(await readFileSafe(absPath) ?? "");
|
|
15709
15734
|
if (!parsed) continue;
|
|
@@ -16055,7 +16080,7 @@ var init_generator24 = __esm({
|
|
|
16055
16080
|
init_constants19();
|
|
16056
16081
|
}
|
|
16057
16082
|
});
|
|
16058
|
-
function
|
|
16083
|
+
function toStringRecord3(value) {
|
|
16059
16084
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
16060
16085
|
const out2 = {};
|
|
16061
16086
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -16081,8 +16106,8 @@ function parseOpenCodeMcp(content) {
|
|
|
16081
16106
|
out2[name] = {
|
|
16082
16107
|
type: "url",
|
|
16083
16108
|
url: entry.url,
|
|
16084
|
-
headers:
|
|
16085
|
-
env:
|
|
16109
|
+
headers: toStringRecord3(entry.headers),
|
|
16110
|
+
env: toStringRecord3(entry.environment),
|
|
16086
16111
|
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
16087
16112
|
};
|
|
16088
16113
|
continue;
|
|
@@ -16096,7 +16121,7 @@ function parseOpenCodeMcp(content) {
|
|
|
16096
16121
|
type: "stdio",
|
|
16097
16122
|
command,
|
|
16098
16123
|
args,
|
|
16099
|
-
env:
|
|
16124
|
+
env: toStringRecord3(entry.environment),
|
|
16100
16125
|
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
16101
16126
|
};
|
|
16102
16127
|
}
|
|
@@ -18918,7 +18943,7 @@ function toStringArray8(value) {
|
|
|
18918
18943
|
}
|
|
18919
18944
|
async function importWorkflows(projectRoot, results, normalize) {
|
|
18920
18945
|
const workflowsDir = join(projectRoot, WINDSURF_WORKFLOWS_DIR);
|
|
18921
|
-
const workflowFiles = await
|
|
18946
|
+
const workflowFiles = await readDirRecursiveNoSymlinks(workflowsDir);
|
|
18922
18947
|
const workflowMdFiles = workflowFiles.filter((f) => f.endsWith(".md"));
|
|
18923
18948
|
const destCommandsDir = join(projectRoot, WINDSURF_CANONICAL_COMMANDS_DIR);
|
|
18924
18949
|
for (const srcPath of workflowMdFiles) {
|
|
@@ -20760,8 +20785,22 @@ function resolveOutputCollisions(results) {
|
|
|
20760
20785
|
}
|
|
20761
20786
|
deduped[existingIdx] = mergeDuplicateMetadata(existing, result);
|
|
20762
20787
|
}
|
|
20788
|
+
assertNoCaseOnlyPathCollisions(deduped);
|
|
20763
20789
|
return deduped;
|
|
20764
20790
|
}
|
|
20791
|
+
function assertNoCaseOnlyPathCollisions(results) {
|
|
20792
|
+
const byLower = /* @__PURE__ */ new Map();
|
|
20793
|
+
for (const result of results) {
|
|
20794
|
+
const key = result.path.toLowerCase();
|
|
20795
|
+
const prior = byLower.get(key);
|
|
20796
|
+
if (prior !== void 0 && prior.path !== result.path) {
|
|
20797
|
+
throw new Error(
|
|
20798
|
+
`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.`
|
|
20799
|
+
);
|
|
20800
|
+
}
|
|
20801
|
+
byLower.set(key, result);
|
|
20802
|
+
}
|
|
20803
|
+
}
|
|
20765
20804
|
function refreshResultStatus(result) {
|
|
20766
20805
|
const status = result.currentContent === void 0 ? "created" : result.currentContent !== result.content ? "updated" : "unchanged";
|
|
20767
20806
|
return result.status === status ? result : { ...result, status };
|
|
@@ -21316,6 +21355,100 @@ function redactUrlSecrets(message) {
|
|
|
21316
21355
|
}
|
|
21317
21356
|
);
|
|
21318
21357
|
}
|
|
21358
|
+
function gitProtocolOptIns() {
|
|
21359
|
+
const on = (v) => v === "1" || v === "true";
|
|
21360
|
+
return {
|
|
21361
|
+
http: on(process.env.AGENTSMESH_ALLOW_INSECURE_GIT),
|
|
21362
|
+
file: on(process.env.AGENTSMESH_ALLOW_LOCAL_GIT)
|
|
21363
|
+
};
|
|
21364
|
+
}
|
|
21365
|
+
function isAllowedGitProtocol(url) {
|
|
21366
|
+
let parsed;
|
|
21367
|
+
try {
|
|
21368
|
+
parsed = new URL(url);
|
|
21369
|
+
} catch {
|
|
21370
|
+
return false;
|
|
21371
|
+
}
|
|
21372
|
+
const { http, file } = gitProtocolOptIns();
|
|
21373
|
+
const allowed = ["https:", "ssh:"];
|
|
21374
|
+
if (http) allowed.push("http:");
|
|
21375
|
+
if (file) allowed.push("file:");
|
|
21376
|
+
return allowed.includes(parsed.protocol);
|
|
21377
|
+
}
|
|
21378
|
+
function gitAllowProtocolEnv() {
|
|
21379
|
+
const { http, file } = gitProtocolOptIns();
|
|
21380
|
+
const protos = ["https", "ssh"];
|
|
21381
|
+
if (http) protos.push("http");
|
|
21382
|
+
if (file) protos.push("file");
|
|
21383
|
+
return protos.join(":");
|
|
21384
|
+
}
|
|
21385
|
+
function assertAllowedGitUrl(url) {
|
|
21386
|
+
if (isAllowedGitProtocol(url)) return;
|
|
21387
|
+
throw new Error(
|
|
21388
|
+
`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.`
|
|
21389
|
+
);
|
|
21390
|
+
}
|
|
21391
|
+
function splitSourceRef(source, prefix, defaultRef) {
|
|
21392
|
+
if (!source.startsWith(prefix)) return null;
|
|
21393
|
+
const rest = source.slice(prefix.length).trim();
|
|
21394
|
+
if (!rest) return null;
|
|
21395
|
+
const refIdx = rest.lastIndexOf("@");
|
|
21396
|
+
if (refIdx < 0) return [rest, defaultRef];
|
|
21397
|
+
const slug = rest.slice(0, refIdx).trim();
|
|
21398
|
+
const ref = rest.slice(refIdx + 1).trim();
|
|
21399
|
+
if (!slug || !ref) return null;
|
|
21400
|
+
return [slug, ref];
|
|
21401
|
+
}
|
|
21402
|
+
function parseGithubSource(source) {
|
|
21403
|
+
const parts = splitSourceRef(source, "github:", "latest");
|
|
21404
|
+
if (!parts) return null;
|
|
21405
|
+
const [slug, tag] = parts;
|
|
21406
|
+
const slash = slug.indexOf("/");
|
|
21407
|
+
if (slash < 0) return null;
|
|
21408
|
+
const org = slug.slice(0, slash).trim();
|
|
21409
|
+
const repo = slug.slice(slash + 1).trim();
|
|
21410
|
+
if (!org || !repo || !tag) return null;
|
|
21411
|
+
return { org, repo, tag };
|
|
21412
|
+
}
|
|
21413
|
+
function parseGitlabSource(source) {
|
|
21414
|
+
const parts = splitSourceRef(source, "gitlab:");
|
|
21415
|
+
if (!parts) return null;
|
|
21416
|
+
const [slug, ref] = parts;
|
|
21417
|
+
const slash = slug.lastIndexOf("/");
|
|
21418
|
+
if (slash < 0) return null;
|
|
21419
|
+
const namespace = slug.slice(0, slash).trim();
|
|
21420
|
+
const project31 = slug.slice(slash + 1).trim();
|
|
21421
|
+
if (!namespace || !project31) return null;
|
|
21422
|
+
return {
|
|
21423
|
+
namespace,
|
|
21424
|
+
project: project31,
|
|
21425
|
+
ref,
|
|
21426
|
+
cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
|
|
21427
|
+
};
|
|
21428
|
+
}
|
|
21429
|
+
function parseGitSource(source) {
|
|
21430
|
+
if (!source.startsWith("git+")) return null;
|
|
21431
|
+
const rest = source.slice(4).trim();
|
|
21432
|
+
if (!rest) return null;
|
|
21433
|
+
const hashIdx = rest.lastIndexOf("#");
|
|
21434
|
+
const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
|
|
21435
|
+
const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
|
|
21436
|
+
if (!url || hashIdx >= 0 && !ref) return null;
|
|
21437
|
+
if (!isAllowedGitProtocol(url)) return null;
|
|
21438
|
+
return { url, ref };
|
|
21439
|
+
}
|
|
21440
|
+
function parseRemoteSource(source) {
|
|
21441
|
+
const github = parseGithubSource(source);
|
|
21442
|
+
if (github) return { kind: "github", ...github };
|
|
21443
|
+
const gitlab = parseGitlabSource(source);
|
|
21444
|
+
if (gitlab) return { kind: "gitlab", ...gitlab };
|
|
21445
|
+
const git = parseGitSource(source);
|
|
21446
|
+
if (git) return { kind: "git", ...git };
|
|
21447
|
+
return null;
|
|
21448
|
+
}
|
|
21449
|
+
function isSupportedRemoteSource(source) {
|
|
21450
|
+
return parseRemoteSource(source) !== null;
|
|
21451
|
+
}
|
|
21319
21452
|
|
|
21320
21453
|
// src/config/remote/git-remote.ts
|
|
21321
21454
|
var execFileAsync = promisify(execFile);
|
|
@@ -21386,7 +21519,8 @@ function resolveCloneUrl(parsed) {
|
|
|
21386
21519
|
}
|
|
21387
21520
|
async function cloneRepo(cloneUrl, repoDir) {
|
|
21388
21521
|
ensureNotFlag(cloneUrl, "clone-url");
|
|
21389
|
-
|
|
21522
|
+
assertAllowedGitUrl(cloneUrl);
|
|
21523
|
+
await runGit(["clone", "-c", "core.symlinks=false", cloneUrl, repoDir]);
|
|
21390
21524
|
}
|
|
21391
21525
|
async function checkoutRef(repoDir, ref) {
|
|
21392
21526
|
ensureNotFlag(ref, "ref");
|
|
@@ -21400,7 +21534,10 @@ async function runGit(args, cwd) {
|
|
|
21400
21534
|
cwd,
|
|
21401
21535
|
env: {
|
|
21402
21536
|
...process.env,
|
|
21403
|
-
GIT_TERMINAL_PROMPT: "0"
|
|
21537
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
21538
|
+
// Constrain transports git may use (incl. mid-clone redirects/insteadOf)
|
|
21539
|
+
// to the same allowlist the clone URL itself passed.
|
|
21540
|
+
GIT_ALLOW_PROTOCOL: gitAllowProtocolEnv()
|
|
21404
21541
|
}
|
|
21405
21542
|
});
|
|
21406
21543
|
return stdout.trim();
|
|
@@ -21588,80 +21725,6 @@ async function fetchGithubDefaultBranch(parsed, extendName, options, cacheDir, b
|
|
|
21588
21725
|
}
|
|
21589
21726
|
throw lastError instanceof Error ? lastError : new Error("Failed to clone GitHub default branch");
|
|
21590
21727
|
}
|
|
21591
|
-
function splitSourceRef(source, prefix, defaultRef) {
|
|
21592
|
-
if (!source.startsWith(prefix)) return null;
|
|
21593
|
-
const rest = source.slice(prefix.length).trim();
|
|
21594
|
-
if (!rest) return null;
|
|
21595
|
-
const refIdx = rest.lastIndexOf("@");
|
|
21596
|
-
if (refIdx < 0) return [rest, defaultRef];
|
|
21597
|
-
const slug = rest.slice(0, refIdx).trim();
|
|
21598
|
-
const ref = rest.slice(refIdx + 1).trim();
|
|
21599
|
-
if (!slug || !ref) return null;
|
|
21600
|
-
return [slug, ref];
|
|
21601
|
-
}
|
|
21602
|
-
function parseGithubSource(source) {
|
|
21603
|
-
const parts = splitSourceRef(source, "github:", "latest");
|
|
21604
|
-
if (!parts) return null;
|
|
21605
|
-
const [slug, tag] = parts;
|
|
21606
|
-
const slash = slug.indexOf("/");
|
|
21607
|
-
if (slash < 0) return null;
|
|
21608
|
-
const org = slug.slice(0, slash).trim();
|
|
21609
|
-
const repo = slug.slice(slash + 1).trim();
|
|
21610
|
-
if (!org || !repo || !tag) return null;
|
|
21611
|
-
return { org, repo, tag };
|
|
21612
|
-
}
|
|
21613
|
-
function parseGitlabSource(source) {
|
|
21614
|
-
const parts = splitSourceRef(source, "gitlab:");
|
|
21615
|
-
if (!parts) return null;
|
|
21616
|
-
const [slug, ref] = parts;
|
|
21617
|
-
const slash = slug.lastIndexOf("/");
|
|
21618
|
-
if (slash < 0) return null;
|
|
21619
|
-
const namespace = slug.slice(0, slash).trim();
|
|
21620
|
-
const project31 = slug.slice(slash + 1).trim();
|
|
21621
|
-
if (!namespace || !project31) return null;
|
|
21622
|
-
return {
|
|
21623
|
-
namespace,
|
|
21624
|
-
project: project31,
|
|
21625
|
-
ref,
|
|
21626
|
-
cloneUrl: `https://gitlab.com/${namespace}/${project31}.git`
|
|
21627
|
-
};
|
|
21628
|
-
}
|
|
21629
|
-
function parseGitSource(source) {
|
|
21630
|
-
if (!source.startsWith("git+")) return null;
|
|
21631
|
-
const rest = source.slice(4).trim();
|
|
21632
|
-
if (!rest) return null;
|
|
21633
|
-
const hashIdx = rest.lastIndexOf("#");
|
|
21634
|
-
const url = (hashIdx < 0 ? rest : rest.slice(0, hashIdx)).trim();
|
|
21635
|
-
const ref = hashIdx < 0 ? void 0 : rest.slice(hashIdx + 1).trim();
|
|
21636
|
-
if (!url || hashIdx >= 0 && !ref) return null;
|
|
21637
|
-
let parsedUrl;
|
|
21638
|
-
try {
|
|
21639
|
-
parsedUrl = new URL(url);
|
|
21640
|
-
} catch {
|
|
21641
|
-
return null;
|
|
21642
|
-
}
|
|
21643
|
-
const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
|
|
21644
|
-
const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
|
|
21645
|
-
const allowed = ["https:", "ssh:"];
|
|
21646
|
-
if (allowInsecure) allowed.push("http:");
|
|
21647
|
-
if (allowLocalGit) allowed.push("file:");
|
|
21648
|
-
if (!allowed.includes(parsedUrl.protocol)) {
|
|
21649
|
-
return null;
|
|
21650
|
-
}
|
|
21651
|
-
return { url, ref };
|
|
21652
|
-
}
|
|
21653
|
-
function parseRemoteSource(source) {
|
|
21654
|
-
const github = parseGithubSource(source);
|
|
21655
|
-
if (github) return { kind: "github", ...github };
|
|
21656
|
-
const gitlab = parseGitlabSource(source);
|
|
21657
|
-
if (gitlab) return { kind: "gitlab", ...gitlab };
|
|
21658
|
-
const git = parseGitSource(source);
|
|
21659
|
-
if (git) return { kind: "git", ...git };
|
|
21660
|
-
return null;
|
|
21661
|
-
}
|
|
21662
|
-
function isSupportedRemoteSource(source) {
|
|
21663
|
-
return parseRemoteSource(source) !== null;
|
|
21664
|
-
}
|
|
21665
21728
|
async function sweepStaleCache(cacheDir, maxAgeMs) {
|
|
21666
21729
|
const dir = cacheDir ?? getCacheDir();
|
|
21667
21730
|
const threshold = Number(process.env.AGENTSMESH_CACHE_MAX_AGE_DAYS ?? 30) * 864e5;
|
|
@@ -21895,15 +21958,17 @@ function assertNoBasenameCollisions(feature, paths, stripExt) {
|
|
|
21895
21958
|
const idx = Math.max(fwdIdx, bckIdx);
|
|
21896
21959
|
const base = idx === -1 ? p : p.slice(idx + 1);
|
|
21897
21960
|
const slug = base.endsWith(stripExt) ? base.slice(0, -stripExt.length) : base;
|
|
21898
|
-
const
|
|
21899
|
-
|
|
21961
|
+
const key = slug.toLowerCase();
|
|
21962
|
+
const prior = seen.get(key);
|
|
21963
|
+
if (prior !== void 0 && prior.path !== p) {
|
|
21964
|
+
const detail = prior.slug === slug ? `"${slug}"` : `"${prior.slug}" vs "${slug}" (case-insensitive)`;
|
|
21900
21965
|
throw new CanonicalNameError(
|
|
21901
21966
|
feature,
|
|
21902
21967
|
slug,
|
|
21903
|
-
`canonical ${feature} files collide on slug
|
|
21968
|
+
`canonical ${feature} files collide on slug ${detail}: ${prior.path} vs ${p}. Rename one.`
|
|
21904
21969
|
);
|
|
21905
21970
|
}
|
|
21906
|
-
seen.set(
|
|
21971
|
+
seen.set(key, { path: p, slug });
|
|
21907
21972
|
}
|
|
21908
21973
|
}
|
|
21909
21974
|
var ALTERNATE_RESOURCE_FORMATS = /* @__PURE__ */ new Set([".toml", ".yaml", ".yml", ".json"]);
|
|
@@ -21937,7 +22002,7 @@ function toStrArray(v) {
|
|
|
21937
22002
|
return [];
|
|
21938
22003
|
}
|
|
21939
22004
|
async function parseRules(rulesDir, opts = {}) {
|
|
21940
|
-
const files = await
|
|
22005
|
+
const files = await readDirRecursiveNoSymlinks(rulesDir);
|
|
21941
22006
|
const mdFiles = files.filter((f) => {
|
|
21942
22007
|
if (!f.endsWith(".md")) return false;
|
|
21943
22008
|
const name = basename(f, ".md");
|
|
@@ -21946,6 +22011,7 @@ async function parseRules(rulesDir, opts = {}) {
|
|
|
21946
22011
|
warnIfUnrecognizedResourceFormats("rules", rulesDir, files, mdFiles, {
|
|
21947
22012
|
handledByOtherReader: opts.handledByOtherReader
|
|
21948
22013
|
});
|
|
22014
|
+
assertNoBasenameCollisions("rule", mdFiles, ".md");
|
|
21949
22015
|
const rules = [];
|
|
21950
22016
|
for (const path of mdFiles) {
|
|
21951
22017
|
const content = await readFileSafe(path);
|
|
@@ -21994,7 +22060,7 @@ function toToolsArray2(v) {
|
|
|
21994
22060
|
return [];
|
|
21995
22061
|
}
|
|
21996
22062
|
async function parseCommands(commandsDir, opts = {}) {
|
|
21997
|
-
const files = await
|
|
22063
|
+
const files = await readDirRecursiveNoSymlinks(commandsDir);
|
|
21998
22064
|
const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
|
|
21999
22065
|
warnIfUnrecognizedResourceFormats("commands", commandsDir, files, mdFiles, {
|
|
22000
22066
|
handledByOtherReader: opts.handledByOtherReader
|
|
@@ -22054,7 +22120,7 @@ function toHooks2(v) {
|
|
|
22054
22120
|
return {};
|
|
22055
22121
|
}
|
|
22056
22122
|
async function parseAgents(agentsDir, opts = {}) {
|
|
22057
|
-
const files = await
|
|
22123
|
+
const files = await readDirRecursiveNoSymlinks(agentsDir);
|
|
22058
22124
|
const mdFiles = files.filter((f) => f.endsWith(".md") && !basename(f).startsWith("_"));
|
|
22059
22125
|
warnIfUnrecognizedResourceFormats("agents", agentsDir, files, mdFiles, {
|
|
22060
22126
|
handledByOtherReader: opts.handledByOtherReader
|
|
@@ -22638,7 +22704,7 @@ async function importEntities(kind, dir, opts) {
|
|
|
22638
22704
|
}
|
|
22639
22705
|
async function readToolNativeEntities(srcDir, targetId, kind, parseOpts = {}) {
|
|
22640
22706
|
const specs = directorySpecsFor(getDescriptor(targetId)?.importer, kind);
|
|
22641
|
-
const allFiles = await
|
|
22707
|
+
const allFiles = await readDirRecursiveNoSymlinks(srcDir);
|
|
22642
22708
|
const nonMdExtensions = /* @__PURE__ */ new Set();
|
|
22643
22709
|
for (const spec of specs) {
|
|
22644
22710
|
for (const ext of spec.extensions) {
|
|
@@ -22995,7 +23061,7 @@ async function stageSingleFile(sourcePath, destinationDir, acceptMdc) {
|
|
|
22995
23061
|
async function stageMarkdownCollection(sourceRoot, destinationDir, acceptMdc) {
|
|
22996
23062
|
const info = await stat(sourceRoot);
|
|
22997
23063
|
if (info.isFile()) return stageSingleFile(sourceRoot, destinationDir, acceptMdc);
|
|
22998
|
-
const files = (await
|
|
23064
|
+
const files = (await readDirRecursiveNoSymlinks(sourceRoot)).filter(
|
|
22999
23065
|
(file) => isAcceptedFile(file, acceptMdc) && !isBoilerplate(basename(file))
|
|
23000
23066
|
);
|
|
23001
23067
|
if (files.length === 0) {
|
|
@@ -23042,7 +23108,7 @@ async function stagePreferredSkills(sourceRoot, destinationDir, preferredSkillNa
|
|
|
23042
23108
|
}
|
|
23043
23109
|
const wanted = new Set(preferredSkillNames);
|
|
23044
23110
|
const matches = /* @__PURE__ */ new Map();
|
|
23045
|
-
for (const file of await
|
|
23111
|
+
for (const file of await readDirRecursiveNoSymlinks(sourceRoot)) {
|
|
23046
23112
|
if (!file.endsWith("/SKILL.md") && !file.endsWith("\\SKILL.md")) continue;
|
|
23047
23113
|
const skillDir = dirname(file);
|
|
23048
23114
|
const skillName = basename(skillDir);
|
|
@@ -23090,7 +23156,7 @@ async function stageSkills(sourceRoot, destinationDir, options = {}) {
|
|
|
23090
23156
|
return;
|
|
23091
23157
|
}
|
|
23092
23158
|
await mkdirp(destinationDir);
|
|
23093
|
-
const entries = await
|
|
23159
|
+
const entries = await readDirRecursiveNoSymlinks(sourceRoot);
|
|
23094
23160
|
const roots = /* @__PURE__ */ new Set();
|
|
23095
23161
|
for (const file of entries.filter(
|
|
23096
23162
|
(entry) => entry.endsWith("/SKILL.md") || entry.endsWith("\\SKILL.md")
|
|
@@ -23784,15 +23850,15 @@ function lessonsPaths(projectRoot) {
|
|
|
23784
23850
|
function toRelPath(projectRoot, absolute) {
|
|
23785
23851
|
return relative(projectRoot, absolute).split(sep).join("/");
|
|
23786
23852
|
}
|
|
23787
|
-
var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING
|
|
23853
|
+
var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING)
|
|
23788
23854
|
|
|
23789
|
-
Graph \`.agentsmesh/lessons/lessons.json\` is canonical
|
|
23855
|
+
Graph \`.agentsmesh/lessons/lessons.json\` is canonical; never hand-edit it. Manual: \`lessons\` skill.
|
|
23790
23856
|
|
|
23791
|
-
**Recall
|
|
23857
|
+
**Recall:** before every file edit or state-changing command, MUST run \`agentsmesh lessons query --file <path> --cmd <command>\` and obey matches. Pure-read commands and recall itself are exempt.
|
|
23792
23858
|
|
|
23793
|
-
**Capture
|
|
23859
|
+
**Capture:** after any failure, user correction, regression, wrong assumption, useful surprise, repeated friction, or non-obvious fix, MUST self-critique and run \`agentsmesh lessons add "<imperative rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`.
|
|
23794
23860
|
|
|
23795
|
-
|
|
23861
|
+
**Before final:** report \`Lesson: captured <id>\` or \`Lesson: none\`. No recall/capture gate = task incomplete. No shell: use \`lessons_query\` / \`lessons_add\`.`;
|
|
23796
23862
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
23797
23863
|
var MAX_FILES = 2e5;
|
|
23798
23864
|
function listProjectFiles(projectRoot) {
|
|
@@ -25741,15 +25807,27 @@ async function acquireLessonsLock(projectRoot, opts = {}) {
|
|
|
25741
25807
|
function emptyGraph() {
|
|
25742
25808
|
return { version: 1, lessons: {}, topics: {}, triggers: {} };
|
|
25743
25809
|
}
|
|
25810
|
+
function findingKey(f) {
|
|
25811
|
+
return `${f.code}|${f.triggerId ?? ""}|${f.lessonId ?? ""}`;
|
|
25812
|
+
}
|
|
25813
|
+
function errorSignatures(report) {
|
|
25814
|
+
return new Set(report.findings.filter((f) => f.level === "error").map(findingKey));
|
|
25815
|
+
}
|
|
25744
25816
|
async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
25745
25817
|
const release = await acquireLessonsLock(projectRoot, { retries: options.retries });
|
|
25746
25818
|
try {
|
|
25747
25819
|
const graph = tryLoadLessonsGraph(projectRoot) ?? emptyGraph();
|
|
25820
|
+
const baseline = errorSignatures(validateLessonsGraph(graph));
|
|
25748
25821
|
const result = await mutator(graph);
|
|
25749
25822
|
const report = validateLessonsGraph(graph);
|
|
25750
|
-
|
|
25751
|
-
|
|
25752
|
-
|
|
25823
|
+
const introduced = report.findings.filter(
|
|
25824
|
+
(f) => f.level === "error" && !baseline.has(findingKey(f))
|
|
25825
|
+
);
|
|
25826
|
+
if (introduced.length > 0) {
|
|
25827
|
+
const errors = introduced.map((f) => `${f.code}: ${f.message}`).join("; ");
|
|
25828
|
+
throw new Error(
|
|
25829
|
+
`mutateLessonsGraph: refusing to write \u2014 this change introduces ${errors}. (Pre-existing graph issues are not blocking; run \`agentsmesh lessons validate\` to review and \`lessons untrigger\`/\`prune\` to repair them.)`
|
|
25830
|
+
);
|
|
25753
25831
|
}
|
|
25754
25832
|
saveLessonsGraph(projectRoot, graph);
|
|
25755
25833
|
return result;
|
|
@@ -26220,6 +26298,17 @@ function isCoveredByExisting(candidate, existing) {
|
|
|
26220
26298
|
|
|
26221
26299
|
// src/targets/projection/lessons-paragraph.ts
|
|
26222
26300
|
init_managed_blocks();
|
|
26301
|
+
var LEGACY_RAW_FORMS = [
|
|
26302
|
+
`## Lessons (BLOCKING REQUIREMENT \u2014 MUST run both, no exceptions; the user will check)
|
|
26303
|
+
|
|
26304
|
+
Graph \`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit. Manual: the \`lessons\` skill.
|
|
26305
|
+
|
|
26306
|
+
**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.
|
|
26307
|
+
|
|
26308
|
+
**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>\`.
|
|
26309
|
+
|
|
26310
|
+
No shell? Use the \`lessons_query\` / \`lessons_add\` MCP tools. Skip either and the system does not exist.`
|
|
26311
|
+
];
|
|
26223
26312
|
var LESSONS_PARAGRAPH_BLOCK = `${LESSONS_CONTRACT_START}
|
|
26224
26313
|
${LESSONS_PROCEDURAL_RULE}
|
|
26225
26314
|
${LESSONS_CONTRACT_END}`;
|
|
@@ -26232,76 +26321,66 @@ function stripLessonsParagraph(content) {
|
|
|
26232
26321
|
return stripRawProceduralRule(withoutBlock).trim();
|
|
26233
26322
|
}
|
|
26234
26323
|
function stripRawProceduralRule(content) {
|
|
26235
|
-
return
|
|
26324
|
+
return [LESSONS_PROCEDURAL_RULE, ...LEGACY_RAW_FORMS].reduce(
|
|
26325
|
+
(next, rule) => next.replace(`
|
|
26236
26326
|
|
|
26237
|
-
${
|
|
26327
|
+
${rule}`, "").replace(rule, ""),
|
|
26328
|
+
content
|
|
26329
|
+
);
|
|
26238
26330
|
}
|
|
26239
26331
|
|
|
26240
26332
|
// src/lessons/skill.ts
|
|
26241
26333
|
init_markdown();
|
|
26242
26334
|
var LESSONS_SKILL_NAME = "lessons";
|
|
26243
|
-
var LESSONS_SKILL_DESCRIPTION = "
|
|
26244
|
-
var LESSONS_SKILL_BODY = `# Lessons \u2014 operating manual
|
|
26335
|
+
var LESSONS_SKILL_DESCRIPTION = "Use when about to edit a file or run a state-changing command, or after any failure, correction, or surprising result.";
|
|
26336
|
+
var LESSONS_SKILL_BODY = `# Lessons \u2014 operating manual (Iron Law)
|
|
26245
26337
|
|
|
26246
|
-
|
|
26247
|
-
\`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit.
|
|
26338
|
+
## The Iron Law
|
|
26248
26339
|
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
\`agentsmesh lessons query --file <path> --cmd <command>\` (add \`--keyword <text>\` to
|
|
26252
|
-
match by task), then apply every rule returned. Scope is MUTATING actions: file edits
|
|
26253
|
-
and state-changing commands (build/test/install/migrate/git-write). Pure-read commands
|
|
26254
|
-
(cat/ls/grep/git-log; read-only) and the recall query itself are **exempt** \u2014 no
|
|
26255
|
-
infinite regress. A predicate-less query is rejected; **keyword-only recall is the
|
|
26256
|
-
anti-pattern** \u2014 most lessons are keyed to a \`file_glob\`/\`command_pattern\` and won't
|
|
26257
|
-
surface (the CLI warns). Excuses ("small edit", "I already know this", "later") all
|
|
26258
|
-
mean: query first \u2014 skipping recall on a mutating action is a process violation, and
|
|
26259
|
-
the user will check.
|
|
26260
|
-
|
|
26261
|
-
## Capture \u2014 immediately after any failure
|
|
26340
|
+
**NO MUTATION WITHOUT RECALL. NO COMPLETION WITHOUT A CAPTURE DECISION.**
|
|
26262
26341
|
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
\`
|
|
26342
|
+
Violating the letter is violating the spirit. Edited a file or ran a state-changing
|
|
26343
|
+
command without recall? Process violation. Did the turn hit a failure / correction /
|
|
26344
|
+
regression / wrong assumption / surprise and you have not captured (nor stated
|
|
26345
|
+
\`Lesson: none\`)? The task is INCOMPLETE \u2014 and the user will check. The graph
|
|
26346
|
+
\`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit.
|
|
26267
26347
|
|
|
26268
|
-
|
|
26269
|
-
(\`UNRECALLABLE_LESSON\`) when EVERY trigger is dead on the mandatory \`--file\`/\`--cmd\`
|
|
26270
|
-
recall path \u2014 a stopword-only keyword ("state of the art"), or an invalid/ReDoS
|
|
26271
|
-
command regex \u2014 because the lesson could never be recalled there. Prefer
|
|
26272
|
-
\`--trigger-file\`: the most reliable trigger, it fires on \`--file\` recall. A keyword
|
|
26273
|
-
alone is discouraged (\`KEYWORD_ONLY_LESSON\`); paraphrasing an existing rule warns
|
|
26274
|
-
(\`NEAR_DUPLICATE_LESSON\` \u2014 update that lesson instead).
|
|
26275
|
-
- **One imperative sentence.** A rule over 2000 chars is rejected (\`OVERSIZED_RULE\`) \u2014
|
|
26276
|
-
trim it or split into separate lessons; don't paste a log/diff.
|
|
26277
|
-
- Widen with \`--trigger-cmd <regex>\` / \`--trigger-kw <text>\`. New area:
|
|
26278
|
-
\`--new-topic --topic-summary "<line>"\` (list ids with \`agentsmesh lessons topics\`).
|
|
26348
|
+
## Recall \u2014 before each file edit and each state-changing command
|
|
26279
26349
|
|
|
26280
|
-
|
|
26350
|
+
\`agentsmesh lessons query --file <path> --cmd <command>\`, then apply every rule.
|
|
26351
|
+
Pure-read commands (read-only) and the query itself are exempt. **keyword-only recall
|
|
26352
|
+
is the anti-pattern** \u2014 lessons are keyed to a \`file_glob\`/\`command_pattern\`.
|
|
26281
26353
|
|
|
26282
|
-
|
|
26283
|
-
\`lessons_deprecate\` (retire). validate / prune / merge / import-md are CLI-only.
|
|
26354
|
+
## Capture \u2014 Gate Function (before any completion claim)
|
|
26284
26355
|
|
|
26285
|
-
|
|
26356
|
+
1. **SELF-CRITIQUE**: any failure, correction, regression, wrong assumption,
|
|
26357
|
+
useful surprise, repeated friction, or non-obvious fix? Failing
|
|
26358
|
+
tests/lint/typecheck and user/review corrections \u2014 yours or anyone's \u2014 all count.
|
|
26359
|
+
2. **CAPTURE** a reusable imperative rule with an effective trigger (else say so):
|
|
26360
|
+
\`agentsmesh lessons add "<rule>" --topic <id> --trigger-file <glob>\`
|
|
26361
|
+
3. **RECEIPT**: emit \`Lesson: captured <id>\` or \`Lesson: none\`.
|
|
26286
26362
|
|
|
26287
|
-
|
|
26288
|
-
\`
|
|
26289
|
-
|
|
26290
|
-
|
|
26363
|
+
At least one _effective_ trigger is required or the capture is rejected
|
|
26364
|
+
(\`UNRECALLABLE_LESSON\`); prefer \`--trigger-file\`. No shell \u2192 MCP \`lessons_query\`,
|
|
26365
|
+
\`lessons_add\`, \`lessons_topics\`, \`lessons_show\`, \`lessons_deprecate\`. Run
|
|
26366
|
+
\`agentsmesh lessons --help\` for every subcommand and flag: query, add, topics, show,
|
|
26367
|
+
deprecate, merge, untrigger, strip-markers, prune, journal, validate, stats, import-md.
|
|
26291
26368
|
|
|
26292
|
-
|
|
26369
|
+
### Rationalization Prevention \u2014 these excuses mean STOP
|
|
26293
26370
|
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26371
|
+
| Excuse | Reality |
|
|
26372
|
+
| --- | --- |
|
|
26373
|
+
| "Small edit / I already know this / later" | Query first \u2014 skipping recall is a process violation |
|
|
26374
|
+
| "Nothing reusable here" | You hit a failure/surprise \u2014 name it or capture it |
|
|
26375
|
+
| "My own TDD red, not a real failure" | A red you did not predict IS a lesson |
|
|
26376
|
+
| "I fixed one site; the twin is obvious" | Capture it \u2014 the unfixed twin is what gets missed |
|
|
26377
|
+
| "Different words, so the rule doesn't apply" | Spirit over letter |
|
|
26299
26378
|
|
|
26300
|
-
##
|
|
26379
|
+
## Lesson gate \u2014 before final response
|
|
26301
26380
|
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
26381
|
+
The final response MUST carry the receipt: \`Lesson: captured <id>\` or \`Lesson: none\`.
|
|
26382
|
+
No receipt = task incomplete. Do not capture one-off facts, task summaries, or project
|
|
26383
|
+
context \u2014 only reusable imperative rules with an effective trigger.`;
|
|
26305
26384
|
var LESSONS_SKILL_FILE = serializeFrontmatter(
|
|
26306
26385
|
{ name: LESSONS_SKILL_NAME, description: LESSONS_SKILL_DESCRIPTION },
|
|
26307
26386
|
LESSONS_SKILL_BODY
|