agentsmesh 0.38.0 → 0.40.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 +61 -0
- package/README.md +33 -19
- package/dist/canonical.js +1479 -2155
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +282 -280
- package/dist/engine.d.ts +2 -2
- package/dist/engine.js +1584 -2358
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1709 -2508
- package/dist/index.js.map +1 -1
- package/dist/{init-z8Dlr5Cm.d.ts → init-B1qdo3Dl.d.ts} +1 -1
- package/dist/lessons.d.ts +2 -2
- package/dist/lessons.js +170 -275
- package/dist/lessons.js.map +1 -1
- package/dist/{target-descriptor-DtgeHKPG.d.ts → target-descriptor-CvTJyDxd.d.ts} +9 -0
- package/dist/targets.d.ts +2 -2
- package/dist/targets.js +1446 -2027
- package/dist/targets.js.map +1 -1
- package/package.json +4 -3
|
@@ -170,7 +170,7 @@ interface AutoPruneSummary {
|
|
|
170
170
|
* because that lesson is captured then silently never recalled. These guardrails
|
|
171
171
|
* are the warn-only complement to that single hard block.
|
|
172
172
|
*/
|
|
173
|
-
type GuardrailCode = 'OVERSIZED_LESSON_TRIGGERS' | 'BROAD_GLOB_TRIGGER' | 'WIDE_GLOB_MATCH' | 'KEYWORD_ONLY_LESSON' | 'LOW_SIGNAL_KEYWORD' | 'STOPWORD_KEYWORD' | 'DEAD_GLOB' | 'NEAR_DUPLICATE_LESSON'
|
|
173
|
+
type GuardrailCode = 'OVERSIZED_LESSON_TRIGGERS' | 'BROAD_GLOB_TRIGGER' | 'WIDE_GLOB_MATCH' | 'KEYWORD_ONLY_LESSON' | 'LOW_SIGNAL_KEYWORD' | 'STOPWORD_KEYWORD' | 'DEAD_GLOB' | 'NEAR_DUPLICATE_LESSON';
|
|
174
174
|
interface GuardrailWarning {
|
|
175
175
|
readonly code: GuardrailCode;
|
|
176
176
|
readonly message: string;
|
package/dist/lessons.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as RankedLesson, j as LessonsQuery, A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult } from './init-
|
|
2
|
-
export { c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, X as DEFAULT_RECALL_MAX_TOKENS, I as ImportLegacyOptions, d as ImportLegacyReport, Y as LESSONS_LOCK_FILENAME, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, Z as LessonsGraphExistsError, h as LessonsGraphSchema, i as LessonsPaths, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, _ as lessonsLockPath, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-
|
|
1
|
+
import { o as RankedLesson, j as LessonsQuery, A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult } from './init-B1qdo3Dl.js';
|
|
2
|
+
export { c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, X as DEFAULT_RECALL_MAX_TOKENS, I as ImportLegacyOptions, d as ImportLegacyReport, Y as LESSONS_LOCK_FILENAME, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, Z as LessonsGraphExistsError, h as LessonsGraphSchema, i as LessonsPaths, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, _ as lessonsLockPath, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-B1qdo3Dl.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/lessons.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
3
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, statSync, renameSync, readdirSync, realpathSync, appendFileSync } from 'fs';
|
|
3
4
|
import { resolve, dirname, join, relative, sep, basename, extname } from 'path';
|
|
4
5
|
import { stringify, parse, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
|
|
5
|
-
import { createHash, randomUUID } from 'crypto';
|
|
6
6
|
import picomatch from 'picomatch';
|
|
7
7
|
import { mkdir, rm, writeFile, readFile, stat, lstat, open, rename } from 'fs/promises';
|
|
8
|
+
import { setTimeout } from 'timers/promises';
|
|
8
9
|
import { tmpdir, hostname } from 'os';
|
|
9
|
-
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
10
10
|
|
|
11
11
|
// src/lessons/graph-schema.ts
|
|
12
12
|
var CURRENT_GRAPH_VERSION = 2;
|
|
@@ -52,6 +52,77 @@ var LessonsGraphSchema = z.object({
|
|
|
52
52
|
function parseGraph(raw) {
|
|
53
53
|
return LessonsGraphSchema.parse(raw);
|
|
54
54
|
}
|
|
55
|
+
function normalizeRule(rule) {
|
|
56
|
+
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
57
|
+
}
|
|
58
|
+
function union(base, extra) {
|
|
59
|
+
const out = [...base];
|
|
60
|
+
for (const item of extra) if (!out.includes(item)) out.push(item);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function mergeTriggers(graph, spec) {
|
|
64
|
+
const requested = [
|
|
65
|
+
// Normalize `\` → `/` so a Windows-shaped glob matches: recall relativizes
|
|
66
|
+
// every `--file` to forward slashes (normalizeRecallFile), so a backslash
|
|
67
|
+
// pattern stored raw would silently never fire. Normalizing here also lets
|
|
68
|
+
// a backslash pattern dedupe against the forward-slash node it equals.
|
|
69
|
+
...(spec.files ?? []).map(
|
|
70
|
+
(p) => ({ kind: "file_glob", pattern: p.replaceAll("\\", "/") })
|
|
71
|
+
),
|
|
72
|
+
...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
|
|
73
|
+
...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
|
|
74
|
+
];
|
|
75
|
+
const reverseLookup = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
77
|
+
reverseLookup.set(triggerKey(trigger), id);
|
|
78
|
+
}
|
|
79
|
+
const triggerIds = [];
|
|
80
|
+
const newTriggerIds = [];
|
|
81
|
+
for (const spec2 of requested) {
|
|
82
|
+
const key = triggerKey(spec2);
|
|
83
|
+
const existing = reverseLookup.get(key);
|
|
84
|
+
if (existing !== void 0) {
|
|
85
|
+
if (!triggerIds.includes(existing)) triggerIds.push(existing);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const id = makeTriggerId(spec2);
|
|
89
|
+
graph.triggers[id] = { kind: spec2.kind, pattern: spec2.pattern };
|
|
90
|
+
reverseLookup.set(key, id);
|
|
91
|
+
triggerIds.push(id);
|
|
92
|
+
newTriggerIds.push(id);
|
|
93
|
+
}
|
|
94
|
+
return { triggerIds, newTriggerIds };
|
|
95
|
+
}
|
|
96
|
+
function triggerKey(t) {
|
|
97
|
+
return `${t.kind}|${t.pattern}`;
|
|
98
|
+
}
|
|
99
|
+
var TRIGGER_PREFIX = {
|
|
100
|
+
file_glob: "glob",
|
|
101
|
+
command_pattern: "cmd",
|
|
102
|
+
keyword: "kw"
|
|
103
|
+
};
|
|
104
|
+
function makeTriggerId(spec) {
|
|
105
|
+
const hash = createHash("sha1").update(triggerKey(spec)).digest("hex").slice(0, 8);
|
|
106
|
+
return `t-${TRIGGER_PREFIX[spec.kind]}-${hash}`;
|
|
107
|
+
}
|
|
108
|
+
function makeLessonId(graph, topic, ruleKey) {
|
|
109
|
+
const slug = ruleToSlug(ruleKey);
|
|
110
|
+
const base = slug.length > 0 ? `${topic}-${slug}` : `${topic}-${createHash("sha1").update(ruleKey).digest("hex").slice(0, 8)}`;
|
|
111
|
+
let candidate = base;
|
|
112
|
+
let i = 2;
|
|
113
|
+
while (graph.lessons[candidate] !== void 0) {
|
|
114
|
+
candidate = `${base}-${i}`;
|
|
115
|
+
i += 1;
|
|
116
|
+
}
|
|
117
|
+
return candidate;
|
|
118
|
+
}
|
|
119
|
+
function ruleToSlug(rule) {
|
|
120
|
+
const words = rule.replace(/[^a-z0-9 ]+/g, " ").split(/\s+/).filter((w) => w.length > 0).slice(0, 5);
|
|
121
|
+
return words.join("-").slice(0, 40).replace(/-+$/, "");
|
|
122
|
+
}
|
|
123
|
+
function todayIso() {
|
|
124
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
125
|
+
}
|
|
55
126
|
var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
|
|
56
127
|
function graphFilePath(projectRoot) {
|
|
57
128
|
return resolve(projectRoot, GRAPH_REL_PATH);
|
|
@@ -136,7 +207,7 @@ function collectClusterTriggerIds(cluster, triggersById, triggerIdByKey) {
|
|
|
136
207
|
const key = `${spec.kind}|${spec.pattern}`;
|
|
137
208
|
let id = triggerIdByKey.get(key);
|
|
138
209
|
if (id === void 0) {
|
|
139
|
-
id =
|
|
210
|
+
id = makeTriggerId2(spec);
|
|
140
211
|
triggerIdByKey.set(key, id);
|
|
141
212
|
triggersById.set(id, { kind: spec.kind, pattern: spec.pattern });
|
|
142
213
|
}
|
|
@@ -144,14 +215,14 @@ function collectClusterTriggerIds(cluster, triggersById, triggerIdByKey) {
|
|
|
144
215
|
}
|
|
145
216
|
return ids;
|
|
146
217
|
}
|
|
147
|
-
var
|
|
218
|
+
var TRIGGER_PREFIX2 = {
|
|
148
219
|
file_glob: "glob",
|
|
149
220
|
command_pattern: "cmd",
|
|
150
221
|
keyword: "kw"
|
|
151
222
|
};
|
|
152
|
-
function
|
|
223
|
+
function makeTriggerId2(spec) {
|
|
153
224
|
const hash = createHash("sha1").update(`${spec.kind}|${spec.pattern}`).digest("hex").slice(0, 8);
|
|
154
|
-
return `t-${
|
|
225
|
+
return `t-${TRIGGER_PREFIX2[spec.kind]}-${hash}`;
|
|
155
226
|
}
|
|
156
227
|
var RULE_HEADING_RE = /^##\s+Rules\b.*$/i;
|
|
157
228
|
var NEXT_HEADING_RE = /^##\s+/;
|
|
@@ -203,81 +274,6 @@ function deleteLegacyArtifacts(baseDir) {
|
|
|
203
274
|
}
|
|
204
275
|
return deleted;
|
|
205
276
|
}
|
|
206
|
-
function normalizeRule(rule) {
|
|
207
|
-
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
208
|
-
}
|
|
209
|
-
function union(base, extra) {
|
|
210
|
-
const out = [...base];
|
|
211
|
-
for (const item of extra) if (!out.includes(item)) out.push(item);
|
|
212
|
-
return out;
|
|
213
|
-
}
|
|
214
|
-
function mergeTriggers(graph, spec) {
|
|
215
|
-
const requested = [
|
|
216
|
-
// Normalize `\` → `/` so a Windows-shaped glob matches: recall relativizes
|
|
217
|
-
// every `--file` to forward slashes (normalizeRecallFile), so a backslash
|
|
218
|
-
// pattern stored raw would silently never fire. Normalizing here also lets
|
|
219
|
-
// a backslash pattern dedupe against the forward-slash node it equals.
|
|
220
|
-
...(spec.files ?? []).map(
|
|
221
|
-
(p) => ({ kind: "file_glob", pattern: p.replaceAll("\\", "/") })
|
|
222
|
-
),
|
|
223
|
-
...(spec.commands ?? []).map((p) => ({ kind: "command_pattern", pattern: p })),
|
|
224
|
-
...(spec.keywords ?? []).map((p) => ({ kind: "keyword", pattern: p }))
|
|
225
|
-
];
|
|
226
|
-
const reverseLookup = /* @__PURE__ */ new Map();
|
|
227
|
-
for (const [id, trigger] of Object.entries(graph.triggers)) {
|
|
228
|
-
reverseLookup.set(triggerKey(trigger), id);
|
|
229
|
-
}
|
|
230
|
-
const triggerIds = [];
|
|
231
|
-
const newTriggerIds = [];
|
|
232
|
-
for (const spec2 of requested) {
|
|
233
|
-
const key = triggerKey(spec2);
|
|
234
|
-
const existing = reverseLookup.get(key);
|
|
235
|
-
if (existing !== void 0) {
|
|
236
|
-
if (!triggerIds.includes(existing)) triggerIds.push(existing);
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
const id = makeTriggerId2(spec2);
|
|
240
|
-
graph.triggers[id] = { kind: spec2.kind, pattern: spec2.pattern };
|
|
241
|
-
reverseLookup.set(key, id);
|
|
242
|
-
triggerIds.push(id);
|
|
243
|
-
newTriggerIds.push(id);
|
|
244
|
-
}
|
|
245
|
-
return { triggerIds, newTriggerIds };
|
|
246
|
-
}
|
|
247
|
-
function triggerKey(t) {
|
|
248
|
-
return `${t.kind}|${t.pattern}`;
|
|
249
|
-
}
|
|
250
|
-
var TRIGGER_PREFIX2 = {
|
|
251
|
-
file_glob: "glob",
|
|
252
|
-
command_pattern: "cmd",
|
|
253
|
-
keyword: "kw"
|
|
254
|
-
};
|
|
255
|
-
function makeTriggerId2(spec) {
|
|
256
|
-
const hash = createHash("sha1").update(triggerKey(spec)).digest("hex").slice(0, 8);
|
|
257
|
-
return `t-${TRIGGER_PREFIX2[spec.kind]}-${hash}`;
|
|
258
|
-
}
|
|
259
|
-
function makeLessonId(graph, topic, ruleKey) {
|
|
260
|
-
const slug = ruleToSlug(ruleKey);
|
|
261
|
-
const base = slug.length > 0 ? `${topic}-${slug}` : `${topic}-${createHash("sha1").update(ruleKey).digest("hex").slice(0, 8)}`;
|
|
262
|
-
let candidate = base;
|
|
263
|
-
let i = 2;
|
|
264
|
-
while (graph.lessons[candidate] !== void 0) {
|
|
265
|
-
candidate = `${base}-${i}`;
|
|
266
|
-
i += 1;
|
|
267
|
-
}
|
|
268
|
-
return candidate;
|
|
269
|
-
}
|
|
270
|
-
function ruleToSlug(rule) {
|
|
271
|
-
const words = rule.replace(/[^a-z0-9 ]+/g, " ").split(/\s+/).filter((w) => w.length > 0).slice(0, 5);
|
|
272
|
-
return words.join("-").slice(0, 40).replace(/-+$/, "");
|
|
273
|
-
}
|
|
274
|
-
function todayIso() {
|
|
275
|
-
const now = /* @__PURE__ */ new Date();
|
|
276
|
-
const y = now.getUTCFullYear();
|
|
277
|
-
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
278
|
-
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
279
|
-
return `${y}-${m}-${d}`;
|
|
280
|
-
}
|
|
281
277
|
|
|
282
278
|
// src/lessons/add-errors.ts
|
|
283
279
|
var EmptyRuleError = class extends Error {
|
|
@@ -1198,7 +1194,7 @@ async function acquireProcessLock(lockPath, opts = {}) {
|
|
|
1198
1194
|
throw new LockAcquisitionError(lockPath, describeHolder(holder), { label: opts.label });
|
|
1199
1195
|
}
|
|
1200
1196
|
attempt++;
|
|
1201
|
-
await
|
|
1197
|
+
await setTimeout(delay);
|
|
1202
1198
|
}
|
|
1203
1199
|
}
|
|
1204
1200
|
async function tryAcquire(lockPath) {
|
|
@@ -1291,9 +1287,6 @@ function isLockMetadata(value) {
|
|
|
1291
1287
|
function getHostname() {
|
|
1292
1288
|
return hostname();
|
|
1293
1289
|
}
|
|
1294
|
-
function sleep(ms) {
|
|
1295
|
-
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
1296
|
-
}
|
|
1297
1290
|
|
|
1298
1291
|
// src/lessons/lessons-lock.ts
|
|
1299
1292
|
var LESSONS_LOCK_FILENAME = ".lessons.lock";
|
|
@@ -1486,7 +1479,7 @@ function collectDuplicateRules(graph, findings) {
|
|
|
1486
1479
|
const byKey = /* @__PURE__ */ new Map();
|
|
1487
1480
|
for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
|
|
1488
1481
|
if (lesson.status !== "active") continue;
|
|
1489
|
-
const key =
|
|
1482
|
+
const key = normalizeRule(lesson.rule);
|
|
1490
1483
|
const bucket = byKey.get(key) ?? [];
|
|
1491
1484
|
bucket.push(lessonId);
|
|
1492
1485
|
byKey.set(key, bucket);
|
|
@@ -1584,9 +1577,6 @@ function collectFanout(graph, findings) {
|
|
|
1584
1577
|
});
|
|
1585
1578
|
}
|
|
1586
1579
|
}
|
|
1587
|
-
function normalizeRule2(rule) {
|
|
1588
|
-
return rule.trim().replace(/\s+/g, " ").toLowerCase();
|
|
1589
|
-
}
|
|
1590
1580
|
var TIED_TRIGGER_SET_THRESHOLD = 5;
|
|
1591
1581
|
function collectTriggerSetCollisions(graph, findings) {
|
|
1592
1582
|
const bySet = /* @__PURE__ */ new Map();
|
|
@@ -1612,14 +1602,10 @@ function collectTriggerSetCollisions(graph, findings) {
|
|
|
1612
1602
|
|
|
1613
1603
|
// src/lessons/validate-keywords.ts
|
|
1614
1604
|
function collectLowSignalKeywords(graph, findings) {
|
|
1615
|
-
const
|
|
1616
|
-
for (const lesson of Object.values(graph.lessons)) {
|
|
1617
|
-
if (lesson.status !== "active") continue;
|
|
1618
|
-
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
1619
|
-
}
|
|
1605
|
+
const active = activeTriggerIds(graph);
|
|
1620
1606
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
1621
1607
|
if (trigger.kind !== "keyword") continue;
|
|
1622
|
-
if (!
|
|
1608
|
+
if (!active.has(triggerId)) continue;
|
|
1623
1609
|
if (!isLowSignalKeyword(trigger.pattern)) continue;
|
|
1624
1610
|
findings.push({
|
|
1625
1611
|
level: "warning",
|
|
@@ -1630,14 +1616,10 @@ function collectLowSignalKeywords(graph, findings) {
|
|
|
1630
1616
|
}
|
|
1631
1617
|
}
|
|
1632
1618
|
function collectStopwordKeywords(graph, findings) {
|
|
1633
|
-
const
|
|
1634
|
-
for (const lesson of Object.values(graph.lessons)) {
|
|
1635
|
-
if (lesson.status !== "active") continue;
|
|
1636
|
-
for (const t of lesson.triggers) activeTriggerIds2.add(t);
|
|
1637
|
-
}
|
|
1619
|
+
const active = activeTriggerIds(graph);
|
|
1638
1620
|
for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
|
|
1639
1621
|
if (trigger.kind !== "keyword") continue;
|
|
1640
|
-
if (!
|
|
1622
|
+
if (!active.has(triggerId)) continue;
|
|
1641
1623
|
if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
|
|
1642
1624
|
continue;
|
|
1643
1625
|
}
|
|
@@ -1934,25 +1916,67 @@ async function importLegacyLessons(projectRoot, options) {
|
|
|
1934
1916
|
}
|
|
1935
1917
|
|
|
1936
1918
|
// src/lessons/auto-migrate.ts
|
|
1937
|
-
function todayIso2() {
|
|
1938
|
-
const now = /* @__PURE__ */ new Date();
|
|
1939
|
-
const y = now.getUTCFullYear();
|
|
1940
|
-
const m = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
1941
|
-
const d = String(now.getUTCDate()).padStart(2, "0");
|
|
1942
|
-
return `${y}-${m}-${d}`;
|
|
1943
|
-
}
|
|
1944
1919
|
async function maybeAutoMigrateLessons(projectRoot) {
|
|
1945
1920
|
if (existsSync(graphFilePath(projectRoot))) return false;
|
|
1946
1921
|
const paths = lessonsPaths(projectRoot);
|
|
1947
1922
|
if (!existsSync(paths.index)) return false;
|
|
1948
1923
|
try {
|
|
1949
|
-
await importLegacyLessons(projectRoot, { migratedAt:
|
|
1924
|
+
await importLegacyLessons(projectRoot, { migratedAt: todayIso() });
|
|
1950
1925
|
return true;
|
|
1951
1926
|
} catch (err) {
|
|
1952
1927
|
if (err instanceof LessonsGraphExistsError) return false;
|
|
1953
1928
|
throw err;
|
|
1954
1929
|
}
|
|
1955
1930
|
}
|
|
1931
|
+
var SEEN_DIR = "agentsmesh-lessons-seen";
|
|
1932
|
+
function shortHash(value) {
|
|
1933
|
+
let h = 5381;
|
|
1934
|
+
for (let i = 0; i < value.length; i += 1) h = (h << 5) + h + value.charCodeAt(i) >>> 0;
|
|
1935
|
+
return h.toString(36);
|
|
1936
|
+
}
|
|
1937
|
+
function seenStorePath(id, projectRoot) {
|
|
1938
|
+
const safe = id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
|
|
1939
|
+
const scoped = projectRoot === void 0 ? safe : `${safe}__${shortHash(resolve(projectRoot))}`;
|
|
1940
|
+
return join(tmpdir(), SEEN_DIR, `${scoped}.json`);
|
|
1941
|
+
}
|
|
1942
|
+
function readSeenStore(path) {
|
|
1943
|
+
if (!existsSync(path)) return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
1944
|
+
try {
|
|
1945
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
1946
|
+
if (Array.isArray(parsed)) {
|
|
1947
|
+
const ids = new Set(parsed.filter((x) => typeof x === "string"));
|
|
1948
|
+
return { ids, stamps: null };
|
|
1949
|
+
}
|
|
1950
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
1951
|
+
const seen = parsed.seen;
|
|
1952
|
+
if (typeof seen === "object" && seen !== null) {
|
|
1953
|
+
const stamps = /* @__PURE__ */ new Map();
|
|
1954
|
+
for (const [id, ms] of Object.entries(seen)) {
|
|
1955
|
+
if (typeof ms === "number") stamps.set(id, ms);
|
|
1956
|
+
}
|
|
1957
|
+
const lastAt = parsed.lastAt;
|
|
1958
|
+
return {
|
|
1959
|
+
ids: new Set(stamps.keys()),
|
|
1960
|
+
stamps,
|
|
1961
|
+
...typeof lastAt === "number" ? { lastAt } : {}
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
1966
|
+
} catch {
|
|
1967
|
+
return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
function writeSeenStore(path, data, lastAt) {
|
|
1971
|
+
try {
|
|
1972
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1973
|
+
const body = data instanceof Map ? JSON.stringify({ v: 2, lastAt: lastAt ?? Date.now(), seen: Object.fromEntries(data) }) : JSON.stringify(data);
|
|
1974
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
1975
|
+
writeFileSync(tmp, body, "utf8");
|
|
1976
|
+
renameSync(tmp, path);
|
|
1977
|
+
} catch {
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1956
1980
|
|
|
1957
1981
|
// src/lessons/keyword-match.ts
|
|
1958
1982
|
function deriveHaystackTokens(query) {
|
|
@@ -2108,11 +2132,6 @@ function appendRecallRecord(projectRoot, record, env = process.env) {
|
|
|
2108
2132
|
|
|
2109
2133
|
// src/lessons/cmd-fastpath.ts
|
|
2110
2134
|
var FASTPATH_DIR = "agentsmesh-lessons-cmdidx";
|
|
2111
|
-
function shortHash(value) {
|
|
2112
|
-
let h = 5381;
|
|
2113
|
-
for (let i = 0; i < value.length; i += 1) h = (h << 5) + h + value.charCodeAt(i) >>> 0;
|
|
2114
|
-
return h.toString(36);
|
|
2115
|
-
}
|
|
2116
2135
|
function commandFastpathCachePath(projectRoot) {
|
|
2117
2136
|
return join(tmpdir(), FASTPATH_DIR, `${shortHash(resolve(projectRoot))}.json`);
|
|
2118
2137
|
}
|
|
@@ -2364,7 +2383,6 @@ function defaultLessonsConfig() {
|
|
|
2364
2383
|
recallLimit: DEFAULT_RECALL_LIMIT,
|
|
2365
2384
|
recallMaxTokens: DEFAULT_RECALL_MAX_TOKENS,
|
|
2366
2385
|
autoPrune: false,
|
|
2367
|
-
repairTriggers: false,
|
|
2368
2386
|
telemetry: false
|
|
2369
2387
|
};
|
|
2370
2388
|
}
|
|
@@ -2391,6 +2409,31 @@ function loadRecallConfig(projectRoot) {
|
|
|
2391
2409
|
}
|
|
2392
2410
|
}
|
|
2393
2411
|
|
|
2412
|
+
// src/lessons/context-key.ts
|
|
2413
|
+
function normalizeCommand(command) {
|
|
2414
|
+
const words = command.trim().split(/\s+/);
|
|
2415
|
+
let start = 0;
|
|
2416
|
+
while (start < words.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(words[start])) start += 1;
|
|
2417
|
+
const rest = words.slice(start);
|
|
2418
|
+
const programIdx = rest.findIndex(isBareWord);
|
|
2419
|
+
if (programIdx === -1) return rest[0] ?? "";
|
|
2420
|
+
const program = rest[programIdx];
|
|
2421
|
+
const next = rest[programIdx + 1];
|
|
2422
|
+
return next !== void 0 && isBareWord(next) ? `${program} ${next}` : program;
|
|
2423
|
+
}
|
|
2424
|
+
function isBareWord(w) {
|
|
2425
|
+
return w.length > 0 && !w.startsWith("-") && !w.includes("/") && !/^["'`]/.test(w);
|
|
2426
|
+
}
|
|
2427
|
+
function contextKey(input, projectRoot) {
|
|
2428
|
+
if (input.file !== void 0 && input.file.length > 0) {
|
|
2429
|
+
return `file:${normalizeRecallFile(input.file, projectRoot)}`;
|
|
2430
|
+
}
|
|
2431
|
+
if (input.command !== void 0 && input.command.length > 0) {
|
|
2432
|
+
return `cmd:${normalizeCommand(input.command)}`;
|
|
2433
|
+
}
|
|
2434
|
+
return "none";
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2394
2437
|
// src/lessons/recall-telemetry.ts
|
|
2395
2438
|
function recordRecallTelemetry(projectRoot, graph, query, matches, lessons, options = {}) {
|
|
2396
2439
|
if (!isTelemetryEnabled(process.env, projectRoot)) return;
|
|
@@ -2406,6 +2449,7 @@ function recordRecallTelemetry(projectRoot, graph, query, matches, lessons, opti
|
|
|
2406
2449
|
returnedCount: lessons.length,
|
|
2407
2450
|
returnedTokens: lessons.reduce((sum, l) => sum + estTokens(l.lesson.rule), 0),
|
|
2408
2451
|
truncated: matches.length > lessons.length,
|
|
2452
|
+
contextKey: contextKey({ file: query.file, command: query.command }, projectRoot),
|
|
2409
2453
|
matchedByKind: {
|
|
2410
2454
|
file: countVia(byKind.file_glob),
|
|
2411
2455
|
command: countVia(byKind.command_pattern),
|
|
@@ -2452,55 +2496,6 @@ function loadEffectiveness(projectRoot) {
|
|
|
2452
2496
|
}
|
|
2453
2497
|
return map;
|
|
2454
2498
|
}
|
|
2455
|
-
var SEEN_DIR = "agentsmesh-lessons-seen";
|
|
2456
|
-
function shortHash2(value) {
|
|
2457
|
-
let h = 5381;
|
|
2458
|
-
for (let i = 0; i < value.length; i += 1) h = (h << 5) + h + value.charCodeAt(i) >>> 0;
|
|
2459
|
-
return h.toString(36);
|
|
2460
|
-
}
|
|
2461
|
-
function seenStorePath(id, projectRoot) {
|
|
2462
|
-
const safe = id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 200);
|
|
2463
|
-
const scoped = projectRoot === void 0 ? safe : `${safe}__${shortHash2(resolve(projectRoot))}`;
|
|
2464
|
-
return join(tmpdir(), SEEN_DIR, `${scoped}.json`);
|
|
2465
|
-
}
|
|
2466
|
-
function readSeenStore(path) {
|
|
2467
|
-
if (!existsSync(path)) return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
2468
|
-
try {
|
|
2469
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2470
|
-
if (Array.isArray(parsed)) {
|
|
2471
|
-
const ids = new Set(parsed.filter((x) => typeof x === "string"));
|
|
2472
|
-
return { ids, stamps: null };
|
|
2473
|
-
}
|
|
2474
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
2475
|
-
const seen = parsed.seen;
|
|
2476
|
-
if (typeof seen === "object" && seen !== null) {
|
|
2477
|
-
const stamps = /* @__PURE__ */ new Map();
|
|
2478
|
-
for (const [id, ms] of Object.entries(seen)) {
|
|
2479
|
-
if (typeof ms === "number") stamps.set(id, ms);
|
|
2480
|
-
}
|
|
2481
|
-
const lastAt = parsed.lastAt;
|
|
2482
|
-
return {
|
|
2483
|
-
ids: new Set(stamps.keys()),
|
|
2484
|
-
stamps,
|
|
2485
|
-
...typeof lastAt === "number" ? { lastAt } : {}
|
|
2486
|
-
};
|
|
2487
|
-
}
|
|
2488
|
-
}
|
|
2489
|
-
return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
2490
|
-
} catch {
|
|
2491
|
-
return { ids: /* @__PURE__ */ new Set(), stamps: null };
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
function writeSeenStore(path, data, lastAt) {
|
|
2495
|
-
try {
|
|
2496
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
2497
|
-
const body = data instanceof Map ? JSON.stringify({ v: 2, lastAt: lastAt ?? Date.now(), seen: Object.fromEntries(data) }) : JSON.stringify(data);
|
|
2498
|
-
const tmp = `${path}.${process.pid}.tmp`;
|
|
2499
|
-
writeFileSync(tmp, body, "utf8");
|
|
2500
|
-
renameSync(tmp, path);
|
|
2501
|
-
} catch {
|
|
2502
|
-
}
|
|
2503
|
-
}
|
|
2504
2499
|
var AUTO_SESSION_IDLE_MS = 30 * 60 * 1e3;
|
|
2505
2500
|
var FUTURE_TOLERANCE_MS = 6e4;
|
|
2506
2501
|
function stampAgeMs(stamp, now = Date.now()) {
|
|
@@ -2559,10 +2554,10 @@ function commitSeen(dedup, returnedIds) {
|
|
|
2559
2554
|
writeSeenStore(dedup.path, merged);
|
|
2560
2555
|
return;
|
|
2561
2556
|
}
|
|
2562
|
-
const
|
|
2563
|
-
for (const id of returnedIds)
|
|
2564
|
-
if (
|
|
2565
|
-
writeSeenStore(dedup.path, [...
|
|
2557
|
+
const union2 = new Set(dedup.seen);
|
|
2558
|
+
for (const id of returnedIds) union2.add(id);
|
|
2559
|
+
if (union2.size === dedup.seen.size) return;
|
|
2560
|
+
writeSeenStore(dedup.path, [...union2]);
|
|
2566
2561
|
}
|
|
2567
2562
|
|
|
2568
2563
|
// src/lessons/recall.ts
|
|
@@ -2770,96 +2765,6 @@ function listProjectFiles(projectRoot) {
|
|
|
2770
2765
|
}
|
|
2771
2766
|
return out;
|
|
2772
2767
|
}
|
|
2773
|
-
function isTriggerRepairEnabled(projectRoot) {
|
|
2774
|
-
const path = lessonsPaths(projectRoot).config;
|
|
2775
|
-
if (!existsSync(path)) return false;
|
|
2776
|
-
try {
|
|
2777
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
2778
|
-
if (typeof parsed !== "object" || parsed === null) return false;
|
|
2779
|
-
return parsed.repairTriggers === true;
|
|
2780
|
-
} catch {
|
|
2781
|
-
return false;
|
|
2782
|
-
}
|
|
2783
|
-
}
|
|
2784
|
-
function evidencePath(evidence, knownPaths) {
|
|
2785
|
-
for (const entry of evidence ?? []) {
|
|
2786
|
-
const candidate = entry.replaceAll("\\", "/").replace(/(:\d+)+$/, "").trim();
|
|
2787
|
-
if (knownPaths.has(candidate)) return candidate;
|
|
2788
|
-
}
|
|
2789
|
-
return void 0;
|
|
2790
|
-
}
|
|
2791
|
-
function classGlobFor(path) {
|
|
2792
|
-
const slash = path.lastIndexOf("/");
|
|
2793
|
-
const dir = slash === -1 ? "" : path.slice(0, slash + 1);
|
|
2794
|
-
const base = path.slice(slash + 1);
|
|
2795
|
-
const dot = base.lastIndexOf(".");
|
|
2796
|
-
return `${dir}*${dot > 0 ? base.slice(dot) : ""}`;
|
|
2797
|
-
}
|
|
2798
|
-
function repairFileGlobs(files, evidence, knownPaths, repairs) {
|
|
2799
|
-
const out = [];
|
|
2800
|
-
for (const glob of files) {
|
|
2801
|
-
const needsNarrow = knownPaths !== void 0 && (isBroadGlob(glob) || fileGlobMatchCount(glob, knownPaths) > WIDE_GLOB_MATCH_COUNT);
|
|
2802
|
-
if (!needsNarrow || evidence === void 0 || !picomatch(glob, { dot: true })(evidence)) {
|
|
2803
|
-
if (!out.includes(glob)) out.push(glob);
|
|
2804
|
-
continue;
|
|
2805
|
-
}
|
|
2806
|
-
const derived = classGlobFor(evidence);
|
|
2807
|
-
const derivedOk = derived !== glob && picomatch(derived, { dot: true })(evidence) && fileGlobMatchCount(derived, knownPaths) <= fileGlobMatchCount(glob, knownPaths);
|
|
2808
|
-
if (!derivedOk) {
|
|
2809
|
-
if (!out.includes(glob)) out.push(glob);
|
|
2810
|
-
continue;
|
|
2811
|
-
}
|
|
2812
|
-
if (!out.includes(derived)) out.push(derived);
|
|
2813
|
-
repairs.push({
|
|
2814
|
-
code: "NARROWED_GLOB",
|
|
2815
|
-
message: `Narrowed broad file glob "${glob}" to the evidence file's class "${derived}". Review: for general/library behavior, re-point at the file-CLASS recurrence surface (a '**/.../*Name*.ts'-style glob) where the rule will actually recur.`
|
|
2816
|
-
});
|
|
2817
|
-
}
|
|
2818
|
-
return out;
|
|
2819
|
-
}
|
|
2820
|
-
function repairKeywords(keywords, repairs) {
|
|
2821
|
-
const out = [];
|
|
2822
|
-
for (const kw of keywords) {
|
|
2823
|
-
const tokens = tokenize(kw);
|
|
2824
|
-
if (tokens.length === 0) {
|
|
2825
|
-
repairs.push({
|
|
2826
|
-
code: "DROPPED_KEYWORD",
|
|
2827
|
-
message: `Dropped keyword trigger "${kw}" \u2014 it tokenizes to nothing (stopwords/short words only) and can never fire.`
|
|
2828
|
-
});
|
|
2829
|
-
continue;
|
|
2830
|
-
}
|
|
2831
|
-
if (!out.includes(kw)) out.push(kw);
|
|
2832
|
-
if (!keywordNeedleLosesTokens(kw) && !isLowSignalKeyword(kw)) continue;
|
|
2833
|
-
const variant = tokens.slice(0, MAX_RECOMMENDED_KEYWORD_TOKENS).join(" ");
|
|
2834
|
-
if (variant.toLowerCase() === kw.toLowerCase() || out.includes(variant)) continue;
|
|
2835
|
-
out.push(variant);
|
|
2836
|
-
repairs.push({
|
|
2837
|
-
code: "KEYWORD_VARIANT_ADDED",
|
|
2838
|
-
message: `Keyword trigger "${kw}" cannot match on the mandatory --file/--cmd token path; added the matchable variant "${variant}" beside it (the original still matches prompt text).`
|
|
2839
|
-
});
|
|
2840
|
-
}
|
|
2841
|
-
return out;
|
|
2842
|
-
}
|
|
2843
|
-
function repairTriggers(input, knownPaths) {
|
|
2844
|
-
const repairs = [];
|
|
2845
|
-
const evidence = knownPaths === void 0 ? void 0 : evidencePath(input.evidence, knownPaths);
|
|
2846
|
-
const files = input.triggers.files === void 0 ? void 0 : repairFileGlobs(input.triggers.files, evidence, knownPaths, repairs);
|
|
2847
|
-
const keywords = input.triggers.keywords === void 0 ? void 0 : repairKeywords(input.triggers.keywords, repairs);
|
|
2848
|
-
if (repairs.length === 0) return { input, repairs };
|
|
2849
|
-
const total = (files?.length ?? 0) + (input.triggers.commands?.length ?? 0) + (keywords?.length ?? 0);
|
|
2850
|
-
if (total === 0) return { input, repairs: [] };
|
|
2851
|
-
return {
|
|
2852
|
-
input: {
|
|
2853
|
-
...input,
|
|
2854
|
-
triggers: {
|
|
2855
|
-
...files !== void 0 ? { files } : {},
|
|
2856
|
-
...input.triggers.commands !== void 0 ? { commands: input.triggers.commands } : {},
|
|
2857
|
-
...keywords !== void 0 ? { keywords } : {}
|
|
2858
|
-
}
|
|
2859
|
-
},
|
|
2860
|
-
repairs
|
|
2861
|
-
};
|
|
2862
|
-
}
|
|
2863
2768
|
|
|
2864
2769
|
// src/lessons/capture.ts
|
|
2865
2770
|
async function captureLesson(projectRoot, input, options = {}) {
|
|
@@ -2870,14 +2775,11 @@ async function captureLesson(projectRoot, input, options = {}) {
|
|
|
2870
2775
|
keyword: input.triggers.keywords?.length ?? 0
|
|
2871
2776
|
};
|
|
2872
2777
|
const knownPaths = options.knownPaths ?? listProjectFiles(projectRoot) ?? void 0;
|
|
2873
|
-
const repair = isTriggerRepairEnabled(projectRoot) ? repairTriggers(input, knownPaths) : null;
|
|
2874
|
-
const effective = repair === null ? input : repair.input;
|
|
2875
2778
|
try {
|
|
2876
|
-
const result = await addLesson(projectRoot,
|
|
2877
|
-
|
|
2878
|
-
recordCapture(projectRoot, triggerKinds, repaired);
|
|
2779
|
+
const result = await addLesson(projectRoot, input, { ...options, knownPaths });
|
|
2780
|
+
recordCapture(projectRoot, triggerKinds, result);
|
|
2879
2781
|
const autoPruned = await maybeAutoPrune(projectRoot, knownPaths);
|
|
2880
|
-
return autoPruned === null ?
|
|
2782
|
+
return autoPruned === null ? result : { ...result, autoPruned };
|
|
2881
2783
|
} catch (err) {
|
|
2882
2784
|
recordCapture(projectRoot, triggerKinds, null);
|
|
2883
2785
|
throw err;
|
|
@@ -2908,20 +2810,13 @@ function mergeInto(graph, loserId, keeperId) {
|
|
|
2908
2810
|
}
|
|
2909
2811
|
graph.lessons[keeperId] = {
|
|
2910
2812
|
...keeper,
|
|
2911
|
-
triggers:
|
|
2912
|
-
topics:
|
|
2913
|
-
evidence:
|
|
2813
|
+
triggers: union(keeper.triggers, loser.triggers),
|
|
2814
|
+
topics: union(keeper.topics, loser.topics),
|
|
2815
|
+
evidence: union(keeper.evidence, loser.evidence)
|
|
2914
2816
|
};
|
|
2915
2817
|
graph.lessons[loserId] = { ...loser, status: "superseded", supersededBy: keeperId };
|
|
2916
2818
|
return { loserId, keeperId };
|
|
2917
2819
|
}
|
|
2918
|
-
function union2(base, extra) {
|
|
2919
|
-
const out = [...base];
|
|
2920
|
-
for (const item of extra) {
|
|
2921
|
-
if (!out.includes(item)) out.push(item);
|
|
2922
|
-
}
|
|
2923
|
-
return out;
|
|
2924
|
-
}
|
|
2925
2820
|
|
|
2926
2821
|
// src/lessons/strip-markers.ts
|
|
2927
2822
|
var LINE_REFS = String.raw`L\d+(?:\s*,\s*L\d+)*`;
|
|
@@ -3170,7 +3065,7 @@ async function renameWithRetry(from, to, options = {}) {
|
|
|
3170
3065
|
const code = err.code;
|
|
3171
3066
|
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
3172
3067
|
if (!transient || attempt >= attempts - 1) throw err;
|
|
3173
|
-
await setTimeout
|
|
3068
|
+
await setTimeout(delayMs * 2 ** attempt);
|
|
3174
3069
|
}
|
|
3175
3070
|
}
|
|
3176
3071
|
}
|