agentsmesh 0.20.0 → 0.22.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 +70 -0
- package/README.md +14 -1
- package/dist/canonical.js +81 -13
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +206 -162
- package/dist/engine.js +181 -15
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +651 -232
- package/dist/index.js.map +1 -1
- package/dist/lessons.d.ts +155 -0
- package/dist/lessons.js +305 -0
- package/dist/lessons.js.map +1 -0
- package/dist/targets.js +22 -2
- package/dist/targets.js.map +1 -1
- package/package.json +12 -3
package/dist/engine.js
CHANGED
|
@@ -2,7 +2,7 @@ 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
4
|
import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, access, realpath, mkdtemp, cp } from 'fs/promises';
|
|
5
|
-
import {
|
|
5
|
+
import { existsSync, readFileSync, constants, realpathSync, statSync } from 'fs';
|
|
6
6
|
import { parse as parse$1 } from 'smol-toml';
|
|
7
7
|
import { Buffer } from 'buffer';
|
|
8
8
|
import { homedir, tmpdir } from 'os';
|
|
@@ -768,6 +768,33 @@ async function readDirRecursive(dir, visited, branchSegments) {
|
|
|
768
768
|
);
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
|
+
async function readDirRecursiveNoSymlinks(dir, branchSegments) {
|
|
772
|
+
const currentBranchSegments = branchSegments ?? [basename(dir)];
|
|
773
|
+
try {
|
|
774
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
775
|
+
const files = [];
|
|
776
|
+
for (const ent of entries) {
|
|
777
|
+
if (ent.isSymbolicLink()) continue;
|
|
778
|
+
const full = join(dir, ent.name);
|
|
779
|
+
if (ent.isDirectory()) {
|
|
780
|
+
const nextSegments = [...currentBranchSegments, ent.name];
|
|
781
|
+
if (shouldSkipRecursiveBranch(nextSegments)) continue;
|
|
782
|
+
files.push(...await readDirRecursiveNoSymlinks(full, nextSegments));
|
|
783
|
+
} else if (ent.isFile()) {
|
|
784
|
+
files.push(full);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return files;
|
|
788
|
+
} catch (err) {
|
|
789
|
+
const e = err;
|
|
790
|
+
if (e.code === "ENOENT" || e.code === "ENOTDIR" || e.code === "EACCES") return [];
|
|
791
|
+
throw new FileSystemError(
|
|
792
|
+
dir,
|
|
793
|
+
`Failed to read directory ${dir}: ${e.message}. Check permissions.`,
|
|
794
|
+
{ cause: err, errnoCode: e.code }
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
771
798
|
var MAX_RECURSIVE_DEPTH, MAX_SEGMENT_REPETITIONS;
|
|
772
799
|
var init_fs_traverse = __esm({
|
|
773
800
|
"src/utils/filesystem/fs-traverse.ts"() {
|
|
@@ -1022,7 +1049,7 @@ ${legacy}`, "");
|
|
|
1022
1049
|
}
|
|
1023
1050
|
return result.trim();
|
|
1024
1051
|
}
|
|
1025
|
-
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
1052
|
+
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY_V9, ROOT_INSTRUCTION_BODY_V10, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_CONTRACT_WITH_V9_BODY, AGENTSMESH_CONTRACT_WITH_V10_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
1026
1053
|
var init_root_instruction_paragraph = __esm({
|
|
1027
1054
|
"src/targets/projection/root-instruction-paragraph.ts"() {
|
|
1028
1055
|
init_managed_blocks();
|
|
@@ -1034,7 +1061,9 @@ var init_root_instruction_paragraph = __esm({
|
|
|
1034
1061
|
ROOT_INSTRUCTION_BODY_V6 = "Create canonical files in `.agentsmesh`: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Then run `agentsmesh generate`.";
|
|
1035
1062
|
ROOT_INSTRUCTION_BODY_V7 = "`.agentsmesh` is the only folder you edit or add these files in: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Do not edit generated tool files; run `agentsmesh generate`.";
|
|
1036
1063
|
ROOT_INSTRUCTION_BODY_V8 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, and `merge` as needed; never edit generated tool files.";
|
|
1037
|
-
|
|
1064
|
+
ROOT_INSTRUCTION_BODY_V9 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
1065
|
+
ROOT_INSTRUCTION_BODY_V10 = "**MUST follow when changing any rule, agent, command, skill, hook, MCP server, permission, or ignore pattern.** `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
1066
|
+
ROOT_INSTRUCTION_BODY = "**NEVER edit generated files** (`.claude/`, `.cursor/`, `AGENTS.md`, `.github/copilot-instructions.md`, and similar target outputs) \u2014 `agentsmesh generate` overwrites them. **All changes MUST go through `.agentsmesh` first**: edit `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally); if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed.";
|
|
1038
1067
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
|
|
1039
1068
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
|
|
1040
1069
|
|
|
@@ -1063,12 +1092,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
|
|
|
1063
1092
|
AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
|
|
1064
1093
|
|
|
1065
1094
|
${ROOT_INSTRUCTION_BODY_V8}`;
|
|
1095
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
|
|
1096
|
+
|
|
1097
|
+
${ROOT_INSTRUCTION_BODY_V9}`;
|
|
1098
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
|
|
1099
|
+
|
|
1100
|
+
${ROOT_INSTRUCTION_BODY_V10}`;
|
|
1066
1101
|
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
|
|
1067
1102
|
## AgentsMesh Generation Contract
|
|
1068
1103
|
|
|
1069
1104
|
${ROOT_INSTRUCTION_BODY}
|
|
1070
1105
|
${ROOT_CONTRACT_END}`;
|
|
1071
1106
|
LEGACY_FORMS = [
|
|
1107
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY,
|
|
1108
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY,
|
|
1072
1109
|
AGENTSMESH_CONTRACT_WITH_V8_BODY,
|
|
1073
1110
|
AGENTSMESH_CONTRACT_WITH_V7_BODY,
|
|
1074
1111
|
AGENTSMESH_CONTRACT_WITH_V6_BODY,
|
|
@@ -15873,9 +15910,18 @@ function generateIgnore12(canonical) {
|
|
|
15873
15910
|
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
15874
15911
|
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
15875
15912
|
}
|
|
15913
|
+
function renderQwenGlobalInstructions(canonical) {
|
|
15914
|
+
const root = canonical.rules.find((rule) => rule.root);
|
|
15915
|
+
const nonRootRules = canonical.rules.filter((rule) => {
|
|
15916
|
+
if (rule.root) return false;
|
|
15917
|
+
return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
|
|
15918
|
+
});
|
|
15919
|
+
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
15920
|
+
}
|
|
15876
15921
|
var init_generator26 = __esm({
|
|
15877
15922
|
"src/targets/qwen-code/generator.ts"() {
|
|
15878
15923
|
init_markdown();
|
|
15924
|
+
init_managed_blocks();
|
|
15879
15925
|
init_constants21();
|
|
15880
15926
|
}
|
|
15881
15927
|
});
|
|
@@ -15958,6 +16004,7 @@ var init_qwen_code2 = __esm({
|
|
|
15958
16004
|
};
|
|
15959
16005
|
globalLayout22 = {
|
|
15960
16006
|
rootInstructionPath: QWEN_GLOBAL_ROOT,
|
|
16007
|
+
renderPrimaryRootInstruction: renderQwenGlobalInstructions,
|
|
15961
16008
|
skillDir: QWEN_GLOBAL_SKILLS_DIR,
|
|
15962
16009
|
managedOutputs: {
|
|
15963
16010
|
dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
|
|
@@ -20147,6 +20194,19 @@ init_fs();
|
|
|
20147
20194
|
|
|
20148
20195
|
// src/config/remote/git-remote.ts
|
|
20149
20196
|
init_fs();
|
|
20197
|
+
|
|
20198
|
+
// src/utils/output/redact-url-secrets.ts
|
|
20199
|
+
var URL_WITH_CREDENTIALS = /([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)([^/@\s"'<>]+)@([^\s"'<>]+)/g;
|
|
20200
|
+
function redactUrlSecrets(message) {
|
|
20201
|
+
return message.replace(
|
|
20202
|
+
URL_WITH_CREDENTIALS,
|
|
20203
|
+
(_full, scheme, _userinfo, rest) => {
|
|
20204
|
+
return `${scheme}***@${rest}`;
|
|
20205
|
+
}
|
|
20206
|
+
);
|
|
20207
|
+
}
|
|
20208
|
+
|
|
20209
|
+
// src/config/remote/git-remote.ts
|
|
20150
20210
|
var execFileAsync = promisify(execFile);
|
|
20151
20211
|
var REPO_DIRNAME = "repo";
|
|
20152
20212
|
function ensureNotFlag(value, kind) {
|
|
@@ -20180,12 +20240,13 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
20180
20240
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
20181
20241
|
const allowFallback = options.allowOfflineFallback !== false;
|
|
20182
20242
|
if (allowFallback && await hasCachedRepo(cacheRepoDir)) {
|
|
20243
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
20183
20244
|
console.warn(
|
|
20184
|
-
`[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${
|
|
20245
|
+
`[agentsmesh] Remote fetch failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
|
|
20185
20246
|
);
|
|
20186
20247
|
return readCachedRepo(cacheRepoDir);
|
|
20187
20248
|
}
|
|
20188
|
-
throw err;
|
|
20249
|
+
throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
|
|
20189
20250
|
}
|
|
20190
20251
|
}
|
|
20191
20252
|
async function readCachedRepo(repoDir) {
|
|
@@ -20346,13 +20407,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
|
|
|
20346
20407
|
if (allowFallback && await exists(extractDir)) {
|
|
20347
20408
|
const topDir2 = await findExtractTopDir(extractDir);
|
|
20348
20409
|
if (topDir2) {
|
|
20410
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
20349
20411
|
console.warn(
|
|
20350
|
-
`[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${
|
|
20412
|
+
`[agentsmesh] Network failed for ${extendName}; using cached version. Error: ${redactUrlSecrets(rawMsg)}`
|
|
20351
20413
|
);
|
|
20352
20414
|
return { resolvedPath: join(extractDir, topDir2), version: tag };
|
|
20353
20415
|
}
|
|
20354
20416
|
}
|
|
20355
|
-
throw err;
|
|
20417
|
+
throw err instanceof Error ? Object.assign(new Error(redactUrlSecrets(err.message)), { cause: err.cause }) : err;
|
|
20356
20418
|
}
|
|
20357
20419
|
await rm(extractDir, { recursive: true, force: true });
|
|
20358
20420
|
await mkdir(extractDir, { recursive: true });
|
|
@@ -20363,12 +20425,14 @@ async function fetchGithubRemoteExtend(parsed, extendName, options, cacheDir, bu
|
|
|
20363
20425
|
file: tarPath,
|
|
20364
20426
|
cwd: extractDir,
|
|
20365
20427
|
strict: true,
|
|
20428
|
+
// Allowlist entry types instead of denylist: only `File` and `Directory`
|
|
20429
|
+
// can be extracted. Hardlinks (`Link`), symlinks (`SymbolicLink`), FIFOs,
|
|
20430
|
+
// character/block devices, and any future/exotic tar entry type are
|
|
20431
|
+
// rejected. A denylist would silently let an unknown variant through.
|
|
20366
20432
|
filter: (entryPath, entry) => {
|
|
20367
20433
|
if (isZipSlipPath(entryPath)) return false;
|
|
20368
|
-
|
|
20369
|
-
|
|
20370
|
-
}
|
|
20371
|
-
return true;
|
|
20434
|
+
const type = entry && "type" in entry ? entry.type : void 0;
|
|
20435
|
+
return type === "File" || type === "Directory";
|
|
20372
20436
|
}
|
|
20373
20437
|
});
|
|
20374
20438
|
} finally {
|
|
@@ -20466,8 +20530,11 @@ function parseGitSource(source) {
|
|
|
20466
20530
|
return null;
|
|
20467
20531
|
}
|
|
20468
20532
|
const allowInsecure = process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "1" || process.env.AGENTSMESH_ALLOW_INSECURE_GIT === "true";
|
|
20469
|
-
const
|
|
20470
|
-
|
|
20533
|
+
const allowLocalGit = process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "1" || process.env.AGENTSMESH_ALLOW_LOCAL_GIT === "true";
|
|
20534
|
+
const allowed = ["https:", "ssh:"];
|
|
20535
|
+
if (allowInsecure) allowed.push("http:");
|
|
20536
|
+
if (allowLocalGit) allowed.push("file:");
|
|
20537
|
+
if (!allowed.includes(parsedUrl.protocol)) {
|
|
20471
20538
|
return null;
|
|
20472
20539
|
}
|
|
20473
20540
|
return { url, ref };
|
|
@@ -20926,6 +20993,7 @@ async function parseAgents(agentsDir, opts = {}) {
|
|
|
20926
20993
|
|
|
20927
20994
|
// src/canonical/features/skills.ts
|
|
20928
20995
|
init_fs();
|
|
20996
|
+
init_fs_traverse();
|
|
20929
20997
|
init_markdown();
|
|
20930
20998
|
init_boilerplate_filter();
|
|
20931
20999
|
async function readContent(path) {
|
|
@@ -20944,7 +21012,7 @@ function sanitizeSkillName(raw) {
|
|
|
20944
21012
|
return raw.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
20945
21013
|
}
|
|
20946
21014
|
async function listSupportingFiles(skillDir) {
|
|
20947
|
-
const files = await
|
|
21015
|
+
const files = await readDirRecursiveNoSymlinks(skillDir);
|
|
20948
21016
|
const result = [];
|
|
20949
21017
|
for (const absPath of files) {
|
|
20950
21018
|
const raw = absPath.slice(skillDir.length + 1);
|
|
@@ -22517,6 +22585,104 @@ function lintRuleScopeInversion(input) {
|
|
|
22517
22585
|
}
|
|
22518
22586
|
return out2;
|
|
22519
22587
|
}
|
|
22588
|
+
var TriggersSchema = z.object({
|
|
22589
|
+
file_globs: z.array(z.string()),
|
|
22590
|
+
command_patterns: z.array(z.string()),
|
|
22591
|
+
keywords: z.array(z.string())
|
|
22592
|
+
}).refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {
|
|
22593
|
+
message: "cluster must declare at least one trigger of any type"
|
|
22594
|
+
});
|
|
22595
|
+
var ClusterSchema = z.object({
|
|
22596
|
+
topic: z.string().regex(/^[a-z0-9-]+$/, "topic must be kebab-case"),
|
|
22597
|
+
/**
|
|
22598
|
+
* Project-relative path (forward slashes) to the cluster's markdown body.
|
|
22599
|
+
* Conventionally `.agentsmesh/lessons/topics/<topic>.md`, but any project
|
|
22600
|
+
* path is accepted — universal across every agent target.
|
|
22601
|
+
*/
|
|
22602
|
+
file: z.string().regex(/\.md$/, "file must be a .md path"),
|
|
22603
|
+
summary: z.string().min(1),
|
|
22604
|
+
triggers: TriggersSchema
|
|
22605
|
+
});
|
|
22606
|
+
var LessonsIndexSchema = z.object({
|
|
22607
|
+
version: z.literal(1),
|
|
22608
|
+
/**
|
|
22609
|
+
* Zero clusters is valid — supports `agentsmesh init --lessons` scaffolding a
|
|
22610
|
+
* fresh project. Topics accumulate via `distill:apply` as failures are
|
|
22611
|
+
* captured.
|
|
22612
|
+
*/
|
|
22613
|
+
clusters: z.array(ClusterSchema)
|
|
22614
|
+
});
|
|
22615
|
+
var BASE_REL = ".agentsmesh/lessons";
|
|
22616
|
+
function lessonsPaths(projectRoot) {
|
|
22617
|
+
const base = join(projectRoot, BASE_REL);
|
|
22618
|
+
return {
|
|
22619
|
+
base,
|
|
22620
|
+
journal: join(base, "journal.md"),
|
|
22621
|
+
index: join(base, "index.yaml"),
|
|
22622
|
+
ledger: join(base, "distill-ledger.yaml"),
|
|
22623
|
+
proposal: join(base, "distill-proposal.md"),
|
|
22624
|
+
topicsDir: join(base, "topics")
|
|
22625
|
+
};
|
|
22626
|
+
}
|
|
22627
|
+
|
|
22628
|
+
// src/core/lint/shared/lessons.ts
|
|
22629
|
+
var LESSONS_TARGET = "lessons";
|
|
22630
|
+
var INDEX_REL = ".agentsmesh/lessons/index.yaml";
|
|
22631
|
+
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
22632
|
+
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
22633
|
+
var RULES_HEADING = /^## Rules\b/m;
|
|
22634
|
+
function lintLessonsSubsystem(projectRoot, scope) {
|
|
22635
|
+
if (scope === "global") return [];
|
|
22636
|
+
const paths = lessonsPaths(projectRoot);
|
|
22637
|
+
if (!existsSync(paths.index)) return [];
|
|
22638
|
+
const out2 = [];
|
|
22639
|
+
const parsed = LessonsIndexSchema.safeParse(parse(readFileSync(paths.index, "utf8")));
|
|
22640
|
+
if (!parsed.success) {
|
|
22641
|
+
return [diag("error", INDEX_REL, `index.yaml is invalid: ${parsed.error.issues[0].message}`)];
|
|
22642
|
+
}
|
|
22643
|
+
for (const cluster of parsed.data.clusters) {
|
|
22644
|
+
const topicAbs = join(projectRoot, cluster.file);
|
|
22645
|
+
if (!existsSync(topicAbs)) {
|
|
22646
|
+
out2.push(
|
|
22647
|
+
diag("error", cluster.file, `topic file for cluster "${cluster.topic}" does not exist.`)
|
|
22648
|
+
);
|
|
22649
|
+
continue;
|
|
22650
|
+
}
|
|
22651
|
+
if (!RULES_HEADING.test(readFileSync(topicAbs, "utf8"))) {
|
|
22652
|
+
out2.push(
|
|
22653
|
+
diag("warning", cluster.file, `topic "${cluster.topic}" is missing a "## Rules" section.`)
|
|
22654
|
+
);
|
|
22655
|
+
}
|
|
22656
|
+
for (const pattern of cluster.triggers.command_patterns) {
|
|
22657
|
+
try {
|
|
22658
|
+
new RegExp(pattern);
|
|
22659
|
+
} catch {
|
|
22660
|
+
out2.push(
|
|
22661
|
+
diag(
|
|
22662
|
+
"warning",
|
|
22663
|
+
INDEX_REL,
|
|
22664
|
+
`cluster "${cluster.topic}" command_patterns entry is not a valid regex: ${pattern}`
|
|
22665
|
+
)
|
|
22666
|
+
);
|
|
22667
|
+
}
|
|
22668
|
+
}
|
|
22669
|
+
}
|
|
22670
|
+
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
22671
|
+
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|
|
22672
|
+
if (!LESSONS_HEADING.test(rootRuleBody)) {
|
|
22673
|
+
out2.push(
|
|
22674
|
+
diag(
|
|
22675
|
+
"warning",
|
|
22676
|
+
ROOT_RULE_REL,
|
|
22677
|
+
'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
|
|
22678
|
+
)
|
|
22679
|
+
);
|
|
22680
|
+
}
|
|
22681
|
+
return out2;
|
|
22682
|
+
}
|
|
22683
|
+
function diag(level, file, message) {
|
|
22684
|
+
return { level, file, target: LESSONS_TARGET, message };
|
|
22685
|
+
}
|
|
22520
22686
|
|
|
22521
22687
|
// src/core/lint/linter.ts
|
|
22522
22688
|
var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
|
|
@@ -22537,7 +22703,7 @@ async function runLint(config, canonical, projectRoot, targetFilter, options = {
|
|
|
22537
22703
|
const hasMcp = config.features.includes("mcp");
|
|
22538
22704
|
const hasPermissions = config.features.includes("permissions");
|
|
22539
22705
|
const hasHooks = config.features.includes("hooks");
|
|
22540
|
-
const diagnostics = [];
|
|
22706
|
+
const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
|
|
22541
22707
|
const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
|
|
22542
22708
|
for (const target31 of targets) {
|
|
22543
22709
|
const fullDesc = getDescriptor(target31);
|