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.
@@ -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' | 'NARROWED_GLOB' | 'KEYWORD_VARIANT_ADDED' | 'DROPPED_KEYWORD';
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-z8Dlr5Cm.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-z8Dlr5Cm.js';
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 = makeTriggerId(spec);
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 TRIGGER_PREFIX = {
218
+ var TRIGGER_PREFIX2 = {
148
219
  file_glob: "glob",
149
220
  command_pattern: "cmd",
150
221
  keyword: "kw"
151
222
  };
152
- function makeTriggerId(spec) {
223
+ function makeTriggerId2(spec) {
153
224
  const hash = createHash("sha1").update(`${spec.kind}|${spec.pattern}`).digest("hex").slice(0, 8);
154
- return `t-${TRIGGER_PREFIX[spec.kind]}-${hash}`;
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 sleep(delay);
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 = normalizeRule2(lesson.rule);
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 activeTriggerIds2 = /* @__PURE__ */ new Set();
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 (!activeTriggerIds2.has(triggerId)) continue;
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 activeTriggerIds2 = /* @__PURE__ */ new Set();
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 (!activeTriggerIds2.has(triggerId)) continue;
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: todayIso2() });
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 union3 = new Set(dedup.seen);
2563
- for (const id of returnedIds) union3.add(id);
2564
- if (union3.size === dedup.seen.size) return;
2565
- writeSeenStore(dedup.path, [...union3]);
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, effective, { ...options, knownPaths });
2877
- const repaired = repair === null || repair.repairs.length === 0 ? result : { ...result, warnings: [...result.warnings, ...repair.repairs] };
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 ? repaired : { ...repaired, autoPruned };
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: union2(keeper.triggers, loser.triggers),
2912
- topics: union2(keeper.topics, loser.topics),
2913
- evidence: union2(keeper.evidence, loser.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$1(delayMs * 2 ** attempt);
3068
+ await setTimeout(delayMs * 2 ** attempt);
3174
3069
  }
3175
3070
  }
3176
3071
  }