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/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { parse,
|
|
3
|
-
import { join, relative, sep, dirname,
|
|
4
|
-
import { access, readdir, readFile, realpath, stat,
|
|
5
|
-
import {
|
|
2
|
+
import { stringify, parse, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
|
|
3
|
+
import { join, resolve, relative, sep, dirname, basename, win32, posix, extname } from 'path';
|
|
4
|
+
import { mkdir, access, readdir, rm, readFile, realpath, stat, writeFile, lstat, unlink, rename, chmod, mkdtemp, cp } from 'fs/promises';
|
|
5
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
6
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, constants, rmSync, renameSync, readdirSync, realpathSync, statSync } from 'fs';
|
|
6
7
|
import { parse as parse$1 } from 'smol-toml';
|
|
7
8
|
import { Buffer } from 'buffer';
|
|
8
|
-
import { homedir, tmpdir } from 'os';
|
|
9
|
+
import { homedir, hostname, tmpdir } from 'os';
|
|
9
10
|
import { createHash } from 'crypto';
|
|
10
11
|
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';
|
|
14
|
-
import { createTwoFilesPatch } from 'diff';
|
|
15
15
|
import picomatch from 'picomatch';
|
|
16
|
+
import { createTwoFilesPatch } from 'diff';
|
|
16
17
|
|
|
17
18
|
var __defProp = Object.defineProperty;
|
|
18
19
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -46,7 +47,7 @@ function validateCapabilityImplementations(descriptor31, capabilities17, ctx, pa
|
|
|
46
47
|
function validateDescriptor(value) {
|
|
47
48
|
return targetDescriptorSchema.parse(value);
|
|
48
49
|
}
|
|
49
|
-
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;
|
|
50
51
|
var init_target_descriptor_schema = __esm({
|
|
51
52
|
"src/targets/catalog/target-descriptor.schema.ts"() {
|
|
52
53
|
capabilityLevelSchema = z.union([
|
|
@@ -120,6 +121,22 @@ var init_target_descriptor_schema = __esm({
|
|
|
120
121
|
officialUrl: z.string().min(1),
|
|
121
122
|
shortDescription: z.string().min(1)
|
|
122
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();
|
|
123
140
|
targetDescriptorSchemaBase = z.object({
|
|
124
141
|
id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Target id must be lowercase with hyphens"),
|
|
125
142
|
metadata: metadataSchema,
|
|
@@ -131,6 +148,7 @@ var init_target_descriptor_schema = __esm({
|
|
|
131
148
|
globalSupport: globalSupportSchema.optional(),
|
|
132
149
|
buildImportPaths: z.function(),
|
|
133
150
|
detectionPaths: z.array(z.string()),
|
|
151
|
+
nativeInstall: nativeInstallSchema.optional(),
|
|
134
152
|
excludeFromStarterInit: z.boolean().optional(),
|
|
135
153
|
conversionDefaults: conversionDefaultsSchema.optional(),
|
|
136
154
|
emitScopedSettings: z.function().optional(),
|
|
@@ -631,15 +649,19 @@ var init_errors = __esm({
|
|
|
631
649
|
LockAcquisitionError = class extends AgentsMeshError {
|
|
632
650
|
lockPath;
|
|
633
651
|
holder;
|
|
652
|
+
/** Human-readable lock name surfaced in the message, e.g. "lessons lock". */
|
|
653
|
+
label;
|
|
634
654
|
constructor(lockPath, holder, options) {
|
|
655
|
+
const label = options?.label ?? "lock";
|
|
635
656
|
super(
|
|
636
657
|
"AM_LOCK_ACQUISITION_FAILED",
|
|
637
|
-
`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.`,
|
|
638
659
|
options
|
|
639
660
|
);
|
|
640
661
|
this.name = "LockAcquisitionError";
|
|
641
662
|
this.lockPath = lockPath;
|
|
642
663
|
this.holder = holder;
|
|
664
|
+
this.label = label;
|
|
643
665
|
}
|
|
644
666
|
};
|
|
645
667
|
FileSystemError = class extends AgentsMeshError {
|
|
@@ -804,6 +826,27 @@ var init_fs_traverse = __esm({
|
|
|
804
826
|
MAX_SEGMENT_REPETITIONS = 3;
|
|
805
827
|
}
|
|
806
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$1(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
|
+
});
|
|
807
850
|
async function readFileSafe(path) {
|
|
808
851
|
try {
|
|
809
852
|
const data = await readFile(path, "utf-8");
|
|
@@ -890,6 +933,7 @@ var init_fs = __esm({
|
|
|
890
933
|
init_fs_text_encoding();
|
|
891
934
|
init_fs_traverse();
|
|
892
935
|
init_fs_text_encoding();
|
|
936
|
+
init_rename_retry();
|
|
893
937
|
}
|
|
894
938
|
});
|
|
895
939
|
function escapeRegExp(value) {
|
|
@@ -898,19 +942,24 @@ function escapeRegExp(value) {
|
|
|
898
942
|
function managedBlockPattern(start, end) {
|
|
899
943
|
return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
|
|
900
944
|
}
|
|
901
|
-
function replaceManagedBlock(content, start, end, block) {
|
|
902
|
-
const pattern = managedBlockPattern(start, end);
|
|
903
|
-
if (pattern.test(content)) {
|
|
904
|
-
return content.replace(pattern, block).trim();
|
|
905
|
-
}
|
|
906
|
-
const trimmed = content.trim();
|
|
907
|
-
return trimmed ? `${trimmed}
|
|
908
|
-
|
|
909
|
-
${block}` : block;
|
|
910
|
-
}
|
|
911
945
|
function stripManagedBlock(content, start, end) {
|
|
912
946
|
return content.replace(managedBlockPattern(start, end), "").trim();
|
|
913
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
|
+
}
|
|
914
963
|
function ruleSource(source) {
|
|
915
964
|
const normalized = source.replace(/\\/g, "/");
|
|
916
965
|
const meshIndex = normalized.lastIndexOf(".agentsmesh/");
|
|
@@ -998,11 +1047,13 @@ function extractEmbeddedRules(content) {
|
|
|
998
1047
|
});
|
|
999
1048
|
return { rootContent: rootContent.trim(), rules };
|
|
1000
1049
|
}
|
|
1001
|
-
var ROOT_CONTRACT_START, ROOT_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
|
|
1050
|
+
var ROOT_CONTRACT_START, ROOT_CONTRACT_END, LESSONS_CONTRACT_START, LESSONS_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
|
|
1002
1051
|
var init_managed_blocks = __esm({
|
|
1003
1052
|
"src/targets/projection/managed-blocks.ts"() {
|
|
1004
1053
|
ROOT_CONTRACT_START = "<!-- agentsmesh:root-generation-contract:start -->";
|
|
1005
1054
|
ROOT_CONTRACT_END = "<!-- agentsmesh:root-generation-contract:end -->";
|
|
1055
|
+
LESSONS_CONTRACT_START = "<!-- agentsmesh:lessons-contract:start -->";
|
|
1056
|
+
LESSONS_CONTRACT_END = "<!-- agentsmesh:lessons-contract:end -->";
|
|
1006
1057
|
EMBEDDED_RULES_START = "<!-- agentsmesh:embedded-rules:start -->";
|
|
1007
1058
|
EMBEDDED_RULES_END = "<!-- agentsmesh:embedded-rules:end -->";
|
|
1008
1059
|
EMBEDDED_RULE_END = "<!-- agentsmesh:embedded-rule:end -->";
|
|
@@ -1012,31 +1063,9 @@ var init_managed_blocks = __esm({
|
|
|
1012
1063
|
});
|
|
1013
1064
|
|
|
1014
1065
|
// src/targets/projection/root-instruction-paragraph.ts
|
|
1015
|
-
function normalizeWhitespace(value) {
|
|
1016
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1017
|
-
}
|
|
1018
1066
|
function appendAgentsmeshRootInstructionParagraph(content) {
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
return replaceManagedBlock(
|
|
1022
|
-
trimmed,
|
|
1023
|
-
ROOT_CONTRACT_START,
|
|
1024
|
-
ROOT_CONTRACT_END,
|
|
1025
|
-
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH
|
|
1026
|
-
);
|
|
1027
|
-
}
|
|
1028
|
-
const norm = normalizeWhitespace(trimmed);
|
|
1029
|
-
if (norm.includes(normalizeWhitespace(AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH))) {
|
|
1030
|
-
return trimmed;
|
|
1031
|
-
}
|
|
1032
|
-
for (const legacy of LEGACY_FORMS) {
|
|
1033
|
-
if (norm.includes(normalizeWhitespace(legacy))) {
|
|
1034
|
-
return trimmed.replace(legacy, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
return trimmed ? `${trimmed}
|
|
1038
|
-
|
|
1039
|
-
${AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH}` : AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH;
|
|
1067
|
+
const withoutPrior = stripAgentsmeshRootInstructionParagraph(content);
|
|
1068
|
+
return insertAtBodyTop(withoutPrior, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1040
1069
|
}
|
|
1041
1070
|
function stripAgentsmeshRootInstructionParagraph(content) {
|
|
1042
1071
|
let result = stripManagedBlock(content, ROOT_CONTRACT_START, ROOT_CONTRACT_END);
|
|
@@ -1286,7 +1315,7 @@ function generateEmbeddedSkills(canonical, skillsDir) {
|
|
|
1286
1315
|
}
|
|
1287
1316
|
return outputs;
|
|
1288
1317
|
}
|
|
1289
|
-
async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results,
|
|
1318
|
+
async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, normalize) {
|
|
1290
1319
|
const entries = await readdir(join(projectRoot, skillsDir), {
|
|
1291
1320
|
encoding: "utf8",
|
|
1292
1321
|
withFileTypes: true
|
|
@@ -1301,7 +1330,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
|
|
|
1301
1330
|
const destinationSkillDir = join(projectRoot, AB_SKILLS, entry.name);
|
|
1302
1331
|
const destinationSkillFile = join(destinationSkillDir, "SKILL.md");
|
|
1303
1332
|
const { frontmatter, body } = parseFrontmatter(
|
|
1304
|
-
|
|
1333
|
+
normalize(sourceSkillContent, sourceSkillFile, destinationSkillFile)
|
|
1305
1334
|
);
|
|
1306
1335
|
const projectedCommand = parseCommandSkillFrontmatter(frontmatter, entry.name);
|
|
1307
1336
|
if (projectedCommand) {
|
|
@@ -1311,7 +1340,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
|
|
|
1311
1340
|
const commandPath = join(destDir, `${projectedCommand.name}.md`);
|
|
1312
1341
|
await writeFileAtomic(
|
|
1313
1342
|
commandPath,
|
|
1314
|
-
serializeImportedCommand(projectedCommand,
|
|
1343
|
+
serializeImportedCommand(projectedCommand, normalize(body, sourceSkillFile, commandPath))
|
|
1315
1344
|
);
|
|
1316
1345
|
results.push({
|
|
1317
1346
|
fromTool,
|
|
@@ -1329,7 +1358,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
|
|
|
1329
1358
|
const agentPath = join(destDir, `${projectedAgent.name}.md`);
|
|
1330
1359
|
await writeFileAtomic(
|
|
1331
1360
|
agentPath,
|
|
1332
|
-
serializeImportedAgent(projectedAgent,
|
|
1361
|
+
serializeImportedAgent(projectedAgent, normalize(body, sourceSkillFile, agentPath))
|
|
1333
1362
|
);
|
|
1334
1363
|
results.push({
|
|
1335
1364
|
fromTool,
|
|
@@ -1360,7 +1389,7 @@ async function importEmbeddedSkills(projectRoot, skillsDir, fromTool, results, n
|
|
|
1360
1389
|
if (sourceContent === null) continue;
|
|
1361
1390
|
const destinationPath = join(destinationSkillDir, relativePath);
|
|
1362
1391
|
await mkdirp(dirname(destinationPath));
|
|
1363
|
-
await writeFileAtomic(destinationPath,
|
|
1392
|
+
await writeFileAtomic(destinationPath, normalize(sourceContent, sourcePath, destinationPath));
|
|
1364
1393
|
results.push({
|
|
1365
1394
|
fromTool,
|
|
1366
1395
|
fromPath: sourcePath,
|
|
@@ -2010,9 +2039,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
|
|
|
2010
2039
|
const before = fullContent[start - 1];
|
|
2011
2040
|
const after = fullContent[end];
|
|
2012
2041
|
if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
|
|
2013
|
-
if (before === "
|
|
2014
|
-
|
|
2015
|
-
}
|
|
2042
|
+
if (before === "`" && after === "`") return true;
|
|
2043
|
+
if (before === "'" && after === "'" || before === '"' && after === '"') return false;
|
|
2016
2044
|
if (before === "<" && after === ">") return true;
|
|
2017
2045
|
if (before === "[" && after === "]") {
|
|
2018
2046
|
if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
|
|
@@ -2517,7 +2545,7 @@ var init_import_descriptor = __esm({
|
|
|
2517
2545
|
];
|
|
2518
2546
|
}
|
|
2519
2547
|
});
|
|
2520
|
-
async function runSingleFile(spec, sources, projectRoot, fromTool,
|
|
2548
|
+
async function runSingleFile(spec, sources, projectRoot, fromTool, normalize) {
|
|
2521
2549
|
if (!spec.canonicalRootFilename) {
|
|
2522
2550
|
throw new Error(`singleFile spec for ${spec.feature} must set canonicalRootFilename`);
|
|
2523
2551
|
}
|
|
@@ -2528,7 +2556,7 @@ async function runSingleFile(spec, sources, projectRoot, fromTool, normalize2) {
|
|
|
2528
2556
|
if (content === null) continue;
|
|
2529
2557
|
await mkdirp(destDir);
|
|
2530
2558
|
const destPath = join(destDir, spec.canonicalRootFilename);
|
|
2531
|
-
const normalizeTo = (destinationFile) =>
|
|
2559
|
+
const normalizeTo = (destinationFile) => normalize(content, srcPath, destinationFile);
|
|
2532
2560
|
if (spec.map) {
|
|
2533
2561
|
let mapping;
|
|
2534
2562
|
try {
|
|
@@ -2571,7 +2599,7 @@ async function runSingleFile(spec, sources, projectRoot, fromTool, normalize2) {
|
|
|
2571
2599
|
}
|
|
2572
2600
|
return [];
|
|
2573
2601
|
}
|
|
2574
|
-
async function runDirectory(spec, sources, projectRoot, fromTool,
|
|
2602
|
+
async function runDirectory(spec, sources, projectRoot, fromTool, normalize) {
|
|
2575
2603
|
const mapper = resolveMapper(spec);
|
|
2576
2604
|
const destDir = join(projectRoot, spec.canonicalDir);
|
|
2577
2605
|
const results = [];
|
|
@@ -2582,7 +2610,7 @@ async function runDirectory(spec, sources, projectRoot, fromTool, normalize2) {
|
|
|
2582
2610
|
destDir,
|
|
2583
2611
|
extensions: [...spec.extensions ?? [".md"]],
|
|
2584
2612
|
fromTool,
|
|
2585
|
-
normalize
|
|
2613
|
+
normalize,
|
|
2586
2614
|
mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
|
|
2587
2615
|
if (isPreservedBoilerplate(basename(srcPath))) return null;
|
|
2588
2616
|
let mapping;
|
|
@@ -2687,28 +2715,28 @@ async function runMcpJson(spec, sources, projectRoot, fromTool) {
|
|
|
2687
2715
|
}
|
|
2688
2716
|
return [];
|
|
2689
2717
|
}
|
|
2690
|
-
function dispatchSpec(spec, sources, projectRoot, fromTool,
|
|
2718
|
+
function dispatchSpec(spec, sources, projectRoot, fromTool, normalize) {
|
|
2691
2719
|
switch (spec.mode) {
|
|
2692
2720
|
case "singleFile":
|
|
2693
|
-
return runSingleFile(spec, sources, projectRoot, fromTool,
|
|
2721
|
+
return runSingleFile(spec, sources, projectRoot, fromTool, normalize);
|
|
2694
2722
|
case "directory":
|
|
2695
|
-
return runDirectory(spec, sources, projectRoot, fromTool,
|
|
2723
|
+
return runDirectory(spec, sources, projectRoot, fromTool, normalize);
|
|
2696
2724
|
case "flatFile":
|
|
2697
2725
|
return runFlatFile(spec, sources, projectRoot, fromTool);
|
|
2698
2726
|
case "mcpJson":
|
|
2699
2727
|
return runMcpJson(spec, sources, projectRoot, fromTool);
|
|
2700
2728
|
}
|
|
2701
2729
|
}
|
|
2702
|
-
async function runSpec(spec, scope, projectRoot, fromTool,
|
|
2730
|
+
async function runSpec(spec, scope, projectRoot, fromTool, normalize) {
|
|
2703
2731
|
const primary = resolveScopedSources(spec.source, scope);
|
|
2704
2732
|
const fallback = resolveScopedSources(spec.fallbacks, scope);
|
|
2705
2733
|
if (primary.length === 0 && fallback.length === 0) return [];
|
|
2706
2734
|
if (primary.length > 0) {
|
|
2707
|
-
const results = await dispatchSpec(spec, primary, projectRoot, fromTool,
|
|
2735
|
+
const results = await dispatchSpec(spec, primary, projectRoot, fromTool, normalize);
|
|
2708
2736
|
if (results.length > 0) return results;
|
|
2709
2737
|
}
|
|
2710
2738
|
if (fallback.length > 0) {
|
|
2711
|
-
return dispatchSpec(spec, fallback, projectRoot, fromTool,
|
|
2739
|
+
return dispatchSpec(spec, fallback, projectRoot, fromTool, normalize);
|
|
2712
2740
|
}
|
|
2713
2741
|
return [];
|
|
2714
2742
|
}
|
|
@@ -2721,11 +2749,11 @@ function specsForFeature(importer, feature) {
|
|
|
2721
2749
|
async function runDescriptorImport(descriptor31, projectRoot, scope, options) {
|
|
2722
2750
|
const importer = descriptor31.importer;
|
|
2723
2751
|
if (!importer) return [];
|
|
2724
|
-
const
|
|
2752
|
+
const normalize = options?.normalize ?? await createImportReferenceNormalizer(descriptor31.id, projectRoot, scope);
|
|
2725
2753
|
const results = [];
|
|
2726
2754
|
for (const feature of IMPORT_FEATURE_ORDER) {
|
|
2727
2755
|
for (const spec of specsForFeature(importer, feature)) {
|
|
2728
|
-
results.push(...await runSpec(spec, scope, projectRoot, descriptor31.id,
|
|
2756
|
+
results.push(...await runSpec(spec, scope, projectRoot, descriptor31.id, normalize));
|
|
2729
2757
|
}
|
|
2730
2758
|
}
|
|
2731
2759
|
return results;
|
|
@@ -2749,10 +2777,10 @@ var init_descriptor_import_runner = __esm({
|
|
|
2749
2777
|
async function importFromAider(projectRoot, options = {}) {
|
|
2750
2778
|
const scope = options.scope ?? "project";
|
|
2751
2779
|
const results = [];
|
|
2752
|
-
const
|
|
2753
|
-
results.push(...await runDescriptorImport(descriptor, projectRoot, scope, { normalize
|
|
2780
|
+
const normalize = await createImportReferenceNormalizer(AIDER_TARGET, projectRoot, scope);
|
|
2781
|
+
results.push(...await runDescriptorImport(descriptor, projectRoot, scope, { normalize }));
|
|
2754
2782
|
const skillsDir = scope === "global" ? AIDER_GLOBAL_SKILLS_DIR : AIDER_SKILLS_DIR;
|
|
2755
|
-
await importEmbeddedSkills(projectRoot, skillsDir, AIDER_TARGET, results,
|
|
2783
|
+
await importEmbeddedSkills(projectRoot, skillsDir, AIDER_TARGET, results, normalize);
|
|
2756
2784
|
return results;
|
|
2757
2785
|
}
|
|
2758
2786
|
var init_importer = __esm({
|
|
@@ -3339,12 +3367,12 @@ var init_augment_code = __esm({
|
|
|
3339
3367
|
});
|
|
3340
3368
|
|
|
3341
3369
|
// src/targets/claude-code/constants.ts
|
|
3342
|
-
var CLAUDE_CODE_TARGET, CLAUDE_ROOT,
|
|
3370
|
+
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;
|
|
3343
3371
|
var init_constants7 = __esm({
|
|
3344
3372
|
"src/targets/claude-code/constants.ts"() {
|
|
3345
3373
|
CLAUDE_CODE_TARGET = "claude-code";
|
|
3346
|
-
CLAUDE_ROOT = "
|
|
3347
|
-
|
|
3374
|
+
CLAUDE_ROOT = "CLAUDE.md";
|
|
3375
|
+
CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
|
|
3348
3376
|
CLAUDE_RULES_DIR = ".claude/rules";
|
|
3349
3377
|
CLAUDE_COMMANDS_DIR = ".claude/commands";
|
|
3350
3378
|
CLAUDE_AGENTS_DIR = ".claude/agents";
|
|
@@ -4991,10 +5019,10 @@ var init_mcp_import = __esm({
|
|
|
4991
5019
|
async function importFromAmp(projectRoot, options = {}) {
|
|
4992
5020
|
const scope = options.scope ?? "project";
|
|
4993
5021
|
const results = [];
|
|
4994
|
-
const
|
|
4995
|
-
results.push(...await runDescriptorImport(descriptor3, projectRoot, scope, { normalize
|
|
5022
|
+
const normalize = await createImportReferenceNormalizer(AMP_TARGET, projectRoot, scope);
|
|
5023
|
+
results.push(...await runDescriptorImport(descriptor3, projectRoot, scope, { normalize }));
|
|
4996
5024
|
const skillsDir = scope === "global" ? AMP_GLOBAL_SKILLS_DIR : AMP_SKILLS_DIR;
|
|
4997
|
-
await importEmbeddedSkills(projectRoot, skillsDir, AMP_TARGET, results,
|
|
5025
|
+
await importEmbeddedSkills(projectRoot, skillsDir, AMP_TARGET, results, normalize);
|
|
4998
5026
|
const mcpFile = scope === "global" ? AMP_GLOBAL_MCP_FILE : AMP_MCP_FILE;
|
|
4999
5027
|
await importAmpMcp(projectRoot, mcpFile, results);
|
|
5000
5028
|
return results;
|
|
@@ -5206,7 +5234,8 @@ var init_amp2 = __esm({
|
|
|
5206
5234
|
markAsRoot: true
|
|
5207
5235
|
}
|
|
5208
5236
|
},
|
|
5209
|
-
emitScopedSettings(canonical, _scope) {
|
|
5237
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
5238
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
5210
5239
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
5211
5240
|
return [
|
|
5212
5241
|
{
|
|
@@ -5342,7 +5371,7 @@ var init_embedded_rules = __esm({
|
|
|
5342
5371
|
init_import_metadata();
|
|
5343
5372
|
}
|
|
5344
5373
|
});
|
|
5345
|
-
async function importRootRule(projectRoot, results,
|
|
5374
|
+
async function importRootRule(projectRoot, results, normalize, scope) {
|
|
5346
5375
|
const primary = scope === "global" ? ANTIGRAVITY_GLOBAL_ROOT : ANTIGRAVITY_RULES_ROOT;
|
|
5347
5376
|
const candidates = scope === "project" ? [primary, ANTIGRAVITY_RULES_ROOT_LEGACY] : [primary];
|
|
5348
5377
|
for (const rel2 of candidates) {
|
|
@@ -5356,10 +5385,10 @@ async function importRootRule(projectRoot, results, normalize2, scope) {
|
|
|
5356
5385
|
rulesDir: ANTIGRAVITY_CANONICAL_RULES_DIR,
|
|
5357
5386
|
sourcePath: srcPath,
|
|
5358
5387
|
fromTool: ANTIGRAVITY_TARGET,
|
|
5359
|
-
normalize
|
|
5388
|
+
normalize
|
|
5360
5389
|
});
|
|
5361
5390
|
results.push(...split.results);
|
|
5362
|
-
const { body } = parseFrontmatter(
|
|
5391
|
+
const { body } = parseFrontmatter(normalize(split.rootContent, srcPath, destPath));
|
|
5363
5392
|
const output = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
|
|
5364
5393
|
await mkdirp(join(projectRoot, ANTIGRAVITY_CANONICAL_RULES_DIR));
|
|
5365
5394
|
await writeFileAtomic(destPath, output);
|
|
@@ -5375,15 +5404,15 @@ async function importRootRule(projectRoot, results, normalize2, scope) {
|
|
|
5375
5404
|
async function importFromAntigravity(projectRoot, options = {}) {
|
|
5376
5405
|
const scope = options.scope ?? "project";
|
|
5377
5406
|
const results = [];
|
|
5378
|
-
const
|
|
5379
|
-
await importRootRule(projectRoot, results,
|
|
5380
|
-
results.push(...await runDescriptorImport(descriptor4, projectRoot, scope, { normalize
|
|
5407
|
+
const normalize = await createImportReferenceNormalizer(ANTIGRAVITY_TARGET, projectRoot, scope);
|
|
5408
|
+
await importRootRule(projectRoot, results, normalize, scope);
|
|
5409
|
+
results.push(...await runDescriptorImport(descriptor4, projectRoot, scope, { normalize }));
|
|
5381
5410
|
await importEmbeddedSkills(
|
|
5382
5411
|
projectRoot,
|
|
5383
5412
|
scope === "global" ? ANTIGRAVITY_GLOBAL_SKILLS_DIR : ANTIGRAVITY_SKILLS_DIR,
|
|
5384
5413
|
ANTIGRAVITY_TARGET,
|
|
5385
5414
|
results,
|
|
5386
|
-
|
|
5415
|
+
normalize
|
|
5387
5416
|
);
|
|
5388
5417
|
return results;
|
|
5389
5418
|
}
|
|
@@ -5799,7 +5828,7 @@ function canonicalRuleMeta(frontmatter, isRoot) {
|
|
|
5799
5828
|
}
|
|
5800
5829
|
return meta;
|
|
5801
5830
|
}
|
|
5802
|
-
async function importRules(projectRoot, results,
|
|
5831
|
+
async function importRules(projectRoot, results, normalize, scope) {
|
|
5803
5832
|
const rulesDir = scope === "global" ? AUGMENT_CODE_GLOBAL_RULES_DIR : AUGMENT_CODE_RULES_DIR;
|
|
5804
5833
|
const destDir = join(projectRoot, AUGMENT_CODE_CANONICAL_RULES_DIR);
|
|
5805
5834
|
results.push(
|
|
@@ -5808,7 +5837,7 @@ async function importRules(projectRoot, results, normalize2, scope) {
|
|
|
5808
5837
|
destDir,
|
|
5809
5838
|
extensions: [".md"],
|
|
5810
5839
|
fromTool: AUGMENT_CODE_TARGET,
|
|
5811
|
-
normalize
|
|
5840
|
+
normalize,
|
|
5812
5841
|
mapEntry: async ({ relativePath, normalizeTo }) => {
|
|
5813
5842
|
const isRoot = relativePath === "_root.md" || basename(relativePath) === "_root.md";
|
|
5814
5843
|
const destPath = join(destDir, relativePath);
|
|
@@ -5827,7 +5856,7 @@ async function importRules(projectRoot, results, normalize2, scope) {
|
|
|
5827
5856
|
})
|
|
5828
5857
|
);
|
|
5829
5858
|
}
|
|
5830
|
-
async function importCommands(projectRoot, results,
|
|
5859
|
+
async function importCommands(projectRoot, results, normalize, scope) {
|
|
5831
5860
|
const commandsDir = scope === "global" ? AUGMENT_CODE_GLOBAL_COMMANDS_DIR : AUGMENT_CODE_COMMANDS_DIR;
|
|
5832
5861
|
const destDir = join(projectRoot, ".agentsmesh/commands");
|
|
5833
5862
|
results.push(
|
|
@@ -5836,7 +5865,7 @@ async function importCommands(projectRoot, results, normalize2, scope) {
|
|
|
5836
5865
|
destDir,
|
|
5837
5866
|
extensions: [".md"],
|
|
5838
5867
|
fromTool: AUGMENT_CODE_TARGET,
|
|
5839
|
-
normalize
|
|
5868
|
+
normalize,
|
|
5840
5869
|
mapEntry: async ({ relativePath, normalizeTo }) => {
|
|
5841
5870
|
const name = basename(relativePath, ".md");
|
|
5842
5871
|
const destPath = join(destDir, `${name}.md`);
|
|
@@ -5859,11 +5888,11 @@ async function importCommands(projectRoot, results, normalize2, scope) {
|
|
|
5859
5888
|
async function importFromAugmentCode(projectRoot, options = {}) {
|
|
5860
5889
|
const scope = options.scope ?? "project";
|
|
5861
5890
|
const results = [];
|
|
5862
|
-
const
|
|
5863
|
-
await importRules(projectRoot, results,
|
|
5864
|
-
await importCommands(projectRoot, results,
|
|
5891
|
+
const normalize = await createImportReferenceNormalizer(AUGMENT_CODE_TARGET, projectRoot, scope);
|
|
5892
|
+
await importRules(projectRoot, results, normalize, scope);
|
|
5893
|
+
await importCommands(projectRoot, results, normalize, scope);
|
|
5865
5894
|
const skillsDir = scope === "global" ? AUGMENT_CODE_GLOBAL_SKILLS_DIR : AUGMENT_CODE_SKILLS_DIR;
|
|
5866
|
-
await importEmbeddedSkills(projectRoot, skillsDir, AUGMENT_CODE_TARGET, results,
|
|
5895
|
+
await importEmbeddedSkills(projectRoot, skillsDir, AUGMENT_CODE_TARGET, results, normalize);
|
|
5867
5896
|
const settingsFile = scope === "global" ? AUGMENT_CODE_GLOBAL_SETTINGS_FILE : AUGMENT_CODE_SETTINGS_FILE;
|
|
5868
5897
|
await importAugmentSettings(projectRoot, settingsFile, results);
|
|
5869
5898
|
if (scope === "project") {
|
|
@@ -5958,12 +5987,12 @@ function mergeAugmentSettings(existing, newContent) {
|
|
|
5958
5987
|
if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
|
|
5959
5988
|
return JSON.stringify(base, null, 2);
|
|
5960
5989
|
}
|
|
5961
|
-
function buildSettingsContent(canonical) {
|
|
5990
|
+
function buildSettingsContent(canonical, enabledFeatures) {
|
|
5962
5991
|
const settings = {};
|
|
5963
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5992
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5964
5993
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
5965
5994
|
}
|
|
5966
|
-
if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5995
|
+
if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5967
5996
|
settings.hooks = serializeHooksForSettings(canonical.hooks);
|
|
5968
5997
|
}
|
|
5969
5998
|
if (Object.keys(settings).length === 0) return null;
|
|
@@ -6092,8 +6121,8 @@ var init_augment_code2 = __esm({
|
|
|
6092
6121
|
],
|
|
6093
6122
|
layout: globalLayout5
|
|
6094
6123
|
},
|
|
6095
|
-
emitScopedSettings(canonical) {
|
|
6096
|
-
const content = buildSettingsContent(canonical);
|
|
6124
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
6125
|
+
const content = buildSettingsContent(canonical, enabledFeatures);
|
|
6097
6126
|
if (content === null) return [];
|
|
6098
6127
|
return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
|
|
6099
6128
|
},
|
|
@@ -6481,7 +6510,7 @@ var init_settings_helpers2 = __esm({
|
|
|
6481
6510
|
init_constants7();
|
|
6482
6511
|
}
|
|
6483
6512
|
});
|
|
6484
|
-
async function importClaudeSkills(projectRoot, results,
|
|
6513
|
+
async function importClaudeSkills(projectRoot, results, normalize) {
|
|
6485
6514
|
const skillsBaseDir = join(projectRoot, CLAUDE_SKILLS_DIR);
|
|
6486
6515
|
const destBase = join(projectRoot, CLAUDE_CANONICAL_SKILLS_DIR);
|
|
6487
6516
|
const allFiles = await readDirRecursive(skillsBaseDir);
|
|
@@ -6492,7 +6521,7 @@ async function importClaudeSkills(projectRoot, results, normalize2) {
|
|
|
6492
6521
|
const destSkillDir = join(destBase, skillName);
|
|
6493
6522
|
const skillMdContent = await readFileSafe(skillMdPath);
|
|
6494
6523
|
if (skillMdContent === null) continue;
|
|
6495
|
-
const normalizedSkillMd =
|
|
6524
|
+
const normalizedSkillMd = normalize(
|
|
6496
6525
|
skillMdContent,
|
|
6497
6526
|
skillMdPath,
|
|
6498
6527
|
join(destSkillDir, "SKILL.md")
|
|
@@ -6510,7 +6539,7 @@ async function importClaudeSkills(projectRoot, results, normalize2) {
|
|
|
6510
6539
|
const relPath = relative(skillDir, filePath);
|
|
6511
6540
|
const destPath = join(destSkillDir, relPath);
|
|
6512
6541
|
await mkdirp(dirname(destPath));
|
|
6513
|
-
const normalized =
|
|
6542
|
+
const normalized = normalize(fileContent, filePath, destPath);
|
|
6514
6543
|
await writeFileAtomic(
|
|
6515
6544
|
destPath,
|
|
6516
6545
|
relPath === "SKILL.md" ? await serializeImportedSkillWithFallback(
|
|
@@ -6542,9 +6571,9 @@ var init_importer_skills = __esm({
|
|
|
6542
6571
|
async function importFromClaudeCode(projectRoot, options = {}) {
|
|
6543
6572
|
const scope = options.scope ?? "project";
|
|
6544
6573
|
const results = [];
|
|
6545
|
-
const
|
|
6546
|
-
results.push(...await runDescriptorImport(descriptor6, projectRoot, scope, { normalize
|
|
6547
|
-
await importClaudeSkills(projectRoot, results,
|
|
6574
|
+
const normalize = await createImportReferenceNormalizer("claude-code", projectRoot, scope);
|
|
6575
|
+
results.push(...await runDescriptorImport(descriptor6, projectRoot, scope, { normalize }));
|
|
6576
|
+
await importClaudeSkills(projectRoot, results, normalize);
|
|
6548
6577
|
await importClaudeHooksJson(projectRoot, results);
|
|
6549
6578
|
await importSettings(projectRoot, results);
|
|
6550
6579
|
return results;
|
|
@@ -6667,7 +6696,10 @@ var init_claude_code2 = __esm({
|
|
|
6667
6696
|
skillDir: ".claude/skills",
|
|
6668
6697
|
managedOutputs: {
|
|
6669
6698
|
dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
|
|
6670
|
-
|
|
6699
|
+
// CLAUDE_NESTED_ROOT is the pre-migration project location; listing it here lets
|
|
6700
|
+
// `cleanupStaleGeneratedOutputs` evict a leftover `.claude/CLAUDE.md` once generation
|
|
6701
|
+
// writes the root `CLAUDE.md`, so Claude Code never concatenates both into context.
|
|
6702
|
+
files: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/settings.json", ".claudeignore", ".mcp.json"]
|
|
6671
6703
|
},
|
|
6672
6704
|
paths: {
|
|
6673
6705
|
rulePath(slug, _rule) {
|
|
@@ -6682,7 +6714,7 @@ var init_claude_code2 = __esm({
|
|
|
6682
6714
|
}
|
|
6683
6715
|
};
|
|
6684
6716
|
globalLayout6 = {
|
|
6685
|
-
rootInstructionPath:
|
|
6717
|
+
rootInstructionPath: CLAUDE_NESTED_ROOT,
|
|
6686
6718
|
skillDir: ".claude/skills",
|
|
6687
6719
|
renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
|
|
6688
6720
|
managedOutputs: {
|
|
@@ -6695,7 +6727,7 @@ var init_claude_code2 = __esm({
|
|
|
6695
6727
|
".agents/skills"
|
|
6696
6728
|
],
|
|
6697
6729
|
files: [
|
|
6698
|
-
|
|
6730
|
+
CLAUDE_NESTED_ROOT,
|
|
6699
6731
|
".claude/settings.json",
|
|
6700
6732
|
CLAUDE_GLOBAL_MCP_JSON,
|
|
6701
6733
|
CLAUDE_HOOKS_JSON,
|
|
@@ -6703,6 +6735,7 @@ var init_claude_code2 = __esm({
|
|
|
6703
6735
|
]
|
|
6704
6736
|
},
|
|
6705
6737
|
rewriteGeneratedPath(path) {
|
|
6738
|
+
if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
|
|
6706
6739
|
if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
|
|
6707
6740
|
return path;
|
|
6708
6741
|
},
|
|
@@ -6766,10 +6799,11 @@ var init_claude_code2 = __esm({
|
|
|
6766
6799
|
importer: {
|
|
6767
6800
|
rules: [
|
|
6768
6801
|
{
|
|
6769
|
-
// Root rule:
|
|
6802
|
+
// Root rule: project prefers root CLAUDE.md, falls back to nested .claude/CLAUDE.md;
|
|
6803
|
+
// global reads the nested .claude/CLAUDE.md.
|
|
6770
6804
|
feature: "rules",
|
|
6771
6805
|
mode: "singleFile",
|
|
6772
|
-
source: { project: [CLAUDE_ROOT,
|
|
6806
|
+
source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
|
|
6773
6807
|
canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
|
|
6774
6808
|
canonicalRootFilename: "_root.md",
|
|
6775
6809
|
markAsRoot: true
|
|
@@ -6815,7 +6849,23 @@ var init_claude_code2 = __esm({
|
|
|
6815
6849
|
}
|
|
6816
6850
|
},
|
|
6817
6851
|
buildImportPaths: buildClaudeCodeImportPaths,
|
|
6818
|
-
detectionPaths: [
|
|
6852
|
+
detectionPaths: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/rules", ".claude/commands"],
|
|
6853
|
+
nativeInstall: {
|
|
6854
|
+
pickPaths: [
|
|
6855
|
+
{
|
|
6856
|
+
prefix: ".claude/commands",
|
|
6857
|
+
feature: "commands",
|
|
6858
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
6859
|
+
},
|
|
6860
|
+
{ prefix: ".claude/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
|
|
6861
|
+
{
|
|
6862
|
+
prefix: ".claude/agents",
|
|
6863
|
+
feature: "agents",
|
|
6864
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
6865
|
+
},
|
|
6866
|
+
{ prefix: ".claude/skills/", feature: "skills", strategy: { kind: "firstSegment" } }
|
|
6867
|
+
]
|
|
6868
|
+
}
|
|
6819
6869
|
};
|
|
6820
6870
|
}
|
|
6821
6871
|
});
|
|
@@ -7004,7 +7054,7 @@ var init_importer_mappers = __esm({
|
|
|
7004
7054
|
init_constants8();
|
|
7005
7055
|
}
|
|
7006
7056
|
});
|
|
7007
|
-
async function importClineRules(projectRoot, results,
|
|
7057
|
+
async function importClineRules(projectRoot, results, normalize) {
|
|
7008
7058
|
const destRulesDir = join(projectRoot, CLINE_CANONICAL_RULES_DIR);
|
|
7009
7059
|
const clineRulesPath = join(projectRoot, CLINE_RULES_DIR);
|
|
7010
7060
|
const clineRulesRaw = join(projectRoot, CLINE_RULES_DIR);
|
|
@@ -7020,7 +7070,7 @@ async function importClineRules(projectRoot, results, normalize2) {
|
|
|
7020
7070
|
await mkdirp(destRulesDir);
|
|
7021
7071
|
const destPath = join(destRulesDir, "_root.md");
|
|
7022
7072
|
const { frontmatter, body } = parseFrontmatter(
|
|
7023
|
-
|
|
7073
|
+
normalize(flatContent, clineRulesRaw, destPath)
|
|
7024
7074
|
);
|
|
7025
7075
|
const hasRoot = frontmatter.root === true;
|
|
7026
7076
|
const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
|
|
@@ -7046,7 +7096,7 @@ async function importClineRules(projectRoot, results, normalize2) {
|
|
|
7046
7096
|
await mkdirp(destRulesDir);
|
|
7047
7097
|
const destPath = join(destRulesDir, "_root.md");
|
|
7048
7098
|
const { frontmatter, body } = parseFrontmatter(
|
|
7049
|
-
|
|
7099
|
+
normalize(agentsMdContent, agentsMdPath, destPath)
|
|
7050
7100
|
);
|
|
7051
7101
|
const hasRoot = frontmatter.root === true;
|
|
7052
7102
|
const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
|
|
@@ -7068,7 +7118,7 @@ async function importClineRules(projectRoot, results, normalize2) {
|
|
|
7068
7118
|
rootSourcePath = first;
|
|
7069
7119
|
await mkdirp(destRulesDir);
|
|
7070
7120
|
const destPath = join(destRulesDir, "_root.md");
|
|
7071
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
7121
|
+
const { frontmatter, body } = parseFrontmatter(normalize(fc, first, destPath));
|
|
7072
7122
|
const hasRoot = frontmatter.root === true;
|
|
7073
7123
|
const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
|
|
7074
7124
|
const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
|
|
@@ -7086,7 +7136,7 @@ async function importClineRules(projectRoot, results, normalize2) {
|
|
|
7086
7136
|
rootSourcePath = rootPath;
|
|
7087
7137
|
await mkdirp(destRulesDir);
|
|
7088
7138
|
const destPath = join(destRulesDir, "_root.md");
|
|
7089
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
7139
|
+
const { frontmatter, body } = parseFrontmatter(normalize(rootContent, rootPath, destPath));
|
|
7090
7140
|
const hasRoot = frontmatter.root === true;
|
|
7091
7141
|
const outFm = hasRoot ? frontmatter : { ...frontmatter, root: true };
|
|
7092
7142
|
const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
|
|
@@ -7104,7 +7154,7 @@ async function importClineRules(projectRoot, results, normalize2) {
|
|
|
7104
7154
|
destDir: destRulesDir,
|
|
7105
7155
|
extensions: [".md"],
|
|
7106
7156
|
fromTool: "cline",
|
|
7107
|
-
normalize
|
|
7157
|
+
normalize,
|
|
7108
7158
|
mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
|
|
7109
7159
|
if (srcPath === rootSourcePath) return null;
|
|
7110
7160
|
return mapClineRuleFile(relativePath, destRulesDir, normalizeTo);
|
|
@@ -7391,12 +7441,12 @@ var init_skill_import_pipeline = __esm({
|
|
|
7391
7441
|
});
|
|
7392
7442
|
|
|
7393
7443
|
// src/targets/cline/skills-adapter.ts
|
|
7394
|
-
async function importClineSkills(projectRoot, results,
|
|
7444
|
+
async function importClineSkills(projectRoot, results, normalize, skillsRelDir = CLINE_SKILLS_DIR) {
|
|
7395
7445
|
const options = {
|
|
7396
7446
|
projectRoot,
|
|
7397
7447
|
destCanonicalSkillsDir: CLINE_CANONICAL_SKILLS_DIR,
|
|
7398
7448
|
targetName: "cline",
|
|
7399
|
-
normalize
|
|
7449
|
+
normalize,
|
|
7400
7450
|
results
|
|
7401
7451
|
};
|
|
7402
7452
|
await importSkillsDirectory([skillsRelDir], options, [
|
|
@@ -7450,8 +7500,8 @@ var init_hook_importer = __esm({
|
|
|
7450
7500
|
});
|
|
7451
7501
|
async function importFromCline(projectRoot) {
|
|
7452
7502
|
const results = [];
|
|
7453
|
-
const
|
|
7454
|
-
const clineRulesIsFile = await importClineRules(projectRoot, results,
|
|
7503
|
+
const normalize = await createImportReferenceNormalizer(CLINE_TARGET, projectRoot);
|
|
7504
|
+
const clineRulesIsFile = await importClineRules(projectRoot, results, normalize);
|
|
7455
7505
|
const ignorePath = join(projectRoot, CLINE_IGNORE);
|
|
7456
7506
|
const ignoreContent = await readFileSafe(ignorePath);
|
|
7457
7507
|
if (ignoreContent !== null && ignoreContent.trim()) {
|
|
@@ -7482,12 +7532,12 @@ async function importFromCline(projectRoot) {
|
|
|
7482
7532
|
destDir: destCommandsDir,
|
|
7483
7533
|
extensions: [".md"],
|
|
7484
7534
|
fromTool: "cline",
|
|
7485
|
-
normalize
|
|
7535
|
+
normalize,
|
|
7486
7536
|
mapEntry: ({ relativePath, normalizeTo }) => mapClineWorkflowFile(relativePath, destCommandsDir, normalizeTo)
|
|
7487
7537
|
})
|
|
7488
7538
|
);
|
|
7489
7539
|
}
|
|
7490
|
-
await importClineSkills(projectRoot, results,
|
|
7540
|
+
await importClineSkills(projectRoot, results, normalize);
|
|
7491
7541
|
await importClineHooks(projectRoot, results);
|
|
7492
7542
|
return results;
|
|
7493
7543
|
}
|
|
@@ -7694,6 +7744,16 @@ var init_cline2 = __esm({
|
|
|
7694
7744
|
},
|
|
7695
7745
|
buildImportPaths: buildClineImportPaths,
|
|
7696
7746
|
detectionPaths: [".clinerules", ".cline"],
|
|
7747
|
+
nativeInstall: {
|
|
7748
|
+
pickPaths: [
|
|
7749
|
+
{ prefix: CLINE_SKILLS_DIR, feature: "skills", strategy: { kind: "skillDir" } },
|
|
7750
|
+
{
|
|
7751
|
+
prefix: CLINE_WORKFLOWS_DIR,
|
|
7752
|
+
feature: "commands",
|
|
7753
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
7754
|
+
}
|
|
7755
|
+
]
|
|
7756
|
+
},
|
|
7697
7757
|
conversionDefaults: { agentsToSkills: true }
|
|
7698
7758
|
};
|
|
7699
7759
|
}
|
|
@@ -8070,12 +8130,12 @@ var init_mcp_helpers = __esm({
|
|
|
8070
8130
|
});
|
|
8071
8131
|
|
|
8072
8132
|
// src/targets/codex-cli/skills-adapter.ts
|
|
8073
|
-
async function importSkills(projectRoot, results,
|
|
8133
|
+
async function importSkills(projectRoot, results, normalize) {
|
|
8074
8134
|
const options = {
|
|
8075
8135
|
projectRoot,
|
|
8076
8136
|
destCanonicalSkillsDir: CODEX_CANONICAL_SKILLS_DIR,
|
|
8077
8137
|
targetName: CODEX_TARGET,
|
|
8078
|
-
normalize
|
|
8138
|
+
normalize,
|
|
8079
8139
|
results
|
|
8080
8140
|
};
|
|
8081
8141
|
await importSkillsDirectory([CODEX_SKILLS_DIR, CODEX_SKILLS_FALLBACK_DIR], options, [
|
|
@@ -8089,7 +8149,7 @@ var init_skills_adapter2 = __esm({
|
|
|
8089
8149
|
init_constants28();
|
|
8090
8150
|
}
|
|
8091
8151
|
});
|
|
8092
|
-
async function importCodexAgentsFromToml(projectRoot, results,
|
|
8152
|
+
async function importCodexAgentsFromToml(projectRoot, results, normalize) {
|
|
8093
8153
|
const agentsPath = join(projectRoot, CODEX_AGENTS_DIR);
|
|
8094
8154
|
const agentsDestDir = join(projectRoot, CODEX_CANONICAL_AGENTS_DIR);
|
|
8095
8155
|
try {
|
|
@@ -8109,7 +8169,7 @@ async function importCodexAgentsFromToml(projectRoot, results, normalize2) {
|
|
|
8109
8169
|
const mcpServers = Array.isArray(parsed.mcp_servers) ? parsed.mcp_servers.filter((s) => typeof s === "string") : [];
|
|
8110
8170
|
await mkdirp(agentsDestDir);
|
|
8111
8171
|
const destPath = join(agentsDestDir, `${name}.md`);
|
|
8112
|
-
const normalizedBody =
|
|
8172
|
+
const normalizedBody = normalize(body, srcPath, destPath);
|
|
8113
8173
|
const agent = {
|
|
8114
8174
|
name,
|
|
8115
8175
|
description,
|
|
@@ -8185,7 +8245,7 @@ var init_codex_rules_embed = __esm({
|
|
|
8185
8245
|
init_constants28();
|
|
8186
8246
|
}
|
|
8187
8247
|
});
|
|
8188
|
-
async function importCodexNonRootRuleFiles(projectRoot, destDir,
|
|
8248
|
+
async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize) {
|
|
8189
8249
|
const results = [];
|
|
8190
8250
|
const codexRulesPath = join(projectRoot, CODEX_RULES_DIR);
|
|
8191
8251
|
try {
|
|
@@ -8196,7 +8256,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
|
|
|
8196
8256
|
if (!content) continue;
|
|
8197
8257
|
const relativePath = relative(codexRulesPath, srcPath).replace(/\\/g, "/");
|
|
8198
8258
|
const destPath = join(destDir, relativePath);
|
|
8199
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
8259
|
+
const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
|
|
8200
8260
|
await mkdirp(destDir);
|
|
8201
8261
|
const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: false };
|
|
8202
8262
|
const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
|
|
@@ -8224,7 +8284,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
|
|
|
8224
8284
|
globs: embedded.meta.globs,
|
|
8225
8285
|
root: false
|
|
8226
8286
|
},
|
|
8227
|
-
|
|
8287
|
+
normalize(embedded.body, srcPath, destPath)
|
|
8228
8288
|
);
|
|
8229
8289
|
await writeFileAtomic(destPath, outContent);
|
|
8230
8290
|
} else {
|
|
@@ -8236,7 +8296,7 @@ async function importCodexNonRootRuleFiles(projectRoot, destDir, normalize2) {
|
|
|
8236
8296
|
globs: [],
|
|
8237
8297
|
codex_emit: "execution"
|
|
8238
8298
|
},
|
|
8239
|
-
|
|
8299
|
+
normalize(raw.trim(), srcPath, destPath)
|
|
8240
8300
|
);
|
|
8241
8301
|
await writeFileAtomic(destPath, outContent);
|
|
8242
8302
|
}
|
|
@@ -8260,7 +8320,7 @@ var init_import_codex_non_root_rules = __esm({
|
|
|
8260
8320
|
init_codex_rules_embed();
|
|
8261
8321
|
}
|
|
8262
8322
|
});
|
|
8263
|
-
async function importCodexRules(projectRoot, results,
|
|
8323
|
+
async function importCodexRules(projectRoot, results, normalize, normalizeWindsurf, layoutScope) {
|
|
8264
8324
|
const codexPath = join(projectRoot, CODEX_MD);
|
|
8265
8325
|
const agentsPath = join(projectRoot, AGENTS_MD);
|
|
8266
8326
|
const globalOverridePath = join(projectRoot, CODEX_GLOBAL_AGENTS_OVERRIDE_MD);
|
|
@@ -8282,14 +8342,14 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
|
|
|
8282
8342
|
rulesDir: CODEX_CANONICAL_RULES_DIR,
|
|
8283
8343
|
sourcePath,
|
|
8284
8344
|
fromTool: "codex-cli",
|
|
8285
|
-
normalize
|
|
8345
|
+
normalize
|
|
8286
8346
|
});
|
|
8287
8347
|
results.push(...split.results);
|
|
8288
|
-
const normalizedContent = sourcePath === agentsPath || sourcePath === globalAgentsPath || sourcePath === globalOverridePath ?
|
|
8348
|
+
const normalizedContent = sourcePath === agentsPath || sourcePath === globalAgentsPath || sourcePath === globalOverridePath ? normalize(
|
|
8289
8349
|
normalizeWindsurf(split.rootContent, sourcePath, destPath),
|
|
8290
8350
|
sourcePath,
|
|
8291
8351
|
destPath
|
|
8292
|
-
) :
|
|
8352
|
+
) : normalize(split.rootContent, sourcePath, destPath);
|
|
8293
8353
|
const { frontmatter, body } = parseFrontmatter(normalizedContent);
|
|
8294
8354
|
const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: true };
|
|
8295
8355
|
const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
|
|
@@ -8301,8 +8361,8 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
|
|
|
8301
8361
|
feature: "rules"
|
|
8302
8362
|
});
|
|
8303
8363
|
}
|
|
8304
|
-
await importInstructionMirrors(projectRoot, destDir, results,
|
|
8305
|
-
results.push(...await importCodexNonRootRuleFiles(projectRoot, destDir,
|
|
8364
|
+
await importInstructionMirrors(projectRoot, destDir, results, normalize);
|
|
8365
|
+
results.push(...await importCodexNonRootRuleFiles(projectRoot, destDir, normalize));
|
|
8306
8366
|
if (layoutScope !== "global") {
|
|
8307
8367
|
results.push(
|
|
8308
8368
|
...await importFileDirectory({
|
|
@@ -8310,7 +8370,7 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
|
|
|
8310
8370
|
destDir,
|
|
8311
8371
|
extensions: ["AGENTS.md", "AGENTS.override.md"],
|
|
8312
8372
|
fromTool: "codex-cli",
|
|
8313
|
-
normalize
|
|
8373
|
+
normalize,
|
|
8314
8374
|
mapEntry: async ({ srcPath, normalizeTo }) => {
|
|
8315
8375
|
const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
|
|
8316
8376
|
const fileName = basename(srcPath);
|
|
@@ -8344,7 +8404,7 @@ async function importCodexRules(projectRoot, results, normalize2, normalizeWinds
|
|
|
8344
8404
|
);
|
|
8345
8405
|
}
|
|
8346
8406
|
}
|
|
8347
|
-
async function importInstructionMirrors(projectRoot, destDir, results,
|
|
8407
|
+
async function importInstructionMirrors(projectRoot, destDir, results, normalize) {
|
|
8348
8408
|
try {
|
|
8349
8409
|
const files = await readDirRecursive(join(projectRoot, CODEX_INSTRUCTIONS_DIR));
|
|
8350
8410
|
const instructionFiles = files.filter((file) => file.endsWith(".md"));
|
|
@@ -8355,7 +8415,7 @@ async function importInstructionMirrors(projectRoot, destDir, results, normalize
|
|
|
8355
8415
|
const content = await readFileSafe(srcPath);
|
|
8356
8416
|
if (!content) continue;
|
|
8357
8417
|
const destPath = join(destDir, relativePath);
|
|
8358
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
8418
|
+
const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
|
|
8359
8419
|
await mkdirp(destDir);
|
|
8360
8420
|
const outFm = frontmatter.root === true ? frontmatter : { ...frontmatter, root: false };
|
|
8361
8421
|
const outContent = await serializeImportedRuleWithFallback(destPath, outFm, body);
|
|
@@ -8388,15 +8448,15 @@ var init_importer_rules2 = __esm({
|
|
|
8388
8448
|
async function importFromCodex(projectRoot, options) {
|
|
8389
8449
|
const layoutScope = options?.scope ?? "project";
|
|
8390
8450
|
const results = [];
|
|
8391
|
-
const
|
|
8451
|
+
const normalize = await createImportReferenceNormalizer(CODEX_TARGET, projectRoot, layoutScope);
|
|
8392
8452
|
const normalizeWindsurf = await createImportReferenceNormalizer(
|
|
8393
8453
|
"windsurf",
|
|
8394
8454
|
projectRoot,
|
|
8395
8455
|
layoutScope
|
|
8396
8456
|
);
|
|
8397
|
-
await importCodexRules(projectRoot, results,
|
|
8398
|
-
await importSkills(projectRoot, results,
|
|
8399
|
-
await importCodexAgentsFromToml(projectRoot, results,
|
|
8457
|
+
await importCodexRules(projectRoot, results, normalize, normalizeWindsurf, layoutScope);
|
|
8458
|
+
await importSkills(projectRoot, results, normalize);
|
|
8459
|
+
await importCodexAgentsFromToml(projectRoot, results, normalize);
|
|
8400
8460
|
await importMcp(projectRoot, results);
|
|
8401
8461
|
return results;
|
|
8402
8462
|
}
|
|
@@ -8612,6 +8672,11 @@ var init_codex_cli2 = __esm({
|
|
|
8612
8672
|
".codex/agents",
|
|
8613
8673
|
".codex/rules"
|
|
8614
8674
|
],
|
|
8675
|
+
nativeInstall: {
|
|
8676
|
+
pickPaths: [
|
|
8677
|
+
{ prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
|
|
8678
|
+
]
|
|
8679
|
+
},
|
|
8615
8680
|
excludeFromStarterInit: true,
|
|
8616
8681
|
conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
|
|
8617
8682
|
};
|
|
@@ -8769,9 +8834,9 @@ async function importMcp2(projectRoot, results) {
|
|
|
8769
8834
|
}
|
|
8770
8835
|
async function importFromContinue(projectRoot) {
|
|
8771
8836
|
const results = [];
|
|
8772
|
-
const
|
|
8773
|
-
results.push(...await runDescriptorImport(descriptor9, projectRoot, "project", { normalize
|
|
8774
|
-
await importEmbeddedSkills(projectRoot, CONTINUE_SKILLS_DIR, CONTINUE_TARGET, results,
|
|
8837
|
+
const normalize = await createImportReferenceNormalizer(CONTINUE_TARGET, projectRoot);
|
|
8838
|
+
results.push(...await runDescriptorImport(descriptor9, projectRoot, "project", { normalize }));
|
|
8839
|
+
await importEmbeddedSkills(projectRoot, CONTINUE_SKILLS_DIR, CONTINUE_TARGET, results, normalize);
|
|
8775
8840
|
await importMcp2(projectRoot, results);
|
|
8776
8841
|
return results;
|
|
8777
8842
|
}
|
|
@@ -9115,6 +9180,21 @@ var init_continue2 = __esm({
|
|
|
9115
9180
|
},
|
|
9116
9181
|
buildImportPaths: buildContinueImportPaths,
|
|
9117
9182
|
detectionPaths: [".continue/rules", ".continue/skills", ".continue/mcpServers"],
|
|
9183
|
+
nativeInstall: {
|
|
9184
|
+
pickPaths: [
|
|
9185
|
+
{
|
|
9186
|
+
prefix: ".continue/rules",
|
|
9187
|
+
feature: "rules",
|
|
9188
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
9189
|
+
},
|
|
9190
|
+
{
|
|
9191
|
+
prefix: ".continue/prompts",
|
|
9192
|
+
feature: "commands",
|
|
9193
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
9194
|
+
},
|
|
9195
|
+
{ prefix: ".continue/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
9196
|
+
]
|
|
9197
|
+
},
|
|
9118
9198
|
conversionDefaults: { agentsToSkills: true }
|
|
9119
9199
|
};
|
|
9120
9200
|
}
|
|
@@ -9436,14 +9516,14 @@ var init_hook_parser = __esm({
|
|
|
9436
9516
|
init_constants29();
|
|
9437
9517
|
}
|
|
9438
9518
|
});
|
|
9439
|
-
async function importSkills2(projectRoot, results,
|
|
9519
|
+
async function importSkills2(projectRoot, results, normalize, skillsDirRel = COPILOT_SKILLS_DIR) {
|
|
9440
9520
|
const skillsDir = join(projectRoot, skillsDirRel);
|
|
9441
9521
|
const directorySkills = await findDirectorySkills(skillsDir);
|
|
9442
9522
|
const options = {
|
|
9443
9523
|
projectRoot,
|
|
9444
9524
|
destCanonicalSkillsDir: COPILOT_CANONICAL_SKILLS_DIR,
|
|
9445
9525
|
targetName: COPILOT_TARGET,
|
|
9446
|
-
normalize
|
|
9526
|
+
normalize,
|
|
9447
9527
|
results
|
|
9448
9528
|
};
|
|
9449
9529
|
for (const [skillName, skillDir] of directorySkills) {
|
|
@@ -9461,12 +9541,12 @@ var init_skills_adapter3 = __esm({
|
|
|
9461
9541
|
async function importFromCopilot(projectRoot, options = {}) {
|
|
9462
9542
|
const scope = options.scope ?? "project";
|
|
9463
9543
|
const results = [];
|
|
9464
|
-
const
|
|
9465
|
-
results.push(...await runDescriptorImport(descriptor10, projectRoot, scope, { normalize
|
|
9544
|
+
const normalize = await createImportReferenceNormalizer(COPILOT_TARGET, projectRoot, scope);
|
|
9545
|
+
results.push(...await runDescriptorImport(descriptor10, projectRoot, scope, { normalize }));
|
|
9466
9546
|
await importSkills2(
|
|
9467
9547
|
projectRoot,
|
|
9468
9548
|
results,
|
|
9469
|
-
|
|
9549
|
+
normalize,
|
|
9470
9550
|
scope === "global" ? COPILOT_GLOBAL_SKILLS_DIR : COPILOT_SKILLS_DIR
|
|
9471
9551
|
);
|
|
9472
9552
|
if (scope === "project") await importHooks(projectRoot, results);
|
|
@@ -9482,6 +9562,80 @@ var init_importer10 = __esm({
|
|
|
9482
9562
|
init_copilot2();
|
|
9483
9563
|
}
|
|
9484
9564
|
});
|
|
9565
|
+
async function skillNamesFromNativeSkillDir(scanRoot) {
|
|
9566
|
+
const files = await readDirRecursive(scanRoot);
|
|
9567
|
+
const names = /* @__PURE__ */ new Set();
|
|
9568
|
+
for (const f of files) {
|
|
9569
|
+
if (basename(f) === "SKILL.md") {
|
|
9570
|
+
names.add(basename(dirname(f)));
|
|
9571
|
+
continue;
|
|
9572
|
+
}
|
|
9573
|
+
const rel2 = relative(scanRoot, f).replace(/\\/g, "/");
|
|
9574
|
+
if (!rel2.includes("/") && f.toLowerCase().endsWith(".md")) {
|
|
9575
|
+
names.add(basename(f, ".md"));
|
|
9576
|
+
}
|
|
9577
|
+
}
|
|
9578
|
+
return [...names].filter(Boolean).sort();
|
|
9579
|
+
}
|
|
9580
|
+
var init_native_skill_scan = __esm({
|
|
9581
|
+
"src/install/native/native-skill-scan.ts"() {
|
|
9582
|
+
init_fs();
|
|
9583
|
+
}
|
|
9584
|
+
});
|
|
9585
|
+
async function inferCopilotPickFromPath(repoRoot, posixPath) {
|
|
9586
|
+
const scan = join(repoRoot, ...posixPath.split("/"));
|
|
9587
|
+
if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
|
|
9588
|
+
const files = await readDirRecursive(scan);
|
|
9589
|
+
const commands = [
|
|
9590
|
+
...new Set(
|
|
9591
|
+
files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
|
|
9592
|
+
)
|
|
9593
|
+
].sort();
|
|
9594
|
+
return commands.length ? { commands } : {};
|
|
9595
|
+
}
|
|
9596
|
+
if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
|
|
9597
|
+
const files = await readDirRecursive(scan);
|
|
9598
|
+
const rules = [
|
|
9599
|
+
...new Set(
|
|
9600
|
+
files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
|
|
9601
|
+
)
|
|
9602
|
+
].sort();
|
|
9603
|
+
return rules.length ? { rules } : {};
|
|
9604
|
+
}
|
|
9605
|
+
if (posixPath.startsWith(".github/instructions")) {
|
|
9606
|
+
const files = await readDirRecursive(scan);
|
|
9607
|
+
const names = /* @__PURE__ */ new Set();
|
|
9608
|
+
for (const f of files) {
|
|
9609
|
+
const b = basename(f);
|
|
9610
|
+
if (b.toLowerCase().endsWith(".instructions.md"))
|
|
9611
|
+
names.add(b.replace(/\.instructions\.md$/i, ""));
|
|
9612
|
+
else if (b.toLowerCase().endsWith(".md")) names.add(basename(f, ".md"));
|
|
9613
|
+
}
|
|
9614
|
+
const rules = [...names].sort();
|
|
9615
|
+
return rules.length ? { rules } : {};
|
|
9616
|
+
}
|
|
9617
|
+
if (posixPath.startsWith(".github/skills")) {
|
|
9618
|
+
const skills = await skillNamesFromNativeSkillDir(scan);
|
|
9619
|
+
return skills.length ? { skills } : {};
|
|
9620
|
+
}
|
|
9621
|
+
if (posixPath.startsWith(".github/agents")) {
|
|
9622
|
+
const files = await readDirRecursive(scan);
|
|
9623
|
+
const agents = [
|
|
9624
|
+
...new Set(
|
|
9625
|
+
files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
|
|
9626
|
+
)
|
|
9627
|
+
].sort();
|
|
9628
|
+
return agents.length ? { agents } : {};
|
|
9629
|
+
}
|
|
9630
|
+
return {};
|
|
9631
|
+
}
|
|
9632
|
+
var init_native_path_pick_infer_copilot = __esm({
|
|
9633
|
+
"src/install/native/native-path-pick-infer-copilot.ts"() {
|
|
9634
|
+
init_fs();
|
|
9635
|
+
init_constants29();
|
|
9636
|
+
init_native_skill_scan();
|
|
9637
|
+
}
|
|
9638
|
+
});
|
|
9485
9639
|
function pruneUndefined3(record) {
|
|
9486
9640
|
for (const key of Object.keys(record)) {
|
|
9487
9641
|
if (record[key] === void 0) delete record[key];
|
|
@@ -9771,6 +9925,7 @@ var init_copilot2 = __esm({
|
|
|
9771
9925
|
init_generator11();
|
|
9772
9926
|
init_constants29();
|
|
9773
9927
|
init_importer10();
|
|
9928
|
+
init_native_path_pick_infer_copilot();
|
|
9774
9929
|
init_import_mappers4();
|
|
9775
9930
|
init_linter10();
|
|
9776
9931
|
init_import_map_builders();
|
|
@@ -9985,7 +10140,8 @@ var init_copilot2 = __esm({
|
|
|
9985
10140
|
".github/skills",
|
|
9986
10141
|
".github/agents",
|
|
9987
10142
|
".github/hooks"
|
|
9988
|
-
]
|
|
10143
|
+
],
|
|
10144
|
+
nativeInstall: { inferPick: inferCopilotPickFromPath }
|
|
9989
10145
|
};
|
|
9990
10146
|
}
|
|
9991
10147
|
});
|
|
@@ -10087,10 +10243,10 @@ var init_generator12 = __esm({
|
|
|
10087
10243
|
async function importFromCrush(projectRoot, options = {}) {
|
|
10088
10244
|
const scope = options.scope ?? "project";
|
|
10089
10245
|
const results = [];
|
|
10090
|
-
const
|
|
10091
|
-
results.push(...await runDescriptorImport(descriptor11, projectRoot, scope, { normalize
|
|
10246
|
+
const normalize = await createImportReferenceNormalizer(CRUSH_TARGET, projectRoot, scope);
|
|
10247
|
+
results.push(...await runDescriptorImport(descriptor11, projectRoot, scope, { normalize }));
|
|
10092
10248
|
const skillsDir = scope === "global" ? CRUSH_GLOBAL_SKILLS_DIR : CRUSH_SKILLS_DIR;
|
|
10093
|
-
await importEmbeddedSkills(projectRoot, skillsDir, CRUSH_TARGET, results,
|
|
10249
|
+
await importEmbeddedSkills(projectRoot, skillsDir, CRUSH_TARGET, results, normalize);
|
|
10094
10250
|
await importCrushConfigJson(projectRoot, results);
|
|
10095
10251
|
return results;
|
|
10096
10252
|
}
|
|
@@ -10775,7 +10931,7 @@ var init_import_root_helpers = __esm({
|
|
|
10775
10931
|
init_constants11();
|
|
10776
10932
|
}
|
|
10777
10933
|
});
|
|
10778
|
-
async function importCursorRules(projectRoot, results,
|
|
10934
|
+
async function importCursorRules(projectRoot, results, normalize) {
|
|
10779
10935
|
const destDir = join(projectRoot, CURSOR_CANONICAL_RULES_DIR);
|
|
10780
10936
|
let rootWritten = false;
|
|
10781
10937
|
const rulesDir = join(projectRoot, CURSOR_RULES_DIR);
|
|
@@ -10785,7 +10941,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
|
|
|
10785
10941
|
destDir,
|
|
10786
10942
|
extensions: [".mdc"],
|
|
10787
10943
|
fromTool: "cursor",
|
|
10788
|
-
normalize
|
|
10944
|
+
normalize,
|
|
10789
10945
|
mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
|
|
10790
10946
|
if (rootWritten) {
|
|
10791
10947
|
const raw = await readFileSafe(srcPath);
|
|
@@ -10809,7 +10965,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
|
|
|
10809
10965
|
results,
|
|
10810
10966
|
sourcePath: agentsPath,
|
|
10811
10967
|
content: agentsContent,
|
|
10812
|
-
normalize
|
|
10968
|
+
normalize
|
|
10813
10969
|
});
|
|
10814
10970
|
}
|
|
10815
10971
|
}
|
|
@@ -10822,7 +10978,7 @@ async function importCursorRules(projectRoot, results, normalize2) {
|
|
|
10822
10978
|
results,
|
|
10823
10979
|
sourcePath: cursorRulesPath,
|
|
10824
10980
|
content: cursorRulesContent,
|
|
10825
|
-
normalize
|
|
10981
|
+
normalize
|
|
10826
10982
|
});
|
|
10827
10983
|
}
|
|
10828
10984
|
}
|
|
@@ -10967,14 +11123,14 @@ var init_settings_helpers3 = __esm({
|
|
|
10967
11123
|
init_constants11();
|
|
10968
11124
|
}
|
|
10969
11125
|
});
|
|
10970
|
-
async function importSkills3(projectRoot, results,
|
|
11126
|
+
async function importSkills3(projectRoot, results, normalize, skillsRelDir = CURSOR_SKILLS_DIR) {
|
|
10971
11127
|
const skillsDir = join(projectRoot, skillsRelDir);
|
|
10972
11128
|
const directorySkills = await findDirectorySkills(skillsDir);
|
|
10973
11129
|
const options = {
|
|
10974
11130
|
projectRoot,
|
|
10975
11131
|
destCanonicalSkillsDir: CURSOR_CANONICAL_SKILLS_DIR,
|
|
10976
11132
|
targetName: "cursor",
|
|
10977
|
-
normalize
|
|
11133
|
+
normalize,
|
|
10978
11134
|
results
|
|
10979
11135
|
};
|
|
10980
11136
|
for (const [skillName, skillDir] of directorySkills) {
|
|
@@ -11015,8 +11171,8 @@ async function hasGlobalCursorArtifacts(projectRoot) {
|
|
|
11015
11171
|
join(projectRoot, CURSOR_COMMANDS_DIR)
|
|
11016
11172
|
];
|
|
11017
11173
|
for (const p of candidates) {
|
|
11018
|
-
const
|
|
11019
|
-
if (
|
|
11174
|
+
const stat9 = await readFileSafe(p);
|
|
11175
|
+
if (stat9 !== null && stat9.trim() !== "") return true;
|
|
11020
11176
|
}
|
|
11021
11177
|
const skillFiles = await readDirRecursive(join(projectRoot, CURSOR_SKILLS_DIR));
|
|
11022
11178
|
if (skillFiles.some((f) => f.endsWith(".md"))) return true;
|
|
@@ -11026,7 +11182,7 @@ async function hasGlobalCursorArtifacts(projectRoot) {
|
|
|
11026
11182
|
if (commandFiles.some((f) => f.endsWith(".md"))) return true;
|
|
11027
11183
|
return false;
|
|
11028
11184
|
}
|
|
11029
|
-
async function importGlobalCursorRulesFromDir(projectRoot, results,
|
|
11185
|
+
async function importGlobalCursorRulesFromDir(projectRoot, results, normalize) {
|
|
11030
11186
|
const destDir = join(projectRoot, CURSOR_CANONICAL_RULES_DIR);
|
|
11031
11187
|
let rootWritten = false;
|
|
11032
11188
|
const rulesDir = join(projectRoot, CURSOR_RULES_DIR);
|
|
@@ -11035,7 +11191,7 @@ async function importGlobalCursorRulesFromDir(projectRoot, results, normalize2)
|
|
|
11035
11191
|
destDir,
|
|
11036
11192
|
extensions: [".mdc"],
|
|
11037
11193
|
fromTool: CURSOR_TARGET2,
|
|
11038
|
-
normalize
|
|
11194
|
+
normalize,
|
|
11039
11195
|
mapEntry: async ({ srcPath, relativePath, normalizeTo }) => {
|
|
11040
11196
|
if (rootWritten) {
|
|
11041
11197
|
const raw = await readFileSafe(srcPath);
|
|
@@ -11052,7 +11208,7 @@ async function importGlobalCursorRulesFromDir(projectRoot, results, normalize2)
|
|
|
11052
11208
|
results.push(...batch);
|
|
11053
11209
|
return rootWritten;
|
|
11054
11210
|
}
|
|
11055
|
-
async function importGlobalUserRules(projectRoot, results,
|
|
11211
|
+
async function importGlobalUserRules(projectRoot, results, normalize) {
|
|
11056
11212
|
const srcPath = join(projectRoot, CURSOR_GLOBAL_USER_RULES);
|
|
11057
11213
|
const raw = await readFileSafe(srcPath);
|
|
11058
11214
|
if (raw === null || raw.trim() === "") return false;
|
|
@@ -11061,10 +11217,10 @@ async function importGlobalUserRules(projectRoot, results, normalize2) {
|
|
|
11061
11217
|
results,
|
|
11062
11218
|
sourcePath: srcPath,
|
|
11063
11219
|
content: raw.trim(),
|
|
11064
|
-
normalize
|
|
11220
|
+
normalize
|
|
11065
11221
|
});
|
|
11066
11222
|
}
|
|
11067
|
-
async function importGlobalDotCursorAgents(projectRoot, results,
|
|
11223
|
+
async function importGlobalDotCursorAgents(projectRoot, results, normalize) {
|
|
11068
11224
|
const srcPath = join(projectRoot, CURSOR_DOT_CURSOR_AGENTS);
|
|
11069
11225
|
const raw = await readFileSafe(srcPath);
|
|
11070
11226
|
if (raw === null || raw.trim() === "") return false;
|
|
@@ -11073,7 +11229,7 @@ async function importGlobalDotCursorAgents(projectRoot, results, normalize2) {
|
|
|
11073
11229
|
results,
|
|
11074
11230
|
sourcePath: srcPath,
|
|
11075
11231
|
content: raw.trim(),
|
|
11076
|
-
normalize
|
|
11232
|
+
normalize
|
|
11077
11233
|
});
|
|
11078
11234
|
}
|
|
11079
11235
|
async function importGlobalMcp(projectRoot, results) {
|
|
@@ -11097,7 +11253,7 @@ async function importGlobalMcp(projectRoot, results) {
|
|
|
11097
11253
|
feature: "mcp"
|
|
11098
11254
|
});
|
|
11099
11255
|
}
|
|
11100
|
-
async function importGlobalAgents(projectRoot, results,
|
|
11256
|
+
async function importGlobalAgents(projectRoot, results, normalize) {
|
|
11101
11257
|
const agentsDir = join(projectRoot, CURSOR_AGENTS_DIR);
|
|
11102
11258
|
const destDir = join(projectRoot, CURSOR_CANONICAL_AGENTS_DIR);
|
|
11103
11259
|
results.push(
|
|
@@ -11106,12 +11262,12 @@ async function importGlobalAgents(projectRoot, results, normalize2) {
|
|
|
11106
11262
|
destDir,
|
|
11107
11263
|
extensions: [".md"],
|
|
11108
11264
|
fromTool: CURSOR_TARGET2,
|
|
11109
|
-
normalize
|
|
11265
|
+
normalize,
|
|
11110
11266
|
mapEntry: ({ relativePath, normalizeTo }) => mapCursorAgentFile(relativePath, destDir, normalizeTo)
|
|
11111
11267
|
})
|
|
11112
11268
|
);
|
|
11113
11269
|
}
|
|
11114
|
-
async function importGlobalCommands(projectRoot, results,
|
|
11270
|
+
async function importGlobalCommands(projectRoot, results, normalize) {
|
|
11115
11271
|
const commandsDir = join(projectRoot, CURSOR_COMMANDS_DIR);
|
|
11116
11272
|
const destDir = join(projectRoot, CURSOR_CANONICAL_COMMANDS_DIR);
|
|
11117
11273
|
results.push(
|
|
@@ -11120,7 +11276,7 @@ async function importGlobalCommands(projectRoot, results, normalize2) {
|
|
|
11120
11276
|
destDir,
|
|
11121
11277
|
extensions: [".md"],
|
|
11122
11278
|
fromTool: CURSOR_TARGET2,
|
|
11123
|
-
normalize
|
|
11279
|
+
normalize,
|
|
11124
11280
|
mapEntry: ({ relativePath, normalizeTo }) => mapCursorCommandFile(relativePath, destDir, normalizeTo)
|
|
11125
11281
|
})
|
|
11126
11282
|
);
|
|
@@ -11142,14 +11298,14 @@ var init_import_global_exports_helpers = __esm({
|
|
|
11142
11298
|
async function importFromCursorGlobalExports(projectRoot) {
|
|
11143
11299
|
if (!await hasGlobalCursorArtifacts(projectRoot)) return [];
|
|
11144
11300
|
const results = [];
|
|
11145
|
-
const
|
|
11146
|
-
let rootWritten = await importGlobalCursorRulesFromDir(projectRoot, results,
|
|
11147
|
-
if (!rootWritten) rootWritten = await importGlobalUserRules(projectRoot, results,
|
|
11148
|
-
if (!rootWritten) await importGlobalDotCursorAgents(projectRoot, results,
|
|
11301
|
+
const normalize = await createImportReferenceNormalizer(CURSOR_TARGET2, projectRoot, "global");
|
|
11302
|
+
let rootWritten = await importGlobalCursorRulesFromDir(projectRoot, results, normalize);
|
|
11303
|
+
if (!rootWritten) rootWritten = await importGlobalUserRules(projectRoot, results, normalize);
|
|
11304
|
+
if (!rootWritten) await importGlobalDotCursorAgents(projectRoot, results, normalize);
|
|
11149
11305
|
await importGlobalMcp(projectRoot, results);
|
|
11150
|
-
await importSkills3(projectRoot, results,
|
|
11151
|
-
await importGlobalAgents(projectRoot, results,
|
|
11152
|
-
await importGlobalCommands(projectRoot, results,
|
|
11306
|
+
await importSkills3(projectRoot, results, normalize, CURSOR_SKILLS_DIR);
|
|
11307
|
+
await importGlobalAgents(projectRoot, results, normalize);
|
|
11308
|
+
await importGlobalCommands(projectRoot, results, normalize);
|
|
11153
11309
|
await importSettings2(projectRoot, results);
|
|
11154
11310
|
await importIgnore(projectRoot, results);
|
|
11155
11311
|
return results;
|
|
@@ -11188,10 +11344,10 @@ async function importFromCursor(projectRoot, options = {}) {
|
|
|
11188
11344
|
return importFromCursorGlobalExports(projectRoot);
|
|
11189
11345
|
}
|
|
11190
11346
|
const results = [];
|
|
11191
|
-
const
|
|
11192
|
-
await importCursorRules(projectRoot, results,
|
|
11193
|
-
results.push(...await runDescriptorImport(descriptor12, projectRoot, "project", { normalize
|
|
11194
|
-
await importSkills3(projectRoot, results,
|
|
11347
|
+
const normalize = await createImportReferenceNormalizer("cursor", projectRoot);
|
|
11348
|
+
await importCursorRules(projectRoot, results, normalize);
|
|
11349
|
+
results.push(...await runDescriptorImport(descriptor12, projectRoot, "project", { normalize }));
|
|
11350
|
+
await importSkills3(projectRoot, results, normalize);
|
|
11195
11351
|
await importMcp3(projectRoot, results);
|
|
11196
11352
|
await importSettings2(projectRoot, results);
|
|
11197
11353
|
await importIgnore(projectRoot, results);
|
|
@@ -11498,6 +11654,23 @@ var init_cursor2 = __esm({
|
|
|
11498
11654
|
},
|
|
11499
11655
|
buildImportPaths: buildCursorImportPaths,
|
|
11500
11656
|
detectionPaths: [".cursor/rules", ".cursor/mcp.json"],
|
|
11657
|
+
nativeInstall: {
|
|
11658
|
+
pickPaths: [
|
|
11659
|
+
{ prefix: ".cursor/rules", feature: "rules", strategy: { kind: "basename", suffix: ".mdc" } },
|
|
11660
|
+
{
|
|
11661
|
+
prefix: ".cursor/commands",
|
|
11662
|
+
feature: "commands",
|
|
11663
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
11664
|
+
},
|
|
11665
|
+
{
|
|
11666
|
+
prefix: ".cursor/agents",
|
|
11667
|
+
feature: "agents",
|
|
11668
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
11669
|
+
},
|
|
11670
|
+
{ prefix: ".cursor/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
11671
|
+
],
|
|
11672
|
+
dialectHints: [{ frontmatterKey: "alwaysApply" }]
|
|
11673
|
+
},
|
|
11501
11674
|
preservesManualActivation: true
|
|
11502
11675
|
};
|
|
11503
11676
|
}
|
|
@@ -11549,14 +11722,14 @@ var init_generator15 = __esm({
|
|
|
11549
11722
|
async function importFromDeepagentsCli(projectRoot, options = {}) {
|
|
11550
11723
|
const scope = options.scope ?? "project";
|
|
11551
11724
|
const results = [];
|
|
11552
|
-
const
|
|
11725
|
+
const normalize = await createImportReferenceNormalizer(
|
|
11553
11726
|
DEEPAGENTS_CLI_TARGET,
|
|
11554
11727
|
projectRoot,
|
|
11555
11728
|
scope
|
|
11556
11729
|
);
|
|
11557
|
-
results.push(...await runDescriptorImport(descriptor13, projectRoot, scope, { normalize
|
|
11730
|
+
results.push(...await runDescriptorImport(descriptor13, projectRoot, scope, { normalize }));
|
|
11558
11731
|
const skillsDir = scope === "global" ? DEEPAGENTS_CLI_GLOBAL_SKILLS_DIR : DEEPAGENTS_CLI_SKILLS_DIR;
|
|
11559
|
-
await importEmbeddedSkills(projectRoot, skillsDir, DEEPAGENTS_CLI_TARGET, results,
|
|
11732
|
+
await importEmbeddedSkills(projectRoot, skillsDir, DEEPAGENTS_CLI_TARGET, results, normalize);
|
|
11560
11733
|
return results;
|
|
11561
11734
|
}
|
|
11562
11735
|
var init_importer13 = __esm({
|
|
@@ -11883,10 +12056,10 @@ var init_mcp_import2 = __esm({
|
|
|
11883
12056
|
async function importFromFactoryDroid(projectRoot, options = {}) {
|
|
11884
12057
|
const scope = options.scope ?? "project";
|
|
11885
12058
|
const results = [];
|
|
11886
|
-
const
|
|
11887
|
-
results.push(...await runDescriptorImport(descriptor14, projectRoot, scope, { normalize
|
|
12059
|
+
const normalize = await createImportReferenceNormalizer(FACTORY_DROID_TARGET, projectRoot, scope);
|
|
12060
|
+
results.push(...await runDescriptorImport(descriptor14, projectRoot, scope, { normalize }));
|
|
11888
12061
|
const skillsDir = scope === "global" ? FACTORY_DROID_GLOBAL_SKILLS_DIR : FACTORY_DROID_SKILLS_DIR;
|
|
11889
|
-
await importEmbeddedSkills(projectRoot, skillsDir, FACTORY_DROID_TARGET, results,
|
|
12062
|
+
await importEmbeddedSkills(projectRoot, skillsDir, FACTORY_DROID_TARGET, results, normalize);
|
|
11890
12063
|
const mcpFile = scope === "global" ? FACTORY_DROID_GLOBAL_MCP_FILE : FACTORY_DROID_MCP_FILE;
|
|
11891
12064
|
await importFactoryDroidMcp(projectRoot, mcpFile, results);
|
|
11892
12065
|
return results;
|
|
@@ -12259,18 +12432,18 @@ function mapHookEvent2(event) {
|
|
|
12259
12432
|
return null;
|
|
12260
12433
|
}
|
|
12261
12434
|
}
|
|
12262
|
-
function generateGeminiSettingsFiles(canonical) {
|
|
12435
|
+
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
12263
12436
|
const settings = {};
|
|
12264
12437
|
let hasAnyNativeSettings = false;
|
|
12265
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12438
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12266
12439
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
12267
12440
|
hasAnyNativeSettings = true;
|
|
12268
12441
|
}
|
|
12269
|
-
if (canonical.agents.length > 0) {
|
|
12442
|
+
if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
|
|
12270
12443
|
settings.experimental = { enableAgents: true };
|
|
12271
12444
|
hasAnyNativeSettings = true;
|
|
12272
12445
|
}
|
|
12273
|
-
if (canonical.hooks) {
|
|
12446
|
+
if (enabledFeatures.has("hooks") && canonical.hooks) {
|
|
12274
12447
|
const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
|
|
12275
12448
|
const mappedEvent = mapHookEvent2(event);
|
|
12276
12449
|
if (!mappedEvent || !Array.isArray(entries)) return [];
|
|
@@ -12700,7 +12873,7 @@ var init_importer_strip = __esm({
|
|
|
12700
12873
|
"src/targets/gemini-cli/importer-strip.ts"() {
|
|
12701
12874
|
}
|
|
12702
12875
|
});
|
|
12703
|
-
async function importGeminiSkillsAndAgents(projectRoot, results,
|
|
12876
|
+
async function importGeminiSkillsAndAgents(projectRoot, results, normalize) {
|
|
12704
12877
|
const geminiSkillsPath = join(projectRoot, GEMINI_SKILLS_DIR);
|
|
12705
12878
|
const skillDirs = await readDirRecursive(geminiSkillsPath);
|
|
12706
12879
|
const skillMdFiles = skillDirs.filter((f) => basename(f) === "SKILL.md");
|
|
@@ -12716,7 +12889,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
|
|
|
12716
12889
|
const agentPath = join(agentsDir, `${projectedAgent.name}.md`);
|
|
12717
12890
|
await writeFileAtomic(
|
|
12718
12891
|
agentPath,
|
|
12719
|
-
serializeImportedAgent(projectedAgent,
|
|
12892
|
+
serializeImportedAgent(projectedAgent, normalize(rawParsed.body, srcPath, agentPath))
|
|
12720
12893
|
);
|
|
12721
12894
|
results.push({
|
|
12722
12895
|
fromTool: "gemini-cli",
|
|
@@ -12727,7 +12900,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
|
|
|
12727
12900
|
continue;
|
|
12728
12901
|
}
|
|
12729
12902
|
const destPath = join(projectRoot, GEMINI_CANONICAL_SKILLS_DIR, skillName, "SKILL.md");
|
|
12730
|
-
const normalized =
|
|
12903
|
+
const normalized = normalize(content, srcPath, destPath);
|
|
12731
12904
|
const skillDir = join(projectRoot, GEMINI_CANONICAL_SKILLS_DIR, skillName);
|
|
12732
12905
|
await mkdirp(skillDir);
|
|
12733
12906
|
const { frontmatter, body } = parseFrontmatter(normalized);
|
|
@@ -12749,7 +12922,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
|
|
|
12749
12922
|
const relPath = relative(dirname(srcPath), absPath).replace(/\\/g, "/");
|
|
12750
12923
|
const destSupportPath = join(skillDir, relPath);
|
|
12751
12924
|
await mkdirp(dirname(destSupportPath));
|
|
12752
|
-
await writeFileAtomic(destSupportPath,
|
|
12925
|
+
await writeFileAtomic(destSupportPath, normalize(supportContent, absPath, destSupportPath));
|
|
12753
12926
|
results.push({
|
|
12754
12927
|
fromTool: "gemini-cli",
|
|
12755
12928
|
fromPath: absPath,
|
|
@@ -12771,7 +12944,7 @@ async function importGeminiSkillsAndAgents(projectRoot, results, normalize2) {
|
|
|
12771
12944
|
const agentsDir = join(projectRoot, GEMINI_CANONICAL_AGENTS_DIR);
|
|
12772
12945
|
await mkdirp(agentsDir);
|
|
12773
12946
|
const destPath = join(agentsDir, relativeMdPath);
|
|
12774
|
-
const normalizedBody =
|
|
12947
|
+
const normalizedBody = normalize(body, srcPath, destPath);
|
|
12775
12948
|
await writeFileAtomic(
|
|
12776
12949
|
destPath,
|
|
12777
12950
|
await serializeImportedAgentWithFallback(
|
|
@@ -12805,7 +12978,7 @@ var init_importer_skills_agents = __esm({
|
|
|
12805
12978
|
init_constants30();
|
|
12806
12979
|
}
|
|
12807
12980
|
});
|
|
12808
|
-
async function importRootRule2(projectRoot, results,
|
|
12981
|
+
async function importRootRule2(projectRoot, results, normalize) {
|
|
12809
12982
|
const normalizeCodex = await createImportReferenceNormalizer("codex-cli", projectRoot);
|
|
12810
12983
|
const rulesDir = join(projectRoot, GEMINI_CANONICAL_RULES_DIR);
|
|
12811
12984
|
const compatAgentsRootPath = join(projectRoot, GEMINI_COMPAT_AGENTS);
|
|
@@ -12836,10 +13009,10 @@ async function importRootRule2(projectRoot, results, normalize2) {
|
|
|
12836
13009
|
rulesDir: GEMINI_CANONICAL_RULES_DIR,
|
|
12837
13010
|
sourcePath: rootSourcePath,
|
|
12838
13011
|
fromTool: GEMINI_TARGET,
|
|
12839
|
-
normalize
|
|
13012
|
+
normalize
|
|
12840
13013
|
});
|
|
12841
13014
|
results.push(...split.results);
|
|
12842
|
-
const compatNormalized =
|
|
13015
|
+
const compatNormalized = normalize(split.rootContent, rootSourcePath, destPath);
|
|
12843
13016
|
const normalizedRoot = stripProjectRootCanonicalPrefix(
|
|
12844
13017
|
compatNormalized.replace(/\.agents\/skills\//g, ".agentsmesh/skills/").replace(/\.agents\\skills\\/g, ".agentsmesh/skills/"),
|
|
12845
13018
|
projectRoot
|
|
@@ -12861,10 +13034,10 @@ async function importRootRule2(projectRoot, results, normalize2) {
|
|
|
12861
13034
|
}
|
|
12862
13035
|
async function importFromGemini(projectRoot) {
|
|
12863
13036
|
const results = [];
|
|
12864
|
-
const
|
|
12865
|
-
await importRootRule2(projectRoot, results,
|
|
12866
|
-
results.push(...await runDescriptorImport(descriptor15, projectRoot, "project", { normalize
|
|
12867
|
-
await importGeminiSkillsAndAgents(projectRoot, results,
|
|
13037
|
+
const normalize = await createImportReferenceNormalizer(GEMINI_TARGET, projectRoot);
|
|
13038
|
+
await importRootRule2(projectRoot, results, normalize);
|
|
13039
|
+
results.push(...await runDescriptorImport(descriptor15, projectRoot, "project", { normalize }));
|
|
13040
|
+
await importGeminiSkillsAndAgents(projectRoot, results, normalize);
|
|
12868
13041
|
await importGeminiSettings(projectRoot, results);
|
|
12869
13042
|
await importGeminiIgnore(projectRoot, results);
|
|
12870
13043
|
results.push(...await importGeminiPolicies(projectRoot));
|
|
@@ -12886,6 +13059,36 @@ var init_importer15 = __esm({
|
|
|
12886
13059
|
init_importer_skills_agents();
|
|
12887
13060
|
}
|
|
12888
13061
|
});
|
|
13062
|
+
function isUnderGeminiCommands(pathInRepoPosix) {
|
|
13063
|
+
const p = pathInRepoPosix.replace(/^\/+|\/+$/g, "");
|
|
13064
|
+
return p === ".gemini/commands" || p.startsWith(".gemini/commands/");
|
|
13065
|
+
}
|
|
13066
|
+
async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
|
|
13067
|
+
const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
|
|
13068
|
+
const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
|
|
13069
|
+
const files = await readDirRecursive(scanDir);
|
|
13070
|
+
const names = [];
|
|
13071
|
+
for (const f of files) {
|
|
13072
|
+
if (!/\.(toml|md)$/i.test(f)) continue;
|
|
13073
|
+
const rel2 = relative(commandsRoot, f).replace(/\\/g, "/");
|
|
13074
|
+
if (rel2.startsWith("..") || rel2 === "") continue;
|
|
13075
|
+
const noExt = rel2.replace(/\.(toml|md)$/i, "");
|
|
13076
|
+
const name = noExt.split("/").filter(Boolean).join(":");
|
|
13077
|
+
if (name) names.push(name);
|
|
13078
|
+
}
|
|
13079
|
+
return [...new Set(names)].sort();
|
|
13080
|
+
}
|
|
13081
|
+
async function inferGeminiPick(repoRoot, posixPath) {
|
|
13082
|
+
if (!isUnderGeminiCommands(posixPath)) return {};
|
|
13083
|
+
const commands = await inferGeminiCommandNamesFromFiles(repoRoot, posixPath);
|
|
13084
|
+
return commands.length ? { commands } : {};
|
|
13085
|
+
}
|
|
13086
|
+
var init_gemini_install_commands = __esm({
|
|
13087
|
+
"src/install/native/gemini-install-commands.ts"() {
|
|
13088
|
+
init_fs();
|
|
13089
|
+
init_constants30();
|
|
13090
|
+
}
|
|
13091
|
+
});
|
|
12889
13092
|
async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
|
|
12890
13093
|
const relativeMdPath = relativePath.replace(/\\/g, "/");
|
|
12891
13094
|
const destPath = join(destDir, relativeMdPath);
|
|
@@ -12997,12 +13200,12 @@ var init_lint12 = __esm({
|
|
|
12997
13200
|
});
|
|
12998
13201
|
|
|
12999
13202
|
// src/targets/gemini-cli/scoped-settings-emit.ts
|
|
13000
|
-
function emitScopedGeminiSettings(canonical, scope) {
|
|
13203
|
+
function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
|
|
13001
13204
|
if (scope === "project") {
|
|
13002
13205
|
const caps = getTargetCapabilities("gemini-cli", scope);
|
|
13003
13206
|
if (caps?.ignore.flavor !== "settings-embedded") return [];
|
|
13004
13207
|
}
|
|
13005
|
-
return generateGeminiSettingsFiles(canonical);
|
|
13208
|
+
return generateGeminiSettingsFiles(canonical, enabledFeatures);
|
|
13006
13209
|
}
|
|
13007
13210
|
var init_scoped_settings_emit = __esm({
|
|
13008
13211
|
"src/targets/gemini-cli/scoped-settings-emit.ts"() {
|
|
@@ -13020,6 +13223,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13020
13223
|
init_policies_generator();
|
|
13021
13224
|
init_constants30();
|
|
13022
13225
|
init_importer15();
|
|
13226
|
+
init_gemini_install_commands();
|
|
13023
13227
|
init_import_mappers6();
|
|
13024
13228
|
init_linter15();
|
|
13025
13229
|
init_import_map_builders();
|
|
@@ -13215,6 +13419,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13215
13419
|
},
|
|
13216
13420
|
buildImportPaths: buildGeminiCliImportPaths,
|
|
13217
13421
|
detectionPaths: ["GEMINI.md", ".gemini"],
|
|
13422
|
+
nativeInstall: { inferPick: inferGeminiPick },
|
|
13218
13423
|
conversionDefaults: { agentsToSkills: false }
|
|
13219
13424
|
};
|
|
13220
13425
|
}
|
|
@@ -13265,10 +13470,10 @@ var init_generator19 = __esm({
|
|
|
13265
13470
|
async function importFromGoose(projectRoot, options = {}) {
|
|
13266
13471
|
const scope = options.scope ?? "project";
|
|
13267
13472
|
const results = [];
|
|
13268
|
-
const
|
|
13269
|
-
results.push(...await runDescriptorImport(descriptor16, projectRoot, scope, { normalize
|
|
13473
|
+
const normalize = await createImportReferenceNormalizer(GOOSE_TARGET, projectRoot, scope);
|
|
13474
|
+
results.push(...await runDescriptorImport(descriptor16, projectRoot, scope, { normalize }));
|
|
13270
13475
|
const skillsDir = scope === "global" ? GOOSE_GLOBAL_SKILLS_DIR : GOOSE_SKILLS_DIR;
|
|
13271
|
-
await importEmbeddedSkills(projectRoot, skillsDir, GOOSE_TARGET, results,
|
|
13476
|
+
await importEmbeddedSkills(projectRoot, skillsDir, GOOSE_TARGET, results, normalize);
|
|
13272
13477
|
return results;
|
|
13273
13478
|
}
|
|
13274
13479
|
var init_importer16 = __esm({
|
|
@@ -13500,8 +13705,8 @@ var init_generator20 = __esm({
|
|
|
13500
13705
|
// src/targets/jules/importer.ts
|
|
13501
13706
|
async function importFromJules(projectRoot, options = {}) {
|
|
13502
13707
|
const scope = options.scope ?? "project";
|
|
13503
|
-
const
|
|
13504
|
-
return runDescriptorImport(descriptor17, projectRoot, scope, { normalize
|
|
13708
|
+
const normalize = await createImportReferenceNormalizer(JULES_TARGET, projectRoot, scope);
|
|
13709
|
+
return runDescriptorImport(descriptor17, projectRoot, scope, { normalize });
|
|
13505
13710
|
}
|
|
13506
13711
|
var init_importer17 = __esm({
|
|
13507
13712
|
"src/targets/jules/importer.ts"() {
|
|
@@ -13770,7 +13975,7 @@ var init_generator21 = __esm({
|
|
|
13770
13975
|
init_constants16();
|
|
13771
13976
|
}
|
|
13772
13977
|
});
|
|
13773
|
-
async function importRootRule3(projectRoot, results,
|
|
13978
|
+
async function importRootRule3(projectRoot, results, normalize) {
|
|
13774
13979
|
const sources = [JUNIE_DOT_AGENTS, JUNIE_GUIDELINES, JUNIE_CI_GUIDELINES, JUNIE_AGENTS_FALLBACK];
|
|
13775
13980
|
const destPath = join(projectRoot, JUNIE_CANONICAL_ROOT_RULE);
|
|
13776
13981
|
for (const relPath of sources) {
|
|
@@ -13783,10 +13988,10 @@ async function importRootRule3(projectRoot, results, normalize2) {
|
|
|
13783
13988
|
rulesDir: JUNIE_CANONICAL_RULES_DIR,
|
|
13784
13989
|
sourcePath: srcPath,
|
|
13785
13990
|
fromTool: JUNIE_TARGET,
|
|
13786
|
-
normalize
|
|
13991
|
+
normalize
|
|
13787
13992
|
});
|
|
13788
13993
|
results.push(...split.results);
|
|
13789
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
13994
|
+
const { frontmatter, body } = parseFrontmatter(normalize(split.rootContent, srcPath, destPath));
|
|
13790
13995
|
const output = await serializeImportedRuleWithFallback(
|
|
13791
13996
|
destPath,
|
|
13792
13997
|
{
|
|
@@ -13808,10 +14013,10 @@ async function importRootRule3(projectRoot, results, normalize2) {
|
|
|
13808
14013
|
}
|
|
13809
14014
|
async function importFromJunie(projectRoot) {
|
|
13810
14015
|
const results = [];
|
|
13811
|
-
const
|
|
13812
|
-
await importRootRule3(projectRoot, results,
|
|
13813
|
-
results.push(...await runDescriptorImport(descriptor18, projectRoot, "project", { normalize
|
|
13814
|
-
await importEmbeddedSkills(projectRoot, JUNIE_SKILLS_DIR, JUNIE_TARGET, results,
|
|
14016
|
+
const normalize = await createImportReferenceNormalizer(JUNIE_TARGET, projectRoot);
|
|
14017
|
+
await importRootRule3(projectRoot, results, normalize);
|
|
14018
|
+
results.push(...await runDescriptorImport(descriptor18, projectRoot, "project", { normalize }));
|
|
14019
|
+
await importEmbeddedSkills(projectRoot, JUNIE_SKILLS_DIR, JUNIE_TARGET, results, normalize);
|
|
13815
14020
|
return results;
|
|
13816
14021
|
}
|
|
13817
14022
|
var init_importer18 = __esm({
|
|
@@ -14056,7 +14261,23 @@ var init_junie2 = __esm({
|
|
|
14056
14261
|
".junie/skills",
|
|
14057
14262
|
".junie/mcp/mcp.json",
|
|
14058
14263
|
".aiignore"
|
|
14059
|
-
]
|
|
14264
|
+
],
|
|
14265
|
+
nativeInstall: {
|
|
14266
|
+
pickPaths: [
|
|
14267
|
+
{
|
|
14268
|
+
prefix: ".junie/commands",
|
|
14269
|
+
feature: "commands",
|
|
14270
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
14271
|
+
},
|
|
14272
|
+
{ prefix: ".junie/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
|
|
14273
|
+
{
|
|
14274
|
+
prefix: ".junie/agents",
|
|
14275
|
+
feature: "agents",
|
|
14276
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
14277
|
+
},
|
|
14278
|
+
{ prefix: ".junie/skills", feature: "skills", strategy: { kind: "skillDir" } }
|
|
14279
|
+
]
|
|
14280
|
+
}
|
|
14060
14281
|
};
|
|
14061
14282
|
}
|
|
14062
14283
|
});
|
|
@@ -14203,7 +14424,7 @@ async function pathExists(absolutePath) {
|
|
|
14203
14424
|
return false;
|
|
14204
14425
|
}
|
|
14205
14426
|
}
|
|
14206
|
-
async function importLegacyRules(projectRoot, results,
|
|
14427
|
+
async function importLegacyRules(projectRoot, results, normalize) {
|
|
14207
14428
|
const srcDir = join(projectRoot, KILO_CODE_LEGACY_RULES_DIR);
|
|
14208
14429
|
if (!await pathExists(srcDir)) return;
|
|
14209
14430
|
const destDir = join(projectRoot, KILO_CODE_CANONICAL_RULES_DIR);
|
|
@@ -14212,7 +14433,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
|
|
|
14212
14433
|
const rootContent = hasCurrentRoot ? null : await readFileSafe(rootSourceFile);
|
|
14213
14434
|
if (rootContent !== null) {
|
|
14214
14435
|
const destPath = join(projectRoot, CANONICAL_ROOT_RULE_PATH);
|
|
14215
|
-
const normalized =
|
|
14436
|
+
const normalized = normalize(rootContent, rootSourceFile, destPath);
|
|
14216
14437
|
const { body } = parseFrontmatter(normalized);
|
|
14217
14438
|
const serialized = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
|
|
14218
14439
|
await writeFileAtomic(destPath, serialized);
|
|
@@ -14229,7 +14450,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
|
|
|
14229
14450
|
destDir,
|
|
14230
14451
|
extensions: [".md"],
|
|
14231
14452
|
fromTool: KILO_CODE_TARGET,
|
|
14232
|
-
normalize
|
|
14453
|
+
normalize,
|
|
14233
14454
|
mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
|
|
14234
14455
|
const mapping = await kiloNonRootRuleMapper({
|
|
14235
14456
|
absolutePath: srcPath,
|
|
@@ -14245,7 +14466,7 @@ async function importLegacyRules(projectRoot, results, normalize2) {
|
|
|
14245
14466
|
})
|
|
14246
14467
|
);
|
|
14247
14468
|
}
|
|
14248
|
-
async function importLegacyWorkflows(projectRoot, results,
|
|
14469
|
+
async function importLegacyWorkflows(projectRoot, results, normalize) {
|
|
14249
14470
|
const srcDir = join(projectRoot, KILO_CODE_LEGACY_WORKFLOWS_DIR);
|
|
14250
14471
|
if (!await pathExists(srcDir)) return;
|
|
14251
14472
|
const destDir = join(projectRoot, KILO_CODE_CANONICAL_COMMANDS_DIR);
|
|
@@ -14255,7 +14476,7 @@ async function importLegacyWorkflows(projectRoot, results, normalize2) {
|
|
|
14255
14476
|
destDir,
|
|
14256
14477
|
extensions: [".md"],
|
|
14257
14478
|
fromTool: KILO_CODE_TARGET,
|
|
14258
|
-
normalize
|
|
14479
|
+
normalize,
|
|
14259
14480
|
mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
|
|
14260
14481
|
const mapping = await kiloCommandMapper({
|
|
14261
14482
|
absolutePath: srcPath,
|
|
@@ -14270,7 +14491,7 @@ async function importLegacyWorkflows(projectRoot, results, normalize2) {
|
|
|
14270
14491
|
})
|
|
14271
14492
|
);
|
|
14272
14493
|
}
|
|
14273
|
-
async function importLegacyModes(projectRoot, results,
|
|
14494
|
+
async function importLegacyModes(projectRoot, results, normalize) {
|
|
14274
14495
|
const sourceFile = join(projectRoot, KILO_CODE_LEGACY_MODES_FILE);
|
|
14275
14496
|
const content = await readFileSafe(sourceFile);
|
|
14276
14497
|
if (content === null) return;
|
|
@@ -14299,7 +14520,7 @@ ${whenToUse}` : role;
|
|
|
14299
14520
|
if (description) frontmatter.description = description;
|
|
14300
14521
|
if (typeof mode.name === "string" && mode.name.length > 0) frontmatter.name = mode.name;
|
|
14301
14522
|
const serialized = await serializeImportedAgentWithFallback(destPath, frontmatter, body);
|
|
14302
|
-
const normalized =
|
|
14523
|
+
const normalized = normalize(serialized, sourceFile, destPath);
|
|
14303
14524
|
await writeFileAtomic(destPath, normalized);
|
|
14304
14525
|
results.push({
|
|
14305
14526
|
feature: "agents",
|
|
@@ -14312,25 +14533,25 @@ ${whenToUse}` : role;
|
|
|
14312
14533
|
async function importFromKiloCode(projectRoot, options = {}) {
|
|
14313
14534
|
const scope = options.scope ?? "project";
|
|
14314
14535
|
const results = [];
|
|
14315
|
-
const
|
|
14316
|
-
results.push(...await runDescriptorImport(descriptor19, projectRoot, scope, { normalize
|
|
14536
|
+
const normalize = await createImportReferenceNormalizer(KILO_CODE_TARGET, projectRoot, scope);
|
|
14537
|
+
results.push(...await runDescriptorImport(descriptor19, projectRoot, scope, { normalize }));
|
|
14317
14538
|
await importEmbeddedSkills(
|
|
14318
14539
|
projectRoot,
|
|
14319
14540
|
KILO_CODE_SKILLS_DIR,
|
|
14320
14541
|
KILO_CODE_TARGET,
|
|
14321
14542
|
results,
|
|
14322
|
-
|
|
14543
|
+
normalize
|
|
14323
14544
|
);
|
|
14324
14545
|
if (scope === "project") {
|
|
14325
|
-
await importLegacyRules(projectRoot, results,
|
|
14326
|
-
await importLegacyWorkflows(projectRoot, results,
|
|
14327
|
-
await importLegacyModes(projectRoot, results,
|
|
14546
|
+
await importLegacyRules(projectRoot, results, normalize);
|
|
14547
|
+
await importLegacyWorkflows(projectRoot, results, normalize);
|
|
14548
|
+
await importLegacyModes(projectRoot, results, normalize);
|
|
14328
14549
|
await importEmbeddedSkills(
|
|
14329
14550
|
projectRoot,
|
|
14330
14551
|
KILO_CODE_LEGACY_SKILLS_DIR,
|
|
14331
14552
|
KILO_CODE_TARGET,
|
|
14332
14553
|
results,
|
|
14333
|
-
|
|
14554
|
+
normalize
|
|
14334
14555
|
);
|
|
14335
14556
|
}
|
|
14336
14557
|
return results;
|
|
@@ -14799,14 +15020,14 @@ function canonicalRuleMeta2(frontmatter) {
|
|
|
14799
15020
|
if (inclusion === "fileMatch") meta.trigger = "glob";
|
|
14800
15021
|
return meta;
|
|
14801
15022
|
}
|
|
14802
|
-
async function importRoot(projectRoot, results,
|
|
15023
|
+
async function importRoot(projectRoot, results, normalize, scope) {
|
|
14803
15024
|
const candidates = scope === "global" ? [KIRO_GLOBAL_STEERING_AGENTS_MD, KIRO_AGENTS_MD] : [KIRO_AGENTS_MD, KIRO_GLOBAL_STEERING_AGENTS_MD];
|
|
14804
15025
|
for (const rel2 of candidates) {
|
|
14805
15026
|
const srcPath = join(projectRoot, rel2);
|
|
14806
15027
|
const content = await readFileSafe(srcPath);
|
|
14807
15028
|
if (content === null) continue;
|
|
14808
15029
|
const destPath = join(projectRoot, KIRO_CANONICAL_ROOT_RULE);
|
|
14809
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
15030
|
+
const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
|
|
14810
15031
|
await writeFileAtomic(
|
|
14811
15032
|
destPath,
|
|
14812
15033
|
await serializeImportedRuleWithFallback(destPath, { ...frontmatter, root: true }, body)
|
|
@@ -14820,7 +15041,7 @@ async function importRoot(projectRoot, results, normalize2, scope) {
|
|
|
14820
15041
|
return;
|
|
14821
15042
|
}
|
|
14822
15043
|
}
|
|
14823
|
-
async function importNonRootRules(projectRoot, results,
|
|
15044
|
+
async function importNonRootRules(projectRoot, results, normalize) {
|
|
14824
15045
|
const destDir = join(projectRoot, KIRO_CANONICAL_RULES_DIR);
|
|
14825
15046
|
results.push(
|
|
14826
15047
|
...await importFileDirectory({
|
|
@@ -14828,7 +15049,7 @@ async function importNonRootRules(projectRoot, results, normalize2) {
|
|
|
14828
15049
|
destDir,
|
|
14829
15050
|
extensions: [".md"],
|
|
14830
15051
|
fromTool: KIRO_TARGET,
|
|
14831
|
-
normalize
|
|
15052
|
+
normalize,
|
|
14832
15053
|
mapEntry: async ({ relativePath, normalizeTo }) => {
|
|
14833
15054
|
if (basename(relativePath) === "AGENTS.md") return null;
|
|
14834
15055
|
const destPath = join(destDir, relativePath);
|
|
@@ -14870,11 +15091,11 @@ async function importHooks2(projectRoot, results) {
|
|
|
14870
15091
|
async function importFromKiro(projectRoot, options = {}) {
|
|
14871
15092
|
const scope = options.scope ?? "project";
|
|
14872
15093
|
const results = [];
|
|
14873
|
-
const
|
|
14874
|
-
await importRoot(projectRoot, results,
|
|
14875
|
-
await importNonRootRules(projectRoot, results,
|
|
14876
|
-
results.push(...await runDescriptorImport(descriptor20, projectRoot, scope, { normalize
|
|
14877
|
-
await importEmbeddedSkills(projectRoot, KIRO_SKILLS_DIR, KIRO_TARGET, results,
|
|
15094
|
+
const normalize = await createImportReferenceNormalizer(KIRO_TARGET, projectRoot, scope);
|
|
15095
|
+
await importRoot(projectRoot, results, normalize, scope);
|
|
15096
|
+
await importNonRootRules(projectRoot, results, normalize);
|
|
15097
|
+
results.push(...await runDescriptorImport(descriptor20, projectRoot, scope, { normalize }));
|
|
15098
|
+
await importEmbeddedSkills(projectRoot, KIRO_SKILLS_DIR, KIRO_TARGET, results, normalize);
|
|
14878
15099
|
if (scope === "project") await importHooks2(projectRoot, results);
|
|
14879
15100
|
return results;
|
|
14880
15101
|
}
|
|
@@ -15258,9 +15479,9 @@ async function importMcp4(projectRoot, scope, results) {
|
|
|
15258
15479
|
async function importFromOpenCode(projectRoot, options = {}) {
|
|
15259
15480
|
const scope = options.scope ?? "project";
|
|
15260
15481
|
const results = [];
|
|
15261
|
-
const
|
|
15262
|
-
results.push(...await runDescriptorImport(descriptor21, projectRoot, scope, { normalize
|
|
15263
|
-
await importEmbeddedSkills(projectRoot, OPENCODE_SKILLS_DIR, OPENCODE_TARGET, results,
|
|
15482
|
+
const normalize = await createImportReferenceNormalizer(OPENCODE_TARGET, projectRoot, scope);
|
|
15483
|
+
results.push(...await runDescriptorImport(descriptor21, projectRoot, scope, { normalize }));
|
|
15484
|
+
await importEmbeddedSkills(projectRoot, OPENCODE_SKILLS_DIR, OPENCODE_TARGET, results, normalize);
|
|
15264
15485
|
await importMcp4(projectRoot, scope, results);
|
|
15265
15486
|
return results;
|
|
15266
15487
|
}
|
|
@@ -15619,10 +15840,10 @@ var init_generator25 = __esm({
|
|
|
15619
15840
|
async function importFromPiAgent(projectRoot, options = {}) {
|
|
15620
15841
|
const scope = options.scope ?? "project";
|
|
15621
15842
|
const results = [];
|
|
15622
|
-
const
|
|
15623
|
-
results.push(...await runDescriptorImport(descriptor22, projectRoot, scope, { normalize
|
|
15843
|
+
const normalize = await createImportReferenceNormalizer(PI_AGENT_TARGET, projectRoot, scope);
|
|
15844
|
+
results.push(...await runDescriptorImport(descriptor22, projectRoot, scope, { normalize }));
|
|
15624
15845
|
const skillsDir = scope === "global" ? PI_AGENT_GLOBAL_SKILLS_DIR : PI_AGENT_SKILLS_DIR;
|
|
15625
|
-
await importEmbeddedSkills(projectRoot, skillsDir, PI_AGENT_TARGET, results,
|
|
15846
|
+
await importEmbeddedSkills(projectRoot, skillsDir, PI_AGENT_TARGET, results, normalize);
|
|
15626
15847
|
return results;
|
|
15627
15848
|
}
|
|
15628
15849
|
var init_importer22 = __esm({
|
|
@@ -15931,10 +16152,10 @@ var init_generator26 = __esm({
|
|
|
15931
16152
|
async function importFromQwenCode(projectRoot, options = {}) {
|
|
15932
16153
|
const scope = options.scope ?? "project";
|
|
15933
16154
|
const results = [];
|
|
15934
|
-
const
|
|
15935
|
-
results.push(...await runDescriptorImport(descriptor23, projectRoot, scope, { normalize
|
|
16155
|
+
const normalize = await createImportReferenceNormalizer(QWEN_CODE_TARGET, projectRoot, scope);
|
|
16156
|
+
results.push(...await runDescriptorImport(descriptor23, projectRoot, scope, { normalize }));
|
|
15936
16157
|
const skillsDir = scope === "global" ? QWEN_GLOBAL_SKILLS_DIR : QWEN_SKILLS_DIR;
|
|
15937
|
-
await importEmbeddedSkills(projectRoot, skillsDir, QWEN_CODE_TARGET, results,
|
|
16158
|
+
await importEmbeddedSkills(projectRoot, skillsDir, QWEN_CODE_TARGET, results, normalize);
|
|
15938
16159
|
return results;
|
|
15939
16160
|
}
|
|
15940
16161
|
var init_importer23 = __esm({
|
|
@@ -16202,14 +16423,14 @@ var init_generator27 = __esm({
|
|
|
16202
16423
|
async function importFromReplitAgent(projectRoot, options = {}) {
|
|
16203
16424
|
const scope = options.scope ?? "project";
|
|
16204
16425
|
const results = [];
|
|
16205
|
-
const
|
|
16206
|
-
results.push(...await runDescriptorImport(descriptor24, projectRoot, scope, { normalize
|
|
16426
|
+
const normalize = await createImportReferenceNormalizer(REPLIT_AGENT_TARGET, projectRoot, scope);
|
|
16427
|
+
results.push(...await runDescriptorImport(descriptor24, projectRoot, scope, { normalize }));
|
|
16207
16428
|
await importEmbeddedSkills(
|
|
16208
16429
|
projectRoot,
|
|
16209
16430
|
REPLIT_AGENT_SKILLS_DIR,
|
|
16210
16431
|
REPLIT_AGENT_TARGET,
|
|
16211
16432
|
results,
|
|
16212
|
-
|
|
16433
|
+
normalize
|
|
16213
16434
|
);
|
|
16214
16435
|
return results;
|
|
16215
16436
|
}
|
|
@@ -16500,7 +16721,7 @@ var init_import_mappers9 = __esm({
|
|
|
16500
16721
|
};
|
|
16501
16722
|
}
|
|
16502
16723
|
});
|
|
16503
|
-
async function importPerModeRules(projectRoot, results,
|
|
16724
|
+
async function importPerModeRules(projectRoot, results, normalize) {
|
|
16504
16725
|
const rooDir = join(projectRoot, ROO_CODE_DIR);
|
|
16505
16726
|
let entries;
|
|
16506
16727
|
try {
|
|
@@ -16517,7 +16738,7 @@ async function importPerModeRules(projectRoot, results, normalize2) {
|
|
|
16517
16738
|
destDir,
|
|
16518
16739
|
extensions: [".md"],
|
|
16519
16740
|
fromTool: ROO_CODE_TARGET,
|
|
16520
|
-
normalize
|
|
16741
|
+
normalize,
|
|
16521
16742
|
mapEntry: async ({ srcPath, relativePath, content, normalizeTo }) => {
|
|
16522
16743
|
const mapping = await rooNonRootRuleMapper({
|
|
16523
16744
|
absolutePath: srcPath,
|
|
@@ -16536,10 +16757,10 @@ async function importPerModeRules(projectRoot, results, normalize2) {
|
|
|
16536
16757
|
async function importFromRooCode(projectRoot, options = {}) {
|
|
16537
16758
|
const scope = options.scope ?? "project";
|
|
16538
16759
|
const results = [];
|
|
16539
|
-
const
|
|
16540
|
-
results.push(...await runDescriptorImport(descriptor25, projectRoot, scope, { normalize
|
|
16541
|
-
await importPerModeRules(projectRoot, results,
|
|
16542
|
-
await importEmbeddedSkills(projectRoot, ROO_CODE_SKILLS_DIR, ROO_CODE_TARGET, results,
|
|
16760
|
+
const normalize = await createImportReferenceNormalizer(ROO_CODE_TARGET, projectRoot, scope);
|
|
16761
|
+
results.push(...await runDescriptorImport(descriptor25, projectRoot, scope, { normalize }));
|
|
16762
|
+
await importPerModeRules(projectRoot, results, normalize);
|
|
16763
|
+
await importEmbeddedSkills(projectRoot, ROO_CODE_SKILLS_DIR, ROO_CODE_TARGET, results, normalize);
|
|
16543
16764
|
return results;
|
|
16544
16765
|
}
|
|
16545
16766
|
var init_importer25 = __esm({
|
|
@@ -16853,10 +17074,10 @@ var init_generator29 = __esm({
|
|
|
16853
17074
|
async function importFromRovodev(projectRoot, options = {}) {
|
|
16854
17075
|
const scope = options.scope ?? "project";
|
|
16855
17076
|
const results = [];
|
|
16856
|
-
const
|
|
16857
|
-
results.push(...await runDescriptorImport(descriptor26, projectRoot, scope, { normalize
|
|
17077
|
+
const normalize = await createImportReferenceNormalizer(ROVODEV_TARGET, projectRoot, scope);
|
|
17078
|
+
results.push(...await runDescriptorImport(descriptor26, projectRoot, scope, { normalize }));
|
|
16858
17079
|
const skillsDir = scope === "global" ? ROVODEV_GLOBAL_SKILLS_DIR : ROVODEV_SKILLS_DIR;
|
|
16859
|
-
await importEmbeddedSkills(projectRoot, skillsDir, ROVODEV_TARGET, results,
|
|
17080
|
+
await importEmbeddedSkills(projectRoot, skillsDir, ROVODEV_TARGET, results, normalize);
|
|
16860
17081
|
return results;
|
|
16861
17082
|
}
|
|
16862
17083
|
var init_importer26 = __esm({
|
|
@@ -17114,14 +17335,14 @@ var init_generator30 = __esm({
|
|
|
17114
17335
|
init_constants25();
|
|
17115
17336
|
}
|
|
17116
17337
|
});
|
|
17117
|
-
async function importRoot2(projectRoot, results,
|
|
17338
|
+
async function importRoot2(projectRoot, results, normalize, scope) {
|
|
17118
17339
|
const candidates = scope === "global" ? [TRAE_GLOBAL_ROOT_RULE, TRAE_PROJECT_RULES] : [TRAE_PROJECT_RULES, TRAE_GLOBAL_ROOT_RULE];
|
|
17119
17340
|
for (const rel2 of candidates) {
|
|
17120
17341
|
const srcPath = join(projectRoot, rel2);
|
|
17121
17342
|
const content = await readFileSafe(srcPath);
|
|
17122
17343
|
if (content === null) continue;
|
|
17123
17344
|
const destPath = join(projectRoot, CANONICAL_ROOT_RULE);
|
|
17124
|
-
const { frontmatter, body } = parseFrontmatter(
|
|
17345
|
+
const { frontmatter, body } = parseFrontmatter(normalize(content, srcPath, destPath));
|
|
17125
17346
|
await mkdirp(join(projectRoot, TRAE_CANONICAL_RULES_DIR));
|
|
17126
17347
|
await writeFileAtomic(
|
|
17127
17348
|
destPath,
|
|
@@ -17136,7 +17357,7 @@ async function importRoot2(projectRoot, results, normalize2, scope) {
|
|
|
17136
17357
|
return;
|
|
17137
17358
|
}
|
|
17138
17359
|
}
|
|
17139
|
-
async function importNonRootRules2(projectRoot, results,
|
|
17360
|
+
async function importNonRootRules2(projectRoot, results, normalize, scope) {
|
|
17140
17361
|
const srcDir = join(projectRoot, scope === "global" ? TRAE_GLOBAL_RULES_DIR : TRAE_RULES_DIR);
|
|
17141
17362
|
const destDir = join(projectRoot, TRAE_CANONICAL_RULES_DIR);
|
|
17142
17363
|
results.push(
|
|
@@ -17145,7 +17366,7 @@ async function importNonRootRules2(projectRoot, results, normalize2, scope) {
|
|
|
17145
17366
|
destDir,
|
|
17146
17367
|
extensions: [".md"],
|
|
17147
17368
|
fromTool: TRAE_TARGET,
|
|
17148
|
-
normalize
|
|
17369
|
+
normalize,
|
|
17149
17370
|
mapEntry: async ({ relativePath, normalizeTo }) => {
|
|
17150
17371
|
const filename = basename(relativePath);
|
|
17151
17372
|
if (filename === "project_rules.md" || filename === "rules.md") return null;
|
|
@@ -17168,16 +17389,16 @@ async function importNonRootRules2(projectRoot, results, normalize2, scope) {
|
|
|
17168
17389
|
async function importFromTrae(projectRoot, options = {}) {
|
|
17169
17390
|
const scope = options.scope ?? "project";
|
|
17170
17391
|
const results = [];
|
|
17171
|
-
const
|
|
17172
|
-
await importRoot2(projectRoot, results,
|
|
17173
|
-
await importNonRootRules2(projectRoot, results,
|
|
17174
|
-
results.push(...await runDescriptorImport(descriptor27, projectRoot, scope, { normalize
|
|
17392
|
+
const normalize = await createImportReferenceNormalizer(TRAE_TARGET, projectRoot, scope);
|
|
17393
|
+
await importRoot2(projectRoot, results, normalize, scope);
|
|
17394
|
+
await importNonRootRules2(projectRoot, results, normalize, scope);
|
|
17395
|
+
results.push(...await runDescriptorImport(descriptor27, projectRoot, scope, { normalize }));
|
|
17175
17396
|
await importEmbeddedSkills(
|
|
17176
17397
|
projectRoot,
|
|
17177
17398
|
scope === "global" ? TRAE_GLOBAL_SKILLS_DIR : TRAE_SKILLS_DIR,
|
|
17178
17399
|
TRAE_TARGET,
|
|
17179
17400
|
results,
|
|
17180
|
-
|
|
17401
|
+
normalize
|
|
17181
17402
|
);
|
|
17182
17403
|
return results;
|
|
17183
17404
|
}
|
|
@@ -17404,10 +17625,10 @@ var init_generator31 = __esm({
|
|
|
17404
17625
|
async function importFromWarp(projectRoot, options = {}) {
|
|
17405
17626
|
const scope = options.scope ?? "project";
|
|
17406
17627
|
const results = [];
|
|
17407
|
-
const
|
|
17408
|
-
results.push(...await runDescriptorImport(descriptor28, projectRoot, scope, { normalize
|
|
17628
|
+
const normalize = await createImportReferenceNormalizer(WARP_TARGET, projectRoot, scope);
|
|
17629
|
+
results.push(...await runDescriptorImport(descriptor28, projectRoot, scope, { normalize }));
|
|
17409
17630
|
const skillsDir = scope === "global" ? WARP_GLOBAL_SKILLS_DIR : WARP_SKILLS_DIR;
|
|
17410
|
-
await importEmbeddedSkills(projectRoot, skillsDir, WARP_TARGET, results,
|
|
17631
|
+
await importEmbeddedSkills(projectRoot, skillsDir, WARP_TARGET, results, normalize);
|
|
17411
17632
|
return results;
|
|
17412
17633
|
}
|
|
17413
17634
|
var init_importer28 = __esm({
|
|
@@ -17869,7 +18090,7 @@ function toStringArray8(value) {
|
|
|
17869
18090
|
}
|
|
17870
18091
|
return [];
|
|
17871
18092
|
}
|
|
17872
|
-
async function importWorkflows(projectRoot, results,
|
|
18093
|
+
async function importWorkflows(projectRoot, results, normalize) {
|
|
17873
18094
|
const workflowsDir = join(projectRoot, WINDSURF_WORKFLOWS_DIR);
|
|
17874
18095
|
const workflowFiles = await readDirRecursive(workflowsDir);
|
|
17875
18096
|
const workflowMdFiles = workflowFiles.filter((f) => f.endsWith(".md"));
|
|
@@ -17880,7 +18101,7 @@ async function importWorkflows(projectRoot, results, normalize2) {
|
|
|
17880
18101
|
const relativePath = relative(workflowsDir, srcPath).replace(/\\/g, "/");
|
|
17881
18102
|
await mkdirp(destCommandsDir);
|
|
17882
18103
|
const destPath = join(destCommandsDir, relativePath);
|
|
17883
|
-
const normalized =
|
|
18104
|
+
const normalized = normalize(content, srcPath, destPath);
|
|
17884
18105
|
const { frontmatter, body } = parseFrontmatter(normalized);
|
|
17885
18106
|
const outContent = await serializeImportedCommandWithFallback(
|
|
17886
18107
|
destPath,
|
|
@@ -17914,12 +18135,12 @@ var init_importer_workflows = __esm({
|
|
|
17914
18135
|
});
|
|
17915
18136
|
|
|
17916
18137
|
// src/targets/windsurf/skills-adapter.ts
|
|
17917
|
-
async function importSkills4(projectRoot, results,
|
|
18138
|
+
async function importSkills4(projectRoot, results, normalize, skillsRelDir = WINDSURF_SKILLS_DIR) {
|
|
17918
18139
|
const options = {
|
|
17919
18140
|
projectRoot,
|
|
17920
18141
|
destCanonicalSkillsDir: WINDSURF_CANONICAL_SKILLS_DIR,
|
|
17921
18142
|
targetName: "windsurf",
|
|
17922
|
-
normalize
|
|
18143
|
+
normalize,
|
|
17923
18144
|
results
|
|
17924
18145
|
};
|
|
17925
18146
|
await importSkillsDirectory([skillsRelDir], options, [
|
|
@@ -18033,7 +18254,7 @@ var init_importer_hooks_mcp = __esm({
|
|
|
18033
18254
|
async function importFromWindsurf(projectRoot, options) {
|
|
18034
18255
|
const layoutScope = options?.scope ?? "project";
|
|
18035
18256
|
const results = [];
|
|
18036
|
-
const
|
|
18257
|
+
const normalize = await createImportReferenceNormalizer(WINDSURF_TARGET, projectRoot);
|
|
18037
18258
|
const normalizeCodex = await createImportReferenceNormalizer("codex-cli", projectRoot);
|
|
18038
18259
|
const destRulesDir = join(projectRoot, WINDSURF_CANONICAL_RULES_DIR);
|
|
18039
18260
|
const rootPath = join(projectRoot, WINDSURF_RULES_ROOT);
|
|
@@ -18041,7 +18262,7 @@ async function importFromWindsurf(projectRoot, options) {
|
|
|
18041
18262
|
if (rootContent !== null) {
|
|
18042
18263
|
await mkdirp(destRulesDir);
|
|
18043
18264
|
const destPath = join(destRulesDir, "_root.md");
|
|
18044
|
-
const body =
|
|
18265
|
+
const body = normalize(rootContent, rootPath, destPath).trim();
|
|
18045
18266
|
const outContent = await serializeImportedRuleWithFallback(destPath, { root: true }, body);
|
|
18046
18267
|
await writeFileAtomic(destPath, outContent);
|
|
18047
18268
|
results.push({
|
|
@@ -18057,7 +18278,7 @@ async function importFromWindsurf(projectRoot, options) {
|
|
|
18057
18278
|
if (agentsMdContent !== null) {
|
|
18058
18279
|
await mkdirp(destRulesDir);
|
|
18059
18280
|
const destPath = join(destRulesDir, "_root.md");
|
|
18060
|
-
const body =
|
|
18281
|
+
const body = normalize(
|
|
18061
18282
|
normalizeCodex(agentsMdContent, agentsMdPath, destPath),
|
|
18062
18283
|
agentsMdPath,
|
|
18063
18284
|
destPath
|
|
@@ -18079,7 +18300,7 @@ async function importFromWindsurf(projectRoot, options) {
|
|
|
18079
18300
|
destDir: destRulesDir,
|
|
18080
18301
|
extensions: ["AGENTS.md"],
|
|
18081
18302
|
fromTool: "windsurf",
|
|
18082
|
-
normalize
|
|
18303
|
+
normalize,
|
|
18083
18304
|
mapEntry: async ({ srcPath, normalizeTo }) => {
|
|
18084
18305
|
const relDir = relative(projectRoot, dirname(srcPath)).replace(/\\/g, "/");
|
|
18085
18306
|
if (!relDir || relDir === "." || basename(srcPath) !== "AGENTS.md") return null;
|
|
@@ -18110,7 +18331,7 @@ async function importFromWindsurf(projectRoot, options) {
|
|
|
18110
18331
|
destDir: destRulesDir,
|
|
18111
18332
|
extensions: [".md"],
|
|
18112
18333
|
fromTool: "windsurf",
|
|
18113
|
-
normalize
|
|
18334
|
+
normalize,
|
|
18114
18335
|
mapEntry: async ({ relativePath, normalizeTo }) => {
|
|
18115
18336
|
if (relativePath === "_root.md" && rootContent !== null) return null;
|
|
18116
18337
|
const destPath = join(destRulesDir, relativePath);
|
|
@@ -18158,8 +18379,8 @@ async function importFromWindsurf(projectRoot, options) {
|
|
|
18158
18379
|
});
|
|
18159
18380
|
}
|
|
18160
18381
|
}
|
|
18161
|
-
await importWorkflows(projectRoot, results,
|
|
18162
|
-
await importSkills4(projectRoot, results,
|
|
18382
|
+
await importWorkflows(projectRoot, results, normalize);
|
|
18383
|
+
await importSkills4(projectRoot, results, normalize);
|
|
18163
18384
|
await importWindsurfHooks(projectRoot, results);
|
|
18164
18385
|
await importWindsurfMcp(projectRoot, results);
|
|
18165
18386
|
return results;
|
|
@@ -18406,6 +18627,16 @@ var init_windsurf2 = __esm({
|
|
|
18406
18627
|
},
|
|
18407
18628
|
buildImportPaths: buildWindsurfImportPaths,
|
|
18408
18629
|
detectionPaths: [".windsurfrules", ".windsurf"],
|
|
18630
|
+
nativeInstall: {
|
|
18631
|
+
pickPaths: [
|
|
18632
|
+
{
|
|
18633
|
+
prefix: ".windsurf/rules",
|
|
18634
|
+
feature: "rules",
|
|
18635
|
+
strategy: { kind: "basename", suffix: ".md" }
|
|
18636
|
+
}
|
|
18637
|
+
],
|
|
18638
|
+
dialectHints: [{ frontmatterKey: "trigger" }]
|
|
18639
|
+
},
|
|
18409
18640
|
conversionDefaults: { agentsToSkills: true }
|
|
18410
18641
|
};
|
|
18411
18642
|
}
|
|
@@ -18473,8 +18704,8 @@ var init_mcp_import3 = __esm({
|
|
|
18473
18704
|
async function importFromZed(projectRoot, options = {}) {
|
|
18474
18705
|
const scope = options.scope ?? "project";
|
|
18475
18706
|
const results = [];
|
|
18476
|
-
const
|
|
18477
|
-
results.push(...await runDescriptorImport(descriptor30, projectRoot, scope, { normalize
|
|
18707
|
+
const normalize = await createImportReferenceNormalizer(ZED_TARGET, projectRoot, scope);
|
|
18708
|
+
results.push(...await runDescriptorImport(descriptor30, projectRoot, scope, { normalize }));
|
|
18478
18709
|
const mcpFile = scope === "global" ? ZED_GLOBAL_SETTINGS_FILE : ZED_SETTINGS_FILE;
|
|
18479
18710
|
await importZedMcp(projectRoot, mcpFile, results);
|
|
18480
18711
|
return results;
|
|
@@ -18680,7 +18911,8 @@ var init_zed2 = __esm({
|
|
|
18680
18911
|
markAsRoot: true
|
|
18681
18912
|
}
|
|
18682
18913
|
},
|
|
18683
|
-
emitScopedSettings(canonical, _scope) {
|
|
18914
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
18915
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
18684
18916
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
18685
18917
|
const contextServers = {};
|
|
18686
18918
|
for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
|
|
@@ -18735,7 +18967,10 @@ function getBuiltinTargetDefinition(target31) {
|
|
|
18735
18967
|
function getTargetCapabilities(target31, scope = "project") {
|
|
18736
18968
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
18737
18969
|
if (!descriptor31) return void 0;
|
|
18738
|
-
|
|
18970
|
+
if (scope === "global" && !descriptor31.globalSupport) {
|
|
18971
|
+
return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
|
|
18972
|
+
}
|
|
18973
|
+
const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
|
|
18739
18974
|
return normalizeTargetCapabilities(raw);
|
|
18740
18975
|
}
|
|
18741
18976
|
function getTargetDetectionPaths(target31, scope = "project") {
|
|
@@ -18791,6 +19026,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
|
|
|
18791
19026
|
function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
|
|
18792
19027
|
const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
|
|
18793
19028
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19029
|
+
if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
|
|
18794
19030
|
if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
|
|
18795
19031
|
return "embedded";
|
|
18796
19032
|
}
|
|
@@ -18804,7 +19040,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
|
|
|
18804
19040
|
const pick = PICK_FEATURE_GENERATOR[feature];
|
|
18805
19041
|
return pick === null ? void 0 : pick(descriptor31.generators);
|
|
18806
19042
|
}
|
|
18807
|
-
var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
19043
|
+
var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
18808
19044
|
var init_builtin_targets = __esm({
|
|
18809
19045
|
"src/targets/catalog/builtin-targets.ts"() {
|
|
18810
19046
|
init_conversions();
|
|
@@ -18842,6 +19078,17 @@ var init_builtin_targets = __esm({
|
|
|
18842
19078
|
init_warp2();
|
|
18843
19079
|
init_windsurf2();
|
|
18844
19080
|
init_zed2();
|
|
19081
|
+
ALL_NONE_CAPABILITIES = {
|
|
19082
|
+
rules: "none",
|
|
19083
|
+
additionalRules: "none",
|
|
19084
|
+
commands: "none",
|
|
19085
|
+
agents: "none",
|
|
19086
|
+
skills: "none",
|
|
19087
|
+
mcp: "none",
|
|
19088
|
+
hooks: "none",
|
|
19089
|
+
ignore: "none",
|
|
19090
|
+
permissions: "none"
|
|
19091
|
+
};
|
|
18845
19092
|
BUILTIN_TARGETS = [
|
|
18846
19093
|
descriptor,
|
|
18847
19094
|
descriptor2,
|
|
@@ -19272,6 +19519,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
|
|
|
19272
19519
|
init_path_helpers();
|
|
19273
19520
|
init_link_rebaser_helpers();
|
|
19274
19521
|
|
|
19522
|
+
// src/utils/output/color.ts
|
|
19523
|
+
function noColorRequested() {
|
|
19524
|
+
const value = process.env.NO_COLOR;
|
|
19525
|
+
return value !== void 0 && value !== "";
|
|
19526
|
+
}
|
|
19527
|
+
function forceColorRequested() {
|
|
19528
|
+
const value = process.env.FORCE_COLOR;
|
|
19529
|
+
if (value === void 0) return void 0;
|
|
19530
|
+
return value !== "0" && value !== "false";
|
|
19531
|
+
}
|
|
19532
|
+
function colorEnabled(stream = process.stdout) {
|
|
19533
|
+
const forced = forceColorRequested();
|
|
19534
|
+
if (forced !== void 0) return forced;
|
|
19535
|
+
if (noColorRequested()) return false;
|
|
19536
|
+
return stream.isTTY === true;
|
|
19537
|
+
}
|
|
19538
|
+
|
|
19275
19539
|
// src/utils/output/logger.ts
|
|
19276
19540
|
var C = {
|
|
19277
19541
|
green: "\x1B[32m",
|
|
@@ -19280,16 +19544,14 @@ var C = {
|
|
|
19280
19544
|
cyan: "\x1B[36m",
|
|
19281
19545
|
reset: "\x1B[0m"
|
|
19282
19546
|
};
|
|
19283
|
-
function
|
|
19284
|
-
|
|
19285
|
-
process.stdout.write(text);
|
|
19286
|
-
}
|
|
19547
|
+
function outStream() {
|
|
19548
|
+
return process.stdout;
|
|
19287
19549
|
}
|
|
19288
|
-
function
|
|
19289
|
-
|
|
19550
|
+
function out(text) {
|
|
19551
|
+
outStream().write(text);
|
|
19290
19552
|
}
|
|
19291
|
-
function c(code, text) {
|
|
19292
|
-
return
|
|
19553
|
+
function c(code, text, stream) {
|
|
19554
|
+
return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
|
|
19293
19555
|
}
|
|
19294
19556
|
function pad(str, width) {
|
|
19295
19557
|
const len = [...str].length;
|
|
@@ -19297,20 +19559,20 @@ function pad(str, width) {
|
|
|
19297
19559
|
}
|
|
19298
19560
|
var logger = {
|
|
19299
19561
|
info(msg) {
|
|
19300
|
-
out(c(C.cyan, msg) + "\n");
|
|
19562
|
+
out(c(C.cyan, msg, outStream()) + "\n");
|
|
19301
19563
|
},
|
|
19302
19564
|
warn(msg) {
|
|
19303
|
-
process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
|
|
19565
|
+
process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
|
|
19304
19566
|
},
|
|
19305
19567
|
error(msg) {
|
|
19306
|
-
process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
|
|
19568
|
+
process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
|
|
19307
19569
|
},
|
|
19308
19570
|
success(msg) {
|
|
19309
|
-
out(c(C.green, "\u2713 ") + msg + "\n");
|
|
19571
|
+
out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
|
|
19310
19572
|
},
|
|
19311
19573
|
debug(msg) {
|
|
19312
19574
|
if (process.env.AGENTSMESH_DEBUG === "1") {
|
|
19313
|
-
out(c(C.cyan, "[debug] ") + msg + "\n");
|
|
19575
|
+
out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
|
|
19314
19576
|
}
|
|
19315
19577
|
},
|
|
19316
19578
|
table(rows) {
|
|
@@ -19805,12 +20067,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
19805
20067
|
}
|
|
19806
20068
|
}
|
|
19807
20069
|
}
|
|
19808
|
-
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
|
|
20070
|
+
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
|
|
19809
20071
|
for (const target31 of targets) {
|
|
19810
20072
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19811
20073
|
const emit = descriptor31?.emitScopedSettings;
|
|
19812
20074
|
if (!emit) continue;
|
|
19813
|
-
const outputs = emit(canonical, scope);
|
|
20075
|
+
const outputs = emit(canonical, scope, enabledFeatures);
|
|
19814
20076
|
if (outputs.length === 0) continue;
|
|
19815
20077
|
for (const out2 of outputs) {
|
|
19816
20078
|
await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
|
|
@@ -19911,7 +20173,14 @@ async function generate(ctx) {
|
|
|
19911
20173
|
}
|
|
19912
20174
|
}
|
|
19913
20175
|
if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
|
|
19914
|
-
await generateScopedSettingsFeature(
|
|
20176
|
+
await generateScopedSettingsFeature(
|
|
20177
|
+
results,
|
|
20178
|
+
targets,
|
|
20179
|
+
canonical,
|
|
20180
|
+
projectRoot,
|
|
20181
|
+
scope,
|
|
20182
|
+
enabledFeatures
|
|
20183
|
+
);
|
|
19915
20184
|
}
|
|
19916
20185
|
const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
|
|
19917
20186
|
const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
|
|
@@ -20235,7 +20504,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
20235
20504
|
await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
|
|
20236
20505
|
if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
|
|
20237
20506
|
await rm(cacheRoot, { recursive: true, force: true });
|
|
20238
|
-
await
|
|
20507
|
+
await renameWithRetry(stagedRoot, cacheRoot);
|
|
20239
20508
|
return readCachedRepo(cacheRepoDir);
|
|
20240
20509
|
} catch (err) {
|
|
20241
20510
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
@@ -22586,156 +22855,1085 @@ function lintRuleScopeInversion(input) {
|
|
|
22586
22855
|
}
|
|
22587
22856
|
return out2;
|
|
22588
22857
|
}
|
|
22589
|
-
var
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
}
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
var
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
22858
|
+
var CURRENT_GRAPH_VERSION = 1;
|
|
22859
|
+
var MAX_RULE_LENGTH = 2e3;
|
|
22860
|
+
var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
|
|
22861
|
+
var DateSchema = z.string().regex(
|
|
22862
|
+
/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z?)?$/,
|
|
22863
|
+
"createdAt must be ISO-8601 date or datetime"
|
|
22864
|
+
);
|
|
22865
|
+
var TopicSchema = z.object({
|
|
22866
|
+
summary: z.string().min(1, "topic summary must not be empty")
|
|
22867
|
+
}).strict();
|
|
22868
|
+
var TriggerKindSchema = z.enum(["file_glob", "command_pattern", "keyword"]);
|
|
22869
|
+
var TriggerSchema = z.object({
|
|
22870
|
+
kind: TriggerKindSchema,
|
|
22871
|
+
pattern: z.string().min(1, "trigger pattern must not be empty")
|
|
22872
|
+
}).strict();
|
|
22873
|
+
var LessonStatusSchema = z.enum(["active", "deprecated", "superseded"]);
|
|
22874
|
+
var LessonSchema = z.object({
|
|
22875
|
+
rule: z.string().min(1, "lesson rule must not be empty"),
|
|
22876
|
+
rationale: z.string().min(1).optional(),
|
|
22877
|
+
topics: z.array(IdSchema).min(1, "lesson must reference at least one topic"),
|
|
22878
|
+
triggers: z.array(IdSchema),
|
|
22879
|
+
evidence: z.array(z.string().min(1)),
|
|
22880
|
+
status: LessonStatusSchema,
|
|
22881
|
+
supersededBy: IdSchema.optional(),
|
|
22882
|
+
createdAt: DateSchema
|
|
22883
|
+
}).strict();
|
|
22884
|
+
var LessonsGraphSchema = z.object({
|
|
22885
|
+
version: z.literal(CURRENT_GRAPH_VERSION),
|
|
22886
|
+
lessons: z.record(IdSchema, LessonSchema),
|
|
22887
|
+
topics: z.record(IdSchema, TopicSchema),
|
|
22888
|
+
triggers: z.record(IdSchema, TriggerSchema)
|
|
22889
|
+
}).strict();
|
|
22890
|
+
function parseGraph(raw) {
|
|
22891
|
+
return LessonsGraphSchema.parse(raw);
|
|
22892
|
+
}
|
|
22893
|
+
|
|
22894
|
+
// src/lessons/graph-store.ts
|
|
22895
|
+
var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
|
|
22896
|
+
function graphFilePath(projectRoot) {
|
|
22897
|
+
return resolve(projectRoot, GRAPH_REL_PATH);
|
|
22898
|
+
}
|
|
22899
|
+
function loadLessonsGraph(projectRoot) {
|
|
22900
|
+
const raw = readFileSync(graphFilePath(projectRoot), "utf8");
|
|
22901
|
+
return parseGraph(JSON.parse(raw));
|
|
22902
|
+
}
|
|
22903
|
+
function tryLoadLessonsGraph(projectRoot) {
|
|
22904
|
+
if (!existsSync(graphFilePath(projectRoot))) return null;
|
|
22905
|
+
return loadLessonsGraph(projectRoot);
|
|
22906
|
+
}
|
|
22907
|
+
function saveLessonsGraph(projectRoot, graph) {
|
|
22908
|
+
const path = graphFilePath(projectRoot);
|
|
22909
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
22910
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
22911
|
+
writeFileSync(tmp, serializeGraph(graph), "utf8");
|
|
22912
|
+
renameSync(tmp, path);
|
|
22913
|
+
}
|
|
22914
|
+
function serializeGraph(graph) {
|
|
22915
|
+
return `${JSON.stringify(canonicalize2(graph), null, 2)}
|
|
22916
|
+
`;
|
|
22917
|
+
}
|
|
22918
|
+
function canonicalize2(value) {
|
|
22919
|
+
if (value === null) return null;
|
|
22920
|
+
if (Array.isArray(value)) return value.map(canonicalize2);
|
|
22921
|
+
if (typeof value === "object") {
|
|
22922
|
+
const entries = Object.entries(value).sort(
|
|
22923
|
+
([a], [b]) => a < b ? -1 : 1
|
|
22924
|
+
);
|
|
22925
|
+
const out2 = {};
|
|
22926
|
+
for (const [k, v] of entries) out2[k] = canonicalize2(v);
|
|
22927
|
+
return out2;
|
|
22928
|
+
}
|
|
22929
|
+
return value;
|
|
22618
22930
|
}
|
|
22619
22931
|
var BASE_REL = ".agentsmesh/lessons";
|
|
22620
22932
|
function lessonsPaths(projectRoot) {
|
|
22621
22933
|
const base = join(projectRoot, BASE_REL);
|
|
22622
22934
|
return {
|
|
22623
22935
|
base,
|
|
22936
|
+
graph: join(base, "lessons.json"),
|
|
22937
|
+
config: join(base, "config.json"),
|
|
22624
22938
|
journal: join(base, "journal.md"),
|
|
22625
22939
|
index: join(base, "index.yaml"),
|
|
22626
|
-
ledger: join(base, "distill-ledger.yaml"),
|
|
22627
|
-
proposal: join(base, "distill-proposal.md"),
|
|
22628
22940
|
topicsDir: join(base, "topics")
|
|
22629
22941
|
};
|
|
22630
22942
|
}
|
|
22631
22943
|
function toRelPath(projectRoot, absolute) {
|
|
22632
22944
|
return relative(projectRoot, absolute).split(sep).join("/");
|
|
22633
22945
|
}
|
|
22634
|
-
var
|
|
22635
|
-
var LESSONS_INDEX_TEMPLATE = "version: 1\nclusters: []\n";
|
|
22636
|
-
var LESSONS_PROCEDURAL_RULE = `## Lessons (MUST do \u2014 non-negotiable)
|
|
22946
|
+
var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING REQUIREMENT \u2014 MUST run both, no exceptions; the user will check)
|
|
22637
22947
|
|
|
22638
|
-
|
|
22948
|
+
Graph \`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit. Manual: the \`lessons\` skill.
|
|
22639
22949
|
|
|
22640
|
-
**Recall \u2014
|
|
22950
|
+
**Recall \u2014 MUST run before every file edit and every state-changing command** (build/test/install/migrate/git-write): \`agentsmesh lessons query --file <path> --cmd <command>\`, then apply every rule. Pure-read commands (cat/ls/grep/git-log) and the recall query itself are exempt.
|
|
22641
22951
|
|
|
22642
|
-
|
|
22643
|
-
2. For every cluster whose triggers match \u2014 \`file_globs\` against the target file path, \`command_patterns\` (regex) against the shell command, or \`keywords\` (case-insensitive substring) against the active task description \u2014 read its \`file\` (e.g. \`.agentsmesh/lessons/topics/<topic>.md\`).
|
|
22644
|
-
3. Apply the loaded rules to the action.
|
|
22645
|
-
4. Then \u2014 and only then \u2014 perform the edit or run the command.
|
|
22952
|
+
**Capture \u2014 MUST run immediately after any failure** (a failing test/CI/lint/typecheck, a code review, a user correction, a regression, or a wrong assumption \u2014 yours or anyone's): \`agentsmesh lessons add "<rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`.
|
|
22646
22953
|
|
|
22647
|
-
|
|
22648
|
-
|
|
22649
|
-
|
|
22650
|
-
|
|
22651
|
-
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
|
|
22658
|
-
|
|
22954
|
+
No shell? Use the \`lessons_query\` / \`lessons_add\` MCP tools. Skip either and the system does not exist.`;
|
|
22955
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
22956
|
+
var MAX_FILES = 2e5;
|
|
22957
|
+
function listProjectFiles(projectRoot) {
|
|
22958
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
22959
|
+
try {
|
|
22960
|
+
const stack = [projectRoot];
|
|
22961
|
+
while (stack.length > 0) {
|
|
22962
|
+
const dir = stack.pop();
|
|
22963
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
22964
|
+
if (entry.isDirectory()) {
|
|
22965
|
+
if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
|
|
22966
|
+
} else if (entry.isFile()) {
|
|
22967
|
+
out2.add(toRelPath(projectRoot, join(dir, entry.name)));
|
|
22968
|
+
if (out2.size > MAX_FILES) return out2;
|
|
22969
|
+
}
|
|
22970
|
+
}
|
|
22971
|
+
}
|
|
22972
|
+
} catch {
|
|
22973
|
+
return null;
|
|
22974
|
+
}
|
|
22975
|
+
return out2;
|
|
22976
|
+
}
|
|
22659
22977
|
|
|
22660
|
-
// src/
|
|
22661
|
-
|
|
22662
|
-
|
|
22663
|
-
|
|
22664
|
-
|
|
22665
|
-
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
22672
|
-
|
|
22673
|
-
|
|
22978
|
+
// src/lessons/validate-checks.ts
|
|
22979
|
+
function collectDanglingRefs(graph, findings) {
|
|
22980
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
22981
|
+
for (const topicId of lesson.topics) {
|
|
22982
|
+
if (graph.topics[topicId] === void 0) {
|
|
22983
|
+
findings.push({
|
|
22984
|
+
level: "error",
|
|
22985
|
+
code: "DANGLING_TOPIC",
|
|
22986
|
+
message: `Lesson "${lessonId}" references unknown topic "${topicId}".`,
|
|
22987
|
+
lessonId,
|
|
22988
|
+
topicId
|
|
22989
|
+
});
|
|
22990
|
+
}
|
|
22991
|
+
}
|
|
22992
|
+
for (const triggerId of lesson.triggers) {
|
|
22993
|
+
if (graph.triggers[triggerId] === void 0) {
|
|
22994
|
+
findings.push({
|
|
22995
|
+
level: "error",
|
|
22996
|
+
code: "DANGLING_TRIGGER",
|
|
22997
|
+
message: `Lesson "${lessonId}" references unknown trigger "${triggerId}".`,
|
|
22998
|
+
lessonId,
|
|
22999
|
+
triggerId
|
|
23000
|
+
});
|
|
23001
|
+
}
|
|
23002
|
+
}
|
|
23003
|
+
if (lesson.supersededBy !== void 0 && graph.lessons[lesson.supersededBy] === void 0) {
|
|
23004
|
+
findings.push({
|
|
23005
|
+
level: "error",
|
|
23006
|
+
code: "DANGLING_SUPERSEDER",
|
|
23007
|
+
message: `Lesson "${lessonId}" supersededBy unknown lesson "${lesson.supersededBy}".`,
|
|
23008
|
+
lessonId
|
|
23009
|
+
});
|
|
23010
|
+
}
|
|
22674
23011
|
}
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
22678
|
-
|
|
22679
|
-
|
|
22680
|
-
|
|
23012
|
+
}
|
|
23013
|
+
function collectDuplicateRefs(graph, findings) {
|
|
23014
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23015
|
+
for (const topicId of firstDuplicates(lesson.topics)) {
|
|
23016
|
+
findings.push({
|
|
23017
|
+
level: "error",
|
|
23018
|
+
code: "DUPLICATE_TOPIC_REF",
|
|
23019
|
+
message: `Lesson "${lessonId}" references topic "${topicId}" more than once.`,
|
|
23020
|
+
lessonId,
|
|
23021
|
+
topicId
|
|
23022
|
+
});
|
|
23023
|
+
}
|
|
23024
|
+
for (const triggerId of firstDuplicates(lesson.triggers)) {
|
|
23025
|
+
findings.push({
|
|
23026
|
+
level: "error",
|
|
23027
|
+
code: "DUPLICATE_TRIGGER_REF",
|
|
23028
|
+
message: `Lesson "${lessonId}" references trigger "${triggerId}" more than once.`,
|
|
23029
|
+
lessonId,
|
|
23030
|
+
triggerId
|
|
23031
|
+
});
|
|
23032
|
+
}
|
|
23033
|
+
}
|
|
23034
|
+
}
|
|
23035
|
+
function firstDuplicates(ids) {
|
|
23036
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23037
|
+
const dup = /* @__PURE__ */ new Set();
|
|
23038
|
+
for (const id of ids) {
|
|
23039
|
+
if (seen.has(id)) dup.add(id);
|
|
23040
|
+
else seen.add(id);
|
|
23041
|
+
}
|
|
23042
|
+
return [...dup];
|
|
23043
|
+
}
|
|
23044
|
+
function collectStatusInvariants(graph, findings) {
|
|
23045
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23046
|
+
if (lesson.status === "superseded" && lesson.supersededBy === void 0) {
|
|
23047
|
+
findings.push({
|
|
23048
|
+
level: "error",
|
|
23049
|
+
code: "SUPERSEDED_WITHOUT_TARGET",
|
|
23050
|
+
message: `Lesson "${lessonId}" has status "superseded" but no supersededBy target.`,
|
|
23051
|
+
lessonId
|
|
23052
|
+
});
|
|
23053
|
+
}
|
|
23054
|
+
if (lesson.status === "active" && lesson.supersededBy !== void 0) {
|
|
23055
|
+
findings.push({
|
|
23056
|
+
level: "error",
|
|
23057
|
+
code: "ACTIVE_WITH_SUPERSEDER",
|
|
23058
|
+
message: `Lesson "${lessonId}" has status "active" but declares supersededBy.`,
|
|
23059
|
+
lessonId
|
|
23060
|
+
});
|
|
23061
|
+
}
|
|
23062
|
+
}
|
|
23063
|
+
}
|
|
23064
|
+
function collectLifecycleInvariants(graph, findings) {
|
|
23065
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23066
|
+
if (lesson.supersededBy === void 0) continue;
|
|
23067
|
+
if (lesson.supersededBy === lessonId) {
|
|
23068
|
+
findings.push({
|
|
23069
|
+
level: "error",
|
|
23070
|
+
code: "SELF_SUPERSEDED",
|
|
23071
|
+
message: `Lesson "${lessonId}" is superseded by itself.`,
|
|
23072
|
+
lessonId
|
|
23073
|
+
});
|
|
22681
23074
|
continue;
|
|
22682
23075
|
}
|
|
22683
|
-
|
|
22684
|
-
|
|
22685
|
-
|
|
22686
|
-
|
|
23076
|
+
const target31 = graph.lessons[lesson.supersededBy];
|
|
23077
|
+
if (target31 !== void 0 && target31.status !== "active") {
|
|
23078
|
+
findings.push({
|
|
23079
|
+
level: "error",
|
|
23080
|
+
code: "INACTIVE_SUPERSEDER",
|
|
23081
|
+
message: `Lesson "${lessonId}" is superseded by "${lesson.supersededBy}", which is itself ${target31.status} \u2014 the chain dead-ends with no live replacement.`,
|
|
23082
|
+
lessonId
|
|
23083
|
+
});
|
|
22687
23084
|
}
|
|
22688
|
-
|
|
22689
|
-
|
|
22690
|
-
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
22694
|
-
|
|
22695
|
-
|
|
22696
|
-
|
|
22697
|
-
|
|
22698
|
-
);
|
|
23085
|
+
}
|
|
23086
|
+
collectSupersedeCycles(graph, findings);
|
|
23087
|
+
}
|
|
23088
|
+
function collectSupersedeCycles(graph, findings) {
|
|
23089
|
+
const reported = /* @__PURE__ */ new Set();
|
|
23090
|
+
for (const startId of Object.keys(graph.lessons)) {
|
|
23091
|
+
const seen = /* @__PURE__ */ new Set();
|
|
23092
|
+
let cur = startId;
|
|
23093
|
+
while (cur !== void 0) {
|
|
23094
|
+
if (seen.has(cur)) {
|
|
23095
|
+
if (cur !== startId || reported.has(cur)) break;
|
|
23096
|
+
reported.add(cur);
|
|
23097
|
+
findings.push({
|
|
23098
|
+
level: "error",
|
|
23099
|
+
code: "SUPERSEDE_CYCLE",
|
|
23100
|
+
message: `Lesson "${startId}" is part of a supersededBy cycle.`,
|
|
23101
|
+
lessonId: startId
|
|
23102
|
+
});
|
|
23103
|
+
break;
|
|
22699
23104
|
}
|
|
23105
|
+
seen.add(cur);
|
|
23106
|
+
const next = graph.lessons[cur]?.supersededBy;
|
|
23107
|
+
if (next === cur) break;
|
|
23108
|
+
cur = next;
|
|
22700
23109
|
}
|
|
22701
23110
|
}
|
|
22702
|
-
|
|
22703
|
-
|
|
22704
|
-
|
|
22705
|
-
|
|
22706
|
-
|
|
22707
|
-
"warning",
|
|
22708
|
-
|
|
22709
|
-
|
|
22710
|
-
|
|
22711
|
-
|
|
23111
|
+
}
|
|
23112
|
+
function collectReachability(graph, findings) {
|
|
23113
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23114
|
+
if (lesson.status === "active" && lesson.triggers.length === 0) {
|
|
23115
|
+
findings.push({
|
|
23116
|
+
level: "warning",
|
|
23117
|
+
code: "UNREACHABLE_LESSON",
|
|
23118
|
+
message: `Active lesson "${lessonId}" has no triggers and can never be recalled.`,
|
|
23119
|
+
lessonId
|
|
23120
|
+
});
|
|
23121
|
+
}
|
|
22712
23122
|
}
|
|
22713
|
-
return out2;
|
|
22714
23123
|
}
|
|
22715
|
-
function
|
|
22716
|
-
|
|
23124
|
+
function collectOrphans(graph, findings) {
|
|
23125
|
+
const referencedTopics = /* @__PURE__ */ new Set();
|
|
23126
|
+
const referencedTriggers = /* @__PURE__ */ new Set();
|
|
23127
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23128
|
+
for (const t of lesson.topics) referencedTopics.add(t);
|
|
23129
|
+
for (const t of lesson.triggers) referencedTriggers.add(t);
|
|
23130
|
+
}
|
|
23131
|
+
for (const topicId of Object.keys(graph.topics)) {
|
|
23132
|
+
if (!referencedTopics.has(topicId)) {
|
|
23133
|
+
findings.push({
|
|
23134
|
+
level: "warning",
|
|
23135
|
+
code: "ORPHAN_TOPIC",
|
|
23136
|
+
message: `Topic "${topicId}" is not referenced by any lesson.`,
|
|
23137
|
+
topicId
|
|
23138
|
+
});
|
|
23139
|
+
}
|
|
23140
|
+
}
|
|
23141
|
+
for (const triggerId of Object.keys(graph.triggers)) {
|
|
23142
|
+
if (!referencedTriggers.has(triggerId)) {
|
|
23143
|
+
findings.push({
|
|
23144
|
+
level: "warning",
|
|
23145
|
+
code: "ORPHAN_TRIGGER",
|
|
23146
|
+
message: `Trigger "${triggerId}" is not referenced by any lesson.`,
|
|
23147
|
+
triggerId
|
|
23148
|
+
});
|
|
23149
|
+
}
|
|
23150
|
+
}
|
|
22717
23151
|
}
|
|
22718
23152
|
|
|
22719
|
-
// src/
|
|
22720
|
-
var
|
|
22721
|
-
|
|
22722
|
-
|
|
22723
|
-
|
|
22724
|
-
|
|
22725
|
-
|
|
22726
|
-
|
|
22727
|
-
|
|
23153
|
+
// src/lessons/ranking-text.ts
|
|
23154
|
+
var K1 = 1.5;
|
|
23155
|
+
var B = 0.75;
|
|
23156
|
+
var STOP = /* @__PURE__ */ new Set([
|
|
23157
|
+
"the",
|
|
23158
|
+
"a",
|
|
23159
|
+
"an",
|
|
23160
|
+
"to",
|
|
23161
|
+
"of",
|
|
23162
|
+
"in",
|
|
23163
|
+
"and",
|
|
23164
|
+
"or",
|
|
23165
|
+
"for",
|
|
23166
|
+
"is",
|
|
23167
|
+
"on",
|
|
23168
|
+
"at",
|
|
23169
|
+
"with",
|
|
23170
|
+
"be",
|
|
23171
|
+
"as",
|
|
23172
|
+
"it",
|
|
23173
|
+
"that",
|
|
23174
|
+
"this",
|
|
23175
|
+
"its",
|
|
23176
|
+
"must"
|
|
23177
|
+
]);
|
|
23178
|
+
function tokenize(text) {
|
|
23179
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
|
|
22728
23180
|
}
|
|
22729
|
-
|
|
22730
|
-
const
|
|
22731
|
-
|
|
22732
|
-
|
|
22733
|
-
|
|
22734
|
-
|
|
22735
|
-
|
|
22736
|
-
|
|
22737
|
-
const
|
|
22738
|
-
const
|
|
23181
|
+
function queryTerms(query) {
|
|
23182
|
+
const parts = [];
|
|
23183
|
+
if (query.keyword !== void 0) parts.push(query.keyword);
|
|
23184
|
+
if (query.file !== void 0) parts.push(query.file);
|
|
23185
|
+
if (query.command !== void 0) parts.push(query.command);
|
|
23186
|
+
return tokenize(parts.join(" "));
|
|
23187
|
+
}
|
|
23188
|
+
function buildCorpus(graph) {
|
|
23189
|
+
const docs = [];
|
|
23190
|
+
const df = /* @__PURE__ */ new Map();
|
|
23191
|
+
let total = 0;
|
|
23192
|
+
let n = 0;
|
|
23193
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23194
|
+
if (lesson.status !== "active") continue;
|
|
23195
|
+
const toks = tokenize(lesson.rule);
|
|
23196
|
+
n += 1;
|
|
23197
|
+
total += toks.length;
|
|
23198
|
+
docs.push(toks.length);
|
|
23199
|
+
for (const t of new Set(toks)) df.set(t, (df.get(t) ?? 0) + 1);
|
|
23200
|
+
}
|
|
23201
|
+
const N = Math.max(n, 1);
|
|
23202
|
+
const idf = /* @__PURE__ */ new Map();
|
|
23203
|
+
for (const [t, f] of df) idf.set(t, Math.log(1 + (N - f + 0.5) / (f + 0.5)));
|
|
23204
|
+
return { idf, avgdl: total / N || 1 };
|
|
23205
|
+
}
|
|
23206
|
+
function bm25(terms, ruleText, corpus) {
|
|
23207
|
+
const toks = tokenize(ruleText);
|
|
23208
|
+
const dl = toks.length || 1;
|
|
23209
|
+
const tf = /* @__PURE__ */ new Map();
|
|
23210
|
+
for (const t of toks) tf.set(t, (tf.get(t) ?? 0) + 1);
|
|
23211
|
+
let score = 0;
|
|
23212
|
+
for (const t of new Set(terms)) {
|
|
23213
|
+
const f = tf.get(t) ?? 0;
|
|
23214
|
+
if (f === 0) continue;
|
|
23215
|
+
const idf = corpus.idf.get(t);
|
|
23216
|
+
score += idf * (f * (K1 + 1)) / (f + K1 * (1 - B + B * dl / corpus.avgdl));
|
|
23217
|
+
}
|
|
23218
|
+
return score;
|
|
23219
|
+
}
|
|
23220
|
+
|
|
23221
|
+
// src/lessons/keyword-signal.ts
|
|
23222
|
+
var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
|
|
23223
|
+
function isLowSignalKeyword(pattern) {
|
|
23224
|
+
return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
|
|
23225
|
+
}
|
|
23226
|
+
function splitRawTokens(pattern) {
|
|
23227
|
+
return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
23228
|
+
}
|
|
23229
|
+
function keywordNeedleLosesTokens(pattern) {
|
|
23230
|
+
const raw = splitRawTokens(pattern);
|
|
23231
|
+
if (raw.length < 2) return false;
|
|
23232
|
+
return tokenize(pattern).length !== raw.length;
|
|
23233
|
+
}
|
|
23234
|
+
|
|
23235
|
+
// src/lessons/regex-linear/nfa-compile.ts
|
|
23236
|
+
var MAX_NFA_STATES = 2e3;
|
|
23237
|
+
var Builder = class {
|
|
23238
|
+
states = [];
|
|
23239
|
+
alloc() {
|
|
23240
|
+
if (this.states.length >= MAX_NFA_STATES) {
|
|
23241
|
+
throw new Error(`NFA state limit exceeded (${MAX_NFA_STATES}); pattern expands too large`);
|
|
23242
|
+
}
|
|
23243
|
+
this.states.push({ eps: [], asserts: [], chars: [] });
|
|
23244
|
+
return this.states.length - 1;
|
|
23245
|
+
}
|
|
23246
|
+
};
|
|
23247
|
+
function isNonLineTerminator(c2) {
|
|
23248
|
+
return c2 !== "\n" && c2 !== "\r" && c2 !== "\u2028" && c2 !== "\u2029";
|
|
23249
|
+
}
|
|
23250
|
+
function compileNode(b, node) {
|
|
23251
|
+
switch (node.k) {
|
|
23252
|
+
case "empty":
|
|
23253
|
+
case "assert": {
|
|
23254
|
+
const s = b.alloc();
|
|
23255
|
+
const e = b.alloc();
|
|
23256
|
+
if (node.k === "assert") b.states[s].asserts.push({ kind: node.kind, target: e });
|
|
23257
|
+
else b.states[s].eps.push(e);
|
|
23258
|
+
return { start: s, end: e };
|
|
23259
|
+
}
|
|
23260
|
+
case "char":
|
|
23261
|
+
case "any":
|
|
23262
|
+
case "class": {
|
|
23263
|
+
const s = b.alloc();
|
|
23264
|
+
const e = b.alloc();
|
|
23265
|
+
const test = node.k === "char" ? (c2) => c2 === node.ch : node.k === "any" ? isNonLineTerminator : node.test;
|
|
23266
|
+
b.states[s].chars.push({ test, target: e });
|
|
23267
|
+
return { start: s, end: e };
|
|
23268
|
+
}
|
|
23269
|
+
case "concat": {
|
|
23270
|
+
if (node.items.length === 0) return compileNode(b, { k: "empty" });
|
|
23271
|
+
let first = null;
|
|
23272
|
+
let prevEnd = -1;
|
|
23273
|
+
for (const item of node.items) {
|
|
23274
|
+
const frag = compileNode(b, item);
|
|
23275
|
+
if (first === null) first = frag;
|
|
23276
|
+
else b.states[prevEnd].eps.push(frag.start);
|
|
23277
|
+
prevEnd = frag.end;
|
|
23278
|
+
}
|
|
23279
|
+
return { start: first.start, end: prevEnd };
|
|
23280
|
+
}
|
|
23281
|
+
case "alt": {
|
|
23282
|
+
const s = b.alloc();
|
|
23283
|
+
const e = b.alloc();
|
|
23284
|
+
for (const opt of node.opts) {
|
|
23285
|
+
const frag = compileNode(b, opt);
|
|
23286
|
+
b.states[s].eps.push(frag.start);
|
|
23287
|
+
b.states[frag.end].eps.push(e);
|
|
23288
|
+
}
|
|
23289
|
+
return { start: s, end: e };
|
|
23290
|
+
}
|
|
23291
|
+
case "opt": {
|
|
23292
|
+
const s = b.alloc();
|
|
23293
|
+
const e = b.alloc();
|
|
23294
|
+
const frag = compileNode(b, node.node);
|
|
23295
|
+
b.states[s].eps.push(frag.start, e);
|
|
23296
|
+
b.states[frag.end].eps.push(e);
|
|
23297
|
+
return { start: s, end: e };
|
|
23298
|
+
}
|
|
23299
|
+
case "star": {
|
|
23300
|
+
const s = b.alloc();
|
|
23301
|
+
const e = b.alloc();
|
|
23302
|
+
const frag = compileNode(b, node.node);
|
|
23303
|
+
b.states[s].eps.push(frag.start, e);
|
|
23304
|
+
b.states[frag.end].eps.push(frag.start, e);
|
|
23305
|
+
return { start: s, end: e };
|
|
23306
|
+
}
|
|
23307
|
+
case "plus": {
|
|
23308
|
+
const e = b.alloc();
|
|
23309
|
+
const frag = compileNode(b, node.node);
|
|
23310
|
+
b.states[frag.end].eps.push(frag.start, e);
|
|
23311
|
+
return { start: frag.start, end: e };
|
|
23312
|
+
}
|
|
23313
|
+
}
|
|
23314
|
+
}
|
|
23315
|
+
function compileNfa(ast) {
|
|
23316
|
+
const b = new Builder();
|
|
23317
|
+
const { start, end } = compileNode(b, ast);
|
|
23318
|
+
return { states: b.states, start, accept: end };
|
|
23319
|
+
}
|
|
23320
|
+
|
|
23321
|
+
// src/lessons/regex-linear/nfa.ts
|
|
23322
|
+
function wordBoundary(input, pos) {
|
|
23323
|
+
const before = pos > 0 && /[A-Za-z0-9_]/.test(input[pos - 1]);
|
|
23324
|
+
const after = pos < input.length && /[A-Za-z0-9_]/.test(input[pos]);
|
|
23325
|
+
return before !== after;
|
|
23326
|
+
}
|
|
23327
|
+
function assertHolds(kind, input, pos) {
|
|
23328
|
+
switch (kind) {
|
|
23329
|
+
case "start":
|
|
23330
|
+
return pos === 0;
|
|
23331
|
+
case "end":
|
|
23332
|
+
return pos === input.length;
|
|
23333
|
+
case "wordB":
|
|
23334
|
+
return wordBoundary(input, pos);
|
|
23335
|
+
case "nonWordB":
|
|
23336
|
+
return !wordBoundary(input, pos);
|
|
23337
|
+
}
|
|
23338
|
+
}
|
|
23339
|
+
function buildMatcher(ast) {
|
|
23340
|
+
const { states, start, accept } = compileNfa(ast);
|
|
23341
|
+
const closure = (set, idx, input, pos, budget) => {
|
|
23342
|
+
const stack = [idx];
|
|
23343
|
+
while (stack.length > 0) {
|
|
23344
|
+
if (budget.remaining <= 0) return;
|
|
23345
|
+
const cur = stack.pop();
|
|
23346
|
+
if (set.has(cur)) continue;
|
|
23347
|
+
set.add(cur);
|
|
23348
|
+
budget.remaining -= 1;
|
|
23349
|
+
for (const t of states[cur].eps) if (!set.has(t)) stack.push(t);
|
|
23350
|
+
for (const a of states[cur].asserts) {
|
|
23351
|
+
if (assertHolds(a.kind, input, pos) && !set.has(a.target)) stack.push(a.target);
|
|
23352
|
+
}
|
|
23353
|
+
}
|
|
23354
|
+
};
|
|
23355
|
+
return {
|
|
23356
|
+
// The input is matched in full (no truncation — truncation would miss suffix
|
|
23357
|
+
// matches and let `$` falsely match an invented endpoint). Work is bounded by
|
|
23358
|
+
// the shared budget instead: when it runs out we report a safe non-match.
|
|
23359
|
+
test(input, budget) {
|
|
23360
|
+
const b = budget ?? { remaining: Number.POSITIVE_INFINITY };
|
|
23361
|
+
if (b.remaining <= 0) return false;
|
|
23362
|
+
let current = /* @__PURE__ */ new Set();
|
|
23363
|
+
for (let pos = 0; pos <= input.length; pos += 1) {
|
|
23364
|
+
closure(current, start, input, pos, b);
|
|
23365
|
+
if (current.has(accept)) return true;
|
|
23366
|
+
if (b.remaining <= 0) return false;
|
|
23367
|
+
if (pos === input.length) break;
|
|
23368
|
+
const ch = input[pos];
|
|
23369
|
+
const next = /* @__PURE__ */ new Set();
|
|
23370
|
+
for (const s of current) {
|
|
23371
|
+
b.remaining -= 1;
|
|
23372
|
+
for (const t of states[s].chars) {
|
|
23373
|
+
if (t.test(ch)) closure(next, t.target, input, pos + 1, b);
|
|
23374
|
+
}
|
|
23375
|
+
}
|
|
23376
|
+
current = next;
|
|
23377
|
+
}
|
|
23378
|
+
return current.has(accept);
|
|
23379
|
+
}
|
|
23380
|
+
};
|
|
23381
|
+
}
|
|
23382
|
+
|
|
23383
|
+
// src/lessons/regex-linear/ast.ts
|
|
23384
|
+
var UnsupportedRegexError = class extends Error {
|
|
23385
|
+
constructor(message) {
|
|
23386
|
+
super(message);
|
|
23387
|
+
this.name = "UnsupportedRegexError";
|
|
23388
|
+
}
|
|
23389
|
+
};
|
|
23390
|
+
|
|
23391
|
+
// src/lessons/regex-linear/parse-helpers.ts
|
|
23392
|
+
var MAX_REPEAT = 1e3;
|
|
23393
|
+
var isWord = (c2) => /[A-Za-z0-9_]/.test(c2);
|
|
23394
|
+
function expandRepeat(atom, min, max) {
|
|
23395
|
+
const items = [];
|
|
23396
|
+
for (let k = 0; k < min; k += 1) items.push(atom);
|
|
23397
|
+
if (max === Infinity) {
|
|
23398
|
+
items.push({ k: "star", node: atom });
|
|
23399
|
+
} else {
|
|
23400
|
+
for (let k = min; k < max; k += 1) items.push({ k: "opt", node: atom });
|
|
23401
|
+
}
|
|
23402
|
+
if (items.length === 0) return { k: "empty" };
|
|
23403
|
+
return items.length === 1 ? items[0] : { k: "concat", items };
|
|
23404
|
+
}
|
|
23405
|
+
function escapeClass(c2) {
|
|
23406
|
+
switch (c2) {
|
|
23407
|
+
case "d":
|
|
23408
|
+
return (x) => x >= "0" && x <= "9";
|
|
23409
|
+
case "D":
|
|
23410
|
+
return (x) => !(x >= "0" && x <= "9");
|
|
23411
|
+
case "w":
|
|
23412
|
+
return isWord;
|
|
23413
|
+
case "W":
|
|
23414
|
+
return (x) => !isWord(x);
|
|
23415
|
+
case "s":
|
|
23416
|
+
return (x) => /\s/.test(x);
|
|
23417
|
+
case "S":
|
|
23418
|
+
return (x) => !/\s/.test(x);
|
|
23419
|
+
default:
|
|
23420
|
+
return null;
|
|
23421
|
+
}
|
|
23422
|
+
}
|
|
23423
|
+
var HEX2 = /^[0-9a-fA-F]{2}$/;
|
|
23424
|
+
var HEX4 = /^[0-9a-fA-F]{4}$/;
|
|
23425
|
+
function readUnicodeEscape(src, i, c2) {
|
|
23426
|
+
if (c2 === "x") {
|
|
23427
|
+
const hex2 = src.slice(i, i + 2);
|
|
23428
|
+
return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
|
|
23429
|
+
}
|
|
23430
|
+
const hex = src.slice(i, i + 4);
|
|
23431
|
+
return HEX4.test(hex) ? { ch: String.fromCharCode(parseInt(hex, 16)), len: 4 } : { ch: "u", len: 0 };
|
|
23432
|
+
}
|
|
23433
|
+
function readControlEscape(src, i) {
|
|
23434
|
+
const x = src[i];
|
|
23435
|
+
if (x === void 0 || !/[A-Za-z]/.test(x)) {
|
|
23436
|
+
throw new UnsupportedRegexError("\\c must be followed by a letter");
|
|
23437
|
+
}
|
|
23438
|
+
return { ch: String.fromCharCode(x.charCodeAt(0) & 31), len: 1 };
|
|
23439
|
+
}
|
|
23440
|
+
function classEscapeChar(src, i, e) {
|
|
23441
|
+
if (e === "b") return { ch: "\b", len: 0 };
|
|
23442
|
+
if (e === "c") return readControlEscape(src, i);
|
|
23443
|
+
if (e === "x" || e === "u") return readUnicodeEscape(src, i, e);
|
|
23444
|
+
return { ch: escapeLiteral(e), len: 0 };
|
|
23445
|
+
}
|
|
23446
|
+
function escapeLiteral(c2) {
|
|
23447
|
+
switch (c2) {
|
|
23448
|
+
case "t":
|
|
23449
|
+
return " ";
|
|
23450
|
+
case "n":
|
|
23451
|
+
return "\n";
|
|
23452
|
+
case "r":
|
|
23453
|
+
return "\r";
|
|
23454
|
+
case "f":
|
|
23455
|
+
return "\f";
|
|
23456
|
+
case "v":
|
|
23457
|
+
return "\v";
|
|
23458
|
+
case "0":
|
|
23459
|
+
return "\0";
|
|
23460
|
+
default:
|
|
23461
|
+
return c2;
|
|
23462
|
+
}
|
|
23463
|
+
}
|
|
23464
|
+
|
|
23465
|
+
// src/lessons/regex-linear/parse.ts
|
|
23466
|
+
function parseRegex(src) {
|
|
23467
|
+
let i = 0;
|
|
23468
|
+
const peek = () => src[i];
|
|
23469
|
+
const eat = () => src[i++];
|
|
23470
|
+
function parseAlt() {
|
|
23471
|
+
const opts = [parseConcat()];
|
|
23472
|
+
while (peek() === "|") {
|
|
23473
|
+
i += 1;
|
|
23474
|
+
opts.push(parseConcat());
|
|
23475
|
+
}
|
|
23476
|
+
return opts.length === 1 ? opts[0] : { k: "alt", opts };
|
|
23477
|
+
}
|
|
23478
|
+
function parseConcat() {
|
|
23479
|
+
const items = [];
|
|
23480
|
+
while (i < src.length && peek() !== "|" && peek() !== ")") {
|
|
23481
|
+
items.push(parseQuantified());
|
|
23482
|
+
}
|
|
23483
|
+
if (items.length === 0) return { k: "empty" };
|
|
23484
|
+
return items.length === 1 ? items[0] : { k: "concat", items };
|
|
23485
|
+
}
|
|
23486
|
+
function parseQuantified() {
|
|
23487
|
+
const atom = parseAtom();
|
|
23488
|
+
const q = peek();
|
|
23489
|
+
if (q === "*" || q === "+" || q === "?") {
|
|
23490
|
+
i += 1;
|
|
23491
|
+
if (peek() === "?") i += 1;
|
|
23492
|
+
return q === "*" ? { k: "star", node: atom } : q === "+" ? { k: "plus", node: atom } : { k: "opt", node: atom };
|
|
23493
|
+
}
|
|
23494
|
+
if (q === "{") {
|
|
23495
|
+
const repeat = tryParseBrace();
|
|
23496
|
+
if (repeat !== null) return expandRepeat(atom, repeat.min, repeat.max);
|
|
23497
|
+
}
|
|
23498
|
+
return atom;
|
|
23499
|
+
}
|
|
23500
|
+
function tryParseBrace() {
|
|
23501
|
+
const m = /^\{(\d+)(,(\d*)?)?\}/.exec(src.slice(i));
|
|
23502
|
+
if (m === null) return null;
|
|
23503
|
+
i += m[0].length;
|
|
23504
|
+
if (peek() === "?") i += 1;
|
|
23505
|
+
const min = Number(m[1]);
|
|
23506
|
+
const max = m[2] === void 0 ? min : m[3] === "" || m[3] === void 0 ? Infinity : Number(m[3]);
|
|
23507
|
+
if (min > MAX_REPEAT || max !== Infinity && max > MAX_REPEAT) {
|
|
23508
|
+
throw new UnsupportedRegexError(`Repeat count over ${MAX_REPEAT} not supported: {${m[1]}\u2026}`);
|
|
23509
|
+
}
|
|
23510
|
+
return { min, max };
|
|
23511
|
+
}
|
|
23512
|
+
function parseAtom() {
|
|
23513
|
+
const c2 = peek();
|
|
23514
|
+
if (c2 === "(") return parseGroup();
|
|
23515
|
+
if (c2 === "[") return parseClass();
|
|
23516
|
+
if (c2 === "\\") return parseEscape();
|
|
23517
|
+
if (c2 === ".") {
|
|
23518
|
+
i += 1;
|
|
23519
|
+
return { k: "any" };
|
|
23520
|
+
}
|
|
23521
|
+
if (c2 === "^") {
|
|
23522
|
+
i += 1;
|
|
23523
|
+
return { k: "assert", kind: "start" };
|
|
23524
|
+
}
|
|
23525
|
+
if (c2 === "$") {
|
|
23526
|
+
i += 1;
|
|
23527
|
+
return { k: "assert", kind: "end" };
|
|
23528
|
+
}
|
|
23529
|
+
if (c2 === void 0 || c2 === "*" || c2 === "+" || c2 === "?" || c2 === ")") {
|
|
23530
|
+
throw new UnsupportedRegexError(`Unexpected '${c2 ?? "<end>"}' in pattern`);
|
|
23531
|
+
}
|
|
23532
|
+
i += 1;
|
|
23533
|
+
return { k: "char", ch: c2 };
|
|
23534
|
+
}
|
|
23535
|
+
function parseGroup() {
|
|
23536
|
+
i += 1;
|
|
23537
|
+
if (peek() === "?") {
|
|
23538
|
+
const c2 = src[i + 1];
|
|
23539
|
+
if (c2 === "=" || c2 === "!" || c2 === "<") {
|
|
23540
|
+
if (!(c2 === "<" && /[A-Za-z]/.test(src[i + 2] ?? ""))) {
|
|
23541
|
+
throw new UnsupportedRegexError("Lookaround assertions are not supported");
|
|
23542
|
+
}
|
|
23543
|
+
}
|
|
23544
|
+
if (c2 === ":") i += 2;
|
|
23545
|
+
else if (c2 === "<") {
|
|
23546
|
+
i += 2;
|
|
23547
|
+
while (i < src.length && src[i] !== ">") i += 1;
|
|
23548
|
+
i += 1;
|
|
23549
|
+
}
|
|
23550
|
+
}
|
|
23551
|
+
const inner = parseAlt();
|
|
23552
|
+
if (peek() !== ")") throw new UnsupportedRegexError("Unbalanced group");
|
|
23553
|
+
i += 1;
|
|
23554
|
+
return inner;
|
|
23555
|
+
}
|
|
23556
|
+
function parseEscape() {
|
|
23557
|
+
i += 1;
|
|
23558
|
+
const c2 = peek();
|
|
23559
|
+
if (c2 === void 0) throw new UnsupportedRegexError("Trailing backslash");
|
|
23560
|
+
if (/[1-9]/.test(c2) || c2 === "k")
|
|
23561
|
+
throw new UnsupportedRegexError("Backreferences are not supported");
|
|
23562
|
+
i += 1;
|
|
23563
|
+
if (c2 === "b") return { k: "assert", kind: "wordB" };
|
|
23564
|
+
if (c2 === "B") return { k: "assert", kind: "nonWordB" };
|
|
23565
|
+
const cls = escapeClass(c2);
|
|
23566
|
+
if (cls !== null) return { k: "class", test: cls };
|
|
23567
|
+
if (c2 === "x" || c2 === "u" || c2 === "c") {
|
|
23568
|
+
const { ch, len } = c2 === "c" ? readControlEscape(src, i) : readUnicodeEscape(src, i, c2);
|
|
23569
|
+
i += len;
|
|
23570
|
+
return { k: "char", ch };
|
|
23571
|
+
}
|
|
23572
|
+
return { k: "char", ch: escapeLiteral(c2) };
|
|
23573
|
+
}
|
|
23574
|
+
function parseClass() {
|
|
23575
|
+
i += 1;
|
|
23576
|
+
const negate = peek() === "^";
|
|
23577
|
+
if (negate) i += 1;
|
|
23578
|
+
const tests = [];
|
|
23579
|
+
while (i < src.length && peek() !== "]") {
|
|
23580
|
+
tests.push(parseClassMember());
|
|
23581
|
+
}
|
|
23582
|
+
if (peek() !== "]") throw new UnsupportedRegexError("Unterminated character class");
|
|
23583
|
+
i += 1;
|
|
23584
|
+
const base = (c2) => tests.some((t) => t(c2));
|
|
23585
|
+
return { k: "class", test: negate ? (c2) => !base(c2) : base };
|
|
23586
|
+
}
|
|
23587
|
+
function parseClassMember() {
|
|
23588
|
+
let lo;
|
|
23589
|
+
if (peek() === "\\") {
|
|
23590
|
+
i += 1;
|
|
23591
|
+
const e = eat();
|
|
23592
|
+
const cls = escapeClass(e);
|
|
23593
|
+
if (cls !== null) return cls;
|
|
23594
|
+
const r = classEscapeChar(src, i, e);
|
|
23595
|
+
i += r.len;
|
|
23596
|
+
lo = r.ch;
|
|
23597
|
+
} else {
|
|
23598
|
+
lo = eat();
|
|
23599
|
+
}
|
|
23600
|
+
if (peek() === "-" && src[i + 1] !== void 0 && src[i + 1] !== "]") {
|
|
23601
|
+
i += 1;
|
|
23602
|
+
let hi;
|
|
23603
|
+
if (peek() === "\\") {
|
|
23604
|
+
i += 1;
|
|
23605
|
+
const e2 = eat();
|
|
23606
|
+
const r = classEscapeChar(src, i, e2);
|
|
23607
|
+
i += r.len;
|
|
23608
|
+
hi = r.ch;
|
|
23609
|
+
} else {
|
|
23610
|
+
hi = eat();
|
|
23611
|
+
}
|
|
23612
|
+
const a = lo.codePointAt(0);
|
|
23613
|
+
const b = hi.codePointAt(0);
|
|
23614
|
+
return (c2) => {
|
|
23615
|
+
const p = c2.codePointAt(0);
|
|
23616
|
+
return p >= a && p <= b;
|
|
23617
|
+
};
|
|
23618
|
+
}
|
|
23619
|
+
return (c2) => c2 === lo;
|
|
23620
|
+
}
|
|
23621
|
+
const ast = parseAlt();
|
|
23622
|
+
if (i !== src.length) throw new UnsupportedRegexError(`Unexpected '${peek()}' at ${i}`);
|
|
23623
|
+
return ast;
|
|
23624
|
+
}
|
|
23625
|
+
|
|
23626
|
+
// src/lessons/regex-linear/index.ts
|
|
23627
|
+
var cache = /* @__PURE__ */ new Map();
|
|
23628
|
+
function compileLinearMatcher(pattern) {
|
|
23629
|
+
const hit = cache.get(pattern);
|
|
23630
|
+
if (hit !== void 0 || cache.has(pattern)) return hit ?? null;
|
|
23631
|
+
let matcher;
|
|
23632
|
+
try {
|
|
23633
|
+
matcher = buildMatcher(parseRegex(pattern));
|
|
23634
|
+
} catch {
|
|
23635
|
+
matcher = null;
|
|
23636
|
+
}
|
|
23637
|
+
cache.set(pattern, matcher);
|
|
23638
|
+
return matcher;
|
|
23639
|
+
}
|
|
23640
|
+
|
|
23641
|
+
// src/lessons/regex-safety.ts
|
|
23642
|
+
var MAX_PATTERN_LENGTH = 1e3;
|
|
23643
|
+
function isSafeRegexPattern(pattern) {
|
|
23644
|
+
if (pattern.length > MAX_PATTERN_LENGTH) return false;
|
|
23645
|
+
return compileLinearMatcher(pattern) !== null;
|
|
23646
|
+
}
|
|
23647
|
+
function getCommandMatcher(pattern) {
|
|
23648
|
+
if (pattern.length > MAX_PATTERN_LENGTH) return null;
|
|
23649
|
+
return compileLinearMatcher(pattern);
|
|
23650
|
+
}
|
|
23651
|
+
|
|
23652
|
+
// src/lessons/validate-quality.ts
|
|
23653
|
+
function collectDuplicateRules(graph, findings) {
|
|
23654
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
23655
|
+
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
23656
|
+
if (lesson.status !== "active") continue;
|
|
23657
|
+
const key = normalizeRule(lesson.rule);
|
|
23658
|
+
const bucket = byKey.get(key) ?? [];
|
|
23659
|
+
bucket.push(lessonId);
|
|
23660
|
+
byKey.set(key, bucket);
|
|
23661
|
+
}
|
|
23662
|
+
for (const [key, ids] of byKey) {
|
|
23663
|
+
if (ids.length < 2) continue;
|
|
23664
|
+
const sorted = [...ids].sort();
|
|
23665
|
+
for (const lessonId of sorted) {
|
|
23666
|
+
const others = sorted.filter((other) => other !== lessonId);
|
|
23667
|
+
findings.push({
|
|
23668
|
+
level: "error",
|
|
23669
|
+
code: "DUPLICATE_RULE",
|
|
23670
|
+
message: `Lesson "${lessonId}" duplicates rule text of: ${others.join(", ")} (normalized key: "${key.slice(0, 60)}").`,
|
|
23671
|
+
lessonId
|
|
23672
|
+
});
|
|
23673
|
+
}
|
|
23674
|
+
}
|
|
23675
|
+
}
|
|
23676
|
+
function collectInvalidTriggerPatterns(graph, findings) {
|
|
23677
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23678
|
+
if (trigger.kind !== "command_pattern") continue;
|
|
23679
|
+
try {
|
|
23680
|
+
new RegExp(trigger.pattern);
|
|
23681
|
+
} catch (err) {
|
|
23682
|
+
findings.push({
|
|
23683
|
+
level: "error",
|
|
23684
|
+
code: "INVALID_TRIGGER_PATTERN",
|
|
23685
|
+
message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${err instanceof Error ? err.message : String(err)}.`,
|
|
23686
|
+
triggerId
|
|
23687
|
+
});
|
|
23688
|
+
continue;
|
|
23689
|
+
}
|
|
23690
|
+
if (!isSafeRegexPattern(trigger.pattern)) {
|
|
23691
|
+
findings.push({
|
|
23692
|
+
level: "error",
|
|
23693
|
+
code: "UNSAFE_TRIGGER_PATTERN",
|
|
23694
|
+
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.`,
|
|
23695
|
+
triggerId
|
|
23696
|
+
});
|
|
23697
|
+
}
|
|
23698
|
+
}
|
|
23699
|
+
}
|
|
23700
|
+
function collectBackslashGlobPatterns(graph, findings) {
|
|
23701
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23702
|
+
if (trigger.kind !== "file_glob") continue;
|
|
23703
|
+
if (!trigger.pattern.includes("\\")) continue;
|
|
23704
|
+
findings.push({
|
|
23705
|
+
level: "error",
|
|
23706
|
+
code: "BACKSLASH_GLOB_PATTERN",
|
|
23707
|
+
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 /.`,
|
|
23708
|
+
triggerId
|
|
23709
|
+
});
|
|
23710
|
+
}
|
|
23711
|
+
}
|
|
23712
|
+
function collectDuplicateTriggers(graph, findings) {
|
|
23713
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
23714
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23715
|
+
const key = `${trigger.kind}|${trigger.pattern}`;
|
|
23716
|
+
const bucket = byKey.get(key) ?? [];
|
|
23717
|
+
bucket.push(triggerId);
|
|
23718
|
+
byKey.set(key, bucket);
|
|
23719
|
+
}
|
|
23720
|
+
for (const [key, ids] of byKey) {
|
|
23721
|
+
if (ids.length < 2) continue;
|
|
23722
|
+
const sorted = [...ids].sort();
|
|
23723
|
+
for (const triggerId of sorted) {
|
|
23724
|
+
const others = sorted.filter((other) => other !== triggerId);
|
|
23725
|
+
findings.push({
|
|
23726
|
+
level: "error",
|
|
23727
|
+
code: "DUPLICATE_TRIGGER",
|
|
23728
|
+
message: `Trigger "${triggerId}" duplicates (kind, pattern) of: ${others.join(", ")} (key: "${key}").`,
|
|
23729
|
+
triggerId
|
|
23730
|
+
});
|
|
23731
|
+
}
|
|
23732
|
+
}
|
|
23733
|
+
}
|
|
23734
|
+
var HIGH_FANOUT_THRESHOLD = 10;
|
|
23735
|
+
function collectFanout(graph, findings) {
|
|
23736
|
+
const fanout = /* @__PURE__ */ new Map();
|
|
23737
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23738
|
+
if (lesson.status !== "active") continue;
|
|
23739
|
+
for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
|
|
23740
|
+
}
|
|
23741
|
+
let over = 0;
|
|
23742
|
+
let max = 0;
|
|
23743
|
+
for (const n of fanout.values()) {
|
|
23744
|
+
if (n > HIGH_FANOUT_THRESHOLD) over += 1;
|
|
23745
|
+
if (n > max) max = n;
|
|
23746
|
+
}
|
|
23747
|
+
if (over > 0) {
|
|
23748
|
+
findings.push({
|
|
23749
|
+
level: "warning",
|
|
23750
|
+
code: "HIGH_FANOUT_TRIGGERS",
|
|
23751
|
+
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.`
|
|
23752
|
+
});
|
|
23753
|
+
}
|
|
23754
|
+
}
|
|
23755
|
+
function collectLowSignalKeywords(graph, findings) {
|
|
23756
|
+
const activeTriggerIds2 = /* @__PURE__ */ new Set();
|
|
23757
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23758
|
+
if (lesson.status !== "active") continue;
|
|
23759
|
+
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
23760
|
+
}
|
|
23761
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23762
|
+
if (trigger.kind !== "keyword") continue;
|
|
23763
|
+
if (!activeTriggerIds2.has(triggerId)) continue;
|
|
23764
|
+
if (!isLowSignalKeyword(trigger.pattern)) continue;
|
|
23765
|
+
findings.push({
|
|
23766
|
+
level: "warning",
|
|
23767
|
+
code: "LOW_SIGNAL_KEYWORD",
|
|
23768
|
+
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.`,
|
|
23769
|
+
triggerId
|
|
23770
|
+
});
|
|
23771
|
+
}
|
|
23772
|
+
}
|
|
23773
|
+
function collectStopwordKeywords(graph, findings) {
|
|
23774
|
+
const activeTriggerIds2 = /* @__PURE__ */ new Set();
|
|
23775
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23776
|
+
if (lesson.status !== "active") continue;
|
|
23777
|
+
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
23778
|
+
}
|
|
23779
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23780
|
+
if (trigger.kind !== "keyword") continue;
|
|
23781
|
+
if (!activeTriggerIds2.has(triggerId)) continue;
|
|
23782
|
+
if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
|
|
23783
|
+
continue;
|
|
23784
|
+
}
|
|
23785
|
+
findings.push({
|
|
23786
|
+
level: "warning",
|
|
23787
|
+
code: "STOPWORD_KEYWORD",
|
|
23788
|
+
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\`.`,
|
|
23789
|
+
triggerId
|
|
23790
|
+
});
|
|
23791
|
+
}
|
|
23792
|
+
}
|
|
23793
|
+
function normalizeRule(rule) {
|
|
23794
|
+
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
23795
|
+
}
|
|
23796
|
+
function activeTriggerIds(graph) {
|
|
23797
|
+
const ids = /* @__PURE__ */ new Set();
|
|
23798
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
23799
|
+
if (lesson.status !== "active") continue;
|
|
23800
|
+
for (const t of lesson.triggers) ids.add(t);
|
|
23801
|
+
}
|
|
23802
|
+
return ids;
|
|
23803
|
+
}
|
|
23804
|
+
function deadFileGlobIds(graph, knownPaths) {
|
|
23805
|
+
const active = activeTriggerIds(graph);
|
|
23806
|
+
const paths = [...knownPaths];
|
|
23807
|
+
const dead = /* @__PURE__ */ new Set();
|
|
23808
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23809
|
+
if (trigger.kind !== "file_glob") continue;
|
|
23810
|
+
if (!active.has(triggerId)) continue;
|
|
23811
|
+
const isMatch = picomatch(trigger.pattern, { dot: true });
|
|
23812
|
+
if (!paths.some((p) => isMatch(p))) dead.add(triggerId);
|
|
23813
|
+
}
|
|
23814
|
+
return dead;
|
|
23815
|
+
}
|
|
23816
|
+
function collectDeadFileGlobs(graph, findings, knownPaths) {
|
|
23817
|
+
for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
|
|
23818
|
+
findings.push({
|
|
23819
|
+
level: "warning",
|
|
23820
|
+
code: "DEAD_FILE_GLOB",
|
|
23821
|
+
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\`.`,
|
|
23822
|
+
triggerId
|
|
23823
|
+
});
|
|
23824
|
+
}
|
|
23825
|
+
}
|
|
23826
|
+
var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
|
|
23827
|
+
function collectRunnerAnchoredPatterns(graph, findings) {
|
|
23828
|
+
const active = activeTriggerIds(graph);
|
|
23829
|
+
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
23830
|
+
if (trigger.kind !== "command_pattern") continue;
|
|
23831
|
+
if (!active.has(triggerId)) continue;
|
|
23832
|
+
if (!RUNNER_ANCHOR.test(trigger.pattern)) continue;
|
|
23833
|
+
findings.push({
|
|
23834
|
+
level: "warning",
|
|
23835
|
+
code: "RUNNER_ANCHORED_PATTERN",
|
|
23836
|
+
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\`).`,
|
|
23837
|
+
triggerId
|
|
23838
|
+
});
|
|
23839
|
+
}
|
|
23840
|
+
}
|
|
23841
|
+
|
|
23842
|
+
// src/lessons/validate.ts
|
|
23843
|
+
function validateLessonsGraph(graph, options = {}) {
|
|
23844
|
+
const findings = [];
|
|
23845
|
+
const schemaResult = LessonsGraphSchema.safeParse(graph);
|
|
23846
|
+
if (!schemaResult.success) {
|
|
23847
|
+
findings.push({
|
|
23848
|
+
level: "error",
|
|
23849
|
+
code: "SCHEMA_INVALID",
|
|
23850
|
+
message: schemaResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
23851
|
+
});
|
|
23852
|
+
return { ok: false, findings };
|
|
23853
|
+
}
|
|
23854
|
+
collectDanglingRefs(graph, findings);
|
|
23855
|
+
collectDuplicateRefs(graph, findings);
|
|
23856
|
+
collectStatusInvariants(graph, findings);
|
|
23857
|
+
collectLifecycleInvariants(graph, findings);
|
|
23858
|
+
collectDuplicateRules(graph, findings);
|
|
23859
|
+
collectReachability(graph, findings);
|
|
23860
|
+
collectInvalidTriggerPatterns(graph, findings);
|
|
23861
|
+
collectBackslashGlobPatterns(graph, findings);
|
|
23862
|
+
collectDuplicateTriggers(graph, findings);
|
|
23863
|
+
collectOrphans(graph, findings);
|
|
23864
|
+
collectFanout(graph, findings);
|
|
23865
|
+
collectLowSignalKeywords(graph, findings);
|
|
23866
|
+
collectStopwordKeywords(graph, findings);
|
|
23867
|
+
collectRunnerAnchoredPatterns(graph, findings);
|
|
23868
|
+
if (options.knownPaths !== void 0) collectDeadFileGlobs(graph, findings, options.knownPaths);
|
|
23869
|
+
const ok = findings.every((f) => f.level !== "error");
|
|
23870
|
+
return { ok, findings };
|
|
23871
|
+
}
|
|
23872
|
+
|
|
23873
|
+
// src/core/lint/shared/lessons.ts
|
|
23874
|
+
var LESSONS_TARGET = "lessons";
|
|
23875
|
+
var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
|
|
23876
|
+
var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
|
|
23877
|
+
var LESSONS_HEADING = /^## Lessons \(/m;
|
|
23878
|
+
function lintLessonsSubsystem(projectRoot, scope) {
|
|
23879
|
+
if (scope === "global") return [];
|
|
23880
|
+
const paths = lessonsPaths(projectRoot);
|
|
23881
|
+
if (!existsSync(paths.graph)) return [];
|
|
23882
|
+
const out2 = [];
|
|
23883
|
+
let graph;
|
|
23884
|
+
try {
|
|
23885
|
+
graph = loadLessonsGraph(projectRoot);
|
|
23886
|
+
} catch (err) {
|
|
23887
|
+
return [
|
|
23888
|
+
diag(
|
|
23889
|
+
"error",
|
|
23890
|
+
GRAPH_REL,
|
|
23891
|
+
`lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
|
|
23892
|
+
)
|
|
23893
|
+
];
|
|
23894
|
+
}
|
|
23895
|
+
const knownPaths = listProjectFiles(projectRoot) ?? void 0;
|
|
23896
|
+
const report = validateLessonsGraph(graph, { knownPaths });
|
|
23897
|
+
for (const finding of report.findings) {
|
|
23898
|
+
out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
|
|
23899
|
+
}
|
|
23900
|
+
const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
|
|
23901
|
+
const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
|
|
23902
|
+
if (!LESSONS_HEADING.test(rootRuleBody)) {
|
|
23903
|
+
out2.push(
|
|
23904
|
+
diag(
|
|
23905
|
+
"warning",
|
|
23906
|
+
ROOT_RULE_REL,
|
|
23907
|
+
'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
|
|
23908
|
+
)
|
|
23909
|
+
);
|
|
23910
|
+
}
|
|
23911
|
+
return out2;
|
|
23912
|
+
}
|
|
23913
|
+
function diag(level, file, message) {
|
|
23914
|
+
return { level, file, target: LESSONS_TARGET, message };
|
|
23915
|
+
}
|
|
23916
|
+
|
|
23917
|
+
// src/core/lint/linter.ts
|
|
23918
|
+
var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
|
|
23919
|
+
async function getProjectFiles(projectRoot) {
|
|
23920
|
+
const all = await readDirRecursive(projectRoot);
|
|
23921
|
+
const filtered = all.filter((p) => {
|
|
23922
|
+
const rel2 = relative(projectRoot, p);
|
|
23923
|
+
return !EXCLUDE_DIRS.some((d) => rel2.includes(`/${d}/`) || rel2.startsWith(`${d}/`));
|
|
23924
|
+
});
|
|
23925
|
+
return filtered.map((p) => relative(projectRoot, p));
|
|
23926
|
+
}
|
|
23927
|
+
async function runLint(config, canonical, projectRoot, targetFilter, options = {}) {
|
|
23928
|
+
const scope = options.scope ?? "project";
|
|
23929
|
+
const allTargets = [...config.targets, ...config.pluginTargets ?? []];
|
|
23930
|
+
const targets = targetFilter ? allTargets.filter((t) => targetFilter.includes(t)) : allTargets;
|
|
23931
|
+
const hasRules = config.features.includes("rules");
|
|
23932
|
+
const hasCommands = config.features.includes("commands");
|
|
23933
|
+
const hasMcp = config.features.includes("mcp");
|
|
23934
|
+
const hasPermissions = config.features.includes("permissions");
|
|
23935
|
+
const hasHooks = config.features.includes("hooks");
|
|
23936
|
+
const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
|
|
22739
23937
|
const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
|
|
22740
23938
|
for (const target31 of targets) {
|
|
22741
23939
|
const fullDesc = getDescriptor(target31);
|
|
@@ -23138,203 +24336,1190 @@ function copyTargetDescriptor(descriptor31) {
|
|
|
23138
24336
|
function getTargetCatalog() {
|
|
23139
24337
|
return Object.freeze(BUILTIN_TARGETS.map(copyTargetDescriptor));
|
|
23140
24338
|
}
|
|
23141
|
-
function
|
|
23142
|
-
return
|
|
23143
|
-
}
|
|
23144
|
-
function
|
|
23145
|
-
|
|
23146
|
-
|
|
23147
|
-
|
|
23148
|
-
|
|
23149
|
-
function
|
|
23150
|
-
const
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
|
|
23154
|
-
|
|
23155
|
-
|
|
23156
|
-
|
|
23157
|
-
|
|
23158
|
-
|
|
23159
|
-
|
|
23160
|
-
|
|
23161
|
-
|
|
23162
|
-
|
|
23163
|
-
|
|
23164
|
-
|
|
23165
|
-
|
|
23166
|
-
|
|
23167
|
-
|
|
23168
|
-
|
|
24339
|
+
function normalizeRule2(rule) {
|
|
24340
|
+
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
24341
|
+
}
|
|
24342
|
+
function union(base, extra) {
|
|
24343
|
+
const out2 = [...base];
|
|
24344
|
+
for (const item of extra) if (!out2.includes(item)) out2.push(item);
|
|
24345
|
+
return out2;
|
|
24346
|
+
}
|
|
24347
|
+
function mergeTriggers(graph, spec) {
|
|
24348
|
+
const requested = [
|
|
24349
|
+
// Normalize `\` → `/` so a Windows-shaped glob matches: recall relativizes
|
|
24350
|
+
// every `--file` to forward slashes (normalizeRecallFile), so a backslash
|
|
24351
|
+
// pattern stored raw would silently never fire. Normalizing here also lets
|
|
24352
|
+
// a backslash pattern dedupe against the forward-slash node it equals.
|
|
24353
|
+
...(spec.files ?? []).map(
|
|
24354
|
+
(p) => ({ kind: "file_glob", pattern: p.replaceAll("\\", "/") })
|
|
24355
|
+
),
|
|
24356
|
+
...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
|
|
24357
|
+
...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
|
|
24358
|
+
];
|
|
24359
|
+
const reverseLookup = /* @__PURE__ */ new Map();
|
|
24360
|
+
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
24361
|
+
reverseLookup.set(triggerKey(trigger), id);
|
|
24362
|
+
}
|
|
24363
|
+
const triggerIds = [];
|
|
24364
|
+
const newTriggerIds = [];
|
|
24365
|
+
for (const spec2 of requested) {
|
|
24366
|
+
const key = triggerKey(spec2);
|
|
24367
|
+
const existing = reverseLookup.get(key);
|
|
24368
|
+
if (existing !== void 0) {
|
|
24369
|
+
if (!triggerIds.includes(existing)) triggerIds.push(existing);
|
|
24370
|
+
continue;
|
|
24371
|
+
}
|
|
24372
|
+
const id = makeTriggerId(spec2);
|
|
24373
|
+
graph.triggers[id] = { kind: spec2.kind, pattern: spec2.pattern };
|
|
24374
|
+
reverseLookup.set(key, id);
|
|
24375
|
+
triggerIds.push(id);
|
|
24376
|
+
newTriggerIds.push(id);
|
|
24377
|
+
}
|
|
24378
|
+
return { triggerIds, newTriggerIds };
|
|
24379
|
+
}
|
|
24380
|
+
function triggerKey(t) {
|
|
24381
|
+
return `${t.kind}|${t.pattern}`;
|
|
24382
|
+
}
|
|
24383
|
+
var TRIGGER_PREFIX = {
|
|
24384
|
+
file_glob: "glob",
|
|
24385
|
+
command_pattern: "cmd",
|
|
24386
|
+
keyword: "kw"
|
|
24387
|
+
};
|
|
24388
|
+
function makeTriggerId(spec) {
|
|
24389
|
+
const hash = createHash("sha1").update(triggerKey(spec)).digest("hex").slice(0, 8);
|
|
24390
|
+
return `t-${TRIGGER_PREFIX[spec.kind]}-${hash}`;
|
|
24391
|
+
}
|
|
24392
|
+
function makeLessonId(graph, topic, ruleKey) {
|
|
24393
|
+
const slug = ruleToSlug(ruleKey);
|
|
24394
|
+
const base = slug.length > 0 ? `${topic}-${slug}` : `${topic}-${createHash("sha1").update(ruleKey).digest("hex").slice(0, 8)}`;
|
|
24395
|
+
let candidate = base;
|
|
24396
|
+
let i = 2;
|
|
24397
|
+
while (graph.lessons[candidate] !== void 0) {
|
|
24398
|
+
candidate = `${base}-${i}`;
|
|
24399
|
+
i += 1;
|
|
24400
|
+
}
|
|
24401
|
+
return candidate;
|
|
24402
|
+
}
|
|
24403
|
+
function ruleToSlug(rule) {
|
|
24404
|
+
const words = rule.replace(/[^a-z0-9 ]+/g, " ").split(/\s+/).filter((w) => w.length > 0).slice(0, 5);
|
|
24405
|
+
return words.join("-").slice(0, 40).replace(/-+$/, "");
|
|
24406
|
+
}
|
|
24407
|
+
function todayIso() {
|
|
24408
|
+
const now = /* @__PURE__ */ new Date();
|
|
24409
|
+
const y = now.getUTCFullYear();
|
|
24410
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
24411
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
24412
|
+
return `${y}-${m}-${d}`;
|
|
24413
|
+
}
|
|
24414
|
+
|
|
24415
|
+
// src/lessons/add-errors.ts
|
|
24416
|
+
var UnknownTopicError = class extends Error {
|
|
24417
|
+
constructor(topic) {
|
|
24418
|
+
super(`Unknown topic: ${topic}. Pass allowNewTopic + topicSummary to create it.`);
|
|
24419
|
+
this.topic = topic;
|
|
24420
|
+
this.name = "UnknownTopicError";
|
|
24421
|
+
}
|
|
24422
|
+
code = "UNKNOWN_TOPIC";
|
|
24423
|
+
};
|
|
24424
|
+
var RuleTooLongError = class extends Error {
|
|
24425
|
+
constructor(length, max) {
|
|
24426
|
+
super(
|
|
24427
|
+
`Lesson rule is ${length} characters (max ${max}). A rule should be one imperative sentence \u2014 trim it to the essential instruction, or split it into separate lessons.`
|
|
24428
|
+
);
|
|
24429
|
+
this.length = length;
|
|
24430
|
+
this.max = max;
|
|
24431
|
+
this.name = "RuleTooLongError";
|
|
24432
|
+
}
|
|
24433
|
+
code = "OVERSIZED_RULE";
|
|
24434
|
+
};
|
|
24435
|
+
var NoTriggerError = class extends Error {
|
|
24436
|
+
code = "NO_TRIGGER";
|
|
24437
|
+
constructor() {
|
|
24438
|
+
super(
|
|
24439
|
+
"A lesson needs at least one trigger to be recallable. Pass --trigger-file <glob> (preferred), --trigger-cmd <regex>, or --trigger-kw <text>."
|
|
24440
|
+
);
|
|
24441
|
+
this.name = "NoTriggerError";
|
|
24442
|
+
}
|
|
24443
|
+
};
|
|
24444
|
+
var UnrecallableLessonError = class extends Error {
|
|
24445
|
+
constructor(deadTriggers) {
|
|
24446
|
+
super(
|
|
24447
|
+
"This capture would create a lesson with no effective trigger \u2014 every trigger is dead on the mandatory --file/--cmd recall path, so the lesson could never be recalled there:\n" + deadTriggers.map((t) => ` \u2022 ${t.kind} "${t.pattern}" \u2014 ${t.reason}`).join("\n") + '\nFix: add a precise --trigger-file <glob> (preferred) or a valid --trigger-cmd <regex>; for a keyword, drop the stopwords (e.g. "state art" not "state of the art").'
|
|
24448
|
+
);
|
|
24449
|
+
this.deadTriggers = deadTriggers;
|
|
24450
|
+
this.name = "UnrecallableLessonError";
|
|
24451
|
+
}
|
|
24452
|
+
code = "UNRECALLABLE_LESSON";
|
|
24453
|
+
};
|
|
24454
|
+
|
|
24455
|
+
// src/lessons/capture-guardrails.ts
|
|
24456
|
+
var NEAR_DUPLICATE_THRESHOLD = 0.6;
|
|
24457
|
+
var MAX_RECOMMENDED_TRIGGERS = 8;
|
|
24458
|
+
function isBroadGlob(pattern) {
|
|
24459
|
+
const p = pattern.trim();
|
|
24460
|
+
if (p === "*" || p === "**") return true;
|
|
24461
|
+
if (!p.includes("**")) return false;
|
|
24462
|
+
const basename69 = p.slice(p.lastIndexOf("/") + 1);
|
|
24463
|
+
return basename69.startsWith("*");
|
|
24464
|
+
}
|
|
24465
|
+
function inspectCapturedLesson(graph, lessonId, knownPaths) {
|
|
24466
|
+
const lesson = graph.lessons[lessonId];
|
|
24467
|
+
if (lesson === void 0) return [];
|
|
24468
|
+
const warnings = [];
|
|
24469
|
+
if (lesson.triggers.length > MAX_RECOMMENDED_TRIGGERS) {
|
|
24470
|
+
warnings.push({
|
|
24471
|
+
code: "OVERSIZED_LESSON_TRIGGERS",
|
|
24472
|
+
message: `Lesson "${lessonId}" has ${lesson.triggers.length} triggers (recommended \u2264 ${MAX_RECOMMENDED_TRIGGERS}); broad trigger sets fire on too many edits and dilute recall \u2014 prefer a few specific triggers.`
|
|
24473
|
+
});
|
|
24474
|
+
}
|
|
24475
|
+
const triggers = lesson.triggers.map((id) => graph.triggers[id]).filter((t) => t !== void 0);
|
|
24476
|
+
const broad = triggers.filter((t) => t.kind === "file_glob" && isBroadGlob(t.pattern)).map((t) => t.pattern);
|
|
24477
|
+
if (broad.length > 0) {
|
|
24478
|
+
warnings.push({
|
|
24479
|
+
code: "BROAD_GLOB_TRIGGER",
|
|
24480
|
+
message: `Lesson "${lessonId}" has broad file glob(s) (${broad.join(", ")}) that match large swaths of the tree; prefer a path specific to the lesson.`
|
|
24481
|
+
});
|
|
24482
|
+
}
|
|
24483
|
+
if (triggers.length > 0 && triggers.every((t) => t.kind === "keyword")) {
|
|
24484
|
+
warnings.push({
|
|
24485
|
+
code: "KEYWORD_ONLY_LESSON",
|
|
24486
|
+
message: `Lesson "${lessonId}" has only keyword triggers; mandatory --file/--cmd recall surfaces these only when the keyword appears as a path/command token, so it fires less reliably \u2014 add a file_glob or command_pattern trigger for precise recall.`
|
|
24487
|
+
});
|
|
24488
|
+
}
|
|
24489
|
+
const lowSignal = triggers.filter((t) => t.kind === "keyword" && isLowSignalKeyword(t.pattern)).map((t) => t.pattern);
|
|
24490
|
+
if (lowSignal.length > 0) {
|
|
24491
|
+
warnings.push({
|
|
24492
|
+
code: "LOW_SIGNAL_KEYWORD",
|
|
24493
|
+
message: `Lesson "${lessonId}" has long keyword trigger(s) (${lowSignal.join(", ")}); recall matches a keyword only as a substring of --keyword or a contiguous token-run in the file/command, so a pattern past ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens rarely fires \u2014 use a short distinctive phrase.`
|
|
24494
|
+
});
|
|
24495
|
+
}
|
|
24496
|
+
const stopworded = triggers.filter((t) => t.kind === "keyword" && keywordNeedleLosesTokens(t.pattern)).map((t) => t.pattern);
|
|
24497
|
+
if (stopworded.length > 0) {
|
|
24498
|
+
warnings.push({
|
|
24499
|
+
code: "STOPWORD_KEYWORD",
|
|
24500
|
+
message: `Lesson "${lessonId}" has keyword trigger(s) containing stopwords/short words (${stopworded.join(", ")}); recall filters them from the pattern but NOT from the file/command text, so the phrase can never match contiguously on the --file/--cmd path \u2014 drop the stopwords (e.g. "state art" instead of "state of the art").`
|
|
24501
|
+
});
|
|
24502
|
+
}
|
|
24503
|
+
if (knownPaths !== void 0) {
|
|
24504
|
+
const dead = deadFileGlobIds(graph, knownPaths);
|
|
24505
|
+
const deadHere = lesson.triggers.filter((id) => dead.has(id)).map((id) => graph.triggers[id]?.pattern).filter((p) => p !== void 0);
|
|
24506
|
+
if (deadHere.length > 0) {
|
|
24507
|
+
warnings.push({
|
|
24508
|
+
code: "DEAD_GLOB",
|
|
24509
|
+
message: `Lesson "${lessonId}" has file_glob trigger(s) (${deadHere.join(", ")}) that match no file in the working tree \u2014 likely a rename. Re-point them at the current path, or the lesson is unreachable via those globs.`
|
|
24510
|
+
});
|
|
24511
|
+
}
|
|
24512
|
+
}
|
|
24513
|
+
return warnings;
|
|
24514
|
+
}
|
|
24515
|
+
function nearDuplicateWarning(graph, lessonId) {
|
|
24516
|
+
const subject = graph.lessons[lessonId];
|
|
24517
|
+
if (subject === void 0) return null;
|
|
24518
|
+
const subjectTokens = new Set(tokenize(subject.rule));
|
|
24519
|
+
if (subjectTokens.size === 0) return null;
|
|
24520
|
+
let best = null;
|
|
24521
|
+
for (const [id, other] of Object.entries(graph.lessons)) {
|
|
24522
|
+
if (id === lessonId || other.status !== "active") continue;
|
|
24523
|
+
const otherTokens = new Set(tokenize(other.rule));
|
|
24524
|
+
if (otherTokens.size === 0) continue;
|
|
24525
|
+
const score = jaccard(subjectTokens, otherTokens);
|
|
24526
|
+
if (score >= NEAR_DUPLICATE_THRESHOLD && (best === null || score > best.score)) {
|
|
24527
|
+
best = { id, score };
|
|
24528
|
+
}
|
|
24529
|
+
}
|
|
24530
|
+
if (best === null) return null;
|
|
24531
|
+
return {
|
|
24532
|
+
code: "NEAR_DUPLICATE_LESSON",
|
|
24533
|
+
message: `Lesson "${lessonId}" closely resembles active lesson "${best.id}" (~${Math.round(best.score * 100)}% token overlap); consider updating "${best.id}" instead of adding a paraphrase (recall would surface both).`
|
|
24534
|
+
};
|
|
24535
|
+
}
|
|
24536
|
+
function jaccard(a, b) {
|
|
24537
|
+
let intersection = 0;
|
|
24538
|
+
for (const t of a) if (b.has(t)) intersection += 1;
|
|
24539
|
+
return intersection / (a.size + b.size - intersection);
|
|
24540
|
+
}
|
|
24541
|
+
var LegacyTriggersSchema = z.object({
|
|
24542
|
+
file_globs: z.array(z.string()),
|
|
24543
|
+
command_patterns: z.array(z.string()),
|
|
24544
|
+
keywords: z.array(z.string())
|
|
24545
|
+
}).refine((t) => t.file_globs.length + t.command_patterns.length + t.keywords.length > 0, {
|
|
24546
|
+
message: "cluster must declare at least one trigger of any type"
|
|
24547
|
+
});
|
|
24548
|
+
var LegacyClusterSchema = z.object({
|
|
24549
|
+
topic: z.string().regex(/^[a-z0-9-]+$/),
|
|
24550
|
+
file: z.string().regex(/\.md$/),
|
|
24551
|
+
summary: z.string().min(1),
|
|
24552
|
+
triggers: LegacyTriggersSchema
|
|
24553
|
+
});
|
|
24554
|
+
var LegacyIndexSchema = z.object({
|
|
24555
|
+
version: z.literal(1),
|
|
24556
|
+
clusters: z.array(LegacyClusterSchema)
|
|
24557
|
+
});
|
|
24558
|
+
function collectClusterTriggerIds(cluster, triggersById, triggerIdByKey) {
|
|
24559
|
+
const specs = [
|
|
24560
|
+
...cluster.triggers.file_globs.map((p) => ({ kind: "file_glob", pattern: p })),
|
|
24561
|
+
...cluster.triggers.command_patterns.map(
|
|
24562
|
+
(p) => ({ kind: "command_pattern", pattern: p })
|
|
24563
|
+
),
|
|
24564
|
+
...cluster.triggers.keywords.map((p) => ({ kind: "keyword", pattern: p }))
|
|
24565
|
+
];
|
|
24566
|
+
const ids = [];
|
|
24567
|
+
for (const spec of specs) {
|
|
24568
|
+
const key = `${spec.kind}|${spec.pattern}`;
|
|
24569
|
+
let id = triggerIdByKey.get(key);
|
|
24570
|
+
if (id === void 0) {
|
|
24571
|
+
id = makeTriggerId2(spec);
|
|
24572
|
+
triggerIdByKey.set(key, id);
|
|
24573
|
+
triggersById.set(id, { kind: spec.kind, pattern: spec.pattern });
|
|
24574
|
+
}
|
|
24575
|
+
if (!ids.includes(id)) ids.push(id);
|
|
24576
|
+
}
|
|
24577
|
+
return ids;
|
|
24578
|
+
}
|
|
24579
|
+
var TRIGGER_PREFIX2 = {
|
|
24580
|
+
file_glob: "glob",
|
|
24581
|
+
command_pattern: "cmd",
|
|
24582
|
+
keyword: "kw"
|
|
24583
|
+
};
|
|
24584
|
+
function makeTriggerId2(spec) {
|
|
24585
|
+
const hash = createHash("sha1").update(`${spec.kind}|${spec.pattern}`).digest("hex").slice(0, 8);
|
|
24586
|
+
return `t-${TRIGGER_PREFIX2[spec.kind]}-${hash}`;
|
|
24587
|
+
}
|
|
24588
|
+
var RULE_HEADING_RE = /^##\s+Rules\b.*$/i;
|
|
24589
|
+
var NEXT_HEADING_RE = /^##\s+/;
|
|
24590
|
+
var RULE_LINE_RE = /^(\d+)\.\s+(.+?)\s*$/;
|
|
24591
|
+
var EVIDENCE_TAIL_RE = /\s*\(Evidence:?\s+([^)]+)\)\s*$/;
|
|
24592
|
+
var EVIDENCE_REF_RE = /L\d+/g;
|
|
24593
|
+
function parseRulesSection(markdown) {
|
|
24594
|
+
const lines = markdown.split(/\r?\n/);
|
|
24595
|
+
let inRules = false;
|
|
24596
|
+
const rules = [];
|
|
24597
|
+
for (const line of lines) {
|
|
24598
|
+
if (!inRules) {
|
|
24599
|
+
if (RULE_HEADING_RE.test(line)) inRules = true;
|
|
24600
|
+
continue;
|
|
24601
|
+
}
|
|
24602
|
+
if (NEXT_HEADING_RE.test(line)) break;
|
|
24603
|
+
const m = RULE_LINE_RE.exec(line);
|
|
24604
|
+
if (m === null) continue;
|
|
24605
|
+
const ruleIndex = Number(m[1]);
|
|
24606
|
+
let body = m[2];
|
|
24607
|
+
const evidence = [];
|
|
24608
|
+
let tail = EVIDENCE_TAIL_RE.exec(body);
|
|
24609
|
+
while (tail !== null) {
|
|
24610
|
+
const refs = tail[1];
|
|
24611
|
+
const matches = refs.match(EVIDENCE_REF_RE);
|
|
24612
|
+
if (matches !== null) evidence.unshift(...matches);
|
|
24613
|
+
body = body.slice(0, tail.index).trimEnd();
|
|
24614
|
+
tail = EVIDENCE_TAIL_RE.exec(body);
|
|
24615
|
+
}
|
|
24616
|
+
rules.push({ index: ruleIndex, body, evidence });
|
|
24617
|
+
}
|
|
24618
|
+
return rules;
|
|
24619
|
+
}
|
|
24620
|
+
var LEGACY_ARTIFACT_REL = [
|
|
24621
|
+
"index.yaml",
|
|
24622
|
+
"journal.md",
|
|
24623
|
+
"journal.legacy.md",
|
|
24624
|
+
"topics",
|
|
24625
|
+
"distill-ledger.yaml",
|
|
24626
|
+
"distill-proposal.md"
|
|
24627
|
+
];
|
|
24628
|
+
function deleteLegacyArtifacts(baseDir) {
|
|
24629
|
+
const deleted = [];
|
|
24630
|
+
for (const rel2 of LEGACY_ARTIFACT_REL) {
|
|
24631
|
+
const abs = join(baseDir, rel2);
|
|
24632
|
+
if (!existsSync(abs)) continue;
|
|
24633
|
+
rmSync(abs, { recursive: true, force: true });
|
|
24634
|
+
deleted.push(abs);
|
|
24635
|
+
}
|
|
24636
|
+
return deleted;
|
|
24637
|
+
}
|
|
24638
|
+
|
|
24639
|
+
// src/lessons/import-legacy-merge.ts
|
|
24640
|
+
async function mergeLegacy(projectRoot, paths, specs, summaryByTopic, options) {
|
|
24641
|
+
let addedLessons = 0;
|
|
24642
|
+
const addedTriggers = /* @__PURE__ */ new Set();
|
|
24643
|
+
const touchedTopics = /* @__PURE__ */ new Set();
|
|
24644
|
+
await mutateLessonsGraphLocked(projectRoot, (g) => {
|
|
24645
|
+
addedLessons = 0;
|
|
24646
|
+
addedTriggers.clear();
|
|
24647
|
+
touchedTopics.clear();
|
|
24648
|
+
for (const spec of specs) {
|
|
24649
|
+
const result = addLessonInto(g, spec, {
|
|
24650
|
+
allowNewTopic: true,
|
|
24651
|
+
topicSummary: summaryByTopic.get(spec.topic),
|
|
24652
|
+
// Legacy lessons may predate the ≥1-trigger requirement; recover them
|
|
24653
|
+
// as-is rather than dropping historical knowledge.
|
|
24654
|
+
allowNoTrigger: true
|
|
24655
|
+
});
|
|
24656
|
+
if (result.isNewLesson) addedLessons += 1;
|
|
24657
|
+
for (const t of result.newTriggerIds) addedTriggers.add(t);
|
|
24658
|
+
touchedTopics.add(spec.topic);
|
|
24659
|
+
}
|
|
24660
|
+
});
|
|
24661
|
+
const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths.base);
|
|
24662
|
+
return {
|
|
24663
|
+
wroteGraphPath: paths.graph,
|
|
24664
|
+
deletedPaths,
|
|
24665
|
+
topicCount: touchedTopics.size,
|
|
24666
|
+
lessonCount: addedLessons,
|
|
24667
|
+
triggerCount: addedTriggers.size
|
|
24668
|
+
};
|
|
24669
|
+
}
|
|
24670
|
+
|
|
24671
|
+
// src/lessons/import-legacy.ts
|
|
24672
|
+
var LessonsGraphExistsError = class extends Error {
|
|
24673
|
+
code = "LESSONS_GRAPH_EXISTS";
|
|
24674
|
+
constructor() {
|
|
24675
|
+
super("importLegacyLessons: a non-empty lessons.json already exists; pass force to overwrite.");
|
|
24676
|
+
this.name = "LessonsGraphExistsError";
|
|
24677
|
+
}
|
|
24678
|
+
};
|
|
24679
|
+
async function importLegacyLessons(projectRoot, options) {
|
|
24680
|
+
const paths = lessonsPaths(projectRoot);
|
|
24681
|
+
const indexRaw = readFileSync(paths.index, "utf8");
|
|
24682
|
+
const index = LegacyIndexSchema.parse(parse(indexRaw));
|
|
24683
|
+
const topics = {};
|
|
24684
|
+
const triggersById = /* @__PURE__ */ new Map();
|
|
24685
|
+
const triggerIdByKey = /* @__PURE__ */ new Map();
|
|
24686
|
+
const lessons = {};
|
|
24687
|
+
const specs = [];
|
|
24688
|
+
const summaryByTopic = /* @__PURE__ */ new Map();
|
|
24689
|
+
for (const cluster of index.clusters) {
|
|
24690
|
+
topics[cluster.topic] = { summary: cluster.summary };
|
|
24691
|
+
summaryByTopic.set(cluster.topic, cluster.summary);
|
|
24692
|
+
const clusterTriggerIds = collectClusterTriggerIds(cluster, triggersById, triggerIdByKey);
|
|
24693
|
+
const topicFile = join(projectRoot, cluster.file);
|
|
24694
|
+
if (!existsSync(topicFile)) {
|
|
24695
|
+
throw new Error(
|
|
24696
|
+
`importLegacyLessons: declared topic file is missing: ${cluster.file}. Refusing to migrate (legacy artifacts left intact).`
|
|
24697
|
+
);
|
|
24698
|
+
}
|
|
24699
|
+
const topicMarkdown = readFileSync(topicFile, "utf8");
|
|
24700
|
+
for (const { index: ruleIndex, body, evidence } of parseRulesSection(topicMarkdown)) {
|
|
24701
|
+
const lessonEvidence = [
|
|
24702
|
+
`legacy:${cluster.file}#rule-${ruleIndex}`,
|
|
24703
|
+
...evidence.map((e) => `legacy:${e}`)
|
|
24704
|
+
];
|
|
24705
|
+
lessons[`${cluster.topic}-rule-${ruleIndex}`] = {
|
|
24706
|
+
rule: body,
|
|
24707
|
+
topics: [cluster.topic],
|
|
24708
|
+
triggers: clusterTriggerIds,
|
|
24709
|
+
evidence: lessonEvidence,
|
|
24710
|
+
status: "active",
|
|
24711
|
+
createdAt: options.migratedAt
|
|
24712
|
+
};
|
|
24713
|
+
specs.push({
|
|
24714
|
+
rule: body,
|
|
24715
|
+
topic: cluster.topic,
|
|
24716
|
+
triggers: {
|
|
24717
|
+
files: cluster.triggers.file_globs,
|
|
24718
|
+
commands: cluster.triggers.command_patterns,
|
|
24719
|
+
keywords: cluster.triggers.keywords
|
|
24720
|
+
},
|
|
24721
|
+
evidence: lessonEvidence,
|
|
24722
|
+
createdAt: options.migratedAt
|
|
24723
|
+
});
|
|
24724
|
+
}
|
|
24725
|
+
}
|
|
24726
|
+
if (options.merge === true)
|
|
24727
|
+
return mergeLegacy(projectRoot, paths, specs, summaryByTopic, options);
|
|
24728
|
+
const triggers = Object.fromEntries(triggersById.entries());
|
|
24729
|
+
await mutateLessonsGraphLocked(projectRoot, (g) => {
|
|
24730
|
+
const populated = Object.keys(g.lessons).length > 0 || Object.keys(g.topics).length > 0 || Object.keys(g.triggers).length > 0;
|
|
24731
|
+
if (options.force !== true && populated) {
|
|
24732
|
+
throw new LessonsGraphExistsError();
|
|
24733
|
+
}
|
|
24734
|
+
g.version = 1;
|
|
24735
|
+
g.lessons = lessons;
|
|
24736
|
+
g.topics = topics;
|
|
24737
|
+
g.triggers = triggers;
|
|
24738
|
+
});
|
|
24739
|
+
const deletedPaths = options.deleteLegacy === false ? [] : deleteLegacyArtifacts(paths.base);
|
|
24740
|
+
return {
|
|
24741
|
+
wroteGraphPath: paths.graph,
|
|
24742
|
+
deletedPaths,
|
|
24743
|
+
topicCount: Object.keys(topics).length,
|
|
24744
|
+
lessonCount: Object.keys(lessons).length,
|
|
24745
|
+
triggerCount: triggersById.size
|
|
24746
|
+
};
|
|
24747
|
+
}
|
|
24748
|
+
|
|
24749
|
+
// src/lessons/auto-migrate.ts
|
|
24750
|
+
function todayIso2() {
|
|
24751
|
+
const now = /* @__PURE__ */ new Date();
|
|
24752
|
+
const y = now.getUTCFullYear();
|
|
24753
|
+
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
24754
|
+
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
24755
|
+
return `${y}-${m}-${d}`;
|
|
24756
|
+
}
|
|
24757
|
+
async function maybeAutoMigrateLessons(projectRoot) {
|
|
24758
|
+
if (existsSync(graphFilePath(projectRoot))) return false;
|
|
24759
|
+
const paths = lessonsPaths(projectRoot);
|
|
24760
|
+
if (!existsSync(paths.index)) return false;
|
|
24761
|
+
try {
|
|
24762
|
+
await importLegacyLessons(projectRoot, { migratedAt: todayIso2() });
|
|
24763
|
+
return true;
|
|
24764
|
+
} catch (err) {
|
|
24765
|
+
if (err instanceof LessonsGraphExistsError) return false;
|
|
24766
|
+
throw err;
|
|
24767
|
+
}
|
|
24768
|
+
}
|
|
24769
|
+
|
|
24770
|
+
// src/utils/filesystem/process-lock.ts
|
|
24771
|
+
init_errors();
|
|
24772
|
+
var DEFAULT_STALE_MS = 6e4;
|
|
24773
|
+
var DEFAULT_RETRIES = 30;
|
|
24774
|
+
var DEFAULT_RETRY_DELAY_MS = 200;
|
|
24775
|
+
var YOUNG_LOCK_GRACE_MS = 2e3;
|
|
24776
|
+
async function acquireProcessLock(lockPath, opts = {}) {
|
|
24777
|
+
const retries = opts.retries ?? DEFAULT_RETRIES;
|
|
24778
|
+
const delay = opts.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
24779
|
+
const stale = opts.staleMs ?? DEFAULT_STALE_MS;
|
|
24780
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
24781
|
+
let attempt = 0;
|
|
24782
|
+
while (true) {
|
|
24783
|
+
const acquired = await tryAcquire(lockPath);
|
|
24784
|
+
if (acquired) return acquired;
|
|
24785
|
+
const existing = await inspectLock(lockPath);
|
|
24786
|
+
if (existing !== "young" && isStale(existing, stale)) {
|
|
24787
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => {
|
|
24788
|
+
});
|
|
24789
|
+
continue;
|
|
24790
|
+
}
|
|
24791
|
+
if (attempt >= retries) {
|
|
24792
|
+
const holder = existing === "young" ? null : existing;
|
|
24793
|
+
throw new LockAcquisitionError(lockPath, describeHolder(holder), { label: opts.label });
|
|
23169
24794
|
}
|
|
24795
|
+
attempt++;
|
|
24796
|
+
await sleep2(delay);
|
|
23170
24797
|
}
|
|
23171
|
-
if (current !== null) bullets.push(current);
|
|
23172
|
-
return bullets;
|
|
23173
24798
|
}
|
|
23174
|
-
function
|
|
23175
|
-
|
|
24799
|
+
async function tryAcquire(lockPath) {
|
|
24800
|
+
try {
|
|
24801
|
+
await mkdir(lockPath, { recursive: false });
|
|
24802
|
+
} catch (err) {
|
|
24803
|
+
if (err.code === "EEXIST") return null;
|
|
24804
|
+
throw err;
|
|
24805
|
+
}
|
|
24806
|
+
const metadataPath = join(lockPath, "holder.json");
|
|
24807
|
+
const metadata = {
|
|
24808
|
+
pid: process.pid,
|
|
24809
|
+
started: Date.now(),
|
|
24810
|
+
hostname: getHostname()
|
|
24811
|
+
};
|
|
24812
|
+
await writeFile(metadataPath, JSON.stringify(metadata), "utf-8");
|
|
24813
|
+
let released = false;
|
|
24814
|
+
const cleanup = () => {
|
|
24815
|
+
if (released) return;
|
|
24816
|
+
released = true;
|
|
24817
|
+
try {
|
|
24818
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
24819
|
+
} catch {
|
|
24820
|
+
}
|
|
24821
|
+
};
|
|
24822
|
+
const signalHandler = (signal) => {
|
|
24823
|
+
cleanup();
|
|
24824
|
+
process.kill(process.pid, signal);
|
|
24825
|
+
};
|
|
24826
|
+
process.once("SIGINT", signalHandler);
|
|
24827
|
+
process.once("SIGTERM", signalHandler);
|
|
24828
|
+
process.once("exit", cleanup);
|
|
24829
|
+
return async () => {
|
|
24830
|
+
if (released) return;
|
|
24831
|
+
released = true;
|
|
24832
|
+
process.off("SIGINT", signalHandler);
|
|
24833
|
+
process.off("SIGTERM", signalHandler);
|
|
24834
|
+
process.off("exit", cleanup);
|
|
24835
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => {
|
|
24836
|
+
});
|
|
24837
|
+
};
|
|
23176
24838
|
}
|
|
23177
|
-
function
|
|
23178
|
-
|
|
24839
|
+
async function inspectLock(lockPath) {
|
|
24840
|
+
try {
|
|
24841
|
+
const raw = await readFile(join(lockPath, "holder.json"), "utf-8");
|
|
24842
|
+
const parsed = JSON.parse(raw);
|
|
24843
|
+
if (!isLockMetadata(parsed)) return null;
|
|
24844
|
+
return parsed;
|
|
24845
|
+
} catch {
|
|
23179
24846
|
try {
|
|
23180
|
-
|
|
24847
|
+
const info = await stat(lockPath);
|
|
24848
|
+
const ageMs = Date.now() - info.mtimeMs;
|
|
24849
|
+
if (ageMs < YOUNG_LOCK_GRACE_MS) return "young";
|
|
23181
24850
|
} catch {
|
|
23182
|
-
return false;
|
|
23183
24851
|
}
|
|
23184
|
-
|
|
24852
|
+
return null;
|
|
24853
|
+
}
|
|
24854
|
+
}
|
|
24855
|
+
function isStale(meta, staleMs) {
|
|
24856
|
+
if (!meta) return true;
|
|
24857
|
+
const age = Date.now() - meta.started;
|
|
24858
|
+
if (age > staleMs) return true;
|
|
24859
|
+
if (meta.hostname && meta.hostname !== getHostname()) return false;
|
|
24860
|
+
return !isProcessAlive(meta.pid);
|
|
23185
24861
|
}
|
|
23186
|
-
function
|
|
23187
|
-
|
|
23188
|
-
|
|
23189
|
-
|
|
23190
|
-
|
|
23191
|
-
|
|
23192
|
-
|
|
23193
|
-
|
|
23194
|
-
|
|
23195
|
-
|
|
23196
|
-
|
|
23197
|
-
|
|
23198
|
-
|
|
23199
|
-
|
|
23200
|
-
|
|
24862
|
+
function isProcessAlive(pid) {
|
|
24863
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
24864
|
+
try {
|
|
24865
|
+
process.kill(pid, 0);
|
|
24866
|
+
return true;
|
|
24867
|
+
} catch (err) {
|
|
24868
|
+
return err.code === "EPERM";
|
|
24869
|
+
}
|
|
24870
|
+
}
|
|
24871
|
+
function describeHolder(meta) {
|
|
24872
|
+
if (!meta) return "unknown (unreadable lock metadata)";
|
|
24873
|
+
const host = meta.hostname ? `${meta.hostname}:` : "";
|
|
24874
|
+
return `${host}pid ${meta.pid} (running ${Date.now() - meta.started}ms)`;
|
|
24875
|
+
}
|
|
24876
|
+
function isLockMetadata(value) {
|
|
24877
|
+
if (typeof value !== "object" || value === null) return false;
|
|
24878
|
+
const v = value;
|
|
24879
|
+
return typeof v.pid === "number" && typeof v.started === "number";
|
|
24880
|
+
}
|
|
24881
|
+
function getHostname() {
|
|
24882
|
+
return hostname();
|
|
24883
|
+
}
|
|
24884
|
+
function sleep2(ms) {
|
|
24885
|
+
return new Promise((resolve9) => setTimeout(resolve9, ms));
|
|
24886
|
+
}
|
|
24887
|
+
|
|
24888
|
+
// src/lessons/lessons-lock.ts
|
|
24889
|
+
var LESSONS_LOCK_FILENAME = ".lessons.lock";
|
|
24890
|
+
function lessonsLockPath(projectRoot) {
|
|
24891
|
+
return resolve(projectRoot, ".agentsmesh/lessons", LESSONS_LOCK_FILENAME);
|
|
24892
|
+
}
|
|
24893
|
+
async function acquireLessonsLock(projectRoot, opts = {}) {
|
|
24894
|
+
const lockPath = lessonsLockPath(projectRoot);
|
|
24895
|
+
await mkdir(dirname(lockPath), { recursive: true });
|
|
24896
|
+
return acquireProcessLock(lockPath, { ...opts, label: "lessons lock" });
|
|
24897
|
+
}
|
|
24898
|
+
|
|
24899
|
+
// src/lessons/mutate.ts
|
|
24900
|
+
function emptyGraph() {
|
|
24901
|
+
return { version: 1, lessons: {}, topics: {}, triggers: {} };
|
|
24902
|
+
}
|
|
24903
|
+
async function mutateLessonsGraphLocked(projectRoot, mutator, options = {}) {
|
|
24904
|
+
const release = await acquireLessonsLock(projectRoot, { retries: options.retries });
|
|
24905
|
+
try {
|
|
24906
|
+
const graph = tryLoadLessonsGraph(projectRoot) ?? emptyGraph();
|
|
24907
|
+
const result = await mutator(graph);
|
|
24908
|
+
const report = validateLessonsGraph(graph);
|
|
24909
|
+
if (!report.ok) {
|
|
24910
|
+
const errors = report.findings.filter((f) => f.level === "error").map((f) => `${f.code}: ${f.message}`).join("; ");
|
|
24911
|
+
throw new Error(`mutateLessonsGraph: refusing to write an invalid graph \u2014 ${errors}`);
|
|
24912
|
+
}
|
|
24913
|
+
saveLessonsGraph(projectRoot, graph);
|
|
24914
|
+
return result;
|
|
24915
|
+
} finally {
|
|
24916
|
+
await release();
|
|
24917
|
+
}
|
|
24918
|
+
}
|
|
24919
|
+
async function mutateLessonsGraph(projectRoot, mutator, options = {}) {
|
|
24920
|
+
await maybeAutoMigrateLessons(projectRoot);
|
|
24921
|
+
return mutateLessonsGraphLocked(projectRoot, mutator, options);
|
|
24922
|
+
}
|
|
24923
|
+
|
|
24924
|
+
// src/lessons/trigger-effectiveness.ts
|
|
24925
|
+
function ineffectiveTriggers(graph, triggerIds) {
|
|
24926
|
+
const out2 = [];
|
|
24927
|
+
for (const id of triggerIds) {
|
|
24928
|
+
const trigger = graph.triggers[id];
|
|
24929
|
+
if (trigger === void 0) continue;
|
|
24930
|
+
const reason = ineffectiveReason(trigger.kind, trigger.pattern);
|
|
24931
|
+
if (reason !== null) out2.push({ id, kind: trigger.kind, pattern: trigger.pattern, reason });
|
|
24932
|
+
}
|
|
24933
|
+
return out2;
|
|
24934
|
+
}
|
|
24935
|
+
function ineffectiveReason(kind, pattern) {
|
|
24936
|
+
if (kind === "keyword") {
|
|
24937
|
+
if (tokenize(pattern).length === 0) {
|
|
24938
|
+
return "keyword has no matchable token after stopword filtering \u2014 it cannot fire on the mandatory --file/--cmd recall path";
|
|
24939
|
+
}
|
|
24940
|
+
if (keywordNeedleLosesTokens(pattern)) {
|
|
24941
|
+
return "keyword contains stopwords/short words, so its needle can never appear as a contiguous run on the mandatory --file/--cmd recall path";
|
|
24942
|
+
}
|
|
24943
|
+
return null;
|
|
24944
|
+
}
|
|
24945
|
+
if (kind === "command_pattern") {
|
|
24946
|
+
let valid = true;
|
|
24947
|
+
try {
|
|
24948
|
+
new RegExp(pattern);
|
|
24949
|
+
} catch {
|
|
24950
|
+
valid = false;
|
|
24951
|
+
}
|
|
24952
|
+
if (!valid) {
|
|
24953
|
+
return "invalid regex \u2014 recall compiles it with new RegExp and swallows the throw as a non-match, so it never fires";
|
|
23201
24954
|
}
|
|
24955
|
+
if (!isSafeRegexPattern(pattern)) {
|
|
24956
|
+
return "regex is outside the provably-linear engine \u2014 recall skips it (ReDoS guard), so it never fires";
|
|
24957
|
+
}
|
|
24958
|
+
return null;
|
|
24959
|
+
}
|
|
24960
|
+
return null;
|
|
24961
|
+
}
|
|
24962
|
+
function blockingDeadTriggers(graph, triggerIds) {
|
|
24963
|
+
return ineffectiveTriggers(graph, triggerIds).filter((t) => t.kind !== "command_pattern");
|
|
24964
|
+
}
|
|
24965
|
+
|
|
24966
|
+
// src/lessons/add.ts
|
|
24967
|
+
function countInputTriggers(triggers) {
|
|
24968
|
+
return (triggers.files?.length ?? 0) + (triggers.commands?.length ?? 0) + (triggers.keywords?.length ?? 0);
|
|
24969
|
+
}
|
|
24970
|
+
async function addLesson(projectRoot, input, options = {}) {
|
|
24971
|
+
return mutateLessonsGraph(projectRoot, (graph) => addLessonInto(graph, input, options), {
|
|
24972
|
+
retries: options.retries
|
|
23202
24973
|
});
|
|
23203
24974
|
}
|
|
23204
|
-
|
|
23205
|
-
|
|
23206
|
-
|
|
23207
|
-
|
|
23208
|
-
|
|
23209
|
-
|
|
23210
|
-
|
|
23211
|
-
|
|
23212
|
-
|
|
23213
|
-
|
|
23214
|
-
|
|
23215
|
-
|
|
23216
|
-
|
|
23217
|
-
|
|
23218
|
-
|
|
23219
|
-
|
|
23220
|
-
const
|
|
23221
|
-
|
|
23222
|
-
|
|
23223
|
-
|
|
23224
|
-
|
|
23225
|
-
|
|
23226
|
-
|
|
23227
|
-
|
|
23228
|
-
|
|
23229
|
-
|
|
23230
|
-
|
|
23231
|
-
|
|
23232
|
-
|
|
23233
|
-
|
|
23234
|
-
|
|
23235
|
-
|
|
23236
|
-
|
|
23237
|
-
|
|
23238
|
-
|
|
23239
|
-
|
|
23240
|
-
|
|
23241
|
-
|
|
23242
|
-
|
|
23243
|
-
|
|
23244
|
-
|
|
23245
|
-
|
|
23246
|
-
|
|
23247
|
-
|
|
23248
|
-
|
|
23249
|
-
|
|
24975
|
+
function addLessonInto(graph, input, options) {
|
|
24976
|
+
const ruleKey = normalizeRule2(input.rule);
|
|
24977
|
+
const trimmedRule = input.rule.trim();
|
|
24978
|
+
if (trimmedRule.length > MAX_RULE_LENGTH) {
|
|
24979
|
+
throw new RuleTooLongError(trimmedRule.length, MAX_RULE_LENGTH);
|
|
24980
|
+
}
|
|
24981
|
+
const existingId = findExistingLessonByRule(graph, ruleKey);
|
|
24982
|
+
const isNewTopic = graph.topics[input.topic] === void 0;
|
|
24983
|
+
if (isNewTopic) {
|
|
24984
|
+
if (options.allowNewTopic !== true) throw new UnknownTopicError(input.topic);
|
|
24985
|
+
if (options.topicSummary === void 0 || options.topicSummary.length === 0) {
|
|
24986
|
+
throw new Error(`addLesson: new topic "${input.topic}" requires topicSummary.`);
|
|
24987
|
+
}
|
|
24988
|
+
graph.topics[input.topic] = { summary: options.topicSummary };
|
|
24989
|
+
}
|
|
24990
|
+
if (options.allowNoTrigger !== true) {
|
|
24991
|
+
const existingTriggers = existingId !== null ? graph.lessons[existingId]?.triggers.length ?? 0 : 0;
|
|
24992
|
+
if (countInputTriggers(input.triggers) === 0 && existingTriggers === 0) {
|
|
24993
|
+
throw new NoTriggerError();
|
|
24994
|
+
}
|
|
24995
|
+
}
|
|
24996
|
+
const { triggerIds, newTriggerIds } = mergeTriggers(graph, input.triggers);
|
|
24997
|
+
if (options.allowNoTrigger !== true) {
|
|
24998
|
+
const resultingTriggers = existingId !== null ? union(graph.lessons[existingId].triggers, triggerIds) : triggerIds;
|
|
24999
|
+
const blockingDead = blockingDeadTriggers(graph, resultingTriggers);
|
|
25000
|
+
if (resultingTriggers.length > 0 && blockingDead.length === resultingTriggers.length) {
|
|
25001
|
+
throw new UnrecallableLessonError(blockingDead);
|
|
25002
|
+
}
|
|
25003
|
+
}
|
|
25004
|
+
if (existingId !== null) {
|
|
25005
|
+
const existing = graph.lessons[existingId];
|
|
25006
|
+
graph.lessons[existingId] = {
|
|
25007
|
+
...existing,
|
|
25008
|
+
topics: union(existing.topics, [input.topic]),
|
|
25009
|
+
triggers: union(existing.triggers, triggerIds),
|
|
25010
|
+
evidence: union(existing.evidence, input.evidence ?? []),
|
|
25011
|
+
...existing.rationale === void 0 && input.rationale !== void 0 ? { rationale: input.rationale } : {}
|
|
25012
|
+
};
|
|
25013
|
+
return {
|
|
25014
|
+
id: existingId,
|
|
25015
|
+
isNewLesson: false,
|
|
25016
|
+
isNewTopic,
|
|
25017
|
+
newTriggerIds,
|
|
25018
|
+
// Near-duplicate detection is meaningless on an upsert (the lesson IS the
|
|
25019
|
+
// match), so only DEAD_GLOB/hygiene warnings apply here.
|
|
25020
|
+
warnings: inspectCapturedLesson(graph, existingId, options.knownPaths)
|
|
25021
|
+
};
|
|
25022
|
+
}
|
|
25023
|
+
const id = makeLessonId(graph, input.topic, ruleKey);
|
|
25024
|
+
graph.lessons[id] = {
|
|
25025
|
+
rule: trimmedRule,
|
|
25026
|
+
topics: [input.topic],
|
|
25027
|
+
triggers: triggerIds,
|
|
25028
|
+
evidence: input.evidence === void 0 ? [] : [...input.evidence],
|
|
25029
|
+
status: "active",
|
|
25030
|
+
createdAt: input.createdAt ?? todayIso(),
|
|
25031
|
+
...input.rationale === void 0 ? {} : { rationale: input.rationale }
|
|
25032
|
+
};
|
|
25033
|
+
const warnings = inspectCapturedLesson(graph, id, options.knownPaths);
|
|
25034
|
+
const nearDup = nearDuplicateWarning(graph, id);
|
|
25035
|
+
return {
|
|
25036
|
+
id,
|
|
25037
|
+
isNewLesson: true,
|
|
25038
|
+
isNewTopic,
|
|
25039
|
+
newTriggerIds,
|
|
25040
|
+
warnings: nearDup === null ? warnings : [...warnings, nearDup]
|
|
25041
|
+
};
|
|
25042
|
+
}
|
|
25043
|
+
function findExistingLessonByRule(graph, ruleKey) {
|
|
25044
|
+
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
25045
|
+
if (lesson.status !== "active") continue;
|
|
25046
|
+
if (normalizeRule2(lesson.rule) === ruleKey) return id;
|
|
25047
|
+
}
|
|
25048
|
+
return null;
|
|
25049
|
+
}
|
|
25050
|
+
|
|
25051
|
+
// src/lessons/ranking-signals.ts
|
|
25052
|
+
function buildFanout(graph) {
|
|
25053
|
+
const fanout = /* @__PURE__ */ new Map();
|
|
25054
|
+
for (const lesson of Object.values(graph.lessons)) {
|
|
25055
|
+
if (lesson.status !== "active") continue;
|
|
25056
|
+
for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
|
|
25057
|
+
}
|
|
25058
|
+
return fanout;
|
|
25059
|
+
}
|
|
25060
|
+
function buildTopicCoherence(matches) {
|
|
25061
|
+
const topicCount = /* @__PURE__ */ new Map();
|
|
25062
|
+
for (const { lesson } of matches) {
|
|
25063
|
+
for (const t of lesson.topics) topicCount.set(t, (topicCount.get(t) ?? 0) + 1);
|
|
25064
|
+
}
|
|
25065
|
+
const coherence = /* @__PURE__ */ new Map();
|
|
25066
|
+
for (const { id, lesson } of matches) {
|
|
25067
|
+
let best = 0;
|
|
25068
|
+
for (const t of lesson.topics) best = Math.max(best, topicCount.get(t));
|
|
25069
|
+
coherence.set(id, best);
|
|
25070
|
+
}
|
|
25071
|
+
return coherence;
|
|
25072
|
+
}
|
|
25073
|
+
function recallLogPath(projectRoot) {
|
|
25074
|
+
return join(lessonsPaths(projectRoot).base, "recall-log.jsonl");
|
|
25075
|
+
}
|
|
25076
|
+
|
|
25077
|
+
// src/lessons/capture-telemetry.ts
|
|
25078
|
+
function captureLogPath(projectRoot) {
|
|
25079
|
+
return join(lessonsPaths(projectRoot).base, "capture-log.jsonl");
|
|
25080
|
+
}
|
|
25081
|
+
|
|
25082
|
+
// src/lessons/keyword-match.ts
|
|
25083
|
+
function splitTokens(text) {
|
|
25084
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
|
|
25085
|
+
}
|
|
25086
|
+
function deriveHaystackTokens(query) {
|
|
25087
|
+
const parts = [];
|
|
25088
|
+
if (query.file !== void 0) parts.push(query.file);
|
|
25089
|
+
if (query.command !== void 0) parts.push(query.command);
|
|
25090
|
+
return parts.length === 0 ? [] : splitTokens(parts.join(" "));
|
|
25091
|
+
}
|
|
25092
|
+
function containsRun(needle, hay) {
|
|
25093
|
+
if (needle.length === 0) return false;
|
|
25094
|
+
for (let i = 0; i + needle.length <= hay.length; i += 1) {
|
|
25095
|
+
let hit = true;
|
|
25096
|
+
for (let j = 0; j < needle.length; j += 1) {
|
|
25097
|
+
if (hay[i + j] !== needle[j]) {
|
|
25098
|
+
hit = false;
|
|
25099
|
+
break;
|
|
23250
25100
|
}
|
|
23251
|
-
return {
|
|
23252
|
-
cluster,
|
|
23253
|
-
relativePath: cluster.file,
|
|
23254
|
-
filePath,
|
|
23255
|
-
content
|
|
23256
|
-
};
|
|
23257
25101
|
}
|
|
25102
|
+
if (hit) return true;
|
|
25103
|
+
}
|
|
25104
|
+
return false;
|
|
25105
|
+
}
|
|
25106
|
+
function keywordMatches(pattern, query) {
|
|
25107
|
+
if (query.keyword !== void 0 && query.keyword.toLowerCase().includes(pattern.toLowerCase())) {
|
|
25108
|
+
return true;
|
|
25109
|
+
}
|
|
25110
|
+
return containsRun(tokenize(pattern), deriveHaystackTokens(query));
|
|
25111
|
+
}
|
|
25112
|
+
|
|
25113
|
+
// src/lessons/query.ts
|
|
25114
|
+
var COMMAND_MATCH_BUDGET = 5e6;
|
|
25115
|
+
function queryLessons(graph, query) {
|
|
25116
|
+
if (query.file === void 0 && query.command === void 0 && query.keyword === void 0) {
|
|
25117
|
+
return [];
|
|
25118
|
+
}
|
|
25119
|
+
const matchedTriggerIds = collectMatchedTriggerIds(graph, query);
|
|
25120
|
+
const matched = [];
|
|
25121
|
+
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
25122
|
+
if (lesson.status !== "active") continue;
|
|
25123
|
+
if (lesson.triggers.some((t) => matchedTriggerIds.has(t))) {
|
|
25124
|
+
matched.push({ id, lesson });
|
|
25125
|
+
}
|
|
25126
|
+
}
|
|
25127
|
+
matched.sort((a, b) => a.id < b.id ? -1 : 1);
|
|
25128
|
+
return matched;
|
|
25129
|
+
}
|
|
25130
|
+
function collectMatchedTriggersByKind(graph, query) {
|
|
25131
|
+
const byKind = {
|
|
25132
|
+
file_glob: /* @__PURE__ */ new Set(),
|
|
25133
|
+
command_pattern: /* @__PURE__ */ new Set(),
|
|
25134
|
+
keyword: /* @__PURE__ */ new Set()
|
|
25135
|
+
};
|
|
25136
|
+
const budget = { remaining: COMMAND_MATCH_BUDGET };
|
|
25137
|
+
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
25138
|
+
if (triggerMatches(trigger, query, budget)) byKind[trigger.kind].add(id);
|
|
25139
|
+
}
|
|
25140
|
+
return byKind;
|
|
25141
|
+
}
|
|
25142
|
+
function collectMatchedTriggerIds(graph, query) {
|
|
25143
|
+
const { file_glob, command_pattern, keyword } = collectMatchedTriggersByKind(graph, query);
|
|
25144
|
+
return /* @__PURE__ */ new Set([...file_glob, ...command_pattern, ...keyword]);
|
|
25145
|
+
}
|
|
25146
|
+
function triggerMatches(trigger, query, budget) {
|
|
25147
|
+
switch (trigger.kind) {
|
|
25148
|
+
case "file_glob":
|
|
25149
|
+
if (query.file === void 0) return false;
|
|
25150
|
+
return picomatch(trigger.pattern, { dot: true })(query.file);
|
|
25151
|
+
case "command_pattern": {
|
|
25152
|
+
if (query.command === void 0) return false;
|
|
25153
|
+
const matcher = getCommandMatcher(trigger.pattern);
|
|
25154
|
+
return matcher !== null && matcher.test(query.command, budget);
|
|
25155
|
+
}
|
|
25156
|
+
case "keyword":
|
|
25157
|
+
return keywordMatches(trigger.pattern, query);
|
|
25158
|
+
}
|
|
25159
|
+
}
|
|
25160
|
+
|
|
25161
|
+
// src/lessons/ranking.ts
|
|
25162
|
+
var DEFAULT_RECALL_LIMIT = 10;
|
|
25163
|
+
var DEFAULT_RECALL_MAX_TOKENS = 400;
|
|
25164
|
+
var RRF_K = 60;
|
|
25165
|
+
var SPECIFICITY_WEIGHT = 3;
|
|
25166
|
+
var TOPIC_COHERENCE_WEIGHT = 2;
|
|
25167
|
+
var BM25_WEIGHT = 1;
|
|
25168
|
+
function rankMap(items) {
|
|
25169
|
+
const sorted = [...items].sort(
|
|
25170
|
+
(a, b) => b.value !== a.value ? b.value - a.value : a.id < b.id ? -1 : 1
|
|
23258
25171
|
);
|
|
25172
|
+
const ranks = /* @__PURE__ */ new Map();
|
|
25173
|
+
let prevValue = null;
|
|
25174
|
+
let prevRank = 0;
|
|
25175
|
+
sorted.forEach((item, i) => {
|
|
25176
|
+
const rank = prevValue !== null && item.value === prevValue ? prevRank : i + 1;
|
|
25177
|
+
ranks.set(item.id, rank);
|
|
25178
|
+
prevValue = item.value;
|
|
25179
|
+
prevRank = rank;
|
|
25180
|
+
});
|
|
25181
|
+
return ranks;
|
|
25182
|
+
}
|
|
25183
|
+
function estTokens(rule) {
|
|
25184
|
+
return Math.ceil(rule.length / 4);
|
|
25185
|
+
}
|
|
25186
|
+
function rankLessons(graph, query, matches, options = {}) {
|
|
25187
|
+
if (matches.length === 0) return [];
|
|
25188
|
+
const terms = queryTerms(query);
|
|
25189
|
+
const corpus = buildCorpus(graph);
|
|
25190
|
+
const fanout = buildFanout(graph);
|
|
25191
|
+
const coherence = buildTopicCoherence(matches);
|
|
25192
|
+
const matchedTriggerIds = collectMatchedTriggerIds(graph, query);
|
|
25193
|
+
const scored = matches.map(({ id, lesson }) => {
|
|
25194
|
+
const hitTriggers = lesson.triggers.filter((t) => matchedTriggerIds.has(t));
|
|
25195
|
+
let specificity = 0;
|
|
25196
|
+
for (const t of hitTriggers) specificity = Math.max(specificity, 1 / fanout.get(t));
|
|
25197
|
+
return {
|
|
25198
|
+
id,
|
|
25199
|
+
lesson,
|
|
25200
|
+
bm25: bm25(terms, lesson.rule, corpus),
|
|
25201
|
+
specificity,
|
|
25202
|
+
// `id` is a matched lesson, and buildTopicCoherence keys every matched id.
|
|
25203
|
+
topicCoherence: coherence.get(id),
|
|
25204
|
+
matchedTriggers: hitTriggers
|
|
25205
|
+
};
|
|
25206
|
+
});
|
|
25207
|
+
const bm25Ranks = rankMap(scored.map((s) => ({ id: s.id, value: s.bm25 })));
|
|
25208
|
+
const specRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.specificity })));
|
|
25209
|
+
const topicRanks = rankMap(scored.map((s) => ({ id: s.id, value: s.topicCoherence })));
|
|
25210
|
+
const ranked = scored.map((s) => ({
|
|
25211
|
+
id: s.id,
|
|
25212
|
+
lesson: s.lesson,
|
|
25213
|
+
score: SPECIFICITY_WEIGHT / (RRF_K + specRanks.get(s.id)) + TOPIC_COHERENCE_WEIGHT / (RRF_K + topicRanks.get(s.id)) + BM25_WEIGHT / (RRF_K + bm25Ranks.get(s.id)),
|
|
25214
|
+
reason: {
|
|
25215
|
+
matchedTriggers: s.matchedTriggers,
|
|
25216
|
+
bm25: s.bm25,
|
|
25217
|
+
specificity: s.specificity,
|
|
25218
|
+
topicCoherence: s.topicCoherence
|
|
25219
|
+
}
|
|
25220
|
+
})).sort((a, b) => {
|
|
25221
|
+
if (b.score !== a.score) return b.score - a.score;
|
|
25222
|
+
const ca = a.lesson.createdAt;
|
|
25223
|
+
const cb = b.lesson.createdAt;
|
|
25224
|
+
if (ca !== cb) return ca < cb ? 1 : -1;
|
|
25225
|
+
return a.id < b.id ? -1 : 1;
|
|
25226
|
+
});
|
|
25227
|
+
return applyCaps(ranked, options);
|
|
25228
|
+
}
|
|
25229
|
+
function applyCaps(ranked, options) {
|
|
25230
|
+
let out2 = ranked;
|
|
25231
|
+
if (options.limit !== void 0 && options.limit >= 0) out2 = out2.slice(0, options.limit);
|
|
25232
|
+
if (options.maxTokens !== void 0 && out2.length > 0) {
|
|
25233
|
+
const budgeted = [out2[0]];
|
|
25234
|
+
let used = estTokens(out2[0].lesson.rule);
|
|
25235
|
+
for (const row of out2.slice(1)) {
|
|
25236
|
+
const cost = estTokens(row.lesson.rule);
|
|
25237
|
+
if (used + cost > options.maxTokens) break;
|
|
25238
|
+
used += cost;
|
|
25239
|
+
budgeted.push(row);
|
|
25240
|
+
}
|
|
25241
|
+
out2 = budgeted;
|
|
25242
|
+
}
|
|
25243
|
+
return out2;
|
|
23259
25244
|
}
|
|
23260
|
-
function
|
|
23261
|
-
|
|
23262
|
-
|
|
23263
|
-
|
|
23264
|
-
|
|
23265
|
-
|
|
23266
|
-
sentence(input.rootCause),
|
|
23267
|
-
sentence(input.rule)
|
|
23268
|
-
].join(" ");
|
|
23269
|
-
}
|
|
23270
|
-
function appendLessonToJournal(projectRoot, input) {
|
|
23271
|
-
const journalPath = lessonsPaths(projectRoot).journal;
|
|
23272
|
-
const bullet = formatLessonBullet(input);
|
|
23273
|
-
const current = existsSync(journalPath) ? readFileSync(journalPath, "utf8") : "";
|
|
23274
|
-
const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
23275
|
-
const lineNumber = nextLineNumber(current);
|
|
23276
|
-
mkdirSync(dirname(journalPath), { recursive: true });
|
|
23277
|
-
appendFileSync(journalPath, `${prefix}${bullet}
|
|
23278
|
-
`, "utf8");
|
|
23279
|
-
return { journalPath, bullet, lineNumber };
|
|
25245
|
+
function defaultLessonsConfig() {
|
|
25246
|
+
return {
|
|
25247
|
+
recallLimit: DEFAULT_RECALL_LIMIT,
|
|
25248
|
+
recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
|
|
25249
|
+
autoPrune: false
|
|
25250
|
+
};
|
|
23280
25251
|
}
|
|
23281
|
-
|
|
23282
|
-
|
|
23283
|
-
|
|
25252
|
+
|
|
25253
|
+
// src/lessons/merge.ts
|
|
25254
|
+
async function mergeLessons(projectRoot, loserId, keeperId, options = {}) {
|
|
25255
|
+
return mutateLessonsGraph(projectRoot, (graph) => mergeInto(graph, loserId, keeperId), {
|
|
25256
|
+
retries: options.retries
|
|
25257
|
+
});
|
|
25258
|
+
}
|
|
25259
|
+
function mergeInto(graph, loserId, keeperId) {
|
|
25260
|
+
if (loserId === keeperId) {
|
|
25261
|
+
throw new Error(`mergeLessons: cannot merge lesson "${loserId}" into itself.`);
|
|
23284
25262
|
}
|
|
23285
|
-
const
|
|
23286
|
-
|
|
23287
|
-
const
|
|
23288
|
-
if (
|
|
23289
|
-
|
|
25263
|
+
const loser = graph.lessons[loserId];
|
|
25264
|
+
if (loser === void 0) throw new Error(`mergeLessons: unknown lesson "${loserId}".`);
|
|
25265
|
+
const keeper = graph.lessons[keeperId];
|
|
25266
|
+
if (keeper === void 0) throw new Error(`mergeLessons: unknown lesson "${keeperId}".`);
|
|
25267
|
+
if (keeper.status !== "active") {
|
|
25268
|
+
throw new Error(`mergeLessons: keeper "${keeperId}" is not active (status: ${keeper.status}).`);
|
|
23290
25269
|
}
|
|
23291
|
-
|
|
25270
|
+
if (loser.status !== "active") {
|
|
25271
|
+
throw new Error(
|
|
25272
|
+
`mergeLessons: loser "${loserId}" is already ${loser.status}; nothing to merge.`
|
|
25273
|
+
);
|
|
25274
|
+
}
|
|
25275
|
+
graph.lessons[keeperId] = {
|
|
25276
|
+
...keeper,
|
|
25277
|
+
triggers: union2(keeper.triggers, loser.triggers),
|
|
25278
|
+
topics: union2(keeper.topics, loser.topics),
|
|
25279
|
+
evidence: union2(keeper.evidence, loser.evidence)
|
|
25280
|
+
};
|
|
25281
|
+
graph.lessons[loserId] = { ...loser, status: "superseded", supersededBy: keeperId };
|
|
25282
|
+
return { loserId, keeperId };
|
|
23292
25283
|
}
|
|
23293
|
-
function
|
|
23294
|
-
|
|
23295
|
-
|
|
25284
|
+
function union2(base, extra) {
|
|
25285
|
+
const out2 = [...base];
|
|
25286
|
+
for (const item of extra) {
|
|
25287
|
+
if (!out2.includes(item)) out2.push(item);
|
|
25288
|
+
}
|
|
25289
|
+
return out2;
|
|
23296
25290
|
}
|
|
23297
|
-
|
|
23298
|
-
|
|
23299
|
-
|
|
23300
|
-
|
|
23301
|
-
|
|
23302
|
-
|
|
25291
|
+
|
|
25292
|
+
// src/lessons/strip-markers.ts
|
|
25293
|
+
var LINE_REFS = String.raw`L\d+(?:\s*,\s*L\d+)*`;
|
|
25294
|
+
var LINE_REF_PATTERNS = [
|
|
25295
|
+
new RegExp(String.raw`\s*\bSee\s+${LINE_REFS}\.?`, "g"),
|
|
25296
|
+
// " See L128." / " See L140, L149"
|
|
25297
|
+
new RegExp(String.raw`\s*\((?:${LINE_REFS})\)\.?`, "g"),
|
|
25298
|
+
// " (L174)" / " (L92, L163)"
|
|
25299
|
+
new RegExp(String.raw`\s*\[(?:${LINE_REFS})\]\.?`, "g")
|
|
25300
|
+
// " [L161, L208]"
|
|
25301
|
+
];
|
|
25302
|
+
var ALSO_RELEVANT_PATTERN = /\s*\(also relevant[^)]*\)\s*/g;
|
|
25303
|
+
function stripLegacyMarkers(rule) {
|
|
25304
|
+
let out2 = rule;
|
|
25305
|
+
for (const pattern of LINE_REF_PATTERNS) out2 = out2.replace(pattern, "");
|
|
25306
|
+
out2 = out2.replace(ALSO_RELEVANT_PATTERN, " ");
|
|
25307
|
+
return out2.trim();
|
|
25308
|
+
}
|
|
25309
|
+
function applyStrip(graph) {
|
|
25310
|
+
const changedIds = [];
|
|
25311
|
+
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
25312
|
+
const stripped = stripLegacyMarkers(lesson.rule);
|
|
25313
|
+
if (stripped === lesson.rule || stripped.length === 0) continue;
|
|
25314
|
+
changedIds.push(id);
|
|
25315
|
+
graph.lessons[id] = { ...lesson, rule: stripped };
|
|
25316
|
+
}
|
|
25317
|
+
return changedIds.sort();
|
|
25318
|
+
}
|
|
25319
|
+
async function stripMarkersInGraph(projectRoot, options = {}) {
|
|
25320
|
+
await maybeAutoMigrateLessons(projectRoot);
|
|
25321
|
+
const existing = tryLoadLessonsGraph(projectRoot);
|
|
25322
|
+
if (existing === null) return { changedIds: [], changedCount: 0 };
|
|
25323
|
+
if (options.dryRun === true) {
|
|
25324
|
+
const changedIds2 = applyStrip(existing);
|
|
25325
|
+
return { changedIds: changedIds2, changedCount: changedIds2.length };
|
|
25326
|
+
}
|
|
25327
|
+
let changedIds = [];
|
|
25328
|
+
await mutateLessonsGraph(projectRoot, (graph) => {
|
|
25329
|
+
changedIds = applyStrip(graph);
|
|
25330
|
+
});
|
|
25331
|
+
return { changedIds, changedCount: changedIds.length };
|
|
25332
|
+
}
|
|
25333
|
+
var RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
|
|
25334
|
+
var RECALL_HOOK_MATCHER = "Edit|Write|Bash";
|
|
25335
|
+
function injectRecallHook(projectRoot) {
|
|
25336
|
+
const path = join(projectRoot, ".agentsmesh", "hooks.yaml");
|
|
25337
|
+
if (!existsSync(path)) return false;
|
|
25338
|
+
const doc = parseDocument(readFileSync(path, "utf8"));
|
|
25339
|
+
const existing = doc.get("PostToolUse");
|
|
25340
|
+
const post = existing instanceof YAMLSeq ? existing : new YAMLSeq();
|
|
25341
|
+
const present = post.items.some(
|
|
25342
|
+
(item) => item instanceof YAMLMap && item.get("command") === RECALL_HOOK_COMMAND
|
|
25343
|
+
);
|
|
25344
|
+
if (present) return false;
|
|
25345
|
+
post.add(
|
|
25346
|
+
doc.createNode({ matcher: RECALL_HOOK_MATCHER, type: "command", command: RECALL_HOOK_COMMAND })
|
|
25347
|
+
);
|
|
25348
|
+
doc.set("PostToolUse", post);
|
|
25349
|
+
writeFileSync(path, String(doc), "utf8");
|
|
25350
|
+
return true;
|
|
23303
25351
|
}
|
|
23304
|
-
|
|
23305
|
-
|
|
25352
|
+
|
|
25353
|
+
// src/utils/filesystem/gitignore.ts
|
|
25354
|
+
init_fs();
|
|
25355
|
+
async function ensureGitignoreEntries(projectRoot, entries) {
|
|
25356
|
+
const gitignorePath = join(projectRoot, ".gitignore");
|
|
25357
|
+
const current = await readFileSafe(gitignorePath) ?? "";
|
|
25358
|
+
const existing = new Set(
|
|
25359
|
+
current.split("\n").map((s) => s.trim()).filter((s) => s.length > 0 && !s.startsWith("#"))
|
|
25360
|
+
);
|
|
25361
|
+
const toAdd = entries.filter((e) => !isCoveredByExisting(e, existing));
|
|
25362
|
+
if (toAdd.length === 0) return false;
|
|
25363
|
+
const suffix = current.endsWith("\n") || current === "" ? "" : "\n";
|
|
25364
|
+
await writeFileAtomic(gitignorePath, current + suffix + toAdd.join("\n") + "\n");
|
|
25365
|
+
return true;
|
|
23306
25366
|
}
|
|
23307
|
-
function
|
|
23308
|
-
|
|
23309
|
-
|
|
23310
|
-
|
|
25367
|
+
function isCoveredByExisting(candidate, existing) {
|
|
25368
|
+
if (existing.has(candidate)) return true;
|
|
25369
|
+
let parent = candidate.replace(/\/$/, "");
|
|
25370
|
+
while (parent.includes("/")) {
|
|
25371
|
+
parent = parent.slice(0, parent.lastIndexOf("/"));
|
|
25372
|
+
if (parent === "") break;
|
|
25373
|
+
if (existing.has(parent) || existing.has(`${parent}/`) || existing.has(`${parent}/**`)) {
|
|
25374
|
+
return true;
|
|
25375
|
+
}
|
|
25376
|
+
}
|
|
25377
|
+
return false;
|
|
25378
|
+
}
|
|
25379
|
+
|
|
25380
|
+
// src/targets/projection/lessons-paragraph.ts
|
|
25381
|
+
init_managed_blocks();
|
|
25382
|
+
var LESSONS_PARAGRAPH_BLOCK = `${LESSONS_CONTRACT_START}
|
|
25383
|
+
${LESSONS_PROCEDURAL_RULE}
|
|
25384
|
+
${LESSONS_CONTRACT_END}`;
|
|
25385
|
+
function appendLessonsParagraph(content) {
|
|
25386
|
+
const withoutPrior = stripLessonsParagraph(content);
|
|
25387
|
+
return insertAtBodyTop(withoutPrior, LESSONS_PARAGRAPH_BLOCK);
|
|
23311
25388
|
}
|
|
23312
|
-
function
|
|
23313
|
-
|
|
23314
|
-
|
|
23315
|
-
return current.endsWith("\n") ? lineCount : lineCount + 1;
|
|
25389
|
+
function stripLessonsParagraph(content) {
|
|
25390
|
+
const withoutBlock = stripManagedBlock(content, LESSONS_CONTRACT_START, LESSONS_CONTRACT_END);
|
|
25391
|
+
return stripRawProceduralRule(withoutBlock).trim();
|
|
23316
25392
|
}
|
|
23317
|
-
function
|
|
25393
|
+
function stripRawProceduralRule(content) {
|
|
25394
|
+
return content.replace(`
|
|
25395
|
+
|
|
25396
|
+
${LESSONS_PROCEDURAL_RULE}`, "").replace(LESSONS_PROCEDURAL_RULE, "");
|
|
25397
|
+
}
|
|
25398
|
+
|
|
25399
|
+
// src/lessons/skill.ts
|
|
25400
|
+
init_markdown();
|
|
25401
|
+
var LESSONS_SKILL_NAME = "lessons";
|
|
25402
|
+
var LESSONS_SKILL_DESCRIPTION = "Full operating manual for the agentsmesh lessons system (recall + capture). Consult when running any `agentsmesh lessons` subcommand (query, add, topics, show, deprecate, merge, untrigger, strip-markers, journal, validate, stats, prune, import-md), choosing a topic or trigger flags, using the lessons MCP tools, or when unsure how to phrase or capture a lesson.";
|
|
25403
|
+
var LESSONS_SKILL_BODY = `# Lessons \u2014 operating manual
|
|
25404
|
+
|
|
25405
|
+
Two commands: **Recall** before you act, **Capture** after any failure. The graph
|
|
25406
|
+
\`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit.
|
|
25407
|
+
|
|
25408
|
+
## Recall \u2014 before each file edit and each state-changing command
|
|
25409
|
+
|
|
25410
|
+
\`agentsmesh lessons query --file <path> --cmd <command>\` (add \`--keyword <text>\` to
|
|
25411
|
+
match by task), then apply every rule returned. Scope is MUTATING actions: file edits
|
|
25412
|
+
and state-changing commands (build/test/install/migrate/git-write). Pure-read commands
|
|
25413
|
+
(cat/ls/grep/git-log; read-only) and the recall query itself are **exempt** \u2014 no
|
|
25414
|
+
infinite regress. A predicate-less query is rejected; **keyword-only recall is the
|
|
25415
|
+
anti-pattern** \u2014 most lessons are keyed to a \`file_glob\`/\`command_pattern\` and won't
|
|
25416
|
+
surface (the CLI warns). Excuses ("small edit", "I already know this", "later") all
|
|
25417
|
+
mean: query first \u2014 skipping recall on a mutating action is a process violation, and
|
|
25418
|
+
the user will check.
|
|
25419
|
+
|
|
25420
|
+
## Capture \u2014 immediately after any failure
|
|
25421
|
+
|
|
25422
|
+
Any failure counts, not just red tests: a failing test/CI/lint/typecheck, a code
|
|
25423
|
+
review, a user correction, a regression, or a wrong assumption \u2014 yours or anyone's.
|
|
25424
|
+
|
|
25425
|
+
\`agentsmesh lessons add "<imperative rule>" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>\`
|
|
25426
|
+
|
|
25427
|
+
- **At least one _effective_ trigger is required.** A capture is rejected
|
|
25428
|
+
(\`UNRECALLABLE_LESSON\`) when EVERY trigger is dead on the mandatory \`--file\`/\`--cmd\`
|
|
25429
|
+
recall path \u2014 a stopword-only keyword ("state of the art"), or an invalid/ReDoS
|
|
25430
|
+
command regex \u2014 because the lesson could never be recalled there. Prefer
|
|
25431
|
+
\`--trigger-file\`: the most reliable trigger, it fires on \`--file\` recall. A keyword
|
|
25432
|
+
alone is discouraged (\`KEYWORD_ONLY_LESSON\`); paraphrasing an existing rule warns
|
|
25433
|
+
(\`NEAR_DUPLICATE_LESSON\` \u2014 update that lesson instead).
|
|
25434
|
+
- **One imperative sentence.** A rule over 2000 chars is rejected (\`OVERSIZED_RULE\`) \u2014
|
|
25435
|
+
trim it or split into separate lessons; don't paste a log/diff.
|
|
25436
|
+
- Widen with \`--trigger-cmd <regex>\` / \`--trigger-kw <text>\`. New area:
|
|
25437
|
+
\`--new-topic --topic-summary "<line>"\` (list ids with \`agentsmesh lessons topics\`).
|
|
25438
|
+
|
|
25439
|
+
## No shell? \u2014 MCP tools
|
|
25440
|
+
|
|
25441
|
+
\`lessons_query\`, \`lessons_add\`, \`lessons_topics\`, \`lessons_show\` (inspect a topic),
|
|
25442
|
+
\`lessons_deprecate\` (retire). validate / prune / merge / import-md are CLI-only.
|
|
25443
|
+
|
|
25444
|
+
## Other subcommands
|
|
25445
|
+
|
|
25446
|
+
\`agentsmesh lessons <cmd>\`: \`show\` \xB7 \`deprecate\` (\`--superseded-by\`) \xB7 \`merge\` \xB7
|
|
25447
|
+
\`untrigger\` \xB7 \`strip-markers\` \xB7 \`prune\` (\`--apply\`; trims over-cap triggers, GCs
|
|
25448
|
+
orphan triggers/topics) \xB7 \`journal\` \xB7 \`validate\` \xB7 \`stats\` \xB7 \`import-md\`. Full
|
|
25449
|
+
help: \`agentsmesh lessons --help\`.
|
|
25450
|
+
|
|
25451
|
+
## Config (\`.agentsmesh/lessons/config.json\`)
|
|
25452
|
+
|
|
25453
|
+
\`recallLimit\` / \`recallMaxTokens\` (canonical recall caps; per-call overrides
|
|
25454
|
+
\`--top\` / \`--max-tokens\`). \`recallMaxTokens\` is approximate \u2014 \`rule.length / 4\`,
|
|
25455
|
+
not a real tokenizer. \`autoPrune: true\` (default off) auto-GCs structural cruft
|
|
25456
|
+
after each capture \u2014 orphan triggers/topics + non-stranding dead globs, the safe
|
|
25457
|
+
half of \`prune\`; never trims/strands an active lesson, git-reversible.
|
|
25458
|
+
|
|
25459
|
+
## Dedup (opt-in)
|
|
25460
|
+
|
|
25461
|
+
Set \`--session <id>\` (or \`AGENTSMESH_SESSION_ID\`) and lessons already delivered this
|
|
25462
|
+
session are suppressed, so each recall carries only what is new (\`--no-dedup\` opts
|
|
25463
|
+
out). With no session id, recall is fully stateless \u2014 unchanged.`;
|
|
25464
|
+
var LESSONS_SKILL_FILE = serializeFrontmatter(
|
|
25465
|
+
{ name: LESSONS_SKILL_NAME, description: LESSONS_SKILL_DESCRIPTION },
|
|
25466
|
+
LESSONS_SKILL_BODY
|
|
25467
|
+
);
|
|
25468
|
+
|
|
25469
|
+
// src/lessons/init.ts
|
|
25470
|
+
async function scaffoldLessons(projectRoot) {
|
|
23318
25471
|
const paths = lessonsPaths(projectRoot);
|
|
23319
25472
|
const created = [];
|
|
25473
|
+
const updated = [];
|
|
23320
25474
|
const skipped = [];
|
|
23321
|
-
mkdirSync(paths.
|
|
23322
|
-
|
|
23323
|
-
|
|
23324
|
-
|
|
23325
|
-
|
|
23326
|
-
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
23330
|
-
|
|
23331
|
-
|
|
23332
|
-
|
|
25475
|
+
mkdirSync(paths.base, { recursive: true });
|
|
25476
|
+
await maybeAutoMigrateLessons(projectRoot);
|
|
25477
|
+
if (existsSync(paths.graph)) {
|
|
25478
|
+
skipped.push(paths.graph);
|
|
25479
|
+
} else {
|
|
25480
|
+
await mutateLessonsGraphLocked(projectRoot, () => {
|
|
25481
|
+
});
|
|
25482
|
+
created.push(paths.graph);
|
|
25483
|
+
}
|
|
25484
|
+
seedLessonsConfig(projectRoot, created, skipped);
|
|
25485
|
+
seedLessonsSkill(projectRoot, created, updated, skipped);
|
|
25486
|
+
const rootRuleUpdated = injectProceduralBlock(projectRoot);
|
|
25487
|
+
const recallHookInjected = injectRecallHook(projectRoot);
|
|
25488
|
+
const gitignoreUpdated = await ensureGitignoreEntries(projectRoot, [
|
|
25489
|
+
toRelPath(projectRoot, recallLogPath(projectRoot)),
|
|
25490
|
+
toRelPath(projectRoot, captureLogPath(projectRoot))
|
|
25491
|
+
]);
|
|
25492
|
+
return { created, updated, skipped, rootRuleUpdated, gitignoreUpdated, recallHookInjected };
|
|
25493
|
+
}
|
|
25494
|
+
function seedLessonsSkill(projectRoot, created, updated, skipped) {
|
|
25495
|
+
const skillPath = join(projectRoot, ".agentsmesh/skills", LESSONS_SKILL_NAME, "SKILL.md");
|
|
25496
|
+
const desired = `${LESSONS_SKILL_FILE}
|
|
25497
|
+
`;
|
|
25498
|
+
if (!existsSync(skillPath)) {
|
|
25499
|
+
mkdirSync(dirname(skillPath), { recursive: true });
|
|
25500
|
+
writeFileSync(skillPath, desired, "utf8");
|
|
25501
|
+
created.push(skillPath);
|
|
25502
|
+
return;
|
|
25503
|
+
}
|
|
25504
|
+
if (readFileSync(skillPath, "utf8") === desired) {
|
|
25505
|
+
skipped.push(skillPath);
|
|
25506
|
+
return;
|
|
25507
|
+
}
|
|
25508
|
+
writeFileSync(skillPath, desired, "utf8");
|
|
25509
|
+
updated.push(skillPath);
|
|
25510
|
+
}
|
|
25511
|
+
function seedLessonsConfig(projectRoot, created, skipped) {
|
|
25512
|
+
const configPath = lessonsPaths(projectRoot).config;
|
|
25513
|
+
if (existsSync(configPath)) {
|
|
25514
|
+
skipped.push(configPath);
|
|
25515
|
+
return;
|
|
23333
25516
|
}
|
|
23334
|
-
|
|
23335
|
-
|
|
25517
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
25518
|
+
writeFileSync(configPath, `${JSON.stringify(defaultLessonsConfig(), null, 2)}
|
|
25519
|
+
`, "utf8");
|
|
25520
|
+
created.push(configPath);
|
|
23336
25521
|
}
|
|
23337
|
-
function
|
|
25522
|
+
function injectProceduralBlock(projectRoot) {
|
|
23338
25523
|
const rootRule = join(projectRoot, ".agentsmesh/rules/_root.md");
|
|
23339
25524
|
if (!existsSync(rootRule)) {
|
|
23340
25525
|
mkdirSync(dirname(rootRule), { recursive: true });
|
|
@@ -23343,23 +25528,21 @@ root: true
|
|
|
23343
25528
|
description: ""
|
|
23344
25529
|
---
|
|
23345
25530
|
|
|
23346
|
-
|
|
25531
|
+
${LESSONS_PARAGRAPH_BLOCK}
|
|
23347
25532
|
|
|
23348
|
-
|
|
25533
|
+
# Operational Guidelines
|
|
23349
25534
|
`;
|
|
23350
25535
|
writeFileSync(rootRule, seeded, "utf8");
|
|
23351
25536
|
return true;
|
|
23352
25537
|
}
|
|
23353
25538
|
const current = readFileSync(rootRule, "utf8");
|
|
23354
|
-
|
|
23355
|
-
const next = current.endsWith("\n") ? current : `${current}
|
|
25539
|
+
const desired = `${appendLessonsParagraph(current)}
|
|
23356
25540
|
`;
|
|
23357
|
-
|
|
23358
|
-
|
|
23359
|
-
`, "utf8");
|
|
25541
|
+
if (desired === current) return false;
|
|
25542
|
+
writeFileSync(rootRule, desired, "utf8");
|
|
23360
25543
|
return true;
|
|
23361
25544
|
}
|
|
23362
25545
|
|
|
23363
|
-
export { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, FileSystemError, GenerationError, ImportError,
|
|
25546
|
+
export { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, DEFAULT_RECALL_LIMIT, FileSystemError, GenerationError, ImportError, LESSONS_PROCEDURAL_RULE, LessonsGraphSchema, LockAcquisitionError, RemoteFetchError, TargetNotFoundError, UnknownTopicError, acquireLessonsLock, addLesson, check, computeDiff2 as computeDiff, diff, formatDiffSummary, generate, getAllDescriptors, getDescriptor, getTargetCatalog, graphFilePath, importFrom, importLegacyLessons, lessonsPaths, lint, loadCanonical, loadCanonicalFiles, loadConfig2 as loadConfig, loadConfigFromDirectory, loadLessonsGraph, loadProjectContext, mergeLessons, mutateLessonsGraph, parseGraph, queryLessons, rankLessons, registerTargetDescriptor, resolveOutputCollisions, scaffoldLessons, serializeGraph, stripLegacyMarkers, stripMarkersInGraph, toRelPath, tryLoadLessonsGraph, validateLessonsGraph };
|
|
23364
25547
|
//# sourceMappingURL=index.js.map
|
|
23365
25548
|
//# sourceMappingURL=index.js.map
|