agentsmesh 0.21.0 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +244 -0
- package/README.md +71 -9
- package/dist/canonical.d.ts +2 -2
- package/dist/canonical.js +339 -46
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +276 -223
- package/dist/engine.d.ts +5 -2
- package/dist/engine.js +1350 -87
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2993 -439
- package/dist/index.js.map +1 -1
- package/dist/init-YKxF2zpQ.d.ts +494 -0
- package/dist/lessons.d.ts +106 -0
- package/dist/lessons.js +2764 -0
- package/dist/lessons.js.map +1 -0
- package/dist/{schema-CLmR2JOb.d.ts → schema-CzaoYJlG.d.ts} +9 -1
- package/dist/{target-descriptor-CkLWz3Xk.d.ts → target-descriptor-D6vLDI1w.d.ts} +62 -2
- package/dist/targets.d.ts +3 -3
- package/dist/targets.js +293 -32
- package/dist/targets.js.map +1 -1
- package/package.json +9 -2
- package/schemas/installs.json +12 -0
package/dist/index.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { stringify, parse } from 'yaml';
|
|
3
|
-
import { join,
|
|
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';
|
|
15
|
+
import picomatch from 'picomatch';
|
|
14
16
|
import { createTwoFilesPatch } from 'diff';
|
|
15
17
|
|
|
16
18
|
var __defProp = Object.defineProperty;
|
|
@@ -45,7 +47,7 @@ function validateCapabilityImplementations(descriptor31, capabilities17, ctx, pa
|
|
|
45
47
|
function validateDescriptor(value) {
|
|
46
48
|
return targetDescriptorSchema.parse(value);
|
|
47
49
|
}
|
|
48
|
-
var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
|
|
50
|
+
var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, nativePickStrategySchema, nativeInstallSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
|
|
49
51
|
var init_target_descriptor_schema = __esm({
|
|
50
52
|
"src/targets/catalog/target-descriptor.schema.ts"() {
|
|
51
53
|
capabilityLevelSchema = z.union([
|
|
@@ -119,6 +121,22 @@ var init_target_descriptor_schema = __esm({
|
|
|
119
121
|
officialUrl: z.string().min(1),
|
|
120
122
|
shortDescription: z.string().min(1)
|
|
121
123
|
}).passthrough();
|
|
124
|
+
nativePickStrategySchema = z.discriminatedUnion("kind", [
|
|
125
|
+
z.object({ kind: z.literal("basename"), suffix: z.string().min(1) }),
|
|
126
|
+
z.object({ kind: z.literal("skillDir") }),
|
|
127
|
+
z.object({ kind: z.literal("firstSegment") })
|
|
128
|
+
]);
|
|
129
|
+
nativeInstallSchema = z.object({
|
|
130
|
+
pickPaths: z.array(
|
|
131
|
+
z.object({
|
|
132
|
+
prefix: z.string().min(1),
|
|
133
|
+
feature: z.enum(["commands", "rules", "agents", "skills"]),
|
|
134
|
+
strategy: nativePickStrategySchema
|
|
135
|
+
})
|
|
136
|
+
).optional(),
|
|
137
|
+
inferPick: z.function().optional(),
|
|
138
|
+
dialectHints: z.array(z.object({ frontmatterKey: z.string().min(1) })).optional()
|
|
139
|
+
}).strict();
|
|
122
140
|
targetDescriptorSchemaBase = z.object({
|
|
123
141
|
id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Target id must be lowercase with hyphens"),
|
|
124
142
|
metadata: metadataSchema,
|
|
@@ -130,6 +148,7 @@ var init_target_descriptor_schema = __esm({
|
|
|
130
148
|
globalSupport: globalSupportSchema.optional(),
|
|
131
149
|
buildImportPaths: z.function(),
|
|
132
150
|
detectionPaths: z.array(z.string()),
|
|
151
|
+
nativeInstall: nativeInstallSchema.optional(),
|
|
133
152
|
excludeFromStarterInit: z.boolean().optional(),
|
|
134
153
|
conversionDefaults: conversionDefaultsSchema.optional(),
|
|
135
154
|
emitScopedSettings: z.function().optional(),
|
|
@@ -630,15 +649,19 @@ var init_errors = __esm({
|
|
|
630
649
|
LockAcquisitionError = class extends AgentsMeshError {
|
|
631
650
|
lockPath;
|
|
632
651
|
holder;
|
|
652
|
+
/** Human-readable lock name surfaced in the message, e.g. "lessons lock". */
|
|
653
|
+
label;
|
|
633
654
|
constructor(lockPath, holder, options) {
|
|
655
|
+
const label = options?.label ?? "lock";
|
|
634
656
|
super(
|
|
635
657
|
"AM_LOCK_ACQUISITION_FAILED",
|
|
636
|
-
`Could not acquire
|
|
658
|
+
`Could not acquire ${label} at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
|
|
637
659
|
options
|
|
638
660
|
);
|
|
639
661
|
this.name = "LockAcquisitionError";
|
|
640
662
|
this.lockPath = lockPath;
|
|
641
663
|
this.holder = holder;
|
|
664
|
+
this.label = label;
|
|
642
665
|
}
|
|
643
666
|
};
|
|
644
667
|
FileSystemError = class extends AgentsMeshError {
|
|
@@ -803,6 +826,27 @@ var init_fs_traverse = __esm({
|
|
|
803
826
|
MAX_SEGMENT_REPETITIONS = 3;
|
|
804
827
|
}
|
|
805
828
|
});
|
|
829
|
+
async function renameWithRetry(from, to, options = {}) {
|
|
830
|
+
const attempts = options.attempts ?? 5;
|
|
831
|
+
const delayMs = options.delayMs ?? 50;
|
|
832
|
+
for (let attempt = 0; ; attempt++) {
|
|
833
|
+
try {
|
|
834
|
+
await rename(from, to);
|
|
835
|
+
return;
|
|
836
|
+
} catch (err) {
|
|
837
|
+
const code = err.code;
|
|
838
|
+
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
839
|
+
if (!transient || attempt >= attempts - 1) throw err;
|
|
840
|
+
await setTimeout$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
|
+
});
|
|
806
850
|
async function readFileSafe(path) {
|
|
807
851
|
try {
|
|
808
852
|
const data = await readFile(path, "utf-8");
|
|
@@ -889,6 +933,7 @@ var init_fs = __esm({
|
|
|
889
933
|
init_fs_text_encoding();
|
|
890
934
|
init_fs_traverse();
|
|
891
935
|
init_fs_text_encoding();
|
|
936
|
+
init_rename_retry();
|
|
892
937
|
}
|
|
893
938
|
});
|
|
894
939
|
function escapeRegExp(value) {
|
|
@@ -897,19 +942,24 @@ function escapeRegExp(value) {
|
|
|
897
942
|
function managedBlockPattern(start, end) {
|
|
898
943
|
return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
|
|
899
944
|
}
|
|
900
|
-
function replaceManagedBlock(content, start, end, block) {
|
|
901
|
-
const pattern = managedBlockPattern(start, end);
|
|
902
|
-
if (pattern.test(content)) {
|
|
903
|
-
return content.replace(pattern, block).trim();
|
|
904
|
-
}
|
|
905
|
-
const trimmed = content.trim();
|
|
906
|
-
return trimmed ? `${trimmed}
|
|
907
|
-
|
|
908
|
-
${block}` : block;
|
|
909
|
-
}
|
|
910
945
|
function stripManagedBlock(content, start, end) {
|
|
911
946
|
return content.replace(managedBlockPattern(start, end), "").trim();
|
|
912
947
|
}
|
|
948
|
+
function splitFrontmatterPrefix(content) {
|
|
949
|
+
if (content.indexOf("---") !== 0) return { prefix: "", body: content.trim() };
|
|
950
|
+
const close = content.indexOf("---", 3);
|
|
951
|
+
if (close === -1) return { prefix: "", body: content.trim() };
|
|
952
|
+
return { prefix: content.slice(0, close + 3), body: content.slice(close + 3).trim() };
|
|
953
|
+
}
|
|
954
|
+
function insertAtBodyTop(content, block) {
|
|
955
|
+
const { prefix, body } = splitFrontmatterPrefix(content);
|
|
956
|
+
const placed = body ? `${block}
|
|
957
|
+
|
|
958
|
+
${body}` : block;
|
|
959
|
+
return prefix ? `${prefix}
|
|
960
|
+
|
|
961
|
+
${placed}` : placed;
|
|
962
|
+
}
|
|
913
963
|
function ruleSource(source) {
|
|
914
964
|
const normalized = source.replace(/\\/g, "/");
|
|
915
965
|
const meshIndex = normalized.lastIndexOf(".agentsmesh/");
|
|
@@ -997,11 +1047,13 @@ function extractEmbeddedRules(content) {
|
|
|
997
1047
|
});
|
|
998
1048
|
return { rootContent: rootContent.trim(), rules };
|
|
999
1049
|
}
|
|
1000
|
-
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;
|
|
1001
1051
|
var init_managed_blocks = __esm({
|
|
1002
1052
|
"src/targets/projection/managed-blocks.ts"() {
|
|
1003
1053
|
ROOT_CONTRACT_START = "<!-- agentsmesh:root-generation-contract:start -->";
|
|
1004
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 -->";
|
|
1005
1057
|
EMBEDDED_RULES_START = "<!-- agentsmesh:embedded-rules:start -->";
|
|
1006
1058
|
EMBEDDED_RULES_END = "<!-- agentsmesh:embedded-rules:end -->";
|
|
1007
1059
|
EMBEDDED_RULE_END = "<!-- agentsmesh:embedded-rule:end -->";
|
|
@@ -1011,31 +1063,9 @@ var init_managed_blocks = __esm({
|
|
|
1011
1063
|
});
|
|
1012
1064
|
|
|
1013
1065
|
// src/targets/projection/root-instruction-paragraph.ts
|
|
1014
|
-
function normalizeWhitespace(value) {
|
|
1015
|
-
return value.replace(/\s+/g, " ").trim();
|
|
1016
|
-
}
|
|
1017
1066
|
function appendAgentsmeshRootInstructionParagraph(content) {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
return replaceManagedBlock(
|
|
1021
|
-
trimmed,
|
|
1022
|
-
ROOT_CONTRACT_START,
|
|
1023
|
-
ROOT_CONTRACT_END,
|
|
1024
|
-
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH
|
|
1025
|
-
);
|
|
1026
|
-
}
|
|
1027
|
-
const norm = normalizeWhitespace(trimmed);
|
|
1028
|
-
if (norm.includes(normalizeWhitespace(AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH))) {
|
|
1029
|
-
return trimmed;
|
|
1030
|
-
}
|
|
1031
|
-
for (const legacy of LEGACY_FORMS) {
|
|
1032
|
-
if (norm.includes(normalizeWhitespace(legacy))) {
|
|
1033
|
-
return trimmed.replace(legacy, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return trimmed ? `${trimmed}
|
|
1037
|
-
|
|
1038
|
-
${AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH}` : AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH;
|
|
1067
|
+
const withoutPrior = stripAgentsmeshRootInstructionParagraph(content);
|
|
1068
|
+
return insertAtBodyTop(withoutPrior, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
|
|
1039
1069
|
}
|
|
1040
1070
|
function stripAgentsmeshRootInstructionParagraph(content) {
|
|
1041
1071
|
let result = stripManagedBlock(content, ROOT_CONTRACT_START, ROOT_CONTRACT_END);
|
|
@@ -1049,7 +1079,7 @@ ${legacy}`, "");
|
|
|
1049
1079
|
}
|
|
1050
1080
|
return result.trim();
|
|
1051
1081
|
}
|
|
1052
|
-
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
1082
|
+
var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY_V9, ROOT_INSTRUCTION_BODY_V10, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_CONTRACT_WITH_V9_BODY, AGENTSMESH_CONTRACT_WITH_V10_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
|
|
1053
1083
|
var init_root_instruction_paragraph = __esm({
|
|
1054
1084
|
"src/targets/projection/root-instruction-paragraph.ts"() {
|
|
1055
1085
|
init_managed_blocks();
|
|
@@ -1061,7 +1091,9 @@ var init_root_instruction_paragraph = __esm({
|
|
|
1061
1091
|
ROOT_INSTRUCTION_BODY_V6 = "Create canonical files in `.agentsmesh`: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Then run `agentsmesh generate`.";
|
|
1062
1092
|
ROOT_INSTRUCTION_BODY_V7 = "`.agentsmesh` is the only folder you edit or add these files in: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Do not edit generated tool files; run `agentsmesh generate`.";
|
|
1063
1093
|
ROOT_INSTRUCTION_BODY_V8 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, and `merge` as needed; never edit generated tool files.";
|
|
1064
|
-
|
|
1094
|
+
ROOT_INSTRUCTION_BODY_V9 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
1095
|
+
ROOT_INSTRUCTION_BODY_V10 = "**MUST follow when changing any rule, agent, command, skill, hook, MCP server, permission, or ignore pattern.** `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
|
|
1096
|
+
ROOT_INSTRUCTION_BODY = "**NEVER edit generated files** (`.claude/`, `.cursor/`, `AGENTS.md`, `.github/copilot-instructions.md`, and similar target outputs) \u2014 `agentsmesh generate` overwrites them. **All changes MUST go through `.agentsmesh` first**: edit `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally); if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed.";
|
|
1065
1097
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
|
|
1066
1098
|
LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
|
|
1067
1099
|
|
|
@@ -1090,12 +1122,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
|
|
|
1090
1122
|
AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
|
|
1091
1123
|
|
|
1092
1124
|
${ROOT_INSTRUCTION_BODY_V8}`;
|
|
1125
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
|
|
1126
|
+
|
|
1127
|
+
${ROOT_INSTRUCTION_BODY_V9}`;
|
|
1128
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
|
|
1129
|
+
|
|
1130
|
+
${ROOT_INSTRUCTION_BODY_V10}`;
|
|
1093
1131
|
AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
|
|
1094
1132
|
## AgentsMesh Generation Contract
|
|
1095
1133
|
|
|
1096
1134
|
${ROOT_INSTRUCTION_BODY}
|
|
1097
1135
|
${ROOT_CONTRACT_END}`;
|
|
1098
1136
|
LEGACY_FORMS = [
|
|
1137
|
+
AGENTSMESH_CONTRACT_WITH_V10_BODY,
|
|
1138
|
+
AGENTSMESH_CONTRACT_WITH_V9_BODY,
|
|
1099
1139
|
AGENTSMESH_CONTRACT_WITH_V8_BODY,
|
|
1100
1140
|
AGENTSMESH_CONTRACT_WITH_V7_BODY,
|
|
1101
1141
|
AGENTSMESH_CONTRACT_WITH_V6_BODY,
|
|
@@ -1999,9 +2039,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
|
|
|
1999
2039
|
const before = fullContent[start - 1];
|
|
2000
2040
|
const after = fullContent[end];
|
|
2001
2041
|
if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
|
|
2002
|
-
if (before === "
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2042
|
+
if (before === "`" && after === "`") return true;
|
|
2043
|
+
if (before === "'" && after === "'" || before === '"' && after === '"') return false;
|
|
2005
2044
|
if (before === "<" && after === ">") return true;
|
|
2006
2045
|
if (before === "[" && after === "]") {
|
|
2007
2046
|
if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
|
|
@@ -3328,12 +3367,12 @@ var init_augment_code = __esm({
|
|
|
3328
3367
|
});
|
|
3329
3368
|
|
|
3330
3369
|
// src/targets/claude-code/constants.ts
|
|
3331
|
-
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;
|
|
3332
3371
|
var init_constants7 = __esm({
|
|
3333
3372
|
"src/targets/claude-code/constants.ts"() {
|
|
3334
3373
|
CLAUDE_CODE_TARGET = "claude-code";
|
|
3335
|
-
CLAUDE_ROOT = "
|
|
3336
|
-
|
|
3374
|
+
CLAUDE_ROOT = "CLAUDE.md";
|
|
3375
|
+
CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
|
|
3337
3376
|
CLAUDE_RULES_DIR = ".claude/rules";
|
|
3338
3377
|
CLAUDE_COMMANDS_DIR = ".claude/commands";
|
|
3339
3378
|
CLAUDE_AGENTS_DIR = ".claude/agents";
|
|
@@ -5195,7 +5234,8 @@ var init_amp2 = __esm({
|
|
|
5195
5234
|
markAsRoot: true
|
|
5196
5235
|
}
|
|
5197
5236
|
},
|
|
5198
|
-
emitScopedSettings(canonical, _scope) {
|
|
5237
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
5238
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
5199
5239
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
5200
5240
|
return [
|
|
5201
5241
|
{
|
|
@@ -5947,12 +5987,12 @@ function mergeAugmentSettings(existing, newContent) {
|
|
|
5947
5987
|
if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
|
|
5948
5988
|
return JSON.stringify(base, null, 2);
|
|
5949
5989
|
}
|
|
5950
|
-
function buildSettingsContent(canonical) {
|
|
5990
|
+
function buildSettingsContent(canonical, enabledFeatures) {
|
|
5951
5991
|
const settings = {};
|
|
5952
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5992
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
5953
5993
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
5954
5994
|
}
|
|
5955
|
-
if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5995
|
+
if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
|
|
5956
5996
|
settings.hooks = serializeHooksForSettings(canonical.hooks);
|
|
5957
5997
|
}
|
|
5958
5998
|
if (Object.keys(settings).length === 0) return null;
|
|
@@ -6081,8 +6121,8 @@ var init_augment_code2 = __esm({
|
|
|
6081
6121
|
],
|
|
6082
6122
|
layout: globalLayout5
|
|
6083
6123
|
},
|
|
6084
|
-
emitScopedSettings(canonical) {
|
|
6085
|
-
const content = buildSettingsContent(canonical);
|
|
6124
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
6125
|
+
const content = buildSettingsContent(canonical, enabledFeatures);
|
|
6086
6126
|
if (content === null) return [];
|
|
6087
6127
|
return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
|
|
6088
6128
|
},
|
|
@@ -6656,7 +6696,10 @@ var init_claude_code2 = __esm({
|
|
|
6656
6696
|
skillDir: ".claude/skills",
|
|
6657
6697
|
managedOutputs: {
|
|
6658
6698
|
dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
|
|
6659
|
-
|
|
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"]
|
|
6660
6703
|
},
|
|
6661
6704
|
paths: {
|
|
6662
6705
|
rulePath(slug, _rule) {
|
|
@@ -6671,7 +6714,7 @@ var init_claude_code2 = __esm({
|
|
|
6671
6714
|
}
|
|
6672
6715
|
};
|
|
6673
6716
|
globalLayout6 = {
|
|
6674
|
-
rootInstructionPath:
|
|
6717
|
+
rootInstructionPath: CLAUDE_NESTED_ROOT,
|
|
6675
6718
|
skillDir: ".claude/skills",
|
|
6676
6719
|
renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
|
|
6677
6720
|
managedOutputs: {
|
|
@@ -6684,7 +6727,7 @@ var init_claude_code2 = __esm({
|
|
|
6684
6727
|
".agents/skills"
|
|
6685
6728
|
],
|
|
6686
6729
|
files: [
|
|
6687
|
-
|
|
6730
|
+
CLAUDE_NESTED_ROOT,
|
|
6688
6731
|
".claude/settings.json",
|
|
6689
6732
|
CLAUDE_GLOBAL_MCP_JSON,
|
|
6690
6733
|
CLAUDE_HOOKS_JSON,
|
|
@@ -6692,6 +6735,7 @@ var init_claude_code2 = __esm({
|
|
|
6692
6735
|
]
|
|
6693
6736
|
},
|
|
6694
6737
|
rewriteGeneratedPath(path) {
|
|
6738
|
+
if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
|
|
6695
6739
|
if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
|
|
6696
6740
|
return path;
|
|
6697
6741
|
},
|
|
@@ -6755,10 +6799,11 @@ var init_claude_code2 = __esm({
|
|
|
6755
6799
|
importer: {
|
|
6756
6800
|
rules: [
|
|
6757
6801
|
{
|
|
6758
|
-
// 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.
|
|
6759
6804
|
feature: "rules",
|
|
6760
6805
|
mode: "singleFile",
|
|
6761
|
-
source: { project: [CLAUDE_ROOT,
|
|
6806
|
+
source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
|
|
6762
6807
|
canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
|
|
6763
6808
|
canonicalRootFilename: "_root.md",
|
|
6764
6809
|
markAsRoot: true
|
|
@@ -6804,7 +6849,23 @@ var init_claude_code2 = __esm({
|
|
|
6804
6849
|
}
|
|
6805
6850
|
},
|
|
6806
6851
|
buildImportPaths: buildClaudeCodeImportPaths,
|
|
6807
|
-
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
|
+
}
|
|
6808
6869
|
};
|
|
6809
6870
|
}
|
|
6810
6871
|
});
|
|
@@ -7683,6 +7744,16 @@ var init_cline2 = __esm({
|
|
|
7683
7744
|
},
|
|
7684
7745
|
buildImportPaths: buildClineImportPaths,
|
|
7685
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
|
+
},
|
|
7686
7757
|
conversionDefaults: { agentsToSkills: true }
|
|
7687
7758
|
};
|
|
7688
7759
|
}
|
|
@@ -8601,6 +8672,11 @@ var init_codex_cli2 = __esm({
|
|
|
8601
8672
|
".codex/agents",
|
|
8602
8673
|
".codex/rules"
|
|
8603
8674
|
],
|
|
8675
|
+
nativeInstall: {
|
|
8676
|
+
pickPaths: [
|
|
8677
|
+
{ prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
|
|
8678
|
+
]
|
|
8679
|
+
},
|
|
8604
8680
|
excludeFromStarterInit: true,
|
|
8605
8681
|
conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
|
|
8606
8682
|
};
|
|
@@ -9104,6 +9180,21 @@ var init_continue2 = __esm({
|
|
|
9104
9180
|
},
|
|
9105
9181
|
buildImportPaths: buildContinueImportPaths,
|
|
9106
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
|
+
},
|
|
9107
9198
|
conversionDefaults: { agentsToSkills: true }
|
|
9108
9199
|
};
|
|
9109
9200
|
}
|
|
@@ -9471,6 +9562,80 @@ var init_importer10 = __esm({
|
|
|
9471
9562
|
init_copilot2();
|
|
9472
9563
|
}
|
|
9473
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
|
+
});
|
|
9474
9639
|
function pruneUndefined3(record) {
|
|
9475
9640
|
for (const key of Object.keys(record)) {
|
|
9476
9641
|
if (record[key] === void 0) delete record[key];
|
|
@@ -9760,6 +9925,7 @@ var init_copilot2 = __esm({
|
|
|
9760
9925
|
init_generator11();
|
|
9761
9926
|
init_constants29();
|
|
9762
9927
|
init_importer10();
|
|
9928
|
+
init_native_path_pick_infer_copilot();
|
|
9763
9929
|
init_import_mappers4();
|
|
9764
9930
|
init_linter10();
|
|
9765
9931
|
init_import_map_builders();
|
|
@@ -9974,7 +10140,8 @@ var init_copilot2 = __esm({
|
|
|
9974
10140
|
".github/skills",
|
|
9975
10141
|
".github/agents",
|
|
9976
10142
|
".github/hooks"
|
|
9977
|
-
]
|
|
10143
|
+
],
|
|
10144
|
+
nativeInstall: { inferPick: inferCopilotPickFromPath }
|
|
9978
10145
|
};
|
|
9979
10146
|
}
|
|
9980
10147
|
});
|
|
@@ -11004,8 +11171,8 @@ async function hasGlobalCursorArtifacts(projectRoot) {
|
|
|
11004
11171
|
join(projectRoot, CURSOR_COMMANDS_DIR)
|
|
11005
11172
|
];
|
|
11006
11173
|
for (const p of candidates) {
|
|
11007
|
-
const
|
|
11008
|
-
if (
|
|
11174
|
+
const stat9 = await readFileSafe(p);
|
|
11175
|
+
if (stat9 !== null && stat9.trim() !== "") return true;
|
|
11009
11176
|
}
|
|
11010
11177
|
const skillFiles = await readDirRecursive(join(projectRoot, CURSOR_SKILLS_DIR));
|
|
11011
11178
|
if (skillFiles.some((f) => f.endsWith(".md"))) return true;
|
|
@@ -11487,6 +11654,23 @@ var init_cursor2 = __esm({
|
|
|
11487
11654
|
},
|
|
11488
11655
|
buildImportPaths: buildCursorImportPaths,
|
|
11489
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
|
+
},
|
|
11490
11674
|
preservesManualActivation: true
|
|
11491
11675
|
};
|
|
11492
11676
|
}
|
|
@@ -12248,18 +12432,18 @@ function mapHookEvent2(event) {
|
|
|
12248
12432
|
return null;
|
|
12249
12433
|
}
|
|
12250
12434
|
}
|
|
12251
|
-
function generateGeminiSettingsFiles(canonical) {
|
|
12435
|
+
function generateGeminiSettingsFiles(canonical, enabledFeatures) {
|
|
12252
12436
|
const settings = {};
|
|
12253
12437
|
let hasAnyNativeSettings = false;
|
|
12254
|
-
if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12438
|
+
if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
|
|
12255
12439
|
settings.mcpServers = canonical.mcp.mcpServers;
|
|
12256
12440
|
hasAnyNativeSettings = true;
|
|
12257
12441
|
}
|
|
12258
|
-
if (canonical.agents.length > 0) {
|
|
12442
|
+
if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
|
|
12259
12443
|
settings.experimental = { enableAgents: true };
|
|
12260
12444
|
hasAnyNativeSettings = true;
|
|
12261
12445
|
}
|
|
12262
|
-
if (canonical.hooks) {
|
|
12446
|
+
if (enabledFeatures.has("hooks") && canonical.hooks) {
|
|
12263
12447
|
const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
|
|
12264
12448
|
const mappedEvent = mapHookEvent2(event);
|
|
12265
12449
|
if (!mappedEvent || !Array.isArray(entries)) return [];
|
|
@@ -12875,6 +13059,36 @@ var init_importer15 = __esm({
|
|
|
12875
13059
|
init_importer_skills_agents();
|
|
12876
13060
|
}
|
|
12877
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
|
+
});
|
|
12878
13092
|
async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
|
|
12879
13093
|
const relativeMdPath = relativePath.replace(/\\/g, "/");
|
|
12880
13094
|
const destPath = join(destDir, relativeMdPath);
|
|
@@ -12986,12 +13200,12 @@ var init_lint12 = __esm({
|
|
|
12986
13200
|
});
|
|
12987
13201
|
|
|
12988
13202
|
// src/targets/gemini-cli/scoped-settings-emit.ts
|
|
12989
|
-
function emitScopedGeminiSettings(canonical, scope) {
|
|
13203
|
+
function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
|
|
12990
13204
|
if (scope === "project") {
|
|
12991
13205
|
const caps = getTargetCapabilities("gemini-cli", scope);
|
|
12992
13206
|
if (caps?.ignore.flavor !== "settings-embedded") return [];
|
|
12993
13207
|
}
|
|
12994
|
-
return generateGeminiSettingsFiles(canonical);
|
|
13208
|
+
return generateGeminiSettingsFiles(canonical, enabledFeatures);
|
|
12995
13209
|
}
|
|
12996
13210
|
var init_scoped_settings_emit = __esm({
|
|
12997
13211
|
"src/targets/gemini-cli/scoped-settings-emit.ts"() {
|
|
@@ -13009,6 +13223,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13009
13223
|
init_policies_generator();
|
|
13010
13224
|
init_constants30();
|
|
13011
13225
|
init_importer15();
|
|
13226
|
+
init_gemini_install_commands();
|
|
13012
13227
|
init_import_mappers6();
|
|
13013
13228
|
init_linter15();
|
|
13014
13229
|
init_import_map_builders();
|
|
@@ -13204,6 +13419,7 @@ var init_gemini_cli2 = __esm({
|
|
|
13204
13419
|
},
|
|
13205
13420
|
buildImportPaths: buildGeminiCliImportPaths,
|
|
13206
13421
|
detectionPaths: ["GEMINI.md", ".gemini"],
|
|
13422
|
+
nativeInstall: { inferPick: inferGeminiPick },
|
|
13207
13423
|
conversionDefaults: { agentsToSkills: false }
|
|
13208
13424
|
};
|
|
13209
13425
|
}
|
|
@@ -14045,7 +14261,23 @@ var init_junie2 = __esm({
|
|
|
14045
14261
|
".junie/skills",
|
|
14046
14262
|
".junie/mcp/mcp.json",
|
|
14047
14263
|
".aiignore"
|
|
14048
|
-
]
|
|
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
|
+
}
|
|
14049
14281
|
};
|
|
14050
14282
|
}
|
|
14051
14283
|
});
|
|
@@ -15900,9 +16132,18 @@ function generateIgnore12(canonical) {
|
|
|
15900
16132
|
if (!canonical.ignore || canonical.ignore.length === 0) return [];
|
|
15901
16133
|
return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
|
|
15902
16134
|
}
|
|
16135
|
+
function renderQwenGlobalInstructions(canonical) {
|
|
16136
|
+
const root = canonical.rules.find((rule) => rule.root);
|
|
16137
|
+
const nonRootRules = canonical.rules.filter((rule) => {
|
|
16138
|
+
if (rule.root) return false;
|
|
16139
|
+
return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
|
|
16140
|
+
});
|
|
16141
|
+
return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
|
|
16142
|
+
}
|
|
15903
16143
|
var init_generator26 = __esm({
|
|
15904
16144
|
"src/targets/qwen-code/generator.ts"() {
|
|
15905
16145
|
init_markdown();
|
|
16146
|
+
init_managed_blocks();
|
|
15906
16147
|
init_constants21();
|
|
15907
16148
|
}
|
|
15908
16149
|
});
|
|
@@ -15985,6 +16226,7 @@ var init_qwen_code2 = __esm({
|
|
|
15985
16226
|
};
|
|
15986
16227
|
globalLayout22 = {
|
|
15987
16228
|
rootInstructionPath: QWEN_GLOBAL_ROOT,
|
|
16229
|
+
renderPrimaryRootInstruction: renderQwenGlobalInstructions,
|
|
15988
16230
|
skillDir: QWEN_GLOBAL_SKILLS_DIR,
|
|
15989
16231
|
managedOutputs: {
|
|
15990
16232
|
dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
|
|
@@ -18385,6 +18627,16 @@ var init_windsurf2 = __esm({
|
|
|
18385
18627
|
},
|
|
18386
18628
|
buildImportPaths: buildWindsurfImportPaths,
|
|
18387
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
|
+
},
|
|
18388
18640
|
conversionDefaults: { agentsToSkills: true }
|
|
18389
18641
|
};
|
|
18390
18642
|
}
|
|
@@ -18659,7 +18911,8 @@ var init_zed2 = __esm({
|
|
|
18659
18911
|
markAsRoot: true
|
|
18660
18912
|
}
|
|
18661
18913
|
},
|
|
18662
|
-
emitScopedSettings(canonical, _scope) {
|
|
18914
|
+
emitScopedSettings(canonical, _scope, enabledFeatures) {
|
|
18915
|
+
if (!enabledFeatures.has("mcp")) return [];
|
|
18663
18916
|
if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
|
|
18664
18917
|
const contextServers = {};
|
|
18665
18918
|
for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
|
|
@@ -18714,7 +18967,10 @@ function getBuiltinTargetDefinition(target31) {
|
|
|
18714
18967
|
function getTargetCapabilities(target31, scope = "project") {
|
|
18715
18968
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
18716
18969
|
if (!descriptor31) return void 0;
|
|
18717
|
-
|
|
18970
|
+
if (scope === "global" && !descriptor31.globalSupport) {
|
|
18971
|
+
return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
|
|
18972
|
+
}
|
|
18973
|
+
const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
|
|
18718
18974
|
return normalizeTargetCapabilities(raw);
|
|
18719
18975
|
}
|
|
18720
18976
|
function getTargetDetectionPaths(target31, scope = "project") {
|
|
@@ -18770,6 +19026,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
|
|
|
18770
19026
|
function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
|
|
18771
19027
|
const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
|
|
18772
19028
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19029
|
+
if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
|
|
18773
19030
|
if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
|
|
18774
19031
|
return "embedded";
|
|
18775
19032
|
}
|
|
@@ -18783,7 +19040,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
|
|
|
18783
19040
|
const pick = PICK_FEATURE_GENERATOR[feature];
|
|
18784
19041
|
return pick === null ? void 0 : pick(descriptor31.generators);
|
|
18785
19042
|
}
|
|
18786
|
-
var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
19043
|
+
var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
|
|
18787
19044
|
var init_builtin_targets = __esm({
|
|
18788
19045
|
"src/targets/catalog/builtin-targets.ts"() {
|
|
18789
19046
|
init_conversions();
|
|
@@ -18821,6 +19078,17 @@ var init_builtin_targets = __esm({
|
|
|
18821
19078
|
init_warp2();
|
|
18822
19079
|
init_windsurf2();
|
|
18823
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
|
+
};
|
|
18824
19092
|
BUILTIN_TARGETS = [
|
|
18825
19093
|
descriptor,
|
|
18826
19094
|
descriptor2,
|
|
@@ -19251,6 +19519,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
|
|
|
19251
19519
|
init_path_helpers();
|
|
19252
19520
|
init_link_rebaser_helpers();
|
|
19253
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
|
+
|
|
19254
19539
|
// src/utils/output/logger.ts
|
|
19255
19540
|
var C = {
|
|
19256
19541
|
green: "\x1B[32m",
|
|
@@ -19259,16 +19544,14 @@ var C = {
|
|
|
19259
19544
|
cyan: "\x1B[36m",
|
|
19260
19545
|
reset: "\x1B[0m"
|
|
19261
19546
|
};
|
|
19262
|
-
function
|
|
19263
|
-
|
|
19264
|
-
process.stdout.write(text);
|
|
19265
|
-
}
|
|
19547
|
+
function outStream() {
|
|
19548
|
+
return process.stdout;
|
|
19266
19549
|
}
|
|
19267
|
-
function
|
|
19268
|
-
|
|
19550
|
+
function out(text) {
|
|
19551
|
+
outStream().write(text);
|
|
19269
19552
|
}
|
|
19270
|
-
function c(code, text) {
|
|
19271
|
-
return
|
|
19553
|
+
function c(code, text, stream) {
|
|
19554
|
+
return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
|
|
19272
19555
|
}
|
|
19273
19556
|
function pad(str, width) {
|
|
19274
19557
|
const len = [...str].length;
|
|
@@ -19276,20 +19559,20 @@ function pad(str, width) {
|
|
|
19276
19559
|
}
|
|
19277
19560
|
var logger = {
|
|
19278
19561
|
info(msg) {
|
|
19279
|
-
out(c(C.cyan, msg) + "\n");
|
|
19562
|
+
out(c(C.cyan, msg, outStream()) + "\n");
|
|
19280
19563
|
},
|
|
19281
19564
|
warn(msg) {
|
|
19282
|
-
process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
|
|
19565
|
+
process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
|
|
19283
19566
|
},
|
|
19284
19567
|
error(msg) {
|
|
19285
|
-
process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
|
|
19568
|
+
process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
|
|
19286
19569
|
},
|
|
19287
19570
|
success(msg) {
|
|
19288
|
-
out(c(C.green, "\u2713 ") + msg + "\n");
|
|
19571
|
+
out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
|
|
19289
19572
|
},
|
|
19290
19573
|
debug(msg) {
|
|
19291
19574
|
if (process.env.AGENTSMESH_DEBUG === "1") {
|
|
19292
|
-
out(c(C.cyan, "[debug] ") + msg + "\n");
|
|
19575
|
+
out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
|
|
19293
19576
|
}
|
|
19294
19577
|
},
|
|
19295
19578
|
table(rows) {
|
|
@@ -19784,12 +20067,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
|
|
|
19784
20067
|
}
|
|
19785
20068
|
}
|
|
19786
20069
|
}
|
|
19787
|
-
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
|
|
20070
|
+
async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
|
|
19788
20071
|
for (const target31 of targets) {
|
|
19789
20072
|
const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
|
|
19790
20073
|
const emit = descriptor31?.emitScopedSettings;
|
|
19791
20074
|
if (!emit) continue;
|
|
19792
|
-
const outputs = emit(canonical, scope);
|
|
20075
|
+
const outputs = emit(canonical, scope, enabledFeatures);
|
|
19793
20076
|
if (outputs.length === 0) continue;
|
|
19794
20077
|
for (const out2 of outputs) {
|
|
19795
20078
|
await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
|
|
@@ -19890,7 +20173,14 @@ async function generate(ctx) {
|
|
|
19890
20173
|
}
|
|
19891
20174
|
}
|
|
19892
20175
|
if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
|
|
19893
|
-
await generateScopedSettingsFeature(
|
|
20176
|
+
await generateScopedSettingsFeature(
|
|
20177
|
+
results,
|
|
20178
|
+
targets,
|
|
20179
|
+
canonical,
|
|
20180
|
+
projectRoot,
|
|
20181
|
+
scope,
|
|
20182
|
+
enabledFeatures
|
|
20183
|
+
);
|
|
19894
20184
|
}
|
|
19895
20185
|
const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
|
|
19896
20186
|
const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
|
|
@@ -20214,7 +20504,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
|
|
|
20214
20504
|
await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
|
|
20215
20505
|
if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
|
|
20216
20506
|
await rm(cacheRoot, { recursive: true, force: true });
|
|
20217
|
-
await
|
|
20507
|
+
await renameWithRetry(stagedRoot, cacheRoot);
|
|
20218
20508
|
return readCachedRepo(cacheRepoDir);
|
|
20219
20509
|
} catch (err) {
|
|
20220
20510
|
await rm(stagedRoot, { recursive: true, force: true });
|
|
@@ -22565,430 +22855,2694 @@ function lintRuleScopeInversion(input) {
|
|
|
22565
22855
|
}
|
|
22566
22856
|
return out2;
|
|
22567
22857
|
}
|
|
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
|
+
}
|
|
22568
22893
|
|
|
22569
|
-
// src/
|
|
22570
|
-
var
|
|
22571
|
-
|
|
22572
|
-
|
|
22573
|
-
const filtered = all.filter((p) => {
|
|
22574
|
-
const rel2 = relative(projectRoot, p);
|
|
22575
|
-
return !EXCLUDE_DIRS.some((d) => rel2.includes(`/${d}/`) || rel2.startsWith(`${d}/`));
|
|
22576
|
-
});
|
|
22577
|
-
return filtered.map((p) => relative(projectRoot, p));
|
|
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);
|
|
22578
22898
|
}
|
|
22579
|
-
|
|
22580
|
-
const
|
|
22581
|
-
|
|
22582
|
-
|
|
22583
|
-
|
|
22584
|
-
|
|
22585
|
-
|
|
22586
|
-
|
|
22587
|
-
|
|
22588
|
-
const
|
|
22589
|
-
|
|
22590
|
-
|
|
22591
|
-
|
|
22592
|
-
|
|
22593
|
-
|
|
22594
|
-
|
|
22595
|
-
|
|
22596
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
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;
|
|
22930
|
+
}
|
|
22931
|
+
var BASE_REL = ".agentsmesh/lessons";
|
|
22932
|
+
function lessonsPaths(projectRoot) {
|
|
22933
|
+
const base = join(projectRoot, BASE_REL);
|
|
22934
|
+
return {
|
|
22935
|
+
base,
|
|
22936
|
+
graph: join(base, "lessons.json"),
|
|
22937
|
+
config: join(base, "config.json"),
|
|
22938
|
+
journal: join(base, "journal.md"),
|
|
22939
|
+
index: join(base, "index.yaml"),
|
|
22940
|
+
topicsDir: join(base, "topics")
|
|
22941
|
+
};
|
|
22942
|
+
}
|
|
22943
|
+
function toRelPath(projectRoot, absolute) {
|
|
22944
|
+
return relative(projectRoot, absolute).split(sep).join("/");
|
|
22945
|
+
}
|
|
22946
|
+
var LESSONS_PROCEDURAL_RULE = `## Lessons (BLOCKING REQUIREMENT \u2014 MUST run both, no exceptions; the user will check)
|
|
22947
|
+
|
|
22948
|
+
Graph \`.agentsmesh/lessons/lessons.json\` is canonical \u2014 never hand-edit. Manual: the \`lessons\` skill.
|
|
22949
|
+
|
|
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.
|
|
22951
|
+
|
|
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>\`.
|
|
22953
|
+
|
|
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
|
+
}
|
|
22611
22971
|
}
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
22618
|
-
|
|
22619
|
-
|
|
22972
|
+
} catch {
|
|
22973
|
+
return null;
|
|
22974
|
+
}
|
|
22975
|
+
return out2;
|
|
22976
|
+
}
|
|
22977
|
+
|
|
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
|
+
}
|
|
22620
22991
|
}
|
|
22621
|
-
|
|
22622
|
-
|
|
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
|
+
}
|
|
22623
23002
|
}
|
|
22624
|
-
if (
|
|
22625
|
-
|
|
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
|
+
});
|
|
22626
23010
|
}
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
23011
|
+
}
|
|
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
|
+
});
|
|
22630
23023
|
}
|
|
22631
|
-
|
|
22632
|
-
|
|
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
|
+
});
|
|
22633
23032
|
}
|
|
22634
|
-
|
|
22635
|
-
|
|
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
|
+
});
|
|
22636
23053
|
}
|
|
22637
|
-
if (
|
|
22638
|
-
|
|
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
|
+
});
|
|
22639
23061
|
}
|
|
22640
23062
|
}
|
|
22641
|
-
const hasErrors = diagnostics.some((d) => d.level === "error");
|
|
22642
|
-
return { diagnostics, hasErrors };
|
|
22643
23063
|
}
|
|
22644
|
-
function
|
|
22645
|
-
const
|
|
22646
|
-
|
|
22647
|
-
|
|
22648
|
-
|
|
22649
|
-
|
|
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
|
+
});
|
|
22650
23074
|
continue;
|
|
22651
23075
|
}
|
|
22652
|
-
|
|
22653
|
-
|
|
22654
|
-
|
|
22655
|
-
|
|
22656
|
-
|
|
22657
|
-
"",
|
|
22658
|
-
|
|
22659
|
-
|
|
22660
|
-
|
|
22661
|
-
|
|
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
|
+
});
|
|
23084
|
+
}
|
|
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;
|
|
23104
|
+
}
|
|
23105
|
+
seen.add(cur);
|
|
23106
|
+
const next = graph.lessons[cur]?.supersededBy;
|
|
23107
|
+
if (next === cur) break;
|
|
23108
|
+
cur = next;
|
|
23109
|
+
}
|
|
23110
|
+
}
|
|
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
|
+
}
|
|
23122
|
+
}
|
|
23123
|
+
}
|
|
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
|
+
}
|
|
23151
|
+
}
|
|
23152
|
+
|
|
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));
|
|
23180
|
+
}
|
|
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)];
|
|
23937
|
+
const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
|
|
23938
|
+
for (const target31 of targets) {
|
|
23939
|
+
const fullDesc = getDescriptor(target31);
|
|
23940
|
+
const descriptor31 = isBuiltinTargetId(target31) ? getTargetCatalogEntry(target31) : fullDesc;
|
|
23941
|
+
if (descriptor31?.capabilities) {
|
|
23942
|
+
diagnostics.push(
|
|
23943
|
+
...lintSilentFeatureDrops({
|
|
23944
|
+
target: target31,
|
|
23945
|
+
capabilities: descriptor31.capabilities,
|
|
23946
|
+
canonical,
|
|
23947
|
+
enabledFeatures: config.features
|
|
23948
|
+
})
|
|
22662
23949
|
);
|
|
22663
|
-
|
|
23950
|
+
}
|
|
23951
|
+
if (hasHooks) {
|
|
23952
|
+
diagnostics.push(
|
|
23953
|
+
...lintHookScriptReferences({
|
|
23954
|
+
target: target31,
|
|
23955
|
+
canonical,
|
|
23956
|
+
hasScriptProjection: fullDesc?.postProcessHookOutputs !== void 0
|
|
23957
|
+
})
|
|
23958
|
+
);
|
|
23959
|
+
}
|
|
23960
|
+
if (hasRules) {
|
|
23961
|
+
diagnostics.push(
|
|
23962
|
+
...lintRuleScopeInversion({
|
|
23963
|
+
target: target31,
|
|
23964
|
+
canonical,
|
|
23965
|
+
preservesManualActivation: fullDesc?.preservesManualActivation === true
|
|
23966
|
+
})
|
|
23967
|
+
);
|
|
23968
|
+
}
|
|
23969
|
+
if (hasRules && descriptor31?.lintRules) {
|
|
23970
|
+
diagnostics.push(...descriptor31.lintRules(canonical, projectRoot, projectFiles, { scope }));
|
|
23971
|
+
}
|
|
23972
|
+
if (fullDesc?.generators.lint) {
|
|
23973
|
+
diagnostics.push(...fullDesc.generators.lint(canonical));
|
|
23974
|
+
}
|
|
23975
|
+
const lintOpts = { scope };
|
|
23976
|
+
if (hasCommands && descriptor31?.lint?.commands) {
|
|
23977
|
+
diagnostics.push(...descriptor31.lint.commands(canonical, lintOpts));
|
|
23978
|
+
}
|
|
23979
|
+
if (hasMcp && descriptor31?.lint?.mcp) {
|
|
23980
|
+
diagnostics.push(...descriptor31.lint.mcp(canonical, lintOpts));
|
|
23981
|
+
}
|
|
23982
|
+
if (hasPermissions && descriptor31?.lint?.permissions) {
|
|
23983
|
+
diagnostics.push(...descriptor31.lint.permissions(canonical, lintOpts));
|
|
23984
|
+
}
|
|
23985
|
+
if (hasHooks && descriptor31?.lint?.hooks) {
|
|
23986
|
+
diagnostics.push(...descriptor31.lint.hooks(canonical, lintOpts));
|
|
23987
|
+
}
|
|
23988
|
+
}
|
|
23989
|
+
const hasErrors = diagnostics.some((d) => d.level === "error");
|
|
23990
|
+
return { diagnostics, hasErrors };
|
|
23991
|
+
}
|
|
23992
|
+
function computeDiff(results) {
|
|
23993
|
+
const diffs = [];
|
|
23994
|
+
const summary = { new: 0, updated: 0, unchanged: 0, deleted: 0 };
|
|
23995
|
+
for (const r of results) {
|
|
23996
|
+
if (r.status === "unchanged") {
|
|
23997
|
+
summary.unchanged++;
|
|
23998
|
+
continue;
|
|
23999
|
+
}
|
|
24000
|
+
if (r.status === "created") {
|
|
24001
|
+
summary.new++;
|
|
24002
|
+
const patch = createTwoFilesPatch(
|
|
24003
|
+
`${r.path} (current)`,
|
|
24004
|
+
`${r.path} (generated)`,
|
|
24005
|
+
"",
|
|
24006
|
+
r.content,
|
|
24007
|
+
void 0,
|
|
24008
|
+
void 0,
|
|
24009
|
+
{ context: 3 }
|
|
24010
|
+
);
|
|
24011
|
+
diffs.push({ path: r.path, patch });
|
|
24012
|
+
continue;
|
|
24013
|
+
}
|
|
24014
|
+
if (r.status === "updated" && r.currentContent !== void 0) {
|
|
24015
|
+
summary.updated++;
|
|
24016
|
+
const patch = createTwoFilesPatch(
|
|
24017
|
+
`${r.path} (current)`,
|
|
24018
|
+
`${r.path} (generated)`,
|
|
24019
|
+
r.currentContent,
|
|
24020
|
+
r.content,
|
|
24021
|
+
void 0,
|
|
24022
|
+
void 0,
|
|
24023
|
+
{ context: 3 }
|
|
24024
|
+
);
|
|
24025
|
+
diffs.push({ path: r.path, patch });
|
|
24026
|
+
continue;
|
|
24027
|
+
}
|
|
24028
|
+
}
|
|
24029
|
+
return { diffs, summary };
|
|
24030
|
+
}
|
|
24031
|
+
function formatDiffSummary(summary) {
|
|
24032
|
+
return `${summary.new} files would be created, ${summary.updated} updated, ${summary.unchanged} unchanged, ${summary.deleted} deleted`;
|
|
24033
|
+
}
|
|
24034
|
+
|
|
24035
|
+
// src/config/core/lock.ts
|
|
24036
|
+
init_fs();
|
|
24037
|
+
|
|
24038
|
+
// src/utils/crypto/hash.ts
|
|
24039
|
+
init_fs_text_encoding();
|
|
24040
|
+
function hashContent(content) {
|
|
24041
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
24042
|
+
}
|
|
24043
|
+
async function hashFile(path) {
|
|
24044
|
+
try {
|
|
24045
|
+
const bytes = await readFile(path);
|
|
24046
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
24047
|
+
} catch (err) {
|
|
24048
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
24049
|
+
return null;
|
|
24050
|
+
}
|
|
24051
|
+
throw err;
|
|
24052
|
+
}
|
|
24053
|
+
}
|
|
24054
|
+
|
|
24055
|
+
// src/config/core/lock.ts
|
|
24056
|
+
var LOCK_FILENAME = ".lock";
|
|
24057
|
+
var CANONICAL_PATTERNS = [
|
|
24058
|
+
(r) => r.startsWith("rules/") && r.endsWith(".md"),
|
|
24059
|
+
(r) => r.startsWith("commands/") && r.endsWith(".md"),
|
|
24060
|
+
(r) => r.startsWith("agents/") && r.endsWith(".md"),
|
|
24061
|
+
(r) => r.match(/^skills\/[^/]+\/.+$/) !== null,
|
|
24062
|
+
(r) => r === "mcp.json",
|
|
24063
|
+
(r) => r === "permissions.yaml",
|
|
24064
|
+
(r) => r === "hooks.yaml",
|
|
24065
|
+
(r) => r === "ignore"
|
|
24066
|
+
];
|
|
24067
|
+
function isCanonical(relPath) {
|
|
24068
|
+
if (relPath.startsWith("packs/")) return false;
|
|
24069
|
+
return CANONICAL_PATTERNS.some((p) => p(relPath));
|
|
24070
|
+
}
|
|
24071
|
+
var FEATURE_PATTERNS = {
|
|
24072
|
+
rules: (path) => path.startsWith("rules/"),
|
|
24073
|
+
commands: (path) => path.startsWith("commands/"),
|
|
24074
|
+
agents: (path) => path.startsWith("agents/"),
|
|
24075
|
+
skills: (path) => /^skills\/[^/]+\/.+$/.test(path),
|
|
24076
|
+
mcp: (path) => path === "mcp.json",
|
|
24077
|
+
permissions: (path) => path === "permissions.yaml",
|
|
24078
|
+
hooks: (path) => path === "hooks.yaml",
|
|
24079
|
+
ignore: (path) => path === "ignore"
|
|
24080
|
+
};
|
|
24081
|
+
async function readLock(abDir) {
|
|
24082
|
+
const lockPath = join(abDir, LOCK_FILENAME);
|
|
24083
|
+
const content = await readFileSafe(lockPath);
|
|
24084
|
+
if (content === null) return null;
|
|
24085
|
+
try {
|
|
24086
|
+
const raw = parse(content);
|
|
24087
|
+
if (!raw || typeof raw !== "object") return null;
|
|
24088
|
+
return {
|
|
24089
|
+
generatedAt: String(raw.generated_at ?? ""),
|
|
24090
|
+
generatedBy: String(raw.generated_by ?? ""),
|
|
24091
|
+
libVersion: String(raw.lib_version ?? ""),
|
|
24092
|
+
checksums: raw.checksums && typeof raw.checksums === "object" ? raw.checksums : {},
|
|
24093
|
+
extends: raw.extends && typeof raw.extends === "object" ? raw.extends : {},
|
|
24094
|
+
packs: raw.packs && typeof raw.packs === "object" ? raw.packs : {}
|
|
24095
|
+
};
|
|
24096
|
+
} catch {
|
|
24097
|
+
return null;
|
|
24098
|
+
}
|
|
24099
|
+
}
|
|
24100
|
+
async function buildChecksums(abDir) {
|
|
24101
|
+
if (!await exists(abDir)) return {};
|
|
24102
|
+
const files = await readDirRecursive(abDir);
|
|
24103
|
+
const result = {};
|
|
24104
|
+
for (const fullPath of files) {
|
|
24105
|
+
const rel2 = relative(abDir, fullPath).replace(/\\/g, "/");
|
|
24106
|
+
if (rel2 === LOCK_FILENAME) continue;
|
|
24107
|
+
if (!isCanonical(rel2)) continue;
|
|
24108
|
+
const h = await hashFile(fullPath);
|
|
24109
|
+
if (h !== null) {
|
|
24110
|
+
result[rel2] = h.startsWith("sha256:") ? h : `sha256:${h}`;
|
|
24111
|
+
}
|
|
24112
|
+
}
|
|
24113
|
+
return result;
|
|
24114
|
+
}
|
|
24115
|
+
function detectLockedFeatureViolations(lockChecksums, currentChecksums, lockFeatures) {
|
|
24116
|
+
if (lockFeatures.length === 0) return [];
|
|
24117
|
+
const matchers = lockFeatures.map((feature) => FEATURE_PATTERNS[feature]).filter((matcher) => matcher !== void 0);
|
|
24118
|
+
if (matchers.length === 0) return [];
|
|
24119
|
+
const allPaths = /* @__PURE__ */ new Set([...Object.keys(lockChecksums), ...Object.keys(currentChecksums)]);
|
|
24120
|
+
const violations = [];
|
|
24121
|
+
for (const path of allPaths) {
|
|
24122
|
+
if (!matchers.some((matcher) => matcher(path))) continue;
|
|
24123
|
+
if (lockChecksums[path] !== currentChecksums[path]) violations.push(path);
|
|
24124
|
+
}
|
|
24125
|
+
return violations;
|
|
24126
|
+
}
|
|
24127
|
+
async function buildExtendChecksums(resolvedExtends) {
|
|
24128
|
+
const result = {};
|
|
24129
|
+
for (const ext of resolvedExtends) {
|
|
24130
|
+
if (ext.version !== void 0) {
|
|
24131
|
+
result[ext.name] = ext.version;
|
|
24132
|
+
continue;
|
|
24133
|
+
}
|
|
24134
|
+
const abDir = join(ext.resolvedPath, ".agentsmesh");
|
|
24135
|
+
const checksums = await buildChecksums(abDir);
|
|
24136
|
+
const fingerprint = Object.keys(checksums).sort().map((p) => `${p}:${checksums[p]}`).join("\n");
|
|
24137
|
+
const h = hashContent(fingerprint);
|
|
24138
|
+
const hex = h.startsWith("sha256:") ? h : `sha256:${h}`;
|
|
24139
|
+
result[ext.name] = `local:${hex}`;
|
|
24140
|
+
}
|
|
24141
|
+
return result;
|
|
24142
|
+
}
|
|
24143
|
+
|
|
24144
|
+
// src/core/check/lock-sync.ts
|
|
24145
|
+
async function checkLockSync(opts) {
|
|
24146
|
+
const { config, configDir, canonicalDir } = opts;
|
|
24147
|
+
const lock = await readLock(canonicalDir);
|
|
24148
|
+
if (lock === null) {
|
|
24149
|
+
return {
|
|
24150
|
+
inSync: false,
|
|
24151
|
+
hasLock: false,
|
|
24152
|
+
modified: [],
|
|
24153
|
+
added: [],
|
|
24154
|
+
removed: [],
|
|
24155
|
+
extendsModified: [],
|
|
24156
|
+
lockedViolations: []
|
|
24157
|
+
};
|
|
24158
|
+
}
|
|
24159
|
+
const current = await buildChecksums(canonicalDir);
|
|
24160
|
+
const resolvedExtends = await resolveExtendPaths(config, configDir);
|
|
24161
|
+
const currentExtends = resolvedExtends.length > 0 ? await buildExtendChecksums(resolvedExtends) : {};
|
|
24162
|
+
const lockPaths = new Set(Object.keys(lock.checksums));
|
|
24163
|
+
const currentPaths = new Set(Object.keys(current));
|
|
24164
|
+
const modified = [];
|
|
24165
|
+
const added = [];
|
|
24166
|
+
const removed = [];
|
|
24167
|
+
for (const path of lockPaths) {
|
|
24168
|
+
const c2 = current[path];
|
|
24169
|
+
if (c2 === void 0) {
|
|
24170
|
+
removed.push(path);
|
|
24171
|
+
} else if (c2 !== lock.checksums[path]) {
|
|
24172
|
+
modified.push(path);
|
|
24173
|
+
}
|
|
24174
|
+
}
|
|
24175
|
+
for (const path of currentPaths) {
|
|
24176
|
+
if (!lockPaths.has(path)) {
|
|
24177
|
+
added.push(path);
|
|
24178
|
+
}
|
|
24179
|
+
}
|
|
24180
|
+
const extendNames = /* @__PURE__ */ new Set([...Object.keys(lock.extends), ...Object.keys(currentExtends)]);
|
|
24181
|
+
const extendsModified = [];
|
|
24182
|
+
for (const name of extendNames) {
|
|
24183
|
+
if (currentExtends[name] !== lock.extends[name]) {
|
|
24184
|
+
extendsModified.push(name);
|
|
24185
|
+
}
|
|
24186
|
+
}
|
|
24187
|
+
const lockedViolations = detectLockedFeatureViolations(
|
|
24188
|
+
lock.checksums,
|
|
24189
|
+
current,
|
|
24190
|
+
config.collaboration?.lock_features ?? []
|
|
24191
|
+
);
|
|
24192
|
+
const inSync = modified.length === 0 && added.length === 0 && removed.length === 0 && extendsModified.length === 0;
|
|
24193
|
+
return {
|
|
24194
|
+
inSync,
|
|
24195
|
+
hasLock: true,
|
|
24196
|
+
modified,
|
|
24197
|
+
added,
|
|
24198
|
+
removed,
|
|
24199
|
+
extendsModified,
|
|
24200
|
+
lockedViolations
|
|
24201
|
+
};
|
|
24202
|
+
}
|
|
24203
|
+
|
|
24204
|
+
// src/public/engine.ts
|
|
24205
|
+
init_registry();
|
|
24206
|
+
init_target_ids();
|
|
24207
|
+
async function importFrom(target31, opts) {
|
|
24208
|
+
const descriptor31 = getDescriptor(target31);
|
|
24209
|
+
if (!descriptor31) {
|
|
24210
|
+
throw new TargetNotFoundError(target31, {
|
|
24211
|
+
supported: [...TARGET_IDS, ...getAllDescriptors().map((d) => d.id)]
|
|
24212
|
+
});
|
|
24213
|
+
}
|
|
24214
|
+
return descriptor31.generators.importFrom(opts.root, { scope: opts.scope ?? "project" });
|
|
24215
|
+
}
|
|
24216
|
+
async function loadConfig2(projectRoot) {
|
|
24217
|
+
return loadConfigFromDir(projectRoot);
|
|
24218
|
+
}
|
|
24219
|
+
async function loadConfigFromDirectory(configDir) {
|
|
24220
|
+
return loadConfigFromExactDir(configDir);
|
|
24221
|
+
}
|
|
24222
|
+
async function loadProjectContext(projectRoot, options = {}) {
|
|
24223
|
+
const scope = options.scope ?? "project";
|
|
24224
|
+
const { config, context } = await loadScopedConfig(projectRoot, scope);
|
|
24225
|
+
await bootstrapPlugins(config, projectRoot);
|
|
24226
|
+
const { canonical } = await loadCanonicalWithExtends(
|
|
24227
|
+
config,
|
|
24228
|
+
context.configDir,
|
|
24229
|
+
{ refreshRemoteCache: options.refreshRemoteCache === true },
|
|
24230
|
+
context.canonicalDir
|
|
24231
|
+
);
|
|
24232
|
+
return {
|
|
24233
|
+
config,
|
|
24234
|
+
canonical,
|
|
24235
|
+
projectRoot: context.rootBase,
|
|
24236
|
+
scope,
|
|
24237
|
+
configDir: context.configDir,
|
|
24238
|
+
canonicalDir: context.canonicalDir
|
|
24239
|
+
};
|
|
24240
|
+
}
|
|
24241
|
+
async function lint(opts) {
|
|
24242
|
+
const filter = opts.targetFilter ? [...opts.targetFilter] : void 0;
|
|
24243
|
+
return runLint(opts.config, opts.canonical, opts.projectRoot, filter, {
|
|
24244
|
+
scope: opts.scope
|
|
24245
|
+
});
|
|
24246
|
+
}
|
|
24247
|
+
function computeDiff2(results) {
|
|
24248
|
+
return computeDiff([...results]);
|
|
24249
|
+
}
|
|
24250
|
+
async function diff(ctx) {
|
|
24251
|
+
const results = await generate(ctx);
|
|
24252
|
+
const computed = computeDiff(results);
|
|
24253
|
+
return { ...computed, results };
|
|
24254
|
+
}
|
|
24255
|
+
async function check(opts) {
|
|
24256
|
+
return checkLockSync(opts);
|
|
24257
|
+
}
|
|
24258
|
+
|
|
24259
|
+
// src/public/canonical.ts
|
|
24260
|
+
init_errors();
|
|
24261
|
+
async function loadCanonical(projectRoot, options = {}) {
|
|
24262
|
+
if (options.includeExtends === false) {
|
|
24263
|
+
return loadCanonicalFiles(options.canonicalDir ?? projectRoot);
|
|
24264
|
+
}
|
|
24265
|
+
if (options.config === void 0 && options.configDir !== void 0 || options.config !== void 0 && options.configDir === void 0) {
|
|
24266
|
+
throw new Error("loadCanonical options require both config and configDir, or neither.");
|
|
24267
|
+
}
|
|
24268
|
+
try {
|
|
24269
|
+
const loaded = options.config !== void 0 && options.configDir !== void 0 ? { config: options.config, configDir: options.configDir } : await loadConfigFromDir(projectRoot);
|
|
24270
|
+
const { canonical } = await loadCanonicalWithExtends(
|
|
24271
|
+
loaded.config,
|
|
24272
|
+
loaded.configDir,
|
|
24273
|
+
{ refreshRemoteCache: options.refreshRemoteCache === true },
|
|
24274
|
+
options.canonicalDir
|
|
24275
|
+
);
|
|
24276
|
+
return canonical;
|
|
24277
|
+
} catch (err) {
|
|
24278
|
+
if (err instanceof ConfigNotFoundError && options.config === void 0 && options.configDir === void 0) {
|
|
24279
|
+
return loadCanonicalFiles(options.canonicalDir ?? projectRoot);
|
|
24280
|
+
}
|
|
24281
|
+
throw err;
|
|
24282
|
+
}
|
|
24283
|
+
}
|
|
24284
|
+
|
|
24285
|
+
// src/public/targets.ts
|
|
24286
|
+
init_builtin_targets();
|
|
24287
|
+
init_registry();
|
|
24288
|
+
init_descriptor_import_runner();
|
|
24289
|
+
function copyCapabilities(capabilities17) {
|
|
24290
|
+
return Object.freeze({ ...capabilities17 });
|
|
24291
|
+
}
|
|
24292
|
+
function copyGenerators(generators) {
|
|
24293
|
+
return Object.freeze({ ...generators });
|
|
24294
|
+
}
|
|
24295
|
+
function copyOutputFamily(family) {
|
|
24296
|
+
return Object.freeze({
|
|
24297
|
+
...family,
|
|
24298
|
+
explicitPaths: family.explicitPaths === void 0 ? void 0 : Object.freeze([...family.explicitPaths])
|
|
24299
|
+
});
|
|
24300
|
+
}
|
|
24301
|
+
function copyManagedOutputs(outputs) {
|
|
24302
|
+
return Object.freeze({
|
|
24303
|
+
dirs: Object.freeze([...outputs.dirs]),
|
|
24304
|
+
files: Object.freeze([...outputs.files])
|
|
24305
|
+
});
|
|
24306
|
+
}
|
|
24307
|
+
function copyLayout(layout) {
|
|
24308
|
+
return Object.freeze({
|
|
24309
|
+
...layout,
|
|
24310
|
+
outputFamilies: layout.outputFamilies === void 0 ? void 0 : Object.freeze(layout.outputFamilies.map(copyOutputFamily)),
|
|
24311
|
+
managedOutputs: layout.managedOutputs === void 0 ? void 0 : copyManagedOutputs(layout.managedOutputs),
|
|
24312
|
+
paths: Object.freeze({ ...layout.paths })
|
|
24313
|
+
});
|
|
24314
|
+
}
|
|
24315
|
+
function copyGlobalSupport(globalSupport) {
|
|
24316
|
+
return Object.freeze({
|
|
24317
|
+
...globalSupport,
|
|
24318
|
+
capabilities: copyCapabilities(globalSupport.capabilities),
|
|
24319
|
+
detectionPaths: Object.freeze([...globalSupport.detectionPaths]),
|
|
24320
|
+
layout: copyLayout(globalSupport.layout)
|
|
24321
|
+
});
|
|
24322
|
+
}
|
|
24323
|
+
function copyTargetDescriptor(descriptor31) {
|
|
24324
|
+
return Object.freeze({
|
|
24325
|
+
...descriptor31,
|
|
24326
|
+
generators: copyGenerators(descriptor31.generators),
|
|
24327
|
+
capabilities: copyCapabilities(descriptor31.capabilities),
|
|
24328
|
+
globalSupport: descriptor31.globalSupport === void 0 ? void 0 : copyGlobalSupport(descriptor31.globalSupport),
|
|
24329
|
+
lint: descriptor31.lint === void 0 ? void 0 : Object.freeze({ ...descriptor31.lint }),
|
|
24330
|
+
project: copyLayout(descriptor31.project),
|
|
24331
|
+
supportsConversion: descriptor31.supportsConversion === void 0 ? void 0 : Object.freeze({ ...descriptor31.supportsConversion }),
|
|
24332
|
+
detectionPaths: Object.freeze([...descriptor31.detectionPaths]),
|
|
24333
|
+
sharedArtifacts: descriptor31.sharedArtifacts === void 0 ? void 0 : Object.freeze({ ...descriptor31.sharedArtifacts })
|
|
24334
|
+
});
|
|
24335
|
+
}
|
|
24336
|
+
function getTargetCatalog() {
|
|
24337
|
+
return Object.freeze(BUILTIN_TARGETS.map(copyTargetDescriptor));
|
|
24338
|
+
}
|
|
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);
|
|
22664
24370
|
continue;
|
|
22665
24371
|
}
|
|
22666
|
-
|
|
22667
|
-
|
|
22668
|
-
|
|
22669
|
-
|
|
22670
|
-
|
|
22671
|
-
|
|
22672
|
-
|
|
22673
|
-
|
|
22674
|
-
|
|
22675
|
-
|
|
22676
|
-
|
|
22677
|
-
|
|
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;
|
|
22678
24600
|
continue;
|
|
22679
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 });
|
|
22680
24617
|
}
|
|
22681
|
-
return
|
|
24618
|
+
return rules;
|
|
22682
24619
|
}
|
|
22683
|
-
|
|
22684
|
-
|
|
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
|
+
};
|
|
22685
24669
|
}
|
|
22686
24670
|
|
|
22687
|
-
// src/
|
|
22688
|
-
|
|
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
|
+
}
|
|
22689
24748
|
|
|
22690
|
-
// src/
|
|
22691
|
-
|
|
22692
|
-
|
|
22693
|
-
|
|
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}`;
|
|
22694
24756
|
}
|
|
22695
|
-
async function
|
|
24757
|
+
async function maybeAutoMigrateLessons(projectRoot) {
|
|
24758
|
+
if (existsSync(graphFilePath(projectRoot))) return false;
|
|
24759
|
+
const paths = lessonsPaths(projectRoot);
|
|
24760
|
+
if (!existsSync(paths.index)) return false;
|
|
22696
24761
|
try {
|
|
22697
|
-
|
|
22698
|
-
return
|
|
24762
|
+
await importLegacyLessons(projectRoot, { migratedAt: todayIso2() });
|
|
24763
|
+
return true;
|
|
22699
24764
|
} catch (err) {
|
|
22700
|
-
if (err instanceof
|
|
22701
|
-
return null;
|
|
22702
|
-
}
|
|
24765
|
+
if (err instanceof LessonsGraphExistsError) return false;
|
|
22703
24766
|
throw err;
|
|
22704
24767
|
}
|
|
22705
24768
|
}
|
|
22706
24769
|
|
|
22707
|
-
// src/
|
|
22708
|
-
|
|
22709
|
-
var
|
|
22710
|
-
|
|
22711
|
-
|
|
22712
|
-
|
|
22713
|
-
|
|
22714
|
-
|
|
22715
|
-
|
|
22716
|
-
|
|
22717
|
-
(
|
|
22718
|
-
|
|
22719
|
-
|
|
22720
|
-
|
|
22721
|
-
|
|
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 });
|
|
24794
|
+
}
|
|
24795
|
+
attempt++;
|
|
24796
|
+
await sleep2(delay);
|
|
24797
|
+
}
|
|
22722
24798
|
}
|
|
22723
|
-
|
|
22724
|
-
rules: (path) => path.startsWith("rules/"),
|
|
22725
|
-
commands: (path) => path.startsWith("commands/"),
|
|
22726
|
-
agents: (path) => path.startsWith("agents/"),
|
|
22727
|
-
skills: (path) => /^skills\/[^/]+\/.+$/.test(path),
|
|
22728
|
-
mcp: (path) => path === "mcp.json",
|
|
22729
|
-
permissions: (path) => path === "permissions.yaml",
|
|
22730
|
-
hooks: (path) => path === "hooks.yaml",
|
|
22731
|
-
ignore: (path) => path === "ignore"
|
|
22732
|
-
};
|
|
22733
|
-
async function readLock(abDir) {
|
|
22734
|
-
const lockPath = join(abDir, LOCK_FILENAME);
|
|
22735
|
-
const content = await readFileSafe(lockPath);
|
|
22736
|
-
if (content === null) return null;
|
|
24799
|
+
async function tryAcquire(lockPath) {
|
|
22737
24800
|
try {
|
|
22738
|
-
|
|
22739
|
-
|
|
22740
|
-
return
|
|
22741
|
-
|
|
22742
|
-
|
|
22743
|
-
|
|
22744
|
-
|
|
22745
|
-
|
|
22746
|
-
|
|
22747
|
-
|
|
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
|
+
};
|
|
24838
|
+
}
|
|
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;
|
|
22748
24845
|
} catch {
|
|
24846
|
+
try {
|
|
24847
|
+
const info = await stat(lockPath);
|
|
24848
|
+
const ageMs = Date.now() - info.mtimeMs;
|
|
24849
|
+
if (ageMs < YOUNG_LOCK_GRACE_MS) return "young";
|
|
24850
|
+
} catch {
|
|
24851
|
+
}
|
|
22749
24852
|
return null;
|
|
22750
24853
|
}
|
|
22751
24854
|
}
|
|
22752
|
-
|
|
22753
|
-
if (!
|
|
22754
|
-
const
|
|
22755
|
-
|
|
22756
|
-
|
|
22757
|
-
|
|
22758
|
-
|
|
22759
|
-
|
|
22760
|
-
|
|
22761
|
-
|
|
22762
|
-
|
|
22763
|
-
|
|
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);
|
|
24861
|
+
}
|
|
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";
|
|
22764
24869
|
}
|
|
22765
|
-
return result;
|
|
22766
24870
|
}
|
|
22767
|
-
function
|
|
22768
|
-
if (
|
|
22769
|
-
const
|
|
22770
|
-
|
|
22771
|
-
|
|
22772
|
-
|
|
22773
|
-
|
|
22774
|
-
|
|
22775
|
-
|
|
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();
|
|
22776
24917
|
}
|
|
22777
|
-
return violations;
|
|
22778
24918
|
}
|
|
22779
|
-
async function
|
|
22780
|
-
|
|
22781
|
-
|
|
22782
|
-
|
|
22783
|
-
|
|
22784
|
-
|
|
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";
|
|
24954
|
+
}
|
|
24955
|
+
if (!isSafeRegexPattern(pattern)) {
|
|
24956
|
+
return "regex is outside the provably-linear engine \u2014 recall skips it (ReDoS guard), so it never fires";
|
|
22785
24957
|
}
|
|
22786
|
-
|
|
22787
|
-
const checksums = await buildChecksums(abDir);
|
|
22788
|
-
const fingerprint = Object.keys(checksums).sort().map((p) => `${p}:${checksums[p]}`).join("\n");
|
|
22789
|
-
const h = hashContent(fingerprint);
|
|
22790
|
-
const hex = h.startsWith("sha256:") ? h : `sha256:${h}`;
|
|
22791
|
-
result[ext.name] = `local:${hex}`;
|
|
24958
|
+
return null;
|
|
22792
24959
|
}
|
|
22793
|
-
return
|
|
24960
|
+
return null;
|
|
24961
|
+
}
|
|
24962
|
+
function blockingDeadTriggers(graph, triggerIds) {
|
|
24963
|
+
return ineffectiveTriggers(graph, triggerIds).filter((t) => t.kind !== "command_pattern");
|
|
22794
24964
|
}
|
|
22795
24965
|
|
|
22796
|
-
// src/
|
|
22797
|
-
|
|
22798
|
-
|
|
22799
|
-
|
|
22800
|
-
|
|
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
|
|
24973
|
+
});
|
|
24974
|
+
}
|
|
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
|
+
};
|
|
22801
25013
|
return {
|
|
22802
|
-
|
|
22803
|
-
|
|
22804
|
-
|
|
22805
|
-
|
|
22806
|
-
|
|
22807
|
-
|
|
22808
|
-
|
|
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)
|
|
22809
25021
|
};
|
|
22810
25022
|
}
|
|
22811
|
-
const
|
|
22812
|
-
|
|
22813
|
-
|
|
22814
|
-
|
|
22815
|
-
|
|
22816
|
-
|
|
22817
|
-
|
|
22818
|
-
|
|
22819
|
-
|
|
22820
|
-
|
|
22821
|
-
|
|
22822
|
-
|
|
22823
|
-
|
|
22824
|
-
|
|
22825
|
-
|
|
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;
|
|
22826
25047
|
}
|
|
22827
|
-
|
|
22828
|
-
|
|
22829
|
-
|
|
22830
|
-
|
|
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);
|
|
22831
25057
|
}
|
|
22832
|
-
|
|
22833
|
-
|
|
22834
|
-
|
|
22835
|
-
|
|
22836
|
-
|
|
22837
|
-
|
|
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);
|
|
22838
25064
|
}
|
|
22839
|
-
const
|
|
22840
|
-
|
|
22841
|
-
|
|
22842
|
-
|
|
22843
|
-
|
|
22844
|
-
|
|
22845
|
-
return
|
|
22846
|
-
|
|
22847
|
-
|
|
22848
|
-
|
|
22849
|
-
added,
|
|
22850
|
-
removed,
|
|
22851
|
-
extendsModified,
|
|
22852
|
-
lockedViolations
|
|
22853
|
-
};
|
|
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");
|
|
22854
25075
|
}
|
|
22855
25076
|
|
|
22856
|
-
// src/
|
|
22857
|
-
|
|
22858
|
-
|
|
22859
|
-
|
|
22860
|
-
|
|
22861
|
-
|
|
22862
|
-
|
|
22863
|
-
|
|
22864
|
-
|
|
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;
|
|
25100
|
+
}
|
|
25101
|
+
}
|
|
25102
|
+
if (hit) return true;
|
|
22865
25103
|
}
|
|
22866
|
-
return
|
|
25104
|
+
return false;
|
|
22867
25105
|
}
|
|
22868
|
-
|
|
22869
|
-
|
|
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));
|
|
22870
25111
|
}
|
|
22871
|
-
|
|
22872
|
-
|
|
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;
|
|
22873
25129
|
}
|
|
22874
|
-
|
|
22875
|
-
const
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
|
|
22880
|
-
|
|
22881
|
-
|
|
22882
|
-
|
|
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
|
|
22883
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;
|
|
25244
|
+
}
|
|
25245
|
+
function defaultLessonsConfig() {
|
|
22884
25246
|
return {
|
|
22885
|
-
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
scope,
|
|
22889
|
-
configDir: context.configDir,
|
|
22890
|
-
canonicalDir: context.canonicalDir
|
|
25247
|
+
recallLimit: DEFAULT_RECALL_LIMIT,
|
|
25248
|
+
recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
|
|
25249
|
+
autoPrune: false
|
|
22891
25250
|
};
|
|
22892
25251
|
}
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
|
|
22896
|
-
|
|
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
|
|
22897
25257
|
});
|
|
22898
25258
|
}
|
|
22899
|
-
function
|
|
22900
|
-
|
|
22901
|
-
}
|
|
22902
|
-
async function diff(ctx) {
|
|
22903
|
-
const results = await generate(ctx);
|
|
22904
|
-
const computed = computeDiff(results);
|
|
22905
|
-
return { ...computed, results };
|
|
22906
|
-
}
|
|
22907
|
-
async function check(opts) {
|
|
22908
|
-
return checkLockSync(opts);
|
|
22909
|
-
}
|
|
22910
|
-
|
|
22911
|
-
// src/public/canonical.ts
|
|
22912
|
-
init_errors();
|
|
22913
|
-
async function loadCanonical(projectRoot, options = {}) {
|
|
22914
|
-
if (options.includeExtends === false) {
|
|
22915
|
-
return loadCanonicalFiles(options.canonicalDir ?? projectRoot);
|
|
25259
|
+
function mergeInto(graph, loserId, keeperId) {
|
|
25260
|
+
if (loserId === keeperId) {
|
|
25261
|
+
throw new Error(`mergeLessons: cannot merge lesson "${loserId}" into itself.`);
|
|
22916
25262
|
}
|
|
22917
|
-
|
|
22918
|
-
|
|
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}).`);
|
|
22919
25269
|
}
|
|
22920
|
-
|
|
22921
|
-
|
|
22922
|
-
|
|
22923
|
-
loaded.config,
|
|
22924
|
-
loaded.configDir,
|
|
22925
|
-
{ refreshRemoteCache: options.refreshRemoteCache === true },
|
|
22926
|
-
options.canonicalDir
|
|
25270
|
+
if (loser.status !== "active") {
|
|
25271
|
+
throw new Error(
|
|
25272
|
+
`mergeLessons: loser "${loserId}" is already ${loser.status}; nothing to merge.`
|
|
22927
25273
|
);
|
|
22928
|
-
return canonical;
|
|
22929
|
-
} catch (err) {
|
|
22930
|
-
if (err instanceof ConfigNotFoundError && options.config === void 0 && options.configDir === void 0) {
|
|
22931
|
-
return loadCanonicalFiles(options.canonicalDir ?? projectRoot);
|
|
22932
|
-
}
|
|
22933
|
-
throw err;
|
|
22934
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 };
|
|
25283
|
+
}
|
|
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;
|
|
22935
25290
|
}
|
|
22936
25291
|
|
|
22937
|
-
// src/
|
|
22938
|
-
|
|
22939
|
-
|
|
22940
|
-
|
|
22941
|
-
|
|
22942
|
-
|
|
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;
|
|
22943
25351
|
}
|
|
22944
|
-
|
|
22945
|
-
|
|
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;
|
|
22946
25366
|
}
|
|
22947
|
-
function
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
22951
|
-
|
|
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;
|
|
22952
25378
|
}
|
|
22953
|
-
|
|
22954
|
-
|
|
22955
|
-
|
|
22956
|
-
|
|
22957
|
-
|
|
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);
|
|
22958
25388
|
}
|
|
22959
|
-
function
|
|
22960
|
-
|
|
22961
|
-
|
|
22962
|
-
outputFamilies: layout.outputFamilies === void 0 ? void 0 : Object.freeze(layout.outputFamilies.map(copyOutputFamily)),
|
|
22963
|
-
managedOutputs: layout.managedOutputs === void 0 ? void 0 : copyManagedOutputs(layout.managedOutputs),
|
|
22964
|
-
paths: Object.freeze({ ...layout.paths })
|
|
22965
|
-
});
|
|
25389
|
+
function stripLessonsParagraph(content) {
|
|
25390
|
+
const withoutBlock = stripManagedBlock(content, LESSONS_CONTRACT_START, LESSONS_CONTRACT_END);
|
|
25391
|
+
return stripRawProceduralRule(withoutBlock).trim();
|
|
22966
25392
|
}
|
|
22967
|
-
function
|
|
22968
|
-
return
|
|
22969
|
-
|
|
22970
|
-
|
|
22971
|
-
detectionPaths: Object.freeze([...globalSupport.detectionPaths]),
|
|
22972
|
-
layout: copyLayout(globalSupport.layout)
|
|
22973
|
-
});
|
|
25393
|
+
function stripRawProceduralRule(content) {
|
|
25394
|
+
return content.replace(`
|
|
25395
|
+
|
|
25396
|
+
${LESSONS_PROCEDURAL_RULE}`, "").replace(LESSONS_PROCEDURAL_RULE, "");
|
|
22974
25397
|
}
|
|
22975
|
-
|
|
22976
|
-
|
|
22977
|
-
|
|
22978
|
-
|
|
22979
|
-
|
|
22980
|
-
|
|
22981
|
-
|
|
22982
|
-
|
|
22983
|
-
|
|
22984
|
-
|
|
22985
|
-
|
|
22986
|
-
|
|
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) {
|
|
25471
|
+
const paths = lessonsPaths(projectRoot);
|
|
25472
|
+
const created = [];
|
|
25473
|
+
const updated = [];
|
|
25474
|
+
const skipped = [];
|
|
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 };
|
|
22987
25493
|
}
|
|
22988
|
-
function
|
|
22989
|
-
|
|
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;
|
|
25516
|
+
}
|
|
25517
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
25518
|
+
writeFileSync(configPath, `${JSON.stringify(defaultLessonsConfig(), null, 2)}
|
|
25519
|
+
`, "utf8");
|
|
25520
|
+
created.push(configPath);
|
|
25521
|
+
}
|
|
25522
|
+
function injectProceduralBlock(projectRoot) {
|
|
25523
|
+
const rootRule = join(projectRoot, ".agentsmesh/rules/_root.md");
|
|
25524
|
+
if (!existsSync(rootRule)) {
|
|
25525
|
+
mkdirSync(dirname(rootRule), { recursive: true });
|
|
25526
|
+
const seeded = `---
|
|
25527
|
+
root: true
|
|
25528
|
+
description: ""
|
|
25529
|
+
---
|
|
25530
|
+
|
|
25531
|
+
${LESSONS_PARAGRAPH_BLOCK}
|
|
25532
|
+
|
|
25533
|
+
# Operational Guidelines
|
|
25534
|
+
`;
|
|
25535
|
+
writeFileSync(rootRule, seeded, "utf8");
|
|
25536
|
+
return true;
|
|
25537
|
+
}
|
|
25538
|
+
const current = readFileSync(rootRule, "utf8");
|
|
25539
|
+
const desired = `${appendLessonsParagraph(current)}
|
|
25540
|
+
`;
|
|
25541
|
+
if (desired === current) return false;
|
|
25542
|
+
writeFileSync(rootRule, desired, "utf8");
|
|
25543
|
+
return true;
|
|
22990
25544
|
}
|
|
22991
25545
|
|
|
22992
|
-
export { AgentsMeshError, ConfigNotFoundError, ConfigValidationError, FileSystemError, GenerationError, ImportError, LockAcquisitionError, RemoteFetchError, TargetNotFoundError, check, computeDiff2 as computeDiff, diff, formatDiffSummary, generate, getAllDescriptors, getDescriptor, getTargetCatalog, importFrom, lint, loadCanonical, loadCanonicalFiles, loadConfig2 as loadConfig, loadConfigFromDirectory, loadProjectContext, registerTargetDescriptor, resolveOutputCollisions };
|
|
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 };
|
|
22993
25547
|
//# sourceMappingURL=index.js.map
|
|
22994
25548
|
//# sourceMappingURL=index.js.map
|