agentsmesh 0.40.0 → 0.41.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.
@@ -150,6 +150,12 @@ declare const DEFAULT_RECALL_MAX_TOKENS = 1200;
150
150
  */
151
151
  declare function rankLessons(graph: LessonsGraph, query: LessonsQuery, matches: readonly MatchedLesson[], options?: RankOptions): RankedLesson[];
152
152
 
153
+ /** Warning for a command trigger dropped at capture because it can never fire. */
154
+ interface DeadCommandWarning {
155
+ readonly code: 'DEAD_COMMAND_PATTERN';
156
+ readonly message: string;
157
+ }
158
+
153
159
  interface AutoPruneSummary {
154
160
  readonly removedTriggers: number;
155
161
  readonly removedTopics: number;
@@ -170,7 +176,7 @@ interface AutoPruneSummary {
170
176
  * because that lesson is captured then silently never recalled. These guardrails
171
177
  * are the warn-only complement to that single hard block.
172
178
  */
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';
179
+ type GuardrailCode = 'OVERSIZED_LESSON_TRIGGERS' | 'BROAD_GLOB_TRIGGER' | 'WIDE_GLOB_MATCH' | 'KEYWORD_ONLY_LESSON' | 'LOW_SIGNAL_KEYWORD' | 'STOPWORD_KEYWORD' | 'DEAD_GLOB' | 'PENDING_GLOB' | 'NEAR_DUPLICATE_LESSON';
174
180
  interface GuardrailWarning {
175
181
  readonly code: GuardrailCode;
176
182
  readonly message: string;
@@ -215,13 +221,17 @@ interface AddLessonOptions {
215
221
  */
216
222
  readonly knownPaths?: ReadonlySet<string>;
217
223
  }
224
+ /** A non-blocking capture warning: a guardrail nudge or a dropped dead command trigger. */
225
+ type AddLessonWarning = GuardrailWarning | DeadCommandWarning;
218
226
  interface AddLessonResult {
219
227
  readonly id: string;
220
228
  readonly isNewLesson: boolean;
221
229
  readonly isNewTopic: boolean;
222
230
  readonly newTriggerIds: string[];
223
- /** Non-blocking capture guardrail warnings for the resulting (merged) lesson. */
224
- readonly warnings: GuardrailWarning[];
231
+ /** What a re-add changed on the existing lesson; empty for a new lesson or a no-op. */
232
+ readonly changes: string[];
233
+ /** Non-blocking capture warnings for the resulting (merged) lesson. */
234
+ readonly warnings: AddLessonWarning[];
225
235
  /**
226
236
  * Counts of structural cruft the opt-in auto-prune cleaned up right after this
227
237
  * capture (config `autoPrune: true`). Present only when something was pruned;
@@ -231,6 +241,33 @@ interface AddLessonResult {
231
241
  }
232
242
  declare function addLesson(projectRoot: string, input: AddLessonInput, options?: AddLessonOptions): Promise<AddLessonResult>;
233
243
 
244
+ type ValidationLevel = 'error' | 'warning';
245
+ interface ValidationFinding {
246
+ readonly level: ValidationLevel;
247
+ readonly code: string;
248
+ readonly message: string;
249
+ readonly lessonId?: string;
250
+ /** Every lesson one aggregate finding covers (machine-readable; the message names a few). */
251
+ readonly lessonIds?: readonly string[];
252
+ readonly topicId?: string;
253
+ readonly triggerId?: string;
254
+ }
255
+ interface ValidationReport {
256
+ /** True when no `error`-level findings exist (warnings do not affect `ok`). */
257
+ readonly ok: boolean;
258
+ readonly findings: ValidationFinding[];
259
+ }
260
+ interface ValidateOptions {
261
+ /**
262
+ * Working-tree file list (project-relative, forward-slash) for the dead-glob
263
+ * liveness check. When omitted the check is SKIPPED — the pure write-barrier
264
+ * call in `mutate.ts` passes nothing, so `add` never walks the tree and a
265
+ * liveness warning can never block a write. The CLI/lint callers supply it.
266
+ */
267
+ readonly knownPaths?: ReadonlySet<string>;
268
+ }
269
+ declare function validateLessonsGraph(graph: LessonsGraph, options?: ValidateOptions): ValidationReport;
270
+
234
271
  interface MutateOptions {
235
272
  readonly retries?: number;
236
273
  }
@@ -302,33 +339,6 @@ declare function stripLegacyMarkers(rule: string): string;
302
339
  */
303
340
  declare function stripMarkersInGraph(projectRoot: string, options?: StripMarkersOptions): Promise<StripMarkersReport>;
304
341
 
305
- type ValidationLevel = 'error' | 'warning';
306
- interface ValidationFinding {
307
- readonly level: ValidationLevel;
308
- readonly code: string;
309
- readonly message: string;
310
- readonly lessonId?: string;
311
- /** Every lesson one aggregate finding covers (machine-readable; the message names a few). */
312
- readonly lessonIds?: readonly string[];
313
- readonly topicId?: string;
314
- readonly triggerId?: string;
315
- }
316
- interface ValidationReport {
317
- /** True when no `error`-level findings exist (warnings do not affect `ok`). */
318
- readonly ok: boolean;
319
- readonly findings: ValidationFinding[];
320
- }
321
- interface ValidateOptions {
322
- /**
323
- * Working-tree file list (project-relative, forward-slash) for the dead-glob
324
- * liveness check. When omitted the check is SKIPPED — the pure write-barrier
325
- * call in `mutate.ts` passes nothing, so `add` never walks the tree and a
326
- * liveness warning can never block a write. The CLI/lint callers supply it.
327
- */
328
- readonly knownPaths?: ReadonlySet<string>;
329
- }
330
- declare function validateLessonsGraph(graph: LessonsGraph, options?: ValidateOptions): ValidationReport;
331
-
332
342
  interface ImportLegacyOptions {
333
343
  /** ISO date stamped onto every imported lesson's `createdAt`. */
334
344
  readonly migratedAt: string;
@@ -353,6 +363,12 @@ interface ImportLegacyOptions {
353
363
  * data; `force` is irrelevant in this mode.
354
364
  */
355
365
  readonly merge?: boolean;
366
+ /**
367
+ * Refuse ({@link LessonsGraphExistsError}) when `lessons.json` exists at write
368
+ * time under the lock, even if empty. Auto-migration sets this so two first
369
+ * writers cannot both migrate.
370
+ */
371
+ readonly requireAbsentGraph?: boolean;
356
372
  }
357
373
  /** Thrown when migration would overwrite an already-populated graph without `force`. */
358
374
  declare class LessonsGraphExistsError extends Error {
@@ -377,31 +393,54 @@ interface ImportLegacyReport {
377
393
  * `existsSync(lessonsPaths(root).index)` before invoking (see
378
394
  * `maybeAutoMigrateLessons` and the `import-md` handler); re-running on a
379
395
  * post-migration tree, where the legacy files are already gone, throws.
396
+ * Topic files outside `.agentsmesh/lessons/` are refused (LegacyTopicPathError).
380
397
  */
381
398
  declare function importLegacyLessons(projectRoot: string, options: ImportLegacyOptions): Promise<ImportLegacyReport>;
382
399
 
383
400
  /**
384
401
  * Cross-platform process lock backed by an atomic mkdir.
385
402
  *
386
- * Stale recovery: the holder writes its PID and start timestamp into the lock
387
- * dir. A dead same-host holder is evicted at once. A live or remote holder is
388
- * evicted only past `staleMs` — an hours-long bound that catches a hung
389
- * process or a recycled PID, never a slow but healthy run.
403
+ * Each acquisition gets a random owner token, kept as an `owner-<token>` marker
404
+ * in the lock dir next to `holder.json` (pid, host, start time). The lock
405
+ * changes hands only by removing that exact marker, so neither a release nor
406
+ * a stale eviction can delete a lock that already passed to another process.
407
+ *
408
+ * Stale recovery: a dead same-host holder, or a live pid that now belongs to
409
+ * another process, is evicted at once. Any holder older than `staleMs` (or
410
+ * dated in the future past clock skew) is evicted too — the bound for hung
411
+ * processes and holders on other hosts. A holder evicted this way sees
412
+ * `isHeld()` turn false, so it can refuse to write.
390
413
  */
391
414
  interface LockOptions {
392
415
  /** Maximum retry attempts before throwing LockAcquisitionError. */
393
416
  retries?: number;
394
- /** Delay between retries in ms. */
417
+ /** Delay before the first retry in ms (default 200). */
395
418
  retryDelayMs?: number;
419
+ /** Cap for the doubling delay. Defaults to `retryDelayMs`, i.e. a fixed delay. */
420
+ maxRetryDelayMs?: number;
421
+ /** Spread each delay over the upper half of its window so waiters do not retry in step. */
422
+ jitter?: boolean;
396
423
  /**
397
- * Secondary age bound (default 6h): a lock older than this is evicted even
398
- * when its holder PID is still alive or cannot be probed (other host).
424
+ * Age bound (default 6h): a lock older than this is evicted even when its
425
+ * holder is still alive or cannot be probed (other host).
399
426
  */
400
427
  staleMs?: number;
401
428
  /** Human-readable lock name surfaced in LockAcquisitionError, e.g. "lessons lock". */
402
429
  label?: string;
430
+ /** Called once, with the holder, when a wait lasts `waitNoticeMs` (default 2000). */
431
+ onWait?: (holder: string) => void;
432
+ waitNoticeMs?: number;
403
433
  }
404
434
  type LockRelease = () => Promise<void>;
435
+ /** The release function of an acquired lock. */
436
+ interface HeldLock extends LockRelease {
437
+ /**
438
+ * False once this acquisition no longer owns the lock: released, or evicted
439
+ * as stale (e.g. the process was paused longer than `staleMs`). Check it
440
+ * right before a write that must not overwrite a later holder's work.
441
+ */
442
+ isHeld(): Promise<boolean>;
443
+ }
405
444
 
406
445
  /**
407
446
  * Process lock for lessons-graph writes.
@@ -410,13 +449,12 @@ type LockRelease = () => Promise<void>;
410
449
  * the rule once per failure, even when a hooked CI step and the user's editor
411
450
  * race for the same `lessons.json`. The lock lives at
412
451
  * `.agentsmesh/lessons/.lessons.lock` and reuses the same `acquireProcessLock`
413
- * primitive as `.install.lock` / `.generate.lock`, so stale-eviction, signal
414
- * cleanup, and PID metadata all behave identically.
452
+ * primitive as `.install.lock` / `.generate.lock`, with its own timing below.
415
453
  */
416
454
 
417
455
  declare const LESSONS_LOCK_FILENAME = ".lessons.lock";
418
456
  declare function lessonsLockPath(projectRoot: string): string;
419
- declare function acquireLessonsLock(projectRoot: string, opts?: LockOptions): Promise<LockRelease>;
457
+ declare function acquireLessonsLock(projectRoot: string, opts?: LockOptions): Promise<HeldLock>;
420
458
 
421
459
  /**
422
460
  * Default on-disk locations for the lessons subsystem.
@@ -472,18 +510,35 @@ declare function toRelPath(projectRoot: string, absolute: string): string;
472
510
  */
473
511
  declare const LESSONS_PROCEDURAL_RULE = "## Lessons (BLOCKING)\n\nGraph `.agentsmesh/lessons/lessons.json` is canonical; never hand-edit it. Manual: `lessons` skill.\n\n**Recall:** before every file edit or state-changing command, MUST run `agentsmesh lessons query --file <path> --cmd <command> --session auto` and obey matches; at task start, ALSO run `agentsmesh lessons query --keyword \"<task terms>\" --always --session auto` for conceptual + universal rules no path/command names. Pure-read commands and recall itself are exempt.\n\n**Capture:** after any failure, user correction, regression, wrong assumption, useful surprise, repeated friction, or non-obvious fix, MUST self-critique and run `agentsmesh lessons add \"<imperative rule>\" --topic <id> --trigger-file <glob> --evidence <sha|lesson-id>`.\n\n**Before final:** report `Lesson: captured <id>` or `Lesson: none`. No recall/capture gate = task incomplete. No shell: use `lessons_query` / `lessons_add`.";
474
512
 
513
+ type MergeDriverSetup = {
514
+ readonly status: 'configured' | 'updated' | 'unchanged' | 'skipped';
515
+ readonly command: string;
516
+ } | {
517
+ readonly status: 'custom';
518
+ readonly command: string;
519
+ readonly existing: string;
520
+ } | {
521
+ readonly status: 'failed';
522
+ readonly command: string;
523
+ readonly reason: string;
524
+ };
525
+
475
526
  interface ScaffoldLessonsResult {
476
527
  readonly created: string[];
477
528
  /** Managed artifacts rewritten to the current wording (e.g. a stale skill). */
478
529
  readonly updated: string[];
479
530
  readonly skipped: string[];
480
531
  readonly rootRuleUpdated: boolean;
481
- /** True when the recall-log gitignore entry was added to `.gitignore`. */
532
+ /** True when any lessons runtime-artifact entry was added to `.gitignore`. */
482
533
  readonly gitignoreUpdated: boolean;
483
534
  /** True when the lessons.json merge-driver entry was added to `.gitattributes`. */
484
535
  readonly gitattributesUpdated: boolean;
485
- /** True when the PostToolUse recall hook was injected into `hooks.yaml`. */
536
+ /** True when the lessons recall hook was injected into `hooks.yaml`. */
486
537
  readonly recallHookInjected: boolean;
538
+ /** What this clone's merge-driver setup did; teammates get it on `generate`. */
539
+ readonly mergeDriver: MergeDriverSetup;
540
+ /** Set when recall hooks still need a global install to reach teammates. */
541
+ readonly recallHookTeamHint: string | null;
487
542
  }
488
543
  /**
489
544
  * Idempotent scaffolder for the lessons subsystem. Backs `agentsmesh init
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-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';
1
+ import { o as RankedLesson, j as LessonsQuery, A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult } from './init-DruMEhnc.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-DruMEhnc.js';
3
3
  import 'zod';
4
4
 
5
5
  /**
@@ -92,6 +92,8 @@ declare function captureLesson(projectRoot: string, input: AddLessonInput, optio
92
92
  * legacy store: if it added first it would create `lessons.json`, which then
93
93
  * permanently blocks the absent-graph auto-migration. Returns true if it
94
94
  * migrated. No-op when a graph already exists or no legacy index is present.
95
+ * The unlocked checks are a fast path; `requireAbsentGraph` repeats the graph
96
+ * check under the lessons lock before the legacy store is read.
95
97
  */
96
98
  declare function maybeAutoMigrateLessons(projectRoot: string): Promise<boolean>;
97
99
 
@@ -108,8 +110,9 @@ declare function maybeAutoMigrateLessons(projectRoot: string): Promise<boolean>;
108
110
  * linear in the input length for any pattern it can compile.
109
111
  *
110
112
  * A pattern is "safe" iff the linear engine can compile it. Patterns it cannot
111
- * evaluate (invalid syntax, backreferences, lookarounds) are rejected at capture
112
- * (UNSAFE_TRIGGER_PATTERN) and skipped at read time — fail closed.
113
+ * evaluate are dead triggers: capture drops them (DEAD_COMMAND_PATTERN), the
114
+ * write barrier and validate flag stored ones (INVALID_/UNSAFE_TRIGGER_PATTERN),
115
+ * and recall skips them: fail closed.
113
116
  */
114
117
 
115
118
  /** True when `pattern` can be matched by the linear engine (no ReDoS risk). */