agentsmesh 0.22.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 +187 -0
- package/README.md +68 -18
- package/dist/canonical.d.ts +2 -2
- package/dist/canonical.js +317 -44
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +270 -259
- package/dist/engine.d.ts +5 -2
- package/dist/engine.js +1290 -145
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2783 -600
- package/dist/index.js.map +1 -1
- package/dist/init-YKxF2zpQ.d.ts +494 -0
- package/dist/lessons.d.ts +88 -137
- package/dist/lessons.js +2680 -221
- package/dist/lessons.js.map +1 -1
- 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 +271 -30
- package/dist/targets.js.map +1 -1
- package/package.json +1 -3
- 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);
|
|
@@ -2009,9 +2037,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
|
|
|
2009
2037
|
const before = fullContent[start - 1];
|
|
2010
2038
|
const after = fullContent[end];
|
|
2011
2039
|
if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
|
|
2012
|
-
if (before === "
|
|
2013
|
-
|
|
2014
|
-
}
|
|
2040
|
+
if (before === "`" && after === "`") return true;
|
|
2041
|
+
if (before === "'" && after === "'" || before === '"' && after === '"') return false;
|
|
2015
2042
|
if (before === "<" && after === ">") return true;
|
|
2016
2043
|
if (before === "[" && after === "]") {
|
|
2017
2044
|
if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
|
|
@@ -3338,12 +3365,12 @@ var init_augment_code = __esm({
|
|
|
3338
3365
|
});
|
|
3339
3366
|
|
|
3340
3367
|
// src/targets/claude-code/constants.ts
|
|
3341
|
-
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;
|
|
3342
3369
|
var init_constants7 = __esm({
|
|
3343
3370
|
"src/targets/claude-code/constants.ts"() {
|
|
3344
3371
|
CLAUDE_CODE_TARGET = "claude-code";
|
|
3345
|
-
CLAUDE_ROOT = "
|
|
3346
|
-
|
|
3372
|
+
CLAUDE_ROOT = "CLAUDE.md";
|
|
3373
|
+
CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
|
|
3347
3374
|
CLAUDE_RULES_DIR = ".claude/rules";
|
|
3348
3375
|
CLAUDE_COMMANDS_DIR = ".claude/commands";
|
|
3349
3376
|
CLAUDE_AGENTS_DIR = ".claude/agents";
|
|
@@ -5205,7 +5232,8 @@ var init_amp2 = __esm({
|
|
|
5205
5232
|
markAsRoot: true
|
|
5206
5233
|
}
|
|
5207
5234
|
},
|
|
5208
|
-
emitScopedSettings(canonical, _scope) {
|
|
5235
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
5236
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
5209
5237
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
5210
5238
|
return [
|
|
5211
5239
|
{
|
|
@@ -5957,12 +5985,12 @@ function mergeAugmentSettings(existing, newContent) {
|
|
|
5957
5985
|
if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
|
|
5958
5986
|
return JSON.stringify(base, null, 2);
|
|
5959
5987
|
}
|
|
5960
|
-
function buildSettingsContent(canonical) {
|
|
5988
|
+
function buildSettingsContent(canonical, enabledFeatures) {
|
|
5961
5989
|
const settings = {};
|
|
5962
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5990
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5963
5991
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
5964
5992
|
}
|
|
5965
|
-
if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5993
|
+
if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5966
5994
|
settings.hooks = serializeHooksForSettings(canonical.hooks);
|
|
5967
5995
|
}
|
|
5968
5996
|
if (Object.keys(settings).length === 0) return null;
|
|
@@ -6091,8 +6119,8 @@ var init_augment_code2 = __esm({
|
|
|
6091
6119
|
],
|
|
6092
6120
|
layout: globalLayout5
|
|
6093
6121
|
},
|
|
6094
|
-
emitScopedSettings(canonical) {
|
|
6095
|
-
const content = buildSettingsContent(canonical);
|
|
6122
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
6123
|
+
const content = buildSettingsContent(canonical, enabledFeatures);
|
|
6096
6124
|
if (content === null) return [];
|
|
6097
6125
|
return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
|
|
6098
6126
|
},
|
|
@@ -6666,7 +6694,10 @@ var init_claude_code2 = __esm({
|
|
|
6666
6694
|
skillDir: ".claude/skills",
|
|
6667
6695
|
managedOutputs: {
|
|
6668
6696
|
dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
|
|
6669
|
-
|
|
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"]
|
|
6670
6701
|
},
|
|
6671
6702
|
paths: {
|
|
6672
6703
|
rulePath(slug, _rule) {
|
|
@@ -6681,7 +6712,7 @@ var init_claude_code2 = __esm({
|
|
|
6681
6712
|
}
|
|
6682
6713
|
};
|
|
6683
6714
|
globalLayout6 = {
|
|
6684
|
-
rootInstructionPath:
|
|
6715
|
+
rootInstructionPath: CLAUDE_NESTED_ROOT,
|
|
6685
6716
|
skillDir: ".claude/skills",
|
|
6686
6717
|
renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
|
|
6687
6718
|
managedOutputs: {
|
|
@@ -6694,7 +6725,7 @@ var init_claude_code2 = __esm({
|
|
|
6694
6725
|
".agents/skills"
|
|
6695
6726
|
],
|
|
6696
6727
|
files: [
|
|
6697
|
-
|
|
6728
|
+
CLAUDE_NESTED_ROOT,
|
|
6698
6729
|
".claude/settings.json",
|
|
6699
6730
|
CLAUDE_GLOBAL_MCP_JSON,
|
|
6700
6731
|
CLAUDE_HOOKS_JSON,
|
|
@@ -6702,6 +6733,7 @@ var init_claude_code2 = __esm({
|
|
|
6702
6733
|
]
|
|
6703
6734
|
},
|
|
6704
6735
|
rewriteGeneratedPath(path) {
|
|
6736
|
+
if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
|
|
6705
6737
|
if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
|
|
6706
6738
|
return path;
|
|
6707
6739
|
},
|
|
@@ -6765,10 +6797,11 @@ var init_claude_code2 = __esm({
|
|
|
6765
6797
|
importer: {
|
|
6766
6798
|
rules: [
|
|
6767
6799
|
{
|
|
6768
|
-
// 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.
|
|
6769
6802
|
feature: "rules",
|
|
6770
6803
|
mode: "singleFile",
|
|
6771
|
-
source: { project: [CLAUDE_ROOT,
|
|
6804
|
+
source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
|
|
6772
6805
|
canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
|
|
6773
6806
|
canonicalRootFilename: "_root.md",
|
|
6774
6807
|
markAsRoot: true
|
|
@@ -6814,7 +6847,23 @@ var init_claude_code2 = __esm({
|
|
|
6814
6847
|
}
|
|
6815
6848
|
},
|
|
6816
6849
|
buildImportPaths: buildClaudeCodeImportPaths,
|
|
6817
|
-
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
|
+
}
|
|
6818
6867
|
};
|
|
6819
6868
|
}
|
|
6820
6869
|
});
|
|
@@ -7693,6 +7742,16 @@ var init_cline2 = __esm({
|
|
|
7693
7742
|
},
|
|
7694
7743
|
buildImportPaths: buildClineImportPaths,
|
|
7695
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
|
+
},
|
|
7696
7755
|
conversionDefaults: { agentsToSkills: true }
|
|
7697
7756
|
};
|
|
7698
7757
|
}
|
|
@@ -8611,6 +8670,11 @@ var init_codex_cli2 = __esm({
|
|
|
8611
8670
|
".codex/agents",
|
|
8612
8671
|
".codex/rules"
|
|
8613
8672
|
],
|
|
8673
|
+
nativeInstall: {
|
|
8674
|
+
pickPaths: [
|
|
8675
|
+
{ prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
|
|
8676
|
+
]
|
|
8677
|
+
},
|
|
8614
8678
|
excludeFromStarterInit: true,
|
|
8615
8679
|
conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
|
|
8616
8680
|
};
|
|
@@ -9114,6 +9178,21 @@ var init_continue2 = __esm({
|
|
|
9114
9178
|
},
|
|
9115
9179
|
buildImportPaths: buildContinueImportPaths,
|
|
9116
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
|
+
},
|
|
9117
9196
|
conversionDefaults: { agentsToSkills: true }
|
|
9118
9197
|
};
|
|
9119
9198
|
}
|
|
@@ -9481,6 +9560,80 @@ var init_importer10 = __esm({
|
|
|
9481
9560
|
init_copilot2();
|
|
9482
9561
|
}
|
|
9483
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
|
+
});
|
|
9484
9637
|
function pruneUndefined3(record) {
|
|
9485
9638
|
for (const key of Object.keys(record)) {
|
|
9486
9639
|
if (record[key] === void 0) delete record[key];
|
|
@@ -9770,6 +9923,7 @@ var init_copilot2 = __esm({
|
|
|
9770
9923
|
init_generator11();
|
|
9771
9924
|
init_constants29();
|
|
9772
9925
|
init_importer10();
|
|
9926
|
+
init_native_path_pick_infer_copilot();
|
|
9773
9927
|
init_import_mappers4();
|
|
9774
9928
|
init_linter10();
|
|
9775
9929
|
init_import_map_builders();
|
|
@@ -9984,7 +10138,8 @@ var init_copilot2 = __esm({
|
|
|
9984
10138
|
".github/skills",
|
|
9985
10139
|
".github/agents",
|
|
9986
10140
|
".github/hooks"
|
|
9987
|
-
]
|
|
10141
|
+
],
|
|
10142
|
+
nativeInstall: { inferPick: inferCopilotPickFromPath }
|
|
9988
10143
|
};
|
|
9989
10144
|
}
|
|
9990
10145
|
});
|
|
@@ -11497,6 +11652,23 @@ var init_cursor2 = __esm({
|
|
|
11497
11652
|
},
|
|
11498
11653
|
buildImportPaths: buildCursorImportPaths,
|
|
11499
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
|
+
},
|
|
11500
11672
|
preservesManualActivation: true
|
|
11501
11673
|
};
|
|
11502
11674
|
}
|
|
@@ -12258,18 +12430,18 @@ function mapHookEvent2(event) {
|
|
|
12258
12430
|
return null;
|
|
12259
12431
|
}
|
|
12260
12432
|
}
|
|
12261
|
-
function generateGeminiSettingsFiles(canonical) {
|
|
12433
|
+
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
12262
12434
|
const settings = {};
|
|
12263
12435
|
let hasAnyNativeSettings = false;
|
|
12264
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12436
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12265
12437
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
12266
12438
|
hasAnyNativeSettings = true;
|
|
12267
12439
|
}
|
|
12268
|
-
if (canonical.agents.length > 0) {
|
|
12440
|
+
if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
|
|
12269
12441
|
settings.experimental = { enableAgents: true };
|
|
12270
12442
|
hasAnyNativeSettings = true;
|
|
12271
12443
|
}
|
|
12272
|
-
if (canonical.hooks) {
|
|
12444
|
+
if (enabledFeatures.has("hooks") && canonical.hooks) {
|
|
12273
12445
|
const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
|
|
12274
12446
|
const mappedEvent = mapHookEvent2(event);
|
|
12275
12447
|
if (!mappedEvent || !Array.isArray(entries)) return [];
|
|
@@ -12885,6 +13057,36 @@ var init_importer15 = __esm({
|
|
|
12885
13057
|
init_importer_skills_agents();
|
|
12886
13058
|
}
|
|
12887
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
|
+
});
|
|
12888
13090
|
async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
|
|
12889
13091
|
const relativeMdPath = relativePath.replace(/\\/g, "/");
|
|
12890
13092
|
const destPath = join(destDir, relativeMdPath);
|
|
@@ -12996,12 +13198,12 @@ var init_lint12 = __esm({
|
|
|
12996
13198
|
});
|
|
12997
13199
|
|
|
12998
13200
|
// src/targets/gemini-cli/scoped-settings-emit.ts
|
|
12999
|
-
function emitScopedGeminiSettings(canonical, scope) {
|
|
13201
|
+
function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
|
|
13000
13202
|
if (scope === "project") {
|
|
13001
13203
|
const caps = getTargetCapabilities("gemini-cli", scope);
|
|
13002
13204
|
if (caps?.ignore.flavor !== "settings-embedded") return [];
|
|
13003
13205
|
}
|
|
13004
|
-
return generateGeminiSettingsFiles(canonical);
|
|
13206
|
+
return generateGeminiSettingsFiles(canonical, enabledFeatures);
|
|
13005
13207
|
}
|
|
13006
13208
|
var init_scoped_settings_emit = __esm({
|
|
13007
13209
|
"src/targets/gemini-cli/scoped-settings-emit.ts"() {
|
|
@@ -13019,6 +13221,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13019
13221
|
init_policies_generator();
|
|
13020
13222
|
init_constants30();
|
|
13021
13223
|
init_importer15();
|
|
13224
|
+
init_gemini_install_commands();
|
|
13022
13225
|
init_import_mappers6();
|
|
13023
13226
|
init_linter15();
|
|
13024
13227
|
init_import_map_builders();
|
|
@@ -13214,6 +13417,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13214
13417
|
},
|
|
13215
13418
|
buildImportPaths: buildGeminiCliImportPaths,
|
|
13216
13419
|
detectionPaths: ["GEMINI.md", ".gemini"],
|
|
13420
|
+
nativeInstall: { inferPick: inferGeminiPick },
|
|
13217
13421
|
conversionDefaults: { agentsToSkills: false }
|
|
13218
13422
|
};
|
|
13219
13423
|
}
|
|
@@ -14055,7 +14259,23 @@ var init_junie2 = __esm({
|
|
|
14055
14259
|
".junie/skills",
|
|
14056
14260
|
".junie/mcp/mcp.json",
|
|
14057
14261
|
".aiignore"
|
|
14058
|
-
]
|
|
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
|
+
}
|
|
14059
14279
|
};
|
|
14060
14280
|
}
|
|
14061
14281
|
});
|
|
@@ -18405,6 +18625,16 @@ var init_windsurf2 = __esm({
|
|
|
18405
18625
|
},
|
|
18406
18626
|
buildImportPaths: buildWindsurfImportPaths,
|
|
18407
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
|
+
},
|
|
18408
18638
|
conversionDefaults: { agentsToSkills: true }
|
|
18409
18639
|
};
|
|
18410
18640
|
}
|
|
@@ -18679,7 +18909,8 @@ var init_zed2 = __esm({
|
|
|
18679
18909
|
markAsRoot: true
|
|
18680
18910
|
}
|
|
18681
18911
|
},
|
|
18682
|
-
emitScopedSettings(canonical, _scope) {
|
|
18912
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
18913
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
18683
18914
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
18684
18915
|
const contextServers = {};
|
|
18685
18916
|
for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
|
|
@@ -18734,7 +18965,10 @@ function getBuiltinTargetDefinition(target31) {
|
|
|
18734
18965
|
function getTargetCapabilities(target31, scope = "project") {
|
|
18735
18966
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
18736
18967
|
if (!descriptor31) return void 0;
|
|
18737
|
-
|
|
18968
|
+
if (scope === "global" && !descriptor31.globalSupport) {
|
|
18969
|
+
return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
|
|
18970
|
+
}
|
|
18971
|
+
const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
|
|
18738
18972
|
return normalizeTargetCapabilities(raw);
|
|
18739
18973
|
}
|
|
18740
18974
|
function getTargetDetectionPaths(target31, scope = "project") {
|
|
@@ -18790,6 +19024,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
|
|
|
18790
19024
|
function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
|
|
18791
19025
|
const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
|
|
18792
19026
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19027
|
+
if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
|
|
18793
19028
|
if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
|
|
18794
19029
|
return "embedded";
|
|
18795
19030
|
}
|
|
@@ -18803,7 +19038,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
|
|
|
18803
19038
|
const pick = PICK_FEATURE_GENERATOR[feature];
|
|
18804
19039
|
return pick === null ? void 0 : pick(descriptor31.generators);
|
|
18805
19040
|
}
|
|
18806
|
-
var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
19041
|
+
var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
18807
19042
|
var init_builtin_targets = __esm({
|
|
18808
19043
|
"src/targets/catalog/builtin-targets.ts"() {
|
|
18809
19044
|
init_conversions();
|
|
@@ -18841,6 +19076,17 @@ var init_builtin_targets = __esm({
|
|
|
18841
19076
|
init_warp2();
|
|
18842
19077
|
init_windsurf2();
|
|
18843
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
|
+
};
|
|
18844
19090
|
BUILTIN_TARGETS = [
|
|
18845
19091
|
descriptor,
|
|
18846
19092
|
descriptor2,
|
|
@@ -19271,6 +19517,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
|
|
|
19271
19517
|
init_path_helpers();
|
|
19272
19518
|
init_link_rebaser_helpers();
|
|
19273
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
|
+
|
|
19274
19537
|
// src/utils/output/logger.ts
|
|
19275
19538
|
var C = {
|
|
19276
19539
|
green: "\x1B[32m",
|
|
@@ -19279,16 +19542,14 @@ var C = {
|
|
|
19279
19542
|
cyan: "\x1B[36m",
|
|
19280
19543
|
reset: "\x1B[0m"
|
|
19281
19544
|
};
|
|
19282
|
-
function
|
|
19283
|
-
|
|
19284
|
-
process.stdout.write(text);
|
|
19285
|
-
}
|
|
19545
|
+
function outStream() {
|
|
19546
|
+
return process.stdout;
|
|
19286
19547
|
}
|
|
19287
|
-
function
|
|
19288
|
-
|
|
19548
|
+
function out(text) {
|
|
19549
|
+
outStream().write(text);
|
|
19289
19550
|
}
|
|
19290
|
-
function c(code, text) {
|
|
19291
|
-
return
|
|
19551
|
+
function c(code, text, stream) {
|
|
19552
|
+
return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
|
|
19292
19553
|
}
|
|
19293
19554
|
function pad(str, width) {
|
|
19294
19555
|
const len = [...str].length;
|
|
@@ -19296,20 +19557,20 @@ function pad(str, width) {
|
|
|
19296
19557
|
}
|
|
19297
19558
|
var logger = {
|
|
19298
19559
|
info(msg) {
|
|
19299
|
-
out(c(C.cyan, msg) + "\n");
|
|
19560
|
+
out(c(C.cyan, msg, outStream()) + "\n");
|
|
19300
19561
|
},
|
|
19301
19562
|
warn(msg) {
|
|
19302
|
-
process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
|
|
19563
|
+
process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
|
|
19303
19564
|
},
|
|
19304
19565
|
error(msg) {
|
|
19305
|
-
process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
|
|
19566
|
+
process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
|
|
19306
19567
|
},
|
|
19307
19568
|
success(msg) {
|
|
19308
|
-
out(c(C.green, "\u2713 ") + msg + "\n");
|
|
19569
|
+
out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
|
|
19309
19570
|
},
|
|
19310
19571
|
debug(msg) {
|
|
19311
19572
|
if (process.env.AGENTSMESH_DEBUG === "1") {
|
|
19312
|
-
out(c(C.cyan, "[debug] ") + msg + "\n");
|
|
19573
|
+
out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
|
|
19313
19574
|
}
|
|
19314
19575
|
},
|
|
19315
19576
|
table(rows) {
|
|
@@ -19804,12 +20065,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
19804
20065
|
}
|
|
19805
20066
|
}
|
|
19806
20067
|
}
|
|
19807
|
-
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
|
|
20068
|
+
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
|
|
19808
20069
|
for (const target31 of targets) {
|
|
19809
20070
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19810
20071
|
const emit = descriptor31?.emitScopedSettings;
|
|
19811
20072
|
if (!emit) continue;
|
|
19812
|
-
const outputs = emit(canonical, scope);
|
|
20073
|
+
const outputs = emit(canonical, scope, enabledFeatures);
|
|
19813
20074
|
if (outputs.length === 0) continue;
|
|
19814
20075
|
for (const out2 of outputs) {
|
|
19815
20076
|
await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
|
|
@@ -19910,7 +20171,14 @@ async function generate(ctx) {
|
|
|
19910
20171
|
}
|
|
19911
20172
|
}
|
|
19912
20173
|
if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
|
|
19913
|
-
await generateScopedSettingsFeature(
|
|
20174
|
+
await generateScopedSettingsFeature(
|
|
20175
|
+
results,
|
|
20176
|
+
targets,
|
|
20177
|
+
canonical,
|
|
20178
|
+
projectRoot,
|
|
20179
|
+
scope,
|
|
20180
|
+
enabledFeatures
|
|
20181
|
+
);
|
|
19914
20182
|
}
|
|
19915
20183
|
const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
|
|
19916
20184
|
const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
|
|
@@ -20234,7 +20502,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
20234
20502
|
await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
|
|
20235
20503
|
if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
|
|
20236
20504
|
await rm(cacheRoot, { recursive: true, force: true });
|
|
20237
|
-
await
|
|
20505
|
+
await renameWithRetry(stagedRoot, cacheRoot);
|
|
20238
20506
|
return readCachedRepo(cacheRepoDir);
|
|
20239
20507
|
} catch (err) {
|
|
20240
20508
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
@@ -22585,87 +22853,964 @@ function lintRuleScopeInversion(input) {
|
|
|
22585
22853
|
}
|
|
22586
22854
|
return out2;
|
|
22587
22855
|
}
|
|
22588
|
-
var
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
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
|
+
}
|
|
22615
22900
|
var BASE_REL = ".agentsmesh/lessons";
|
|
22616
22901
|
function lessonsPaths(projectRoot) {
|
|
22617
22902
|
const base = join(projectRoot, BASE_REL);
|
|
22618
22903
|
return {
|
|
22619
22904
|
base,
|
|
22905
|
+
graph: join(base, "lessons.json"),
|
|
22906
|
+
config: join(base, "config.json"),
|
|
22620
22907
|
journal: join(base, "journal.md"),
|
|
22621
22908
|
index: join(base, "index.yaml"),
|
|
22622
|
-
ledger: join(base, "distill-ledger.yaml"),
|
|
22623
|
-
proposal: join(base, "distill-proposal.md"),
|
|
22624
22909
|
topicsDir: join(base, "topics")
|
|
22625
22910
|
};
|
|
22626
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
|
+
}
|
|
22627
23787
|
|
|
22628
23788
|
// src/core/lint/shared/lessons.ts
|
|
22629
23789
|
var LESSONS_TARGET = "lessons";
|
|
22630
|
-
var
|
|
23790
|
+
var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
|
|
22631
23791
|
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
22632
23792
|
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
22633
|
-
var RULES_HEADING = /^## Rules\b/m;
|
|
22634
23793
|
function lintLessonsSubsystem(projectRoot, scope) {
|
|
22635
23794
|
if (scope === "global") return [];
|
|
22636
23795
|
const paths = lessonsPaths(projectRoot);
|
|
22637
|
-
if (!existsSync(paths.
|
|
23796
|
+
if (!existsSync(paths.graph)) return [];
|
|
22638
23797
|
const out2 = [];
|
|
22639
|
-
|
|
22640
|
-
|
|
22641
|
-
|
|
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
|
+
];
|
|
22642
23809
|
}
|
|
22643
|
-
|
|
22644
|
-
|
|
22645
|
-
|
|
22646
|
-
|
|
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
|
-
}
|
|
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}`));
|
|
22669
23814
|
}
|
|
22670
23815
|
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
22671
23816
|
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|