opencode-memory-pro 1.3.6 → 1.3.7

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/README.md CHANGED
@@ -40,7 +40,7 @@ Published on npm — install directly (requires OpenCode ≥ 1.x and Node.js ≥
40
40
  opencode plugin opencode-memory-pro
41
41
  ```
42
42
 
43
- The latest release is **v1.3.5** on [npm](https://www.npmjs.com/package/opencode-memory-pro); source and releases are on [GitHub](https://github.com/tman204-50/opencode-memory-pro).
43
+ The latest release is **v1.3.7** on [npm](https://www.npmjs.com/package/opencode-memory-pro); source and releases are on [GitHub](https://github.com/tman204-50/opencode-memory-pro).
44
44
 
45
45
  Remove the old plugin pin at the same time:
46
46
 
@@ -408,6 +408,52 @@ so your memories and graph carry over untouched.
408
408
 
409
409
  ## Changelog
410
410
 
411
+ ### v1.3.7 (2026-09-06)
412
+
413
+ Scope normalization — fixes lost memories when a `scope` argument is
414
+ explicitly passed to a tool while `scoping` is `"global"`:
415
+
416
+ - **Explicit scopes now collapse to `global` in global mode**: previously
417
+ `memory_remember(scope="project")` (and every other tool accepting a `scope`
418
+ arg) stored the row under the literal string `"project"` — but scope-filtered
419
+ reads derive the scope via `deriveProjectScope()`, which returns `"global"`
420
+ in global mode, so the memory was effectively lost (invisible to search,
421
+ promote, why, `memory_global_list`, ...; reachable only by passing
422
+ `scope="project"` explicitly).
423
+ - **New `resolveScope(scope, worktree)` helper** in `dist/scope.js` — collapses
424
+ any explicit scope to `"global"` in global mode and honors it in project
425
+ mode (falling back to the derived project scope when omitted). Applied to all
426
+ 30 scope-arg sites across `dist/tools/memory.js`, `dist/tools/episodic.js`,
427
+ and `dist/tools/feedback.js`, including `memory_clear`, which previously
428
+ called `clearScope(args.scope)` without any normalization.
429
+ - **Tests**: two new unit tests cover the collapse-to-global and
430
+ honor-in-project-mode behavior.
431
+
432
+ ### v1.3.6 (2026-09-06)
433
+
434
+ Compaction lock hardening — fixes the "Compaction commit failed; leaving N
435
+ rewritten fragment(s) in place for GC" warning reappearing on the TUI at
436
+ startup / first turn when two opencode instances share one store:
437
+
438
+ - **No more lock stealing during the owner's init window**: the owner creates
439
+ `.optimize.lock` with `open("wx")` and *then* writes its pid; a contender
440
+ reading in between saw an empty file, declared it stale, deleted it, and
441
+ created its own — so both processes "owned" the lock and raced `optimize()`
442
+ (the native LanceDB stderr line is uninterceptable by the plugin). The lock
443
+ now treats an empty file as "being initialized" for a short grace instead of
444
+ reclaiming it.
445
+ - **Contenders wait instead of giving up instantly**: when a live process holds
446
+ the lock, the second instance now polls up to 10s for it to finish (serializing
447
+ compaction across processes) before skipping this cycle and retrying next
448
+ interval, instead of racing it.
449
+ - **In-process guard set synchronously**: `maybeOptimizeAll` now sets
450
+ `optimizing = true` before any `await`, so overlapping calls in one process
451
+ (fire-and-forget write trigger + awaited explicit call on the first turn)
452
+ can no longer both run `optimize()` concurrently.
453
+ - **Tests**: two new unit tests cover the open→write TOCTOU (old lock returns
454
+ `true` and steals; new lock returns `false` and preserves ownership) and
455
+ stale-lock reclamation.
456
+
411
457
  ### v1.3.5 (2026-09-06)
412
458
 
413
459
  Code-review hardening pass — bug fixes, no breaking changes:
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import { requestLLMCapture, isOwnSession } from "./llm.js";
11
11
  import { createMemoryTools, createFeedbackTools, createEpisodicTools } from "./tools/index.js";
12
12
  import { sweepExpiredMemories } from "./tools/memory.js";
13
13
  import { createGraphStore } from "./graph.js";
14
- const PLUGIN_VERSION = "1.3.6";
14
+ const PLUGIN_VERSION = "1.3.7";
15
15
  const SCHEMA_VERSION = 1;
16
16
  // Event-driven dedup: run consolidateDuplicates on session.idle (throttled to
17
17
  // this interval so chatty sessions aren't re-scanning the store every turn)
package/dist/scope.js CHANGED
@@ -26,6 +26,18 @@ export function buildScopeFilter(activeScope, includeGlobal) {
26
26
  const scopes = includeGlobal ? [activeScope, "global"] : [activeScope];
27
27
  return [...new Set(scopes)];
28
28
  }
29
+ // SCOPE_NORMALIZE (1.3.7): resolves a caller-supplied scope argument against
30
+ // the active scoping mode. In "global" mode every explicit scope (e.g. a
31
+ // memory_remember(scope="project")) collapses to "global" — otherwise such
32
+ // rows would be stored under a literal scope that scope-filtered tools never
33
+ // query (the rows are effectively lost). In "project" mode the argument is
34
+ // honored, falling back to the derived project scope.
35
+ export function resolveScope(scope, worktree) {
36
+ if (resolveScoping(worktree) !== "project") {
37
+ return "global";
38
+ }
39
+ return scope ?? deriveProjectScope(worktree);
40
+ }
29
41
  function resolveScoping(worktree) {
30
42
  try {
31
43
  return resolveMemoryConfig({}, worktree).scoping === "project" ? "project" : "global";
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
- import { deriveProjectScope } from "../scope.js";
2
+ import { deriveProjectScope, resolveScope } from "../scope.js";
3
3
  import { generateId, parseJsonObject } from "../utils.js";
4
4
  function unavailableMessage(provider) {
5
5
  return `Memory store unavailable (${provider} embedding may be offline). Will retry automatically.`;
@@ -17,7 +17,7 @@ export function createEpisodicTools(state) {
17
17
  await state.ensureInitialized();
18
18
  if (!state.initialized)
19
19
  return unavailableMessage(state.config.embedding.provider);
20
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
20
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
21
21
  const episode = {
22
22
  id: generateId(),
23
23
  sessionId: context.sessionID,
@@ -48,7 +48,7 @@ export function createEpisodicTools(state) {
48
48
  await state.ensureInitialized();
49
49
  if (!state.initialized)
50
50
  return unavailableMessage(state.config.embedding.provider);
51
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
51
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
52
52
  const stateFilter = args.state;
53
53
  const episodes = await state.store.queryTaskEpisodes(activeScope, stateFilter);
54
54
  if (episodes.length === 0) {
@@ -73,7 +73,7 @@ export function createEpisodicTools(state) {
73
73
  await state.ensureInitialized();
74
74
  if (!state.initialized)
75
75
  return unavailableMessage(state.config.embedding.provider);
76
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
76
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
77
77
  let queryVector = [];
78
78
  try {
79
79
  queryVector = await state.embedder.embed(args.query);
@@ -107,7 +107,7 @@ export function createEpisodicTools(state) {
107
107
  await state.ensureInitialized();
108
108
  if (!state.initialized)
109
109
  return unavailableMessage(state.config.embedding.provider);
110
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
110
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
111
111
  const result = await state.store.suggestRetryBudget(activeScope, args.minSamples ?? 3);
112
112
  if (!result) {
113
113
  return `Insufficient data for retry budget suggestion (need at least ${args.minSamples} failed tasks)`;
@@ -131,7 +131,7 @@ export function createEpisodicTools(state) {
131
131
  await state.ensureInitialized();
132
132
  if (!state.initialized)
133
133
  return unavailableMessage(state.config.embedding.provider);
134
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
134
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
135
135
  const strategies = await state.store.suggestRecoveryStrategies(activeScope, args.taskId);
136
136
  if (strategies.length === 0) {
137
137
  return `No recovery strategies found for task ${args.taskId}`;
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
- import { deriveProjectScope, buildScopeFilter } from "../scope.js";
2
+ import { deriveProjectScope, buildScopeFilter, resolveScope } from "../scope.js";
3
3
  import { generateId } from "../utils.js";
4
4
  function unavailableMessage(provider) {
5
5
  return `Memory store unavailable (${provider} embedding may be offline). Will retry automatically.`;
@@ -17,7 +17,7 @@ export function createFeedbackTools(state) {
17
17
  await state.ensureInitialized();
18
18
  if (!state.initialized)
19
19
  return unavailableMessage(state.config.embedding.provider);
20
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
20
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
21
21
  await state.store.putEvent({
22
22
  id: generateId(),
23
23
  type: "feedback",
@@ -43,7 +43,7 @@ export function createFeedbackTools(state) {
43
43
  await state.ensureInitialized();
44
44
  if (!state.initialized)
45
45
  return unavailableMessage(state.config.embedding.provider);
46
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
46
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
47
47
  const scopes = buildScopeFilter(scope, state.config.includeGlobalScope);
48
48
  const exists = await state.store.hasMemory(args.id, scopes);
49
49
  if (!exists) {
@@ -74,7 +74,7 @@ export function createFeedbackTools(state) {
74
74
  await state.ensureInitialized();
75
75
  if (!state.initialized)
76
76
  return unavailableMessage(state.config.embedding.provider);
77
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
77
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
78
78
  const scopes = buildScopeFilter(scope, state.config.includeGlobalScope);
79
79
  const exists = await state.store.hasMemory(args.id, scopes);
80
80
  if (!exists) {
@@ -103,7 +103,7 @@ export function createFeedbackTools(state) {
103
103
  await state.ensureInitialized();
104
104
  if (!state.initialized)
105
105
  return unavailableMessage(state.config.embedding.provider);
106
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
106
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
107
107
  const summary = await state.store.summarizeEvents(scope, state.config.includeGlobalScope);
108
108
  return JSON.stringify(summary, null, 2);
109
109
  },
@@ -1,5 +1,5 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
- import { deriveProjectScope, buildScopeFilter } from "../scope.js";
2
+ import { deriveProjectScope, buildScopeFilter, resolveScope } from "../scope.js";
3
3
  import { generateId } from "../utils.js";
4
4
  import { getEmbedderHealth } from "../embedder.js";
5
5
  import { extractiveDigest, retentionCandidates } from "../store.js";
@@ -43,7 +43,7 @@ export function createMemoryTools(state) {
43
43
  await state.ensureInitialized();
44
44
  if (!state.initialized)
45
45
  return unavailableMessage(state.config.embedding.provider);
46
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
46
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
47
47
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
48
48
  let queryVector = [];
49
49
  let embedderFailed = false;
@@ -208,7 +208,7 @@ export function createMemoryTools(state) {
208
208
  if (!args.confirm) {
209
209
  return "Rejected: memory_delete requires confirm=true.";
210
210
  }
211
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
211
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
212
212
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
213
213
  const deleted = await state.store.deleteById(args.id, scopes);
214
214
  return deleted ? `Deleted memory ${args.id}.` : `Memory ${args.id} not found in current scope.`;
@@ -220,15 +220,16 @@ export function createMemoryTools(state) {
220
220
  scope: tool.schema.string(),
221
221
  confirm: tool.schema.boolean().default(false),
222
222
  },
223
- execute: async (args) => {
223
+ execute: async (args, context) => {
224
224
  await state.ensureInitialized();
225
225
  if (!state.initialized)
226
226
  return unavailableMessage(state.config.embedding.provider);
227
227
  if (!args.confirm) {
228
228
  return "Rejected: destructive clear requires confirm=true.";
229
229
  }
230
- const count = await state.store.clearScope(args.scope);
231
- return `Cleared ${count} memories from scope ${args.scope}.`;
230
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
231
+ const count = await state.store.clearScope(activeScope);
232
+ return `Cleared ${count} memories from scope ${activeScope}.`;
232
233
  },
233
234
  }),
234
235
  memory_stats: tool({
@@ -240,7 +241,7 @@ export function createMemoryTools(state) {
240
241
  await state.ensureInitialized();
241
242
  if (!state.initialized)
242
243
  return unavailableMessage(state.config.embedding.provider);
243
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
244
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
244
245
  const entries = await state.store.list(scope, 20);
245
246
  const incompatibleVectors = await state.store.countIncompatibleVectors(buildScopeFilter(scope, state.config.includeGlobalScope), await state.embedder.dim());
246
247
  const health = state.store.getIndexHealth();
@@ -303,7 +304,7 @@ export function createMemoryTools(state) {
303
304
  // passed `args.scope` to cleanupExpiredEvents where `undefined`
304
305
  // meant ALL scopes (and the store's `scope LIKE 'project:%'`
305
306
  // matched every project scope).
306
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
307
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
307
308
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
308
309
  const cutoffTimestamp = Date.now() - status.retentionDays * 24 * 60 * 60 * 1000;
309
310
  if (args.dryRun) {
@@ -370,7 +371,7 @@ export function createMemoryTools(state) {
370
371
  if (args.text.length < state.config.minCaptureChars) {
371
372
  return `Content too short (minimum ${state.config.minCaptureChars} characters).`;
372
373
  }
373
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
374
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
374
375
  let vector = [];
375
376
  try {
376
377
  vector = await state.embedder.embed(args.text);
@@ -443,7 +444,7 @@ export function createMemoryTools(state) {
443
444
  await state.ensureInitialized();
444
445
  if (!state.initialized)
445
446
  return unavailableMessage(state.config.embedding.provider);
446
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
447
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
447
448
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
448
449
  if (args.force) {
449
450
  const deleted = await state.store.deleteById(args.id, scopes);
@@ -499,7 +500,7 @@ export function createMemoryTools(state) {
499
500
  await state.ensureInitialized();
500
501
  if (!state.initialized)
501
502
  return unavailableMessage(state.config.embedding.provider);
502
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
503
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
503
504
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
504
505
  const citation = await state.store.getCitation(args.id, scopes);
505
506
  if (!citation) {
@@ -535,7 +536,7 @@ export function createMemoryTools(state) {
535
536
  await state.ensureInitialized();
536
537
  if (!state.initialized)
537
538
  return unavailableMessage(state.config.embedding.provider);
538
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
539
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
539
540
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
540
541
  const result = await state.store.validateCitation(args.id, scopes);
541
542
  return JSON.stringify({
@@ -556,7 +557,7 @@ export function createMemoryTools(state) {
556
557
  await state.ensureInitialized();
557
558
  if (!state.initialized)
558
559
  return unavailableMessage(state.config.embedding.provider);
559
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
560
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
560
561
  const sinceTimestamp = Date.now() - (args.days ?? 7) * 24 * 60 * 60 * 1000;
561
562
  const memories = await state.store.listSince(activeScope, sinceTimestamp, 1000);
562
563
  if (memories.length === 0) {
@@ -598,7 +599,7 @@ ${recentSamples}
598
599
  await state.ensureInitialized();
599
600
  if (!state.initialized)
600
601
  return unavailableMessage(state.config.embedding.provider);
601
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
602
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
602
603
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
603
604
  const explanation = await state.store.explainMemory(args.id, scopes, activeScope, state.config.retrieval.recencyHalfLifeHours, state.config.globalDiscountFactor);
604
605
  if (!explanation) {
@@ -637,7 +638,7 @@ Explanation:
637
638
  if (!lastRecall) {
638
639
  return "No recent recall to explain. Use memory_search or wait for auto-recall first.";
639
640
  }
640
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
641
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
641
642
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
642
643
  const explanations = [];
643
644
  for (const result of lastRecall.results) {
@@ -697,7 +698,7 @@ ${explanations.join("\n")}`;
697
698
  if (!args.confirm) {
698
699
  return "Rejected: memory_scope_demote requires confirm=true.";
699
700
  }
700
- const projectScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
701
+ const projectScope = resolveScope(args.scope, context.directory || context.worktree);
701
702
  const globalExists = await state.store.hasMemory(args.id, ["global"]);
702
703
  if (!globalExists) {
703
704
  return `Memory ${args.id} not found in global scope or is not a global memory.`;
@@ -774,7 +775,7 @@ ${explanations.join("\n")}`;
774
775
  if (!args.confirm) {
775
776
  return "Rejected: memory_consolidate requires confirm=true.";
776
777
  }
777
- const targetScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
778
+ const targetScope = resolveScope(args.scope, context.directory || context.worktree);
778
779
  if (state.consolidationInProgress.get(targetScope)) {
779
780
  return JSON.stringify({ scope: targetScope, status: "already_in_progress", message: "Consolidation already in progress for this scope" });
780
781
  }
@@ -980,7 +981,7 @@ ${explanations.join("\n")}`;
980
981
  await state.ensureInitialized();
981
982
  if (!state.initialized)
982
983
  return unavailableMessage(state.config.embedding.provider);
983
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
984
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
984
985
  const dashboard = await state.store.getWeeklyEffectivenessSummary(scope, state.config.includeGlobalScope, args.days ?? 7);
985
986
  return JSON.stringify(dashboard, null, 2);
986
987
  },
@@ -995,7 +996,7 @@ ${explanations.join("\n")}`;
995
996
  await state.ensureInitialized();
996
997
  if (!state.initialized)
997
998
  return unavailableMessage(state.config.embedding.provider);
998
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
999
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
999
1000
  const kpi = await state.store.getKpiSummary(scope, args.days ?? 30);
1000
1001
  return JSON.stringify(kpi, null, 2);
1001
1002
  },
@@ -1012,7 +1013,7 @@ ${explanations.join("\n")}`;
1012
1013
  await state.ensureInitialized();
1013
1014
  if (!state.initialized)
1014
1015
  return unavailableMessage(state.config.embedding.provider);
1015
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1016
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1016
1017
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1017
1018
  const records = await state.store.exportAllRecords(scopes);
1018
1019
  if (args.dryRun) {
@@ -1073,7 +1074,7 @@ ${explanations.join("\n")}`;
1073
1074
  if (!Array.isArray(payload?.memories)) {
1074
1075
  return JSON.stringify({ error: "Not a memory_export backup (missing memories array)" }, null, 2);
1075
1076
  }
1076
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1077
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1077
1078
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1078
1079
  const source = payload?.memories ?? [];
1079
1080
  let imported = 0;
@@ -1203,7 +1204,7 @@ ${explanations.join("\n")}`;
1203
1204
  if (summarizeCfg.enabled === false) {
1204
1205
  return JSON.stringify({ error: "Summarization disabled via config summarize.enabled=false" }, null, 2);
1205
1206
  }
1206
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1207
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1207
1208
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1208
1209
  const minAgeDays = args.minAgeDays ?? summarizeCfg.minAgeDays;
1209
1210
  const minGroupSize = args.minGroupSize ?? summarizeCfg.minGroupSize;
@@ -1347,7 +1348,7 @@ ${explanations.join("\n")}`;
1347
1348
  return JSON.stringify({ error: "Memory retention disabled via config retention.memory.enabled=false" }, null, 2);
1348
1349
  }
1349
1350
  const result = await sweepExpiredMemories(state, {
1350
- scope: args.scope ?? deriveProjectScope(context.directory || context.worktree),
1351
+ scope: resolveScope(args.scope, context.directory || context.worktree),
1351
1352
  dryRun: args.dryRun === true,
1352
1353
  unusedDays: args.unusedDays,
1353
1354
  minAgeDays: args.minAgeDays,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-memory-pro",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
4
4
  "description": "LanceDB-backed long-term memory provider for OpenCode — standalone fork of lancedb-opencode-pro with entity graph, lifecycle, and retention",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",