agentsmesh 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +244 -0
- package/README.md +71 -9
- package/dist/canonical.d.ts +2 -2
- package/dist/canonical.js +339 -46
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +276 -223
- package/dist/engine.d.ts +5 -2
- package/dist/engine.js +1350 -87
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2993 -439
- package/dist/index.js.map +1 -1
- package/dist/init-YKxF2zpQ.d.ts +494 -0
- package/dist/lessons.d.ts +106 -0
- package/dist/lessons.js +2764 -0
- package/dist/lessons.js.map +1 -0
- package/dist/{schema-CLmR2JOb.d.ts → schema-CzaoYJlG.d.ts} +9 -1
- package/dist/{target-descriptor-CkLWz3Xk.d.ts → target-descriptor-D6vLDI1w.d.ts} +62 -2
- package/dist/targets.d.ts +3 -3
- package/dist/targets.js +293 -32
- package/dist/targets.js.map +1 -1
- package/package.json +9 -2
- package/schemas/installs.json +12 -0
package/dist/engine.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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,
|
|
5
|
-
import {
|
|
4
|
+
import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, realpath, access, mkdtemp, cp } from 'fs/promises';
|
|
5
|
+
import { setTimeout } from 'timers/promises';
|
|
6
|
+
import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
|
|
6
7
|
import { parse as parse$1 } from 'smol-toml';
|
|
7
8
|
import { Buffer } from 'buffer';
|
|
8
9
|
import { homedir, tmpdir } from 'os';
|
|
@@ -11,6 +12,7 @@ import { execFile } from 'child_process';
|
|
|
11
12
|
import { fileURLToPath, pathToFileURL, URL } from 'url';
|
|
12
13
|
import { promisify } from 'util';
|
|
13
14
|
import * as tar from 'tar';
|
|
15
|
+
import picomatch from 'picomatch';
|
|
14
16
|
import { createTwoFilesPatch } from 'diff';
|
|
15
17
|
|
|
16
18
|
var __defProp = Object.defineProperty;
|
|
@@ -45,7 +47,7 @@ function validateCapabilityImplementations(descriptor31, capabilities17, ctx, pa
|
|
|
45
47
|
function validateDescriptor(value) {
|
|
46
48
|
return targetDescriptorSchema.parse(value);
|
|
47
49
|
}
|
|
48
|
-
var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
|
|
50
|
+
var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, nativePickStrategySchema, nativeInstallSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
|
|
49
51
|
var init_target_descriptor_schema = __esm({
|
|
50
52
|
"src/targets/catalog/target-descriptor.schema.ts"() {
|
|
51
53
|
capabilityLevelSchema = z.union([
|
|
@@ -119,6 +121,22 @@ var init_target_descriptor_schema = __esm({
|
|
|
119
121
|
officialUrl: z.string().min(1),
|
|
120
122
|
shortDescription: z.string().min(1)
|
|
121
123
|
}).passthrough();
|
|
124
|
+
nativePickStrategySchema = z.discriminatedUnion("kind", [
|
|
125
|
+
z.object({ kind: z.literal("basename"), suffix: z.string().min(1) }),
|
|
126
|
+
z.object({ kind: z.literal("skillDir") }),
|
|
127
|
+
z.object({ kind: z.literal("firstSegment") })
|
|
128
|
+
]);
|
|
129
|
+
nativeInstallSchema = z.object({
|
|
130
|
+
pickPaths: z.array(
|
|
131
|
+
z.object({
|
|
132
|
+
prefix: z.string().min(1),
|
|
133
|
+
feature: z.enum(["commands", "rules", "agents", "skills"]),
|
|
134
|
+
strategy: nativePickStrategySchema
|
|
135
|
+
})
|
|
136
|
+
).optional(),
|
|
137
|
+
inferPick: z.function().optional(),
|
|
138
|
+
dialectHints: z.array(z.object({ frontmatterKey: z.string().min(1) })).optional()
|
|
139
|
+
}).strict();
|
|
122
140
|
targetDescriptorSchemaBase = z.object({
|
|
123
141
|
id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Target id must be lowercase with hyphens"),
|
|
124
142
|
metadata: metadataSchema,
|
|
@@ -130,6 +148,7 @@ var init_target_descriptor_schema = __esm({
|
|
|
130
148
|
globalSupport: globalSupportSchema.optional(),
|
|
131
149
|
buildImportPaths: z.function(),
|
|
132
150
|
detectionPaths: z.array(z.string()),
|
|
151
|
+
nativeInstall: nativeInstallSchema.optional(),
|
|
133
152
|
excludeFromStarterInit: z.boolean().optional(),
|
|
134
153
|
conversionDefaults: conversionDefaultsSchema.optional(),
|
|
135
154
|
emitScopedSettings: z.function().optional(),
|
|
@@ -630,15 +649,19 @@ var init_errors = __esm({
|
|
|
630
649
|
LockAcquisitionError = class extends AgentsMeshError {
|
|
631
650
|
lockPath;
|
|
632
651
|
holder;
|
|
652
|
+
/** Human-readable lock name surfaced in the message, e.g. "lessons lock". */
|
|
653
|
+
label;
|
|
633
654
|
constructor(lockPath, holder, options) {
|
|
655
|
+
const label = options?.label ?? "lock";
|
|
634
656
|
super(
|
|
635
657
|
"AM_LOCK_ACQUISITION_FAILED",
|
|
636
|
-
`Could not acquire
|
|
658
|
+
`Could not acquire ${label} at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
|
|
637
659
|
options
|
|
638
660
|
);
|
|
639
661
|
this.name = "LockAcquisitionError";
|
|
640
662
|
this.lockPath = lockPath;
|
|
641
663
|
this.holder = holder;
|
|
664
|
+
this.label = label;
|
|
642
665
|
}
|
|
643
666
|
};
|
|
644
667
|
FileSystemError = class extends AgentsMeshError {
|
|
@@ -803,6 +826,27 @@ var init_fs_traverse = __esm({
|
|
|
803
826
|
MAX_SEGMENT_REPETITIONS = 3;
|
|
804
827
|
}
|
|
805
828
|
});
|
|
829
|
+
async function renameWithRetry(from, to, options = {}) {
|
|
830
|
+
const attempts = options.attempts ?? 5;
|
|
831
|
+
const delayMs = options.delayMs ?? 50;
|
|
832
|
+
for (let attempt = 0; ; attempt++) {
|
|
833
|
+
try {
|
|
834
|
+
await rename(from, to);
|
|
835
|
+
return;
|
|
836
|
+
} catch (err) {
|
|
837
|
+
const code = err.code;
|
|
838
|
+
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
839
|
+
if (!transient || attempt >= attempts - 1) throw err;
|
|
840
|
+
await setTimeout(delayMs * 2 ** attempt);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
var TRANSIENT_RENAME_CODES;
|
|
845
|
+
var init_rename_retry = __esm({
|
|
846
|
+
"src/utils/filesystem/rename-retry.ts"() {
|
|
847
|
+
TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
|
|
848
|
+
}
|
|
849
|
+
});
|
|
806
850
|
async function readFileSafe(path) {
|
|
807
851
|
try {
|
|
808
852
|
const data = await readFile(path, "utf-8");
|
|
@@ -889,6 +933,7 @@ var init_fs = __esm({
|
|
|
889
933
|
init_fs_text_encoding();
|
|
890
934
|
init_fs_traverse();
|
|
891
935
|
init_fs_text_encoding();
|
|
936
|
+
init_rename_retry();
|
|
892
937
|
}
|
|
893
938
|
});
|
|
894
939
|
function escapeRegExp(value) {
|
|
@@ -897,19 +942,24 @@ function escapeRegExp(value) {
|
|
|
897
942
|
function managedBlockPattern(start, end) {
|
|
898
943
|
return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
|
|
899
944
|
}
|
|
900
|
-
function replaceManagedBlock(content, start, end, block) {
|
|
901
|
-
const pattern = managedBlockPattern(start, end);
|
|
902
|
-
if (pattern.test(content)) {
|
|
903
|
-
return content.replace(pattern, block).trim();
|
|
904
|
-
}
|
|
905
|
-
const trimmed = content.trim();
|
|
906
|
-
return trimmed ? `${trimmed}
|
|
907
|
-
|
|
908
|
-
${block}` : block;
|
|
909
|
-
}
|
|
910
945
|
function stripManagedBlock(content, start, end) {
|
|
911
946
|
return content.replace(managedBlockPattern(start, end), "").trim();
|
|
912
947
|
}
|
|
948
|
+
function splitFrontmatterPrefix(content) {
|
|
949
|
+
if (content.indexOf("---") !== 0) return { prefix: "", body: content.trim() };
|
|
950
|
+
const close = content.indexOf("---", 3);
|
|
951
|
+
if (close === -1) return { prefix: "", body: content.trim() };
|
|
952
|
+
return { prefix: content.slice(0, close + 3), body: content.slice(close + 3).trim() };
|
|
953
|
+
}
|
|
954
|
+
function insertAtBodyTop(content, block) {
|
|
955
|
+
const { prefix, body } = splitFrontmatterPrefix(content);
|
|
956
|
+
const placed = body ? `${block}
|
|
957
|
+
|
|
958
|
+
${body}` : block;
|
|
959
|
+
return prefix ? `${prefix}
|
|
960
|
+
|
|
961
|
+
${placed}` : placed;
|
|
962
|
+
}
|
|
913
963
|
function ruleSource(source) {
|
|
914
964
|
const normalized = source.replace(/\\/g, "/");
|
|
915
965
|
const meshIndex = normalized.lastIndexOf(".agentsmesh/");
|
|
@@ -1011,31 +1061,9 @@ var init_managed_blocks = __esm({
|
|
|
1011
1061
|
});
|
|
1012
1062
|
|
|
1013
1063
|
// src/targets/projection/root-instruction-paragraph.ts
|
|
1014
|
-
function normalizeWhitespace(value) {
|
|
1015
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1016
|
-
}
|
|
1017
1064
|
function appendAgentsmeshRootInstructionParagraph(content) {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
return replaceManagedBlock(
|
|
1021
|
-
trimmed,
|
|
1022
|
-
ROOT_CONTRACT_START,
|
|
1023
|
-
ROOT_CONTRACT_END,
|
|
1024
|
-
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
const norm = normalizeWhitespace(trimmed);
|
|
1028
|
-
if (norm.includes(normalizeWhitespace(AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH))) {
|
|
1029
|
-
return trimmed;
|
|
1030
|
-
}
|
|
1031
|
-
for (const legacy of LEGACY_FORMS) {
|
|
1032
|
-
if (norm.includes(normalizeWhitespace(legacy))) {
|
|
1033
|
-
return trimmed.replace(legacy, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return trimmed ? `${trimmed}
|
|
1037
|
-
|
|
1038
|
-
${AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH}` : AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH;
|
|
1065
|
+
const withoutPrior = stripAgentsmeshRootInstructionParagraph(content);
|
|
1066
|
+
return insertAtBodyTop(withoutPrior, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1039
1067
|
}
|
|
1040
1068
|
function stripAgentsmeshRootInstructionParagraph(content) {
|
|
1041
1069
|
let result = stripManagedBlock(content, ROOT_CONTRACT_START, ROOT_CONTRACT_END);
|
|
@@ -1049,7 +1077,7 @@ ${legacy}`, "");
|
|
|
1049
1077
|
}
|
|
1050
1078
|
return result.trim();
|
|
1051
1079
|
}
|
|
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, 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;
|
|
1080
|
+
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;
|
|
1053
1081
|
var init_root_instruction_paragraph = __esm({
|
|
1054
1082
|
"src/targets/projection/root-instruction-paragraph.ts"() {
|
|
1055
1083
|
init_managed_blocks();
|
|
@@ -1061,7 +1089,9 @@ var init_root_instruction_paragraph = __esm({
|
|
|
1061
1089
|
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`.";
|
|
1062
1090
|
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`.";
|
|
1063
1091
|
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.";
|
|
1064
|
-
|
|
1092
|
+
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.";
|
|
1093
|
+
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.";
|
|
1094
|
+
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.";
|
|
1065
1095
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
|
|
1066
1096
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
|
|
1067
1097
|
|
|
@@ -1090,12 +1120,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
|
|
|
1090
1120
|
AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
|
|
1091
1121
|
|
|
1092
1122
|
${ROOT_INSTRUCTION_BODY_V8}`;
|
|
1123
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
|
|
1124
|
+
|
|
1125
|
+
${ROOT_INSTRUCTION_BODY_V9}`;
|
|
1126
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
|
|
1127
|
+
|
|
1128
|
+
${ROOT_INSTRUCTION_BODY_V10}`;
|
|
1093
1129
|
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
|
|
1094
1130
|
## AgentsMesh Generation Contract
|
|
1095
1131
|
|
|
1096
1132
|
${ROOT_INSTRUCTION_BODY}
|
|
1097
1133
|
${ROOT_CONTRACT_END}`;
|
|
1098
1134
|
LEGACY_FORMS = [
|
|
1135
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY,
|
|
1136
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY,
|
|
1099
1137
|
AGENTSMESH_CONTRACT_WITH_V8_BODY,
|
|
1100
1138
|
AGENTSMESH_CONTRACT_WITH_V7_BODY,
|
|
1101
1139
|
AGENTSMESH_CONTRACT_WITH_V6_BODY,
|
|
@@ -1999,9 +2037,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
|
|
|
1999
2037
|
const before = fullContent[start - 1];
|
|
2000
2038
|
const after = fullContent[end];
|
|
2001
2039
|
if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
|
|
2002
|
-
if (before === "
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2040
|
+
if (before === "`" && after === "`") return true;
|
|
2041
|
+
if (before === "'" && after === "'" || before === '"' && after === '"') return false;
|
|
2005
2042
|
if (before === "<" && after === ">") return true;
|
|
2006
2043
|
if (before === "[" && after === "]") {
|
|
2007
2044
|
if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
|
|
@@ -3328,12 +3365,12 @@ var init_augment_code = __esm({
|
|
|
3328
3365
|
});
|
|
3329
3366
|
|
|
3330
3367
|
// src/targets/claude-code/constants.ts
|
|
3331
|
-
var CLAUDE_CODE_TARGET, CLAUDE_ROOT,
|
|
3368
|
+
var CLAUDE_CODE_TARGET, CLAUDE_ROOT, CLAUDE_NESTED_ROOT, CLAUDE_RULES_DIR, CLAUDE_COMMANDS_DIR, CLAUDE_AGENTS_DIR, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS, CLAUDE_HOOKS_JSON, CLAUDE_OUTPUT_STYLES_DIR, CLAUDE_IGNORE, CLAUDE_MCP_JSON, CLAUDE_GLOBAL_MCP_JSON, CLAUDE_CANONICAL_RULES_DIR, CLAUDE_CANONICAL_COMMANDS_DIR, CLAUDE_CANONICAL_AGENTS_DIR, CLAUDE_CANONICAL_SKILLS_DIR, CLAUDE_CANONICAL_MCP, CLAUDE_CANONICAL_PERMISSIONS, CLAUDE_CANONICAL_HOOKS, CLAUDE_CANONICAL_IGNORE;
|
|
3332
3369
|
var init_constants7 = __esm({
|
|
3333
3370
|
"src/targets/claude-code/constants.ts"() {
|
|
3334
3371
|
CLAUDE_CODE_TARGET = "claude-code";
|
|
3335
|
-
CLAUDE_ROOT = "
|
|
3336
|
-
|
|
3372
|
+
CLAUDE_ROOT = "CLAUDE.md";
|
|
3373
|
+
CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
|
|
3337
3374
|
CLAUDE_RULES_DIR = ".claude/rules";
|
|
3338
3375
|
CLAUDE_COMMANDS_DIR = ".claude/commands";
|
|
3339
3376
|
CLAUDE_AGENTS_DIR = ".claude/agents";
|
|
@@ -5195,7 +5232,8 @@ var init_amp2 = __esm({
|
|
|
5195
5232
|
markAsRoot: true
|
|
5196
5233
|
}
|
|
5197
5234
|
},
|
|
5198
|
-
emitScopedSettings(canonical, _scope) {
|
|
5235
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
5236
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
5199
5237
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
5200
5238
|
return [
|
|
5201
5239
|
{
|
|
@@ -5947,12 +5985,12 @@ function mergeAugmentSettings(existing, newContent) {
|
|
|
5947
5985
|
if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
|
|
5948
5986
|
return JSON.stringify(base, null, 2);
|
|
5949
5987
|
}
|
|
5950
|
-
function buildSettingsContent(canonical) {
|
|
5988
|
+
function buildSettingsContent(canonical, enabledFeatures) {
|
|
5951
5989
|
const settings = {};
|
|
5952
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5990
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5953
5991
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
5954
5992
|
}
|
|
5955
|
-
if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5993
|
+
if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5956
5994
|
settings.hooks = serializeHooksForSettings(canonical.hooks);
|
|
5957
5995
|
}
|
|
5958
5996
|
if (Object.keys(settings).length === 0) return null;
|
|
@@ -6081,8 +6119,8 @@ var init_augment_code2 = __esm({
|
|
|
6081
6119
|
],
|
|
6082
6120
|
layout: globalLayout5
|
|
6083
6121
|
},
|
|
6084
|
-
emitScopedSettings(canonical) {
|
|
6085
|
-
const content = buildSettingsContent(canonical);
|
|
6122
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
6123
|
+
const content = buildSettingsContent(canonical, enabledFeatures);
|
|
6086
6124
|
if (content === null) return [];
|
|
6087
6125
|
return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
|
|
6088
6126
|
},
|
|
@@ -6656,7 +6694,10 @@ var init_claude_code2 = __esm({
|
|
|
6656
6694
|
skillDir: ".claude/skills",
|
|
6657
6695
|
managedOutputs: {
|
|
6658
6696
|
dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
|
|
6659
|
-
|
|
6697
|
+
// CLAUDE_NESTED_ROOT is the pre-migration project location; listing it here lets
|
|
6698
|
+
// `cleanupStaleGeneratedOutputs` evict a leftover `.claude/CLAUDE.md` once generation
|
|
6699
|
+
// writes the root `CLAUDE.md`, so Claude Code never concatenates both into context.
|
|
6700
|
+
files: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/settings.json", ".claudeignore", ".mcp.json"]
|
|
6660
6701
|
},
|
|
6661
6702
|
paths: {
|
|
6662
6703
|
rulePath(slug, _rule) {
|
|
@@ -6671,7 +6712,7 @@ var init_claude_code2 = __esm({
|
|
|
6671
6712
|
}
|
|
6672
6713
|
};
|
|
6673
6714
|
globalLayout6 = {
|
|
6674
|
-
rootInstructionPath:
|
|
6715
|
+
rootInstructionPath: CLAUDE_NESTED_ROOT,
|
|
6675
6716
|
skillDir: ".claude/skills",
|
|
6676
6717
|
renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
|
|
6677
6718
|
managedOutputs: {
|
|
@@ -6684,7 +6725,7 @@ var init_claude_code2 = __esm({
|
|
|
6684
6725
|
".agents/skills"
|
|
6685
6726
|
],
|
|
6686
6727
|
files: [
|
|
6687
|
-
|
|
6728
|
+
CLAUDE_NESTED_ROOT,
|
|
6688
6729
|
".claude/settings.json",
|
|
6689
6730
|
CLAUDE_GLOBAL_MCP_JSON,
|
|
6690
6731
|
CLAUDE_HOOKS_JSON,
|
|
@@ -6692,6 +6733,7 @@ var init_claude_code2 = __esm({
|
|
|
6692
6733
|
]
|
|
6693
6734
|
},
|
|
6694
6735
|
rewriteGeneratedPath(path) {
|
|
6736
|
+
if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
|
|
6695
6737
|
if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
|
|
6696
6738
|
return path;
|
|
6697
6739
|
},
|
|
@@ -6755,10 +6797,11 @@ var init_claude_code2 = __esm({
|
|
|
6755
6797
|
importer: {
|
|
6756
6798
|
rules: [
|
|
6757
6799
|
{
|
|
6758
|
-
// Root rule:
|
|
6800
|
+
// Root rule: project prefers root CLAUDE.md, falls back to nested .claude/CLAUDE.md;
|
|
6801
|
+
// global reads the nested .claude/CLAUDE.md.
|
|
6759
6802
|
feature: "rules",
|
|
6760
6803
|
mode: "singleFile",
|
|
6761
|
-
source: { project: [CLAUDE_ROOT,
|
|
6804
|
+
source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
|
|
6762
6805
|
canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
|
|
6763
6806
|
canonicalRootFilename: "_root.md",
|
|
6764
6807
|
markAsRoot: true
|
|
@@ -6804,7 +6847,23 @@ var init_claude_code2 = __esm({
|
|
|
6804
6847
|
}
|
|
6805
6848
|
},
|
|
6806
6849
|
buildImportPaths: buildClaudeCodeImportPaths,
|
|
6807
|
-
detectionPaths: [
|
|
6850
|
+
detectionPaths: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/rules", ".claude/commands"],
|
|
6851
|
+
nativeInstall: {
|
|
6852
|
+
pickPaths: [
|
|
6853
|
+
{
|
|
6854
|
+
prefix: ".claude/commands",
|
|
6855
|
+
feature: "commands",
|
|
6856
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
6857
|
+
},
|
|
6858
|
+
{ prefix: ".claude/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
|
|
6859
|
+
{
|
|
6860
|
+
prefix: ".claude/agents",
|
|
6861
|
+
feature: "agents",
|
|
6862
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
6863
|
+
},
|
|
6864
|
+
{ prefix: ".claude/skills/", feature: "skills", strategy: { kind: "firstSegment" } }
|
|
6865
|
+
]
|
|
6866
|
+
}
|
|
6808
6867
|
};
|
|
6809
6868
|
}
|
|
6810
6869
|
});
|
|
@@ -7683,6 +7742,16 @@ var init_cline2 = __esm({
|
|
|
7683
7742
|
},
|
|
7684
7743
|
buildImportPaths: buildClineImportPaths,
|
|
7685
7744
|
detectionPaths: [".clinerules", ".cline"],
|
|
7745
|
+
nativeInstall: {
|
|
7746
|
+
pickPaths: [
|
|
7747
|
+
{ prefix: CLINE_SKILLS_DIR, feature: "skills", strategy: { kind: "skillDir" } },
|
|
7748
|
+
{
|
|
7749
|
+
prefix: CLINE_WORKFLOWS_DIR,
|
|
7750
|
+
feature: "commands",
|
|
7751
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
7752
|
+
}
|
|
7753
|
+
]
|
|
7754
|
+
},
|
|
7686
7755
|
conversionDefaults: { agentsToSkills: true }
|
|
7687
7756
|
};
|
|
7688
7757
|
}
|
|
@@ -8601,6 +8670,11 @@ var init_codex_cli2 = __esm({
|
|
|
8601
8670
|
".codex/agents",
|
|
8602
8671
|
".codex/rules"
|
|
8603
8672
|
],
|
|
8673
|
+
nativeInstall: {
|
|
8674
|
+
pickPaths: [
|
|
8675
|
+
{ prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
|
|
8676
|
+
]
|
|
8677
|
+
},
|
|
8604
8678
|
excludeFromStarterInit: true,
|
|
8605
8679
|
conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
|
|
8606
8680
|
};
|
|
@@ -9104,6 +9178,21 @@ var init_continue2 = __esm({
|
|
|
9104
9178
|
},
|
|
9105
9179
|
buildImportPaths: buildContinueImportPaths,
|
|
9106
9180
|
detectionPaths: [".continue/rules", ".continue/skills", ".continue/mcpServers"],
|
|
9181
|
+
nativeInstall: {
|
|
9182
|
+
pickPaths: [
|
|
9183
|
+
{
|
|
9184
|
+
prefix: ".continue/rules",
|
|
9185
|
+
feature: "rules",
|
|
9186
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
9187
|
+
},
|
|
9188
|
+
{
|
|
9189
|
+
prefix: ".continue/prompts",
|
|
9190
|
+
feature: "commands",
|
|
9191
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
9192
|
+
},
|
|
9193
|
+
{ prefix: ".continue/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
9194
|
+
]
|
|
9195
|
+
},
|
|
9107
9196
|
conversionDefaults: { agentsToSkills: true }
|
|
9108
9197
|
};
|
|
9109
9198
|
}
|
|
@@ -9471,6 +9560,80 @@ var init_importer10 = __esm({
|
|
|
9471
9560
|
init_copilot2();
|
|
9472
9561
|
}
|
|
9473
9562
|
});
|
|
9563
|
+
async function skillNamesFromNativeSkillDir(scanRoot) {
|
|
9564
|
+
const files = await readDirRecursive(scanRoot);
|
|
9565
|
+
const names = /* @__PURE__ */ new Set();
|
|
9566
|
+
for (const f of files) {
|
|
9567
|
+
if (basename(f) === "SKILL.md") {
|
|
9568
|
+
names.add(basename(dirname(f)));
|
|
9569
|
+
continue;
|
|
9570
|
+
}
|
|
9571
|
+
const rel2 = relative(scanRoot, f).replace(/\\/g, "/");
|
|
9572
|
+
if (!rel2.includes("/") && f.toLowerCase().endsWith(".md")) {
|
|
9573
|
+
names.add(basename(f, ".md"));
|
|
9574
|
+
}
|
|
9575
|
+
}
|
|
9576
|
+
return [...names].filter(Boolean).sort();
|
|
9577
|
+
}
|
|
9578
|
+
var init_native_skill_scan = __esm({
|
|
9579
|
+
"src/install/native/native-skill-scan.ts"() {
|
|
9580
|
+
init_fs();
|
|
9581
|
+
}
|
|
9582
|
+
});
|
|
9583
|
+
async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
9584
|
+
const scan = join(repoRoot, ...posixPath.split("/"));
|
|
9585
|
+
if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
|
|
9586
|
+
const files = await readDirRecursive(scan);
|
|
9587
|
+
const commands = [
|
|
9588
|
+
...new Set(
|
|
9589
|
+
files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
|
|
9590
|
+
)
|
|
9591
|
+
].sort();
|
|
9592
|
+
return commands.length ? { commands } : {};
|
|
9593
|
+
}
|
|
9594
|
+
if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
|
|
9595
|
+
const files = await readDirRecursive(scan);
|
|
9596
|
+
const rules = [
|
|
9597
|
+
...new Set(
|
|
9598
|
+
files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
|
|
9599
|
+
)
|
|
9600
|
+
].sort();
|
|
9601
|
+
return rules.length ? { rules } : {};
|
|
9602
|
+
}
|
|
9603
|
+
if (posixPath.startsWith(".github/instructions")) {
|
|
9604
|
+
const files = await readDirRecursive(scan);
|
|
9605
|
+
const names = /* @__PURE__ */ new Set();
|
|
9606
|
+
for (const f of files) {
|
|
9607
|
+
const b = basename(f);
|
|
9608
|
+
if (b.toLowerCase().endsWith(".instructions.md"))
|
|
9609
|
+
names.add(b.replace(/\.instructions\.md$/i, ""));
|
|
9610
|
+
else if (b.toLowerCase().endsWith(".md")) names.add(basename(f, ".md"));
|
|
9611
|
+
}
|
|
9612
|
+
const rules = [...names].sort();
|
|
9613
|
+
return rules.length ? { rules } : {};
|
|
9614
|
+
}
|
|
9615
|
+
if (posixPath.startsWith(".github/skills")) {
|
|
9616
|
+
const skills = await skillNamesFromNativeSkillDir(scan);
|
|
9617
|
+
return skills.length ? { skills } : {};
|
|
9618
|
+
}
|
|
9619
|
+
if (posixPath.startsWith(".github/agents")) {
|
|
9620
|
+
const files = await readDirRecursive(scan);
|
|
9621
|
+
const agents = [
|
|
9622
|
+
...new Set(
|
|
9623
|
+
files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
|
|
9624
|
+
)
|
|
9625
|
+
].sort();
|
|
9626
|
+
return agents.length ? { agents } : {};
|
|
9627
|
+
}
|
|
9628
|
+
return {};
|
|
9629
|
+
}
|
|
9630
|
+
var init_native_path_pick_infer_copilot = __esm({
|
|
9631
|
+
"src/install/native/native-path-pick-infer-copilot.ts"() {
|
|
9632
|
+
init_fs();
|
|
9633
|
+
init_constants29();
|
|
9634
|
+
init_native_skill_scan();
|
|
9635
|
+
}
|
|
9636
|
+
});
|
|
9474
9637
|
function pruneUndefined3(record) {
|
|
9475
9638
|
for (const key of Object.keys(record)) {
|
|
9476
9639
|
if (record[key] === void 0) delete record[key];
|
|
@@ -9760,6 +9923,7 @@ var init_copilot2 = __esm({
|
|
|
9760
9923
|
init_generator11();
|
|
9761
9924
|
init_constants29();
|
|
9762
9925
|
init_importer10();
|
|
9926
|
+
init_native_path_pick_infer_copilot();
|
|
9763
9927
|
init_import_mappers4();
|
|
9764
9928
|
init_linter10();
|
|
9765
9929
|
init_import_map_builders();
|
|
@@ -9974,7 +10138,8 @@ var init_copilot2 = __esm({
|
|
|
9974
10138
|
".github/skills",
|
|
9975
10139
|
".github/agents",
|
|
9976
10140
|
".github/hooks"
|
|
9977
|
-
]
|
|
10141
|
+
],
|
|
10142
|
+
nativeInstall: { inferPick: inferCopilotPickFromPath }
|
|
9978
10143
|
};
|
|
9979
10144
|
}
|
|
9980
10145
|
});
|
|
@@ -11487,6 +11652,23 @@ var init_cursor2 = __esm({
|
|
|
11487
11652
|
},
|
|
11488
11653
|
buildImportPaths: buildCursorImportPaths,
|
|
11489
11654
|
detectionPaths: [".cursor/rules", ".cursor/mcp.json"],
|
|
11655
|
+
nativeInstall: {
|
|
11656
|
+
pickPaths: [
|
|
11657
|
+
{ prefix: ".cursor/rules", feature: "rules", strategy: { kind: "basename", suffix: ".mdc" } },
|
|
11658
|
+
{
|
|
11659
|
+
prefix: ".cursor/commands",
|
|
11660
|
+
feature: "commands",
|
|
11661
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
11662
|
+
},
|
|
11663
|
+
{
|
|
11664
|
+
prefix: ".cursor/agents",
|
|
11665
|
+
feature: "agents",
|
|
11666
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
11667
|
+
},
|
|
11668
|
+
{ prefix: ".cursor/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
11669
|
+
],
|
|
11670
|
+
dialectHints: [{ frontmatterKey: "alwaysApply" }]
|
|
11671
|
+
},
|
|
11490
11672
|
preservesManualActivation: true
|
|
11491
11673
|
};
|
|
11492
11674
|
}
|
|
@@ -12248,18 +12430,18 @@ function mapHookEvent2(event) {
|
|
|
12248
12430
|
return null;
|
|
12249
12431
|
}
|
|
12250
12432
|
}
|
|
12251
|
-
function generateGeminiSettingsFiles(canonical) {
|
|
12433
|
+
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
12252
12434
|
const settings = {};
|
|
12253
12435
|
let hasAnyNativeSettings = false;
|
|
12254
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12436
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12255
12437
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
12256
12438
|
hasAnyNativeSettings = true;
|
|
12257
12439
|
}
|
|
12258
|
-
if (canonical.agents.length > 0) {
|
|
12440
|
+
if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
|
|
12259
12441
|
settings.experimental = { enableAgents: true };
|
|
12260
12442
|
hasAnyNativeSettings = true;
|
|
12261
12443
|
}
|
|
12262
|
-
if (canonical.hooks) {
|
|
12444
|
+
if (enabledFeatures.has("hooks") && canonical.hooks) {
|
|
12263
12445
|
const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
|
|
12264
12446
|
const mappedEvent = mapHookEvent2(event);
|
|
12265
12447
|
if (!mappedEvent || !Array.isArray(entries)) return [];
|
|
@@ -12875,6 +13057,36 @@ var init_importer15 = __esm({
|
|
|
12875
13057
|
init_importer_skills_agents();
|
|
12876
13058
|
}
|
|
12877
13059
|
});
|
|
13060
|
+
function isUnderGeminiCommands(pathInRepoPosix) {
|
|
13061
|
+
const p = pathInRepoPosix.replace(/^\/+|\/+$/g, "");
|
|
13062
|
+
return p === ".gemini/commands" || p.startsWith(".gemini/commands/");
|
|
13063
|
+
}
|
|
13064
|
+
async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
|
|
13065
|
+
const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
|
|
13066
|
+
const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
|
|
13067
|
+
const files = await readDirRecursive(scanDir);
|
|
13068
|
+
const names = [];
|
|
13069
|
+
for (const f of files) {
|
|
13070
|
+
if (!/\.(toml|md)$/i.test(f)) continue;
|
|
13071
|
+
const rel2 = relative(commandsRoot, f).replace(/\\/g, "/");
|
|
13072
|
+
if (rel2.startsWith("..") || rel2 === "") continue;
|
|
13073
|
+
const noExt = rel2.replace(/\.(toml|md)$/i, "");
|
|
13074
|
+
const name = noExt.split("/").filter(Boolean).join(":");
|
|
13075
|
+
if (name) names.push(name);
|
|
13076
|
+
}
|
|
13077
|
+
return [...new Set(names)].sort();
|
|
13078
|
+
}
|
|
13079
|
+
async function inferGeminiPick(repoRoot, posixPath) {
|
|
13080
|
+
if (!isUnderGeminiCommands(posixPath)) return {};
|
|
13081
|
+
const commands = await inferGeminiCommandNamesFromFiles(repoRoot, posixPath);
|
|
13082
|
+
return commands.length ? { commands } : {};
|
|
13083
|
+
}
|
|
13084
|
+
var init_gemini_install_commands = __esm({
|
|
13085
|
+
"src/install/native/gemini-install-commands.ts"() {
|
|
13086
|
+
init_fs();
|
|
13087
|
+
init_constants30();
|
|
13088
|
+
}
|
|
13089
|
+
});
|
|
12878
13090
|
async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
|
|
12879
13091
|
const relativeMdPath = relativePath.replace(/\\/g, "/");
|
|
12880
13092
|
const destPath = join(destDir, relativeMdPath);
|
|
@@ -12986,12 +13198,12 @@ var init_lint12 = __esm({
|
|
|
12986
13198
|
});
|
|
12987
13199
|
|
|
12988
13200
|
// src/targets/gemini-cli/scoped-settings-emit.ts
|
|
12989
|
-
function emitScopedGeminiSettings(canonical, scope) {
|
|
13201
|
+
function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
|
|
12990
13202
|
if (scope === "project") {
|
|
12991
13203
|
const caps = getTargetCapabilities("gemini-cli", scope);
|
|
12992
13204
|
if (caps?.ignore.flavor !== "settings-embedded") return [];
|
|
12993
13205
|
}
|
|
12994
|
-
return generateGeminiSettingsFiles(canonical);
|
|
13206
|
+
return generateGeminiSettingsFiles(canonical, enabledFeatures);
|
|
12995
13207
|
}
|
|
12996
13208
|
var init_scoped_settings_emit = __esm({
|
|
12997
13209
|
"src/targets/gemini-cli/scoped-settings-emit.ts"() {
|
|
@@ -13009,6 +13221,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13009
13221
|
init_policies_generator();
|
|
13010
13222
|
init_constants30();
|
|
13011
13223
|
init_importer15();
|
|
13224
|
+
init_gemini_install_commands();
|
|
13012
13225
|
init_import_mappers6();
|
|
13013
13226
|
init_linter15();
|
|
13014
13227
|
init_import_map_builders();
|
|
@@ -13204,6 +13417,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13204
13417
|
},
|
|
13205
13418
|
buildImportPaths: buildGeminiCliImportPaths,
|
|
13206
13419
|
detectionPaths: ["GEMINI.md", ".gemini"],
|
|
13420
|
+
nativeInstall: { inferPick: inferGeminiPick },
|
|
13207
13421
|
conversionDefaults: { agentsToSkills: false }
|
|
13208
13422
|
};
|
|
13209
13423
|
}
|
|
@@ -14045,7 +14259,23 @@ var init_junie2 = __esm({
|
|
|
14045
14259
|
".junie/skills",
|
|
14046
14260
|
".junie/mcp/mcp.json",
|
|
14047
14261
|
".aiignore"
|
|
14048
|
-
]
|
|
14262
|
+
],
|
|
14263
|
+
nativeInstall: {
|
|
14264
|
+
pickPaths: [
|
|
14265
|
+
{
|
|
14266
|
+
prefix: ".junie/commands",
|
|
14267
|
+
feature: "commands",
|
|
14268
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
14269
|
+
},
|
|
14270
|
+
{ prefix: ".junie/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
|
|
14271
|
+
{
|
|
14272
|
+
prefix: ".junie/agents",
|
|
14273
|
+
feature: "agents",
|
|
14274
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
14275
|
+
},
|
|
14276
|
+
{ prefix: ".junie/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
14277
|
+
]
|
|
14278
|
+
}
|
|
14049
14279
|
};
|
|
14050
14280
|
}
|
|
14051
14281
|
});
|
|
@@ -15900,9 +16130,18 @@ function generateIgnore12(canonical) {
|
|
|
15900
16130
|
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
15901
16131
|
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
15902
16132
|
}
|
|
16133
|
+
function renderQwenGlobalInstructions(canonical) {
|
|
16134
|
+
const root = canonical.rules.find((rule) => rule.root);
|
|
16135
|
+
const nonRootRules = canonical.rules.filter((rule) => {
|
|
16136
|
+
if (rule.root) return false;
|
|
16137
|
+
return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
|
|
16138
|
+
});
|
|
16139
|
+
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
16140
|
+
}
|
|
15903
16141
|
var init_generator26 = __esm({
|
|
15904
16142
|
"src/targets/qwen-code/generator.ts"() {
|
|
15905
16143
|
init_markdown();
|
|
16144
|
+
init_managed_blocks();
|
|
15906
16145
|
init_constants21();
|
|
15907
16146
|
}
|
|
15908
16147
|
});
|
|
@@ -15985,6 +16224,7 @@ var init_qwen_code2 = __esm({
|
|
|
15985
16224
|
};
|
|
15986
16225
|
globalLayout22 = {
|
|
15987
16226
|
rootInstructionPath: QWEN_GLOBAL_ROOT,
|
|
16227
|
+
renderPrimaryRootInstruction: renderQwenGlobalInstructions,
|
|
15988
16228
|
skillDir: QWEN_GLOBAL_SKILLS_DIR,
|
|
15989
16229
|
managedOutputs: {
|
|
15990
16230
|
dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
|
|
@@ -18385,6 +18625,16 @@ var init_windsurf2 = __esm({
|
|
|
18385
18625
|
},
|
|
18386
18626
|
buildImportPaths: buildWindsurfImportPaths,
|
|
18387
18627
|
detectionPaths: [".windsurfrules", ".windsurf"],
|
|
18628
|
+
nativeInstall: {
|
|
18629
|
+
pickPaths: [
|
|
18630
|
+
{
|
|
18631
|
+
prefix: ".windsurf/rules",
|
|
18632
|
+
feature: "rules",
|
|
18633
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
18634
|
+
}
|
|
18635
|
+
],
|
|
18636
|
+
dialectHints: [{ frontmatterKey: "trigger" }]
|
|
18637
|
+
},
|
|
18388
18638
|
conversionDefaults: { agentsToSkills: true }
|
|
18389
18639
|
};
|
|
18390
18640
|
}
|
|
@@ -18659,7 +18909,8 @@ var init_zed2 = __esm({
|
|
|
18659
18909
|
markAsRoot: true
|
|
18660
18910
|
}
|
|
18661
18911
|
},
|
|
18662
|
-
emitScopedSettings(canonical, _scope) {
|
|
18912
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
18913
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
18663
18914
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
18664
18915
|
const contextServers = {};
|
|
18665
18916
|
for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
|
|
@@ -18714,7 +18965,10 @@ function getBuiltinTargetDefinition(target31) {
|
|
|
18714
18965
|
function getTargetCapabilities(target31, scope = "project") {
|
|
18715
18966
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
18716
18967
|
if (!descriptor31) return void 0;
|
|
18717
|
-
|
|
18968
|
+
if (scope === "global" && !descriptor31.globalSupport) {
|
|
18969
|
+
return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
|
|
18970
|
+
}
|
|
18971
|
+
const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
|
|
18718
18972
|
return normalizeTargetCapabilities(raw);
|
|
18719
18973
|
}
|
|
18720
18974
|
function getTargetDetectionPaths(target31, scope = "project") {
|
|
@@ -18770,6 +19024,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
|
|
|
18770
19024
|
function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
|
|
18771
19025
|
const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
|
|
18772
19026
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19027
|
+
if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
|
|
18773
19028
|
if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
|
|
18774
19029
|
return "embedded";
|
|
18775
19030
|
}
|
|
@@ -18783,7 +19038,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
|
|
|
18783
19038
|
const pick = PICK_FEATURE_GENERATOR[feature];
|
|
18784
19039
|
return pick === null ? void 0 : pick(descriptor31.generators);
|
|
18785
19040
|
}
|
|
18786
|
-
var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
19041
|
+
var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
18787
19042
|
var init_builtin_targets = __esm({
|
|
18788
19043
|
"src/targets/catalog/builtin-targets.ts"() {
|
|
18789
19044
|
init_conversions();
|
|
@@ -18821,6 +19076,17 @@ var init_builtin_targets = __esm({
|
|
|
18821
19076
|
init_warp2();
|
|
18822
19077
|
init_windsurf2();
|
|
18823
19078
|
init_zed2();
|
|
19079
|
+
ALL_NONE_CAPABILITIES = {
|
|
19080
|
+
rules: "none",
|
|
19081
|
+
additionalRules: "none",
|
|
19082
|
+
commands: "none",
|
|
19083
|
+
agents: "none",
|
|
19084
|
+
skills: "none",
|
|
19085
|
+
mcp: "none",
|
|
19086
|
+
hooks: "none",
|
|
19087
|
+
ignore: "none",
|
|
19088
|
+
permissions: "none"
|
|
19089
|
+
};
|
|
18824
19090
|
BUILTIN_TARGETS = [
|
|
18825
19091
|
descriptor,
|
|
18826
19092
|
descriptor2,
|
|
@@ -19251,6 +19517,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
|
|
|
19251
19517
|
init_path_helpers();
|
|
19252
19518
|
init_link_rebaser_helpers();
|
|
19253
19519
|
|
|
19520
|
+
// src/utils/output/color.ts
|
|
19521
|
+
function noColorRequested() {
|
|
19522
|
+
const value = process.env.NO_COLOR;
|
|
19523
|
+
return value !== void 0 && value !== "";
|
|
19524
|
+
}
|
|
19525
|
+
function forceColorRequested() {
|
|
19526
|
+
const value = process.env.FORCE_COLOR;
|
|
19527
|
+
if (value === void 0) return void 0;
|
|
19528
|
+
return value !== "0" && value !== "false";
|
|
19529
|
+
}
|
|
19530
|
+
function colorEnabled(stream = process.stdout) {
|
|
19531
|
+
const forced = forceColorRequested();
|
|
19532
|
+
if (forced !== void 0) return forced;
|
|
19533
|
+
if (noColorRequested()) return false;
|
|
19534
|
+
return stream.isTTY === true;
|
|
19535
|
+
}
|
|
19536
|
+
|
|
19254
19537
|
// src/utils/output/logger.ts
|
|
19255
19538
|
var C = {
|
|
19256
19539
|
green: "\x1B[32m",
|
|
@@ -19259,16 +19542,14 @@ var C = {
|
|
|
19259
19542
|
cyan: "\x1B[36m",
|
|
19260
19543
|
reset: "\x1B[0m"
|
|
19261
19544
|
};
|
|
19262
|
-
function
|
|
19263
|
-
|
|
19264
|
-
process.stdout.write(text);
|
|
19265
|
-
}
|
|
19545
|
+
function outStream() {
|
|
19546
|
+
return process.stdout;
|
|
19266
19547
|
}
|
|
19267
|
-
function
|
|
19268
|
-
|
|
19548
|
+
function out(text) {
|
|
19549
|
+
outStream().write(text);
|
|
19269
19550
|
}
|
|
19270
|
-
function c(code, text) {
|
|
19271
|
-
return
|
|
19551
|
+
function c(code, text, stream) {
|
|
19552
|
+
return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
|
|
19272
19553
|
}
|
|
19273
19554
|
function pad(str, width) {
|
|
19274
19555
|
const len = [...str].length;
|
|
@@ -19276,20 +19557,20 @@ function pad(str, width) {
|
|
|
19276
19557
|
}
|
|
19277
19558
|
var logger = {
|
|
19278
19559
|
info(msg) {
|
|
19279
|
-
out(c(C.cyan, msg) + "\n");
|
|
19560
|
+
out(c(C.cyan, msg, outStream()) + "\n");
|
|
19280
19561
|
},
|
|
19281
19562
|
warn(msg) {
|
|
19282
|
-
process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
|
|
19563
|
+
process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
|
|
19283
19564
|
},
|
|
19284
19565
|
error(msg) {
|
|
19285
|
-
process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
|
|
19566
|
+
process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
|
|
19286
19567
|
},
|
|
19287
19568
|
success(msg) {
|
|
19288
|
-
out(c(C.green, "\u2713 ") + msg + "\n");
|
|
19569
|
+
out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
|
|
19289
19570
|
},
|
|
19290
19571
|
debug(msg) {
|
|
19291
19572
|
if (process.env.AGENTSMESH_DEBUG === "1") {
|
|
19292
|
-
out(c(C.cyan, "[debug] ") + msg + "\n");
|
|
19573
|
+
out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
|
|
19293
19574
|
}
|
|
19294
19575
|
},
|
|
19295
19576
|
table(rows) {
|
|
@@ -19784,12 +20065,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
19784
20065
|
}
|
|
19785
20066
|
}
|
|
19786
20067
|
}
|
|
19787
|
-
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
|
|
20068
|
+
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
|
|
19788
20069
|
for (const target31 of targets) {
|
|
19789
20070
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19790
20071
|
const emit = descriptor31?.emitScopedSettings;
|
|
19791
20072
|
if (!emit) continue;
|
|
19792
|
-
const outputs = emit(canonical, scope);
|
|
20073
|
+
const outputs = emit(canonical, scope, enabledFeatures);
|
|
19793
20074
|
if (outputs.length === 0) continue;
|
|
19794
20075
|
for (const out2 of outputs) {
|
|
19795
20076
|
await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
|
|
@@ -19890,7 +20171,14 @@ async function generate(ctx) {
|
|
|
19890
20171
|
}
|
|
19891
20172
|
}
|
|
19892
20173
|
if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
|
|
19893
|
-
await generateScopedSettingsFeature(
|
|
20174
|
+
await generateScopedSettingsFeature(
|
|
20175
|
+
results,
|
|
20176
|
+
targets,
|
|
20177
|
+
canonical,
|
|
20178
|
+
projectRoot,
|
|
20179
|
+
scope,
|
|
20180
|
+
enabledFeatures
|
|
20181
|
+
);
|
|
19894
20182
|
}
|
|
19895
20183
|
const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
|
|
19896
20184
|
const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
|
|
@@ -20214,7 +20502,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
20214
20502
|
await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
|
|
20215
20503
|
if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
|
|
20216
20504
|
await rm(cacheRoot, { recursive: true, force: true });
|
|
20217
|
-
await
|
|
20505
|
+
await renameWithRetry(stagedRoot, cacheRoot);
|
|
20218
20506
|
return readCachedRepo(cacheRepoDir);
|
|
20219
20507
|
} catch (err) {
|
|
20220
20508
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
@@ -22565,6 +22853,981 @@ function lintRuleScopeInversion(input) {
|
|
|
22565
22853
|
}
|
|
22566
22854
|
return out2;
|
|
22567
22855
|
}
|
|
22856
|
+
var CURRENT_GRAPH_VERSION = 1;
|
|
22857
|
+
var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
|
|
22858
|
+
var DateSchema = z.string().regex(
|
|
22859
|
+
/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z?)?$/,
|
|
22860
|
+
"createdAt must be ISO-8601 date or datetime"
|
|
22861
|
+
);
|
|
22862
|
+
var TopicSchema = z.object({
|
|
22863
|
+
summary: z.string().min(1, "topic summary must not be empty")
|
|
22864
|
+
}).strict();
|
|
22865
|
+
var TriggerKindSchema = z.enum(["file_glob", "command_pattern", "keyword"]);
|
|
22866
|
+
var TriggerSchema = z.object({
|
|
22867
|
+
kind: TriggerKindSchema,
|
|
22868
|
+
pattern: z.string().min(1, "trigger pattern must not be empty")
|
|
22869
|
+
}).strict();
|
|
22870
|
+
var LessonStatusSchema = z.enum(["active", "deprecated", "superseded"]);
|
|
22871
|
+
var LessonSchema = z.object({
|
|
22872
|
+
rule: z.string().min(1, "lesson rule must not be empty"),
|
|
22873
|
+
rationale: z.string().min(1).optional(),
|
|
22874
|
+
topics: z.array(IdSchema).min(1, "lesson must reference at least one topic"),
|
|
22875
|
+
triggers: z.array(IdSchema),
|
|
22876
|
+
evidence: z.array(z.string().min(1)),
|
|
22877
|
+
status: LessonStatusSchema,
|
|
22878
|
+
supersededBy: IdSchema.optional(),
|
|
22879
|
+
createdAt: DateSchema
|
|
22880
|
+
}).strict();
|
|
22881
|
+
var LessonsGraphSchema = z.object({
|
|
22882
|
+
version: z.literal(CURRENT_GRAPH_VERSION),
|
|
22883
|
+
lessons: z.record(IdSchema, LessonSchema),
|
|
22884
|
+
topics: z.record(IdSchema, TopicSchema),
|
|
22885
|
+
triggers: z.record(IdSchema, TriggerSchema)
|
|
22886
|
+
}).strict();
|
|
22887
|
+
function parseGraph(raw) {
|
|
22888
|
+
return LessonsGraphSchema.parse(raw);
|
|
22889
|
+
}
|
|
22890
|
+
|
|
22891
|
+
// src/lessons/graph-store.ts
|
|
22892
|
+
var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
|
|
22893
|
+
function graphFilePath(projectRoot) {
|
|
22894
|
+
return resolve(projectRoot, GRAPH_REL_PATH);
|
|
22895
|
+
}
|
|
22896
|
+
function loadLessonsGraph(projectRoot) {
|
|
22897
|
+
const raw = readFileSync(graphFilePath(projectRoot), "utf8");
|
|
22898
|
+
return parseGraph(JSON.parse(raw));
|
|
22899
|
+
}
|
|
22900
|
+
var BASE_REL = ".agentsmesh/lessons";
|
|
22901
|
+
function lessonsPaths(projectRoot) {
|
|
22902
|
+
const base = join(projectRoot, BASE_REL);
|
|
22903
|
+
return {
|
|
22904
|
+
base,
|
|
22905
|
+
graph: join(base, "lessons.json"),
|
|
22906
|
+
config: join(base, "config.json"),
|
|
22907
|
+
journal: join(base, "journal.md"),
|
|
22908
|
+
index: join(base, "index.yaml"),
|
|
22909
|
+
topicsDir: join(base, "topics")
|
|
22910
|
+
};
|
|
22911
|
+
}
|
|
22912
|
+
function toRelPath(projectRoot, absolute) {
|
|
22913
|
+
return relative(projectRoot, absolute).split(sep).join("/");
|
|
22914
|
+
}
|
|
22915
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
22916
|
+
var MAX_FILES = 2e5;
|
|
22917
|
+
function listProjectFiles(projectRoot) {
|
|
22918
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
22919
|
+
try {
|
|
22920
|
+
const stack = [projectRoot];
|
|
22921
|
+
while (stack.length > 0) {
|
|
22922
|
+
const dir = stack.pop();
|
|
22923
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
22924
|
+
if (entry.isDirectory()) {
|
|
22925
|
+
if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
|
|
22926
|
+
} else if (entry.isFile()) {
|
|
22927
|
+
out2.add(toRelPath(projectRoot, join(dir, entry.name)));
|
|
22928
|
+
if (out2.size > MAX_FILES) return out2;
|
|
22929
|
+
}
|
|
22930
|
+
}
|
|
22931
|
+
}
|
|
22932
|
+
} catch {
|
|
22933
|
+
return null;
|
|
22934
|
+
}
|
|
22935
|
+
return out2;
|
|
22936
|
+
}
|
|
22937
|
+
|
|
22938
|
+
// src/lessons/validate-checks.ts
|
|
22939
|
+
function collectDanglingRefs(graph, findings) {
|
|
22940
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
22941
|
+
for (const topicId of lesson.topics) {
|
|
22942
|
+
if (graph.topics[topicId] === void 0) {
|
|
22943
|
+
findings.push({
|
|
22944
|
+
level: "error",
|
|
22945
|
+
code: "DANGLING_TOPIC",
|
|
22946
|
+
message: `Lesson "${lessonId}" references unknown topic "${topicId}".`,
|
|
22947
|
+
lessonId,
|
|
22948
|
+
topicId
|
|
22949
|
+
});
|
|
22950
|
+
}
|
|
22951
|
+
}
|
|
22952
|
+
for (const triggerId of lesson.triggers) {
|
|
22953
|
+
if (graph.triggers[triggerId] === void 0) {
|
|
22954
|
+
findings.push({
|
|
22955
|
+
level: "error",
|
|
22956
|
+
code: "DANGLING_TRIGGER",
|
|
22957
|
+
message: `Lesson "${lessonId}" references unknown trigger "${triggerId}".`,
|
|
22958
|
+
lessonId,
|
|
22959
|
+
triggerId
|
|
22960
|
+
});
|
|
22961
|
+
}
|
|
22962
|
+
}
|
|
22963
|
+
if (lesson.supersededBy !== void 0 && graph.lessons[lesson.supersededBy] === void 0) {
|
|
22964
|
+
findings.push({
|
|
22965
|
+
level: "error",
|
|
22966
|
+
code: "DANGLING_SUPERSEDER",
|
|
22967
|
+
message: `Lesson "${lessonId}" supersededBy unknown lesson "${lesson.supersededBy}".`,
|
|
22968
|
+
lessonId
|
|
22969
|
+
});
|
|
22970
|
+
}
|
|
22971
|
+
}
|
|
22972
|
+
}
|
|
22973
|
+
function collectDuplicateRefs(graph, findings) {
|
|
22974
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
22975
|
+
for (const topicId of firstDuplicates(lesson.topics)) {
|
|
22976
|
+
findings.push({
|
|
22977
|
+
level: "error",
|
|
22978
|
+
code: "DUPLICATE_TOPIC_REF",
|
|
22979
|
+
message: `Lesson "${lessonId}" references topic "${topicId}" more than once.`,
|
|
22980
|
+
lessonId,
|
|
22981
|
+
topicId
|
|
22982
|
+
});
|
|
22983
|
+
}
|
|
22984
|
+
for (const triggerId of firstDuplicates(lesson.triggers)) {
|
|
22985
|
+
findings.push({
|
|
22986
|
+
level: "error",
|
|
22987
|
+
code: "DUPLICATE_TRIGGER_REF",
|
|
22988
|
+
message: `Lesson "${lessonId}" references trigger "${triggerId}" more than once.`,
|
|
22989
|
+
lessonId,
|
|
22990
|
+
triggerId
|
|
22991
|
+
});
|
|
22992
|
+
}
|
|
22993
|
+
}
|
|
22994
|
+
}
|
|
22995
|
+
function firstDuplicates(ids) {
|
|
22996
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22997
|
+
const dup = /* @__PURE__ */ new Set();
|
|
22998
|
+
for (const id of ids) {
|
|
22999
|
+
if (seen.has(id)) dup.add(id);
|
|
23000
|
+
else seen.add(id);
|
|
23001
|
+
}
|
|
23002
|
+
return [...dup];
|
|
23003
|
+
}
|
|
23004
|
+
function collectStatusInvariants(graph, findings) {
|
|
23005
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23006
|
+
if (lesson.status === "superseded" && lesson.supersededBy === void 0) {
|
|
23007
|
+
findings.push({
|
|
23008
|
+
level: "error",
|
|
23009
|
+
code: "SUPERSEDED_WITHOUT_TARGET",
|
|
23010
|
+
message: `Lesson "${lessonId}" has status "superseded" but no supersededBy target.`,
|
|
23011
|
+
lessonId
|
|
23012
|
+
});
|
|
23013
|
+
}
|
|
23014
|
+
if (lesson.status === "active" && lesson.supersededBy !== void 0) {
|
|
23015
|
+
findings.push({
|
|
23016
|
+
level: "error",
|
|
23017
|
+
code: "ACTIVE_WITH_SUPERSEDER",
|
|
23018
|
+
message: `Lesson "${lessonId}" has status "active" but declares supersededBy.`,
|
|
23019
|
+
lessonId
|
|
23020
|
+
});
|
|
23021
|
+
}
|
|
23022
|
+
}
|
|
23023
|
+
}
|
|
23024
|
+
function collectLifecycleInvariants(graph, findings) {
|
|
23025
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23026
|
+
if (lesson.supersededBy === void 0) continue;
|
|
23027
|
+
if (lesson.supersededBy === lessonId) {
|
|
23028
|
+
findings.push({
|
|
23029
|
+
level: "error",
|
|
23030
|
+
code: "SELF_SUPERSEDED",
|
|
23031
|
+
message: `Lesson "${lessonId}" is superseded by itself.`,
|
|
23032
|
+
lessonId
|
|
23033
|
+
});
|
|
23034
|
+
continue;
|
|
23035
|
+
}
|
|
23036
|
+
const target31 = graph.lessons[lesson.supersededBy];
|
|
23037
|
+
if (target31 !== void 0 && target31.status !== "active") {
|
|
23038
|
+
findings.push({
|
|
23039
|
+
level: "error",
|
|
23040
|
+
code: "INACTIVE_SUPERSEDER",
|
|
23041
|
+
message: `Lesson "${lessonId}" is superseded by "${lesson.supersededBy}", which is itself ${target31.status} \u2014 the chain dead-ends with no live replacement.`,
|
|
23042
|
+
lessonId
|
|
23043
|
+
});
|
|
23044
|
+
}
|
|
23045
|
+
}
|
|
23046
|
+
collectSupersedeCycles(graph, findings);
|
|
23047
|
+
}
|
|
23048
|
+
function collectSupersedeCycles(graph, findings) {
|
|
23049
|
+
const reported = /* @__PURE__ */ new Set();
|
|
23050
|
+
for (const startId of Object.keys(graph.lessons)) {
|
|
23051
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23052
|
+
let cur = startId;
|
|
23053
|
+
while (cur !== void 0) {
|
|
23054
|
+
if (seen.has(cur)) {
|
|
23055
|
+
if (cur !== startId || reported.has(cur)) break;
|
|
23056
|
+
reported.add(cur);
|
|
23057
|
+
findings.push({
|
|
23058
|
+
level: "error",
|
|
23059
|
+
code: "SUPERSEDE_CYCLE",
|
|
23060
|
+
message: `Lesson "${startId}" is part of a supersededBy cycle.`,
|
|
23061
|
+
lessonId: startId
|
|
23062
|
+
});
|
|
23063
|
+
break;
|
|
23064
|
+
}
|
|
23065
|
+
seen.add(cur);
|
|
23066
|
+
const next = graph.lessons[cur]?.supersededBy;
|
|
23067
|
+
if (next === cur) break;
|
|
23068
|
+
cur = next;
|
|
23069
|
+
}
|
|
23070
|
+
}
|
|
23071
|
+
}
|
|
23072
|
+
function collectReachability(graph, findings) {
|
|
23073
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23074
|
+
if (lesson.status === "active" && lesson.triggers.length === 0) {
|
|
23075
|
+
findings.push({
|
|
23076
|
+
level: "warning",
|
|
23077
|
+
code: "UNREACHABLE_LESSON",
|
|
23078
|
+
message: `Active lesson "${lessonId}" has no triggers and can never be recalled.`,
|
|
23079
|
+
lessonId
|
|
23080
|
+
});
|
|
23081
|
+
}
|
|
23082
|
+
}
|
|
23083
|
+
}
|
|
23084
|
+
function collectOrphans(graph, findings) {
|
|
23085
|
+
const referencedTopics = /* @__PURE__ */ new Set();
|
|
23086
|
+
const referencedTriggers = /* @__PURE__ */ new Set();
|
|
23087
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23088
|
+
for (const t of lesson.topics) referencedTopics.add(t);
|
|
23089
|
+
for (const t of lesson.triggers) referencedTriggers.add(t);
|
|
23090
|
+
}
|
|
23091
|
+
for (const topicId of Object.keys(graph.topics)) {
|
|
23092
|
+
if (!referencedTopics.has(topicId)) {
|
|
23093
|
+
findings.push({
|
|
23094
|
+
level: "warning",
|
|
23095
|
+
code: "ORPHAN_TOPIC",
|
|
23096
|
+
message: `Topic "${topicId}" is not referenced by any lesson.`,
|
|
23097
|
+
topicId
|
|
23098
|
+
});
|
|
23099
|
+
}
|
|
23100
|
+
}
|
|
23101
|
+
for (const triggerId of Object.keys(graph.triggers)) {
|
|
23102
|
+
if (!referencedTriggers.has(triggerId)) {
|
|
23103
|
+
findings.push({
|
|
23104
|
+
level: "warning",
|
|
23105
|
+
code: "ORPHAN_TRIGGER",
|
|
23106
|
+
message: `Trigger "${triggerId}" is not referenced by any lesson.`,
|
|
23107
|
+
triggerId
|
|
23108
|
+
});
|
|
23109
|
+
}
|
|
23110
|
+
}
|
|
23111
|
+
}
|
|
23112
|
+
|
|
23113
|
+
// src/lessons/ranking-text.ts
|
|
23114
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
23115
|
+
"the",
|
|
23116
|
+
"a",
|
|
23117
|
+
"an",
|
|
23118
|
+
"to",
|
|
23119
|
+
"of",
|
|
23120
|
+
"in",
|
|
23121
|
+
"and",
|
|
23122
|
+
"or",
|
|
23123
|
+
"for",
|
|
23124
|
+
"is",
|
|
23125
|
+
"on",
|
|
23126
|
+
"at",
|
|
23127
|
+
"with",
|
|
23128
|
+
"be",
|
|
23129
|
+
"as",
|
|
23130
|
+
"it",
|
|
23131
|
+
"that",
|
|
23132
|
+
"this",
|
|
23133
|
+
"its",
|
|
23134
|
+
"must"
|
|
23135
|
+
]);
|
|
23136
|
+
function tokenize(text) {
|
|
23137
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
|
|
23138
|
+
}
|
|
23139
|
+
|
|
23140
|
+
// src/lessons/keyword-signal.ts
|
|
23141
|
+
var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
|
|
23142
|
+
function isLowSignalKeyword(pattern) {
|
|
23143
|
+
return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
|
|
23144
|
+
}
|
|
23145
|
+
function splitRawTokens(pattern) {
|
|
23146
|
+
return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
23147
|
+
}
|
|
23148
|
+
function keywordNeedleLosesTokens(pattern) {
|
|
23149
|
+
const raw = splitRawTokens(pattern);
|
|
23150
|
+
if (raw.length < 2) return false;
|
|
23151
|
+
return tokenize(pattern).length !== raw.length;
|
|
23152
|
+
}
|
|
23153
|
+
|
|
23154
|
+
// src/lessons/regex-linear/nfa-compile.ts
|
|
23155
|
+
var MAX_NFA_STATES = 2e3;
|
|
23156
|
+
var Builder = class {
|
|
23157
|
+
states = [];
|
|
23158
|
+
alloc() {
|
|
23159
|
+
if (this.states.length >= MAX_NFA_STATES) {
|
|
23160
|
+
throw new Error(`NFA state limit exceeded (${MAX_NFA_STATES}); pattern expands too large`);
|
|
23161
|
+
}
|
|
23162
|
+
this.states.push({ eps: [], asserts: [], chars: [] });
|
|
23163
|
+
return this.states.length - 1;
|
|
23164
|
+
}
|
|
23165
|
+
};
|
|
23166
|
+
function isNonLineTerminator(c2) {
|
|
23167
|
+
return c2 !== "\n" && c2 !== "\r" && c2 !== "\u2028" && c2 !== "\u2029";
|
|
23168
|
+
}
|
|
23169
|
+
function compileNode(b, node) {
|
|
23170
|
+
switch (node.k) {
|
|
23171
|
+
case "empty":
|
|
23172
|
+
case "assert": {
|
|
23173
|
+
const s = b.alloc();
|
|
23174
|
+
const e = b.alloc();
|
|
23175
|
+
if (node.k === "assert") b.states[s].asserts.push({ kind: node.kind, target: e });
|
|
23176
|
+
else b.states[s].eps.push(e);
|
|
23177
|
+
return { start: s, end: e };
|
|
23178
|
+
}
|
|
23179
|
+
case "char":
|
|
23180
|
+
case "any":
|
|
23181
|
+
case "class": {
|
|
23182
|
+
const s = b.alloc();
|
|
23183
|
+
const e = b.alloc();
|
|
23184
|
+
const test = node.k === "char" ? (c2) => c2 === node.ch : node.k === "any" ? isNonLineTerminator : node.test;
|
|
23185
|
+
b.states[s].chars.push({ test, target: e });
|
|
23186
|
+
return { start: s, end: e };
|
|
23187
|
+
}
|
|
23188
|
+
case "concat": {
|
|
23189
|
+
if (node.items.length === 0) return compileNode(b, { k: "empty" });
|
|
23190
|
+
let first = null;
|
|
23191
|
+
let prevEnd = -1;
|
|
23192
|
+
for (const item of node.items) {
|
|
23193
|
+
const frag = compileNode(b, item);
|
|
23194
|
+
if (first === null) first = frag;
|
|
23195
|
+
else b.states[prevEnd].eps.push(frag.start);
|
|
23196
|
+
prevEnd = frag.end;
|
|
23197
|
+
}
|
|
23198
|
+
return { start: first.start, end: prevEnd };
|
|
23199
|
+
}
|
|
23200
|
+
case "alt": {
|
|
23201
|
+
const s = b.alloc();
|
|
23202
|
+
const e = b.alloc();
|
|
23203
|
+
for (const opt of node.opts) {
|
|
23204
|
+
const frag = compileNode(b, opt);
|
|
23205
|
+
b.states[s].eps.push(frag.start);
|
|
23206
|
+
b.states[frag.end].eps.push(e);
|
|
23207
|
+
}
|
|
23208
|
+
return { start: s, end: e };
|
|
23209
|
+
}
|
|
23210
|
+
case "opt": {
|
|
23211
|
+
const s = b.alloc();
|
|
23212
|
+
const e = b.alloc();
|
|
23213
|
+
const frag = compileNode(b, node.node);
|
|
23214
|
+
b.states[s].eps.push(frag.start, e);
|
|
23215
|
+
b.states[frag.end].eps.push(e);
|
|
23216
|
+
return { start: s, end: e };
|
|
23217
|
+
}
|
|
23218
|
+
case "star": {
|
|
23219
|
+
const s = b.alloc();
|
|
23220
|
+
const e = b.alloc();
|
|
23221
|
+
const frag = compileNode(b, node.node);
|
|
23222
|
+
b.states[s].eps.push(frag.start, e);
|
|
23223
|
+
b.states[frag.end].eps.push(frag.start, e);
|
|
23224
|
+
return { start: s, end: e };
|
|
23225
|
+
}
|
|
23226
|
+
case "plus": {
|
|
23227
|
+
const e = b.alloc();
|
|
23228
|
+
const frag = compileNode(b, node.node);
|
|
23229
|
+
b.states[frag.end].eps.push(frag.start, e);
|
|
23230
|
+
return { start: frag.start, end: e };
|
|
23231
|
+
}
|
|
23232
|
+
}
|
|
23233
|
+
}
|
|
23234
|
+
function compileNfa(ast) {
|
|
23235
|
+
const b = new Builder();
|
|
23236
|
+
const { start, end } = compileNode(b, ast);
|
|
23237
|
+
return { states: b.states, start, accept: end };
|
|
23238
|
+
}
|
|
23239
|
+
|
|
23240
|
+
// src/lessons/regex-linear/nfa.ts
|
|
23241
|
+
function wordBoundary(input, pos) {
|
|
23242
|
+
const before = pos > 0 && /[A-Za-z0-9_]/.test(input[pos - 1]);
|
|
23243
|
+
const after = pos < input.length && /[A-Za-z0-9_]/.test(input[pos]);
|
|
23244
|
+
return before !== after;
|
|
23245
|
+
}
|
|
23246
|
+
function assertHolds(kind, input, pos) {
|
|
23247
|
+
switch (kind) {
|
|
23248
|
+
case "start":
|
|
23249
|
+
return pos === 0;
|
|
23250
|
+
case "end":
|
|
23251
|
+
return pos === input.length;
|
|
23252
|
+
case "wordB":
|
|
23253
|
+
return wordBoundary(input, pos);
|
|
23254
|
+
case "nonWordB":
|
|
23255
|
+
return !wordBoundary(input, pos);
|
|
23256
|
+
}
|
|
23257
|
+
}
|
|
23258
|
+
function buildMatcher(ast) {
|
|
23259
|
+
const { states, start, accept } = compileNfa(ast);
|
|
23260
|
+
const closure = (set, idx, input, pos, budget) => {
|
|
23261
|
+
const stack = [idx];
|
|
23262
|
+
while (stack.length > 0) {
|
|
23263
|
+
if (budget.remaining <= 0) return;
|
|
23264
|
+
const cur = stack.pop();
|
|
23265
|
+
if (set.has(cur)) continue;
|
|
23266
|
+
set.add(cur);
|
|
23267
|
+
budget.remaining -= 1;
|
|
23268
|
+
for (const t of states[cur].eps) if (!set.has(t)) stack.push(t);
|
|
23269
|
+
for (const a of states[cur].asserts) {
|
|
23270
|
+
if (assertHolds(a.kind, input, pos) && !set.has(a.target)) stack.push(a.target);
|
|
23271
|
+
}
|
|
23272
|
+
}
|
|
23273
|
+
};
|
|
23274
|
+
return {
|
|
23275
|
+
// The input is matched in full (no truncation — truncation would miss suffix
|
|
23276
|
+
// matches and let `$` falsely match an invented endpoint). Work is bounded by
|
|
23277
|
+
// the shared budget instead: when it runs out we report a safe non-match.
|
|
23278
|
+
test(input, budget) {
|
|
23279
|
+
const b = budget ?? { remaining: Number.POSITIVE_INFINITY };
|
|
23280
|
+
if (b.remaining <= 0) return false;
|
|
23281
|
+
let current = /* @__PURE__ */ new Set();
|
|
23282
|
+
for (let pos = 0; pos <= input.length; pos += 1) {
|
|
23283
|
+
closure(current, start, input, pos, b);
|
|
23284
|
+
if (current.has(accept)) return true;
|
|
23285
|
+
if (b.remaining <= 0) return false;
|
|
23286
|
+
if (pos === input.length) break;
|
|
23287
|
+
const ch = input[pos];
|
|
23288
|
+
const next = /* @__PURE__ */ new Set();
|
|
23289
|
+
for (const s of current) {
|
|
23290
|
+
b.remaining -= 1;
|
|
23291
|
+
for (const t of states[s].chars) {
|
|
23292
|
+
if (t.test(ch)) closure(next, t.target, input, pos + 1, b);
|
|
23293
|
+
}
|
|
23294
|
+
}
|
|
23295
|
+
current = next;
|
|
23296
|
+
}
|
|
23297
|
+
return current.has(accept);
|
|
23298
|
+
}
|
|
23299
|
+
};
|
|
23300
|
+
}
|
|
23301
|
+
|
|
23302
|
+
// src/lessons/regex-linear/ast.ts
|
|
23303
|
+
var UnsupportedRegexError = class extends Error {
|
|
23304
|
+
constructor(message) {
|
|
23305
|
+
super(message);
|
|
23306
|
+
this.name = "UnsupportedRegexError";
|
|
23307
|
+
}
|
|
23308
|
+
};
|
|
23309
|
+
|
|
23310
|
+
// src/lessons/regex-linear/parse-helpers.ts
|
|
23311
|
+
var MAX_REPEAT = 1e3;
|
|
23312
|
+
var isWord = (c2) => /[A-Za-z0-9_]/.test(c2);
|
|
23313
|
+
function expandRepeat(atom, min, max) {
|
|
23314
|
+
const items = [];
|
|
23315
|
+
for (let k = 0; k < min; k += 1) items.push(atom);
|
|
23316
|
+
if (max === Infinity) {
|
|
23317
|
+
items.push({ k: "star", node: atom });
|
|
23318
|
+
} else {
|
|
23319
|
+
for (let k = min; k < max; k += 1) items.push({ k: "opt", node: atom });
|
|
23320
|
+
}
|
|
23321
|
+
if (items.length === 0) return { k: "empty" };
|
|
23322
|
+
return items.length === 1 ? items[0] : { k: "concat", items };
|
|
23323
|
+
}
|
|
23324
|
+
function escapeClass(c2) {
|
|
23325
|
+
switch (c2) {
|
|
23326
|
+
case "d":
|
|
23327
|
+
return (x) => x >= "0" && x <= "9";
|
|
23328
|
+
case "D":
|
|
23329
|
+
return (x) => !(x >= "0" && x <= "9");
|
|
23330
|
+
case "w":
|
|
23331
|
+
return isWord;
|
|
23332
|
+
case "W":
|
|
23333
|
+
return (x) => !isWord(x);
|
|
23334
|
+
case "s":
|
|
23335
|
+
return (x) => /\s/.test(x);
|
|
23336
|
+
case "S":
|
|
23337
|
+
return (x) => !/\s/.test(x);
|
|
23338
|
+
default:
|
|
23339
|
+
return null;
|
|
23340
|
+
}
|
|
23341
|
+
}
|
|
23342
|
+
var HEX2 = /^[0-9a-fA-F]{2}$/;
|
|
23343
|
+
var HEX4 = /^[0-9a-fA-F]{4}$/;
|
|
23344
|
+
function readUnicodeEscape(src, i, c2) {
|
|
23345
|
+
if (c2 === "x") {
|
|
23346
|
+
const hex2 = src.slice(i, i + 2);
|
|
23347
|
+
return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
|
|
23348
|
+
}
|
|
23349
|
+
const hex = src.slice(i, i + 4);
|
|
23350
|
+
return HEX4.test(hex) ? { ch: String.fromCharCode(parseInt(hex, 16)), len: 4 } : { ch: "u", len: 0 };
|
|
23351
|
+
}
|
|
23352
|
+
function readControlEscape(src, i) {
|
|
23353
|
+
const x = src[i];
|
|
23354
|
+
if (x === void 0 || !/[A-Za-z]/.test(x)) {
|
|
23355
|
+
throw new UnsupportedRegexError("\\c must be followed by a letter");
|
|
23356
|
+
}
|
|
23357
|
+
return { ch: String.fromCharCode(x.charCodeAt(0) & 31), len: 1 };
|
|
23358
|
+
}
|
|
23359
|
+
function classEscapeChar(src, i, e) {
|
|
23360
|
+
if (e === "b") return { ch: "\b", len: 0 };
|
|
23361
|
+
if (e === "c") return readControlEscape(src, i);
|
|
23362
|
+
if (e === "x" || e === "u") return readUnicodeEscape(src, i, e);
|
|
23363
|
+
return { ch: escapeLiteral(e), len: 0 };
|
|
23364
|
+
}
|
|
23365
|
+
function escapeLiteral(c2) {
|
|
23366
|
+
switch (c2) {
|
|
23367
|
+
case "t":
|
|
23368
|
+
return " ";
|
|
23369
|
+
case "n":
|
|
23370
|
+
return "\n";
|
|
23371
|
+
case "r":
|
|
23372
|
+
return "\r";
|
|
23373
|
+
case "f":
|
|
23374
|
+
return "\f";
|
|
23375
|
+
case "v":
|
|
23376
|
+
return "\v";
|
|
23377
|
+
case "0":
|
|
23378
|
+
return "\0";
|
|
23379
|
+
default:
|
|
23380
|
+
return c2;
|
|
23381
|
+
}
|
|
23382
|
+
}
|
|
23383
|
+
|
|
23384
|
+
// src/lessons/regex-linear/parse.ts
|
|
23385
|
+
function parseRegex(src) {
|
|
23386
|
+
let i = 0;
|
|
23387
|
+
const peek = () => src[i];
|
|
23388
|
+
const eat = () => src[i++];
|
|
23389
|
+
function parseAlt() {
|
|
23390
|
+
const opts = [parseConcat()];
|
|
23391
|
+
while (peek() === "|") {
|
|
23392
|
+
i += 1;
|
|
23393
|
+
opts.push(parseConcat());
|
|
23394
|
+
}
|
|
23395
|
+
return opts.length === 1 ? opts[0] : { k: "alt", opts };
|
|
23396
|
+
}
|
|
23397
|
+
function parseConcat() {
|
|
23398
|
+
const items = [];
|
|
23399
|
+
while (i < src.length && peek() !== "|" && peek() !== ")") {
|
|
23400
|
+
items.push(parseQuantified());
|
|
23401
|
+
}
|
|
23402
|
+
if (items.length === 0) return { k: "empty" };
|
|
23403
|
+
return items.length === 1 ? items[0] : { k: "concat", items };
|
|
23404
|
+
}
|
|
23405
|
+
function parseQuantified() {
|
|
23406
|
+
const atom = parseAtom();
|
|
23407
|
+
const q = peek();
|
|
23408
|
+
if (q === "*" || q === "+" || q === "?") {
|
|
23409
|
+
i += 1;
|
|
23410
|
+
if (peek() === "?") i += 1;
|
|
23411
|
+
return q === "*" ? { k: "star", node: atom } : q === "+" ? { k: "plus", node: atom } : { k: "opt", node: atom };
|
|
23412
|
+
}
|
|
23413
|
+
if (q === "{") {
|
|
23414
|
+
const repeat = tryParseBrace();
|
|
23415
|
+
if (repeat !== null) return expandRepeat(atom, repeat.min, repeat.max);
|
|
23416
|
+
}
|
|
23417
|
+
return atom;
|
|
23418
|
+
}
|
|
23419
|
+
function tryParseBrace() {
|
|
23420
|
+
const m = /^\{(\d+)(,(\d*)?)?\}/.exec(src.slice(i));
|
|
23421
|
+
if (m === null) return null;
|
|
23422
|
+
i += m[0].length;
|
|
23423
|
+
if (peek() === "?") i += 1;
|
|
23424
|
+
const min = Number(m[1]);
|
|
23425
|
+
const max = m[2] === void 0 ? min : m[3] === "" || m[3] === void 0 ? Infinity : Number(m[3]);
|
|
23426
|
+
if (min > MAX_REPEAT || max !== Infinity && max > MAX_REPEAT) {
|
|
23427
|
+
throw new UnsupportedRegexError(`Repeat count over ${MAX_REPEAT} not supported: {${m[1]}\u2026}`);
|
|
23428
|
+
}
|
|
23429
|
+
return { min, max };
|
|
23430
|
+
}
|
|
23431
|
+
function parseAtom() {
|
|
23432
|
+
const c2 = peek();
|
|
23433
|
+
if (c2 === "(") return parseGroup();
|
|
23434
|
+
if (c2 === "[") return parseClass();
|
|
23435
|
+
if (c2 === "\\") return parseEscape();
|
|
23436
|
+
if (c2 === ".") {
|
|
23437
|
+
i += 1;
|
|
23438
|
+
return { k: "any" };
|
|
23439
|
+
}
|
|
23440
|
+
if (c2 === "^") {
|
|
23441
|
+
i += 1;
|
|
23442
|
+
return { k: "assert", kind: "start" };
|
|
23443
|
+
}
|
|
23444
|
+
if (c2 === "$") {
|
|
23445
|
+
i += 1;
|
|
23446
|
+
return { k: "assert", kind: "end" };
|
|
23447
|
+
}
|
|
23448
|
+
if (c2 === void 0 || c2 === "*" || c2 === "+" || c2 === "?" || c2 === ")") {
|
|
23449
|
+
throw new UnsupportedRegexError(`Unexpected '${c2 ?? "<end>"}' in pattern`);
|
|
23450
|
+
}
|
|
23451
|
+
i += 1;
|
|
23452
|
+
return { k: "char", ch: c2 };
|
|
23453
|
+
}
|
|
23454
|
+
function parseGroup() {
|
|
23455
|
+
i += 1;
|
|
23456
|
+
if (peek() === "?") {
|
|
23457
|
+
const c2 = src[i + 1];
|
|
23458
|
+
if (c2 === "=" || c2 === "!" || c2 === "<") {
|
|
23459
|
+
if (!(c2 === "<" && /[A-Za-z]/.test(src[i + 2] ?? ""))) {
|
|
23460
|
+
throw new UnsupportedRegexError("Lookaround assertions are not supported");
|
|
23461
|
+
}
|
|
23462
|
+
}
|
|
23463
|
+
if (c2 === ":") i += 2;
|
|
23464
|
+
else if (c2 === "<") {
|
|
23465
|
+
i += 2;
|
|
23466
|
+
while (i < src.length && src[i] !== ">") i += 1;
|
|
23467
|
+
i += 1;
|
|
23468
|
+
}
|
|
23469
|
+
}
|
|
23470
|
+
const inner = parseAlt();
|
|
23471
|
+
if (peek() !== ")") throw new UnsupportedRegexError("Unbalanced group");
|
|
23472
|
+
i += 1;
|
|
23473
|
+
return inner;
|
|
23474
|
+
}
|
|
23475
|
+
function parseEscape() {
|
|
23476
|
+
i += 1;
|
|
23477
|
+
const c2 = peek();
|
|
23478
|
+
if (c2 === void 0) throw new UnsupportedRegexError("Trailing backslash");
|
|
23479
|
+
if (/[1-9]/.test(c2) || c2 === "k")
|
|
23480
|
+
throw new UnsupportedRegexError("Backreferences are not supported");
|
|
23481
|
+
i += 1;
|
|
23482
|
+
if (c2 === "b") return { k: "assert", kind: "wordB" };
|
|
23483
|
+
if (c2 === "B") return { k: "assert", kind: "nonWordB" };
|
|
23484
|
+
const cls = escapeClass(c2);
|
|
23485
|
+
if (cls !== null) return { k: "class", test: cls };
|
|
23486
|
+
if (c2 === "x" || c2 === "u" || c2 === "c") {
|
|
23487
|
+
const { ch, len } = c2 === "c" ? readControlEscape(src, i) : readUnicodeEscape(src, i, c2);
|
|
23488
|
+
i += len;
|
|
23489
|
+
return { k: "char", ch };
|
|
23490
|
+
}
|
|
23491
|
+
return { k: "char", ch: escapeLiteral(c2) };
|
|
23492
|
+
}
|
|
23493
|
+
function parseClass() {
|
|
23494
|
+
i += 1;
|
|
23495
|
+
const negate = peek() === "^";
|
|
23496
|
+
if (negate) i += 1;
|
|
23497
|
+
const tests = [];
|
|
23498
|
+
while (i < src.length && peek() !== "]") {
|
|
23499
|
+
tests.push(parseClassMember());
|
|
23500
|
+
}
|
|
23501
|
+
if (peek() !== "]") throw new UnsupportedRegexError("Unterminated character class");
|
|
23502
|
+
i += 1;
|
|
23503
|
+
const base = (c2) => tests.some((t) => t(c2));
|
|
23504
|
+
return { k: "class", test: negate ? (c2) => !base(c2) : base };
|
|
23505
|
+
}
|
|
23506
|
+
function parseClassMember() {
|
|
23507
|
+
let lo;
|
|
23508
|
+
if (peek() === "\\") {
|
|
23509
|
+
i += 1;
|
|
23510
|
+
const e = eat();
|
|
23511
|
+
const cls = escapeClass(e);
|
|
23512
|
+
if (cls !== null) return cls;
|
|
23513
|
+
const r = classEscapeChar(src, i, e);
|
|
23514
|
+
i += r.len;
|
|
23515
|
+
lo = r.ch;
|
|
23516
|
+
} else {
|
|
23517
|
+
lo = eat();
|
|
23518
|
+
}
|
|
23519
|
+
if (peek() === "-" && src[i + 1] !== void 0 && src[i + 1] !== "]") {
|
|
23520
|
+
i += 1;
|
|
23521
|
+
let hi;
|
|
23522
|
+
if (peek() === "\\") {
|
|
23523
|
+
i += 1;
|
|
23524
|
+
const e2 = eat();
|
|
23525
|
+
const r = classEscapeChar(src, i, e2);
|
|
23526
|
+
i += r.len;
|
|
23527
|
+
hi = r.ch;
|
|
23528
|
+
} else {
|
|
23529
|
+
hi = eat();
|
|
23530
|
+
}
|
|
23531
|
+
const a = lo.codePointAt(0);
|
|
23532
|
+
const b = hi.codePointAt(0);
|
|
23533
|
+
return (c2) => {
|
|
23534
|
+
const p = c2.codePointAt(0);
|
|
23535
|
+
return p >= a && p <= b;
|
|
23536
|
+
};
|
|
23537
|
+
}
|
|
23538
|
+
return (c2) => c2 === lo;
|
|
23539
|
+
}
|
|
23540
|
+
const ast = parseAlt();
|
|
23541
|
+
if (i !== src.length) throw new UnsupportedRegexError(`Unexpected '${peek()}' at ${i}`);
|
|
23542
|
+
return ast;
|
|
23543
|
+
}
|
|
23544
|
+
|
|
23545
|
+
// src/lessons/regex-linear/index.ts
|
|
23546
|
+
var cache = /* @__PURE__ */ new Map();
|
|
23547
|
+
function compileLinearMatcher(pattern) {
|
|
23548
|
+
const hit = cache.get(pattern);
|
|
23549
|
+
if (hit !== void 0 || cache.has(pattern)) return hit ?? null;
|
|
23550
|
+
let matcher;
|
|
23551
|
+
try {
|
|
23552
|
+
matcher = buildMatcher(parseRegex(pattern));
|
|
23553
|
+
} catch {
|
|
23554
|
+
matcher = null;
|
|
23555
|
+
}
|
|
23556
|
+
cache.set(pattern, matcher);
|
|
23557
|
+
return matcher;
|
|
23558
|
+
}
|
|
23559
|
+
|
|
23560
|
+
// src/lessons/regex-safety.ts
|
|
23561
|
+
var MAX_PATTERN_LENGTH = 1e3;
|
|
23562
|
+
function isSafeRegexPattern(pattern) {
|
|
23563
|
+
if (pattern.length > MAX_PATTERN_LENGTH) return false;
|
|
23564
|
+
return compileLinearMatcher(pattern) !== null;
|
|
23565
|
+
}
|
|
23566
|
+
|
|
23567
|
+
// src/lessons/validate-quality.ts
|
|
23568
|
+
function collectDuplicateRules(graph, findings) {
|
|
23569
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
23570
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23571
|
+
if (lesson.status !== "active") continue;
|
|
23572
|
+
const key = normalizeRule(lesson.rule);
|
|
23573
|
+
const bucket = byKey.get(key) ?? [];
|
|
23574
|
+
bucket.push(lessonId);
|
|
23575
|
+
byKey.set(key, bucket);
|
|
23576
|
+
}
|
|
23577
|
+
for (const [key, ids] of byKey) {
|
|
23578
|
+
if (ids.length < 2) continue;
|
|
23579
|
+
const sorted = [...ids].sort();
|
|
23580
|
+
for (const lessonId of sorted) {
|
|
23581
|
+
const others = sorted.filter((other) => other !== lessonId);
|
|
23582
|
+
findings.push({
|
|
23583
|
+
level: "error",
|
|
23584
|
+
code: "DUPLICATE_RULE",
|
|
23585
|
+
message: `Lesson "${lessonId}" duplicates rule text of: ${others.join(", ")} (normalized key: "${key.slice(0, 60)}").`,
|
|
23586
|
+
lessonId
|
|
23587
|
+
});
|
|
23588
|
+
}
|
|
23589
|
+
}
|
|
23590
|
+
}
|
|
23591
|
+
function collectInvalidTriggerPatterns(graph, findings) {
|
|
23592
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23593
|
+
if (trigger.kind !== "command_pattern") continue;
|
|
23594
|
+
try {
|
|
23595
|
+
new RegExp(trigger.pattern);
|
|
23596
|
+
} catch (err) {
|
|
23597
|
+
findings.push({
|
|
23598
|
+
level: "error",
|
|
23599
|
+
code: "INVALID_TRIGGER_PATTERN",
|
|
23600
|
+
message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${err instanceof Error ? err.message : String(err)}.`,
|
|
23601
|
+
triggerId
|
|
23602
|
+
});
|
|
23603
|
+
continue;
|
|
23604
|
+
}
|
|
23605
|
+
if (!isSafeRegexPattern(trigger.pattern)) {
|
|
23606
|
+
findings.push({
|
|
23607
|
+
level: "error",
|
|
23608
|
+
code: "UNSAFE_TRIGGER_PATTERN",
|
|
23609
|
+
message: `Trigger "${triggerId}" has a command_pattern regex outside the provably-linear subset (${trigger.pattern}): it can backtrack catastrophically (e.g. a quantified group like (a+)+ or (a|aa)+, adjacent repetition like a+a+, or a backreference/lookaround). Rewrite using a linear pattern.`,
|
|
23610
|
+
triggerId
|
|
23611
|
+
});
|
|
23612
|
+
}
|
|
23613
|
+
}
|
|
23614
|
+
}
|
|
23615
|
+
function collectBackslashGlobPatterns(graph, findings) {
|
|
23616
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23617
|
+
if (trigger.kind !== "file_glob") continue;
|
|
23618
|
+
if (!trigger.pattern.includes("\\")) continue;
|
|
23619
|
+
findings.push({
|
|
23620
|
+
level: "error",
|
|
23621
|
+
code: "BACKSLASH_GLOB_PATTERN",
|
|
23622
|
+
message: `Trigger "${triggerId}" has a file_glob pattern with a backslash (${trigger.pattern}); recall normalizes paths to forward slashes, so it never fires. Replace \\ with /.`,
|
|
23623
|
+
triggerId
|
|
23624
|
+
});
|
|
23625
|
+
}
|
|
23626
|
+
}
|
|
23627
|
+
function collectDuplicateTriggers(graph, findings) {
|
|
23628
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
23629
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23630
|
+
const key = `${trigger.kind}|${trigger.pattern}`;
|
|
23631
|
+
const bucket = byKey.get(key) ?? [];
|
|
23632
|
+
bucket.push(triggerId);
|
|
23633
|
+
byKey.set(key, bucket);
|
|
23634
|
+
}
|
|
23635
|
+
for (const [key, ids] of byKey) {
|
|
23636
|
+
if (ids.length < 2) continue;
|
|
23637
|
+
const sorted = [...ids].sort();
|
|
23638
|
+
for (const triggerId of sorted) {
|
|
23639
|
+
const others = sorted.filter((other) => other !== triggerId);
|
|
23640
|
+
findings.push({
|
|
23641
|
+
level: "error",
|
|
23642
|
+
code: "DUPLICATE_TRIGGER",
|
|
23643
|
+
message: `Trigger "${triggerId}" duplicates (kind, pattern) of: ${others.join(", ")} (key: "${key}").`,
|
|
23644
|
+
triggerId
|
|
23645
|
+
});
|
|
23646
|
+
}
|
|
23647
|
+
}
|
|
23648
|
+
}
|
|
23649
|
+
var HIGH_FANOUT_THRESHOLD = 10;
|
|
23650
|
+
function collectFanout(graph, findings) {
|
|
23651
|
+
const fanout = /* @__PURE__ */ new Map();
|
|
23652
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23653
|
+
if (lesson.status !== "active") continue;
|
|
23654
|
+
for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
|
|
23655
|
+
}
|
|
23656
|
+
let over = 0;
|
|
23657
|
+
let max = 0;
|
|
23658
|
+
for (const n of fanout.values()) {
|
|
23659
|
+
if (n > HIGH_FANOUT_THRESHOLD) over += 1;
|
|
23660
|
+
if (n > max) max = n;
|
|
23661
|
+
}
|
|
23662
|
+
if (over > 0) {
|
|
23663
|
+
findings.push({
|
|
23664
|
+
level: "warning",
|
|
23665
|
+
code: "HIGH_FANOUT_TRIGGERS",
|
|
23666
|
+
message: `${over} trigger(s) each match more than ${HIGH_FANOUT_THRESHOLD} active lessons (max ${max}); recall returns the ranked top by default \u2014 consider per-lesson trigger refinement to improve precision.`
|
|
23667
|
+
});
|
|
23668
|
+
}
|
|
23669
|
+
}
|
|
23670
|
+
function collectLowSignalKeywords(graph, findings) {
|
|
23671
|
+
const activeTriggerIds2 = /* @__PURE__ */ new Set();
|
|
23672
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23673
|
+
if (lesson.status !== "active") continue;
|
|
23674
|
+
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
23675
|
+
}
|
|
23676
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23677
|
+
if (trigger.kind !== "keyword") continue;
|
|
23678
|
+
if (!activeTriggerIds2.has(triggerId)) continue;
|
|
23679
|
+
if (!isLowSignalKeyword(trigger.pattern)) continue;
|
|
23680
|
+
findings.push({
|
|
23681
|
+
level: "warning",
|
|
23682
|
+
code: "LOW_SIGNAL_KEYWORD",
|
|
23683
|
+
message: `Keyword trigger "${triggerId}" carries more than ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens (${trigger.pattern}); recall matches a keyword only as a substring of --keyword or a contiguous token-run in the file/command, so it rarely fires \u2014 use a short distinctive phrase.`,
|
|
23684
|
+
triggerId
|
|
23685
|
+
});
|
|
23686
|
+
}
|
|
23687
|
+
}
|
|
23688
|
+
function collectStopwordKeywords(graph, findings) {
|
|
23689
|
+
const activeTriggerIds2 = /* @__PURE__ */ new Set();
|
|
23690
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23691
|
+
if (lesson.status !== "active") continue;
|
|
23692
|
+
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
23693
|
+
}
|
|
23694
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23695
|
+
if (trigger.kind !== "keyword") continue;
|
|
23696
|
+
if (!activeTriggerIds2.has(triggerId)) continue;
|
|
23697
|
+
if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
|
|
23698
|
+
continue;
|
|
23699
|
+
}
|
|
23700
|
+
findings.push({
|
|
23701
|
+
level: "warning",
|
|
23702
|
+
code: "STOPWORD_KEYWORD",
|
|
23703
|
+
message: `Keyword trigger "${triggerId}" (${trigger.pattern}) loses tokens to stopword filtering, so its needle can never appear as a contiguous run on the mandatory --file/--cmd recall path \u2014 drop the stopwords (e.g. "state art" instead of "state of the art"), or detach it with \`lessons untrigger\`.`,
|
|
23704
|
+
triggerId
|
|
23705
|
+
});
|
|
23706
|
+
}
|
|
23707
|
+
}
|
|
23708
|
+
function normalizeRule(rule) {
|
|
23709
|
+
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
23710
|
+
}
|
|
23711
|
+
function activeTriggerIds(graph) {
|
|
23712
|
+
const ids = /* @__PURE__ */ new Set();
|
|
23713
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23714
|
+
if (lesson.status !== "active") continue;
|
|
23715
|
+
for (const t of lesson.triggers) ids.add(t);
|
|
23716
|
+
}
|
|
23717
|
+
return ids;
|
|
23718
|
+
}
|
|
23719
|
+
function deadFileGlobIds(graph, knownPaths) {
|
|
23720
|
+
const active = activeTriggerIds(graph);
|
|
23721
|
+
const paths = [...knownPaths];
|
|
23722
|
+
const dead = /* @__PURE__ */ new Set();
|
|
23723
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23724
|
+
if (trigger.kind !== "file_glob") continue;
|
|
23725
|
+
if (!active.has(triggerId)) continue;
|
|
23726
|
+
const isMatch = picomatch(trigger.pattern, { dot: true });
|
|
23727
|
+
if (!paths.some((p) => isMatch(p))) dead.add(triggerId);
|
|
23728
|
+
}
|
|
23729
|
+
return dead;
|
|
23730
|
+
}
|
|
23731
|
+
function collectDeadFileGlobs(graph, findings, knownPaths) {
|
|
23732
|
+
for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
|
|
23733
|
+
findings.push({
|
|
23734
|
+
level: "warning",
|
|
23735
|
+
code: "DEAD_FILE_GLOB",
|
|
23736
|
+
message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file in the working tree \u2014 the lesson is unreachable via this trigger (a rename likely moved the path). Re-point it at the current path, or detach it with \`lessons untrigger\`, or run \`lessons prune --apply\`.`,
|
|
23737
|
+
triggerId
|
|
23738
|
+
});
|
|
23739
|
+
}
|
|
23740
|
+
}
|
|
23741
|
+
var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
|
|
23742
|
+
function collectRunnerAnchoredPatterns(graph, findings) {
|
|
23743
|
+
const active = activeTriggerIds(graph);
|
|
23744
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23745
|
+
if (trigger.kind !== "command_pattern") continue;
|
|
23746
|
+
if (!active.has(triggerId)) continue;
|
|
23747
|
+
if (!RUNNER_ANCHOR.test(trigger.pattern)) continue;
|
|
23748
|
+
findings.push({
|
|
23749
|
+
level: "warning",
|
|
23750
|
+
code: "RUNNER_ANCHORED_PATTERN",
|
|
23751
|
+
message: `command_pattern trigger "${triggerId}" (${trigger.pattern}) is anchored to one runner \u2014 it won't fire for the same task via another runner (e.g. \`npx\` vs \`pnpm\`). Drop the \`^<runner>\` anchor and key on the task (e.g. \`\\bvitest\\b\`).`,
|
|
23752
|
+
triggerId
|
|
23753
|
+
});
|
|
23754
|
+
}
|
|
23755
|
+
}
|
|
23756
|
+
|
|
23757
|
+
// src/lessons/validate.ts
|
|
23758
|
+
function validateLessonsGraph(graph, options = {}) {
|
|
23759
|
+
const findings = [];
|
|
23760
|
+
const schemaResult = LessonsGraphSchema.safeParse(graph);
|
|
23761
|
+
if (!schemaResult.success) {
|
|
23762
|
+
findings.push({
|
|
23763
|
+
level: "error",
|
|
23764
|
+
code: "SCHEMA_INVALID",
|
|
23765
|
+
message: schemaResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
23766
|
+
});
|
|
23767
|
+
return { ok: false, findings };
|
|
23768
|
+
}
|
|
23769
|
+
collectDanglingRefs(graph, findings);
|
|
23770
|
+
collectDuplicateRefs(graph, findings);
|
|
23771
|
+
collectStatusInvariants(graph, findings);
|
|
23772
|
+
collectLifecycleInvariants(graph, findings);
|
|
23773
|
+
collectDuplicateRules(graph, findings);
|
|
23774
|
+
collectReachability(graph, findings);
|
|
23775
|
+
collectInvalidTriggerPatterns(graph, findings);
|
|
23776
|
+
collectBackslashGlobPatterns(graph, findings);
|
|
23777
|
+
collectDuplicateTriggers(graph, findings);
|
|
23778
|
+
collectOrphans(graph, findings);
|
|
23779
|
+
collectFanout(graph, findings);
|
|
23780
|
+
collectLowSignalKeywords(graph, findings);
|
|
23781
|
+
collectStopwordKeywords(graph, findings);
|
|
23782
|
+
collectRunnerAnchoredPatterns(graph, findings);
|
|
23783
|
+
if (options.knownPaths !== void 0) collectDeadFileGlobs(graph, findings, options.knownPaths);
|
|
23784
|
+
const ok = findings.every((f) => f.level !== "error");
|
|
23785
|
+
return { ok, findings };
|
|
23786
|
+
}
|
|
23787
|
+
|
|
23788
|
+
// src/core/lint/shared/lessons.ts
|
|
23789
|
+
var LESSONS_TARGET = "lessons";
|
|
23790
|
+
var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
|
|
23791
|
+
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
23792
|
+
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
23793
|
+
function lintLessonsSubsystem(projectRoot, scope) {
|
|
23794
|
+
if (scope === "global") return [];
|
|
23795
|
+
const paths = lessonsPaths(projectRoot);
|
|
23796
|
+
if (!existsSync(paths.graph)) return [];
|
|
23797
|
+
const out2 = [];
|
|
23798
|
+
let graph;
|
|
23799
|
+
try {
|
|
23800
|
+
graph = loadLessonsGraph(projectRoot);
|
|
23801
|
+
} catch (err) {
|
|
23802
|
+
return [
|
|
23803
|
+
diag(
|
|
23804
|
+
"error",
|
|
23805
|
+
GRAPH_REL,
|
|
23806
|
+
`lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
23807
|
+
)
|
|
23808
|
+
];
|
|
23809
|
+
}
|
|
23810
|
+
const knownPaths = listProjectFiles(projectRoot) ?? void 0;
|
|
23811
|
+
const report = validateLessonsGraph(graph, { knownPaths });
|
|
23812
|
+
for (const finding of report.findings) {
|
|
23813
|
+
out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
|
|
23814
|
+
}
|
|
23815
|
+
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
23816
|
+
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|
|
23817
|
+
if (!LESSONS_HEADING.test(rootRuleBody)) {
|
|
23818
|
+
out2.push(
|
|
23819
|
+
diag(
|
|
23820
|
+
"warning",
|
|
23821
|
+
ROOT_RULE_REL,
|
|
23822
|
+
'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
|
|
23823
|
+
)
|
|
23824
|
+
);
|
|
23825
|
+
}
|
|
23826
|
+
return out2;
|
|
23827
|
+
}
|
|
23828
|
+
function diag(level, file, message) {
|
|
23829
|
+
return { level, file, target: LESSONS_TARGET, message };
|
|
23830
|
+
}
|
|
22568
23831
|
|
|
22569
23832
|
// src/core/lint/linter.ts
|
|
22570
23833
|
var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
|
|
@@ -22585,7 +23848,7 @@ async function runLint(config, canonical, projectRoot, targetFilter, options = {
|
|
|
22585
23848
|
const hasMcp = config.features.includes("mcp");
|
|
22586
23849
|
const hasPermissions = config.features.includes("permissions");
|
|
22587
23850
|
const hasHooks = config.features.includes("hooks");
|
|
22588
|
-
const diagnostics = [];
|
|
23851
|
+
const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
|
|
22589
23852
|
const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
|
|
22590
23853
|
for (const target31 of targets) {
|
|
22591
23854
|
const fullDesc = getDescriptor(target31);
|