opencode-memory-pro 1.3.6 → 1.3.8

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.8** 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,68 @@ so your memories and graph carry over untouched.
408
408
 
409
409
  ## Changelog
410
410
 
411
+ ### v1.3.8 (2026-09-06)
412
+
413
+ Fixes `memory_forget(force=true)` being unable to permanently delete a memory
414
+ that was soft-deleted first:
415
+
416
+ - **Force delete now sees hidden rows**: `softDeleteMemory` marks a row
417
+ `status='disabled'`, and the force path previously used `deleteById`, whose
418
+ `readByScopes` query filters out `status='disabled'` (and `merged`/`digested`)
419
+ rows — so "Use force=true for permanent deletion" silently failed and left
420
+ the hidden row on disk forever. The force path now uses the new
421
+ `deleteByIdForce` in `dist/store.js`, which tries the exact-id raw delete
422
+ first and otherwise scans unfiltered rows (so id prefixes still match).
423
+ - **Tests**: integration test covers the exact scenario — soft-delete, confirm
424
+ the old path returns `false`, then `deleteByIdForce` removes the row and
425
+ reports `false` on a second attempt.
426
+
427
+ ### v1.3.7 (2026-09-06)
428
+
429
+ Scope normalization — fixes lost memories when a `scope` argument is
430
+ explicitly passed to a tool while `scoping` is `"global"`:
431
+
432
+ - **Explicit scopes now collapse to `global` in global mode**: previously
433
+ `memory_remember(scope="project")` (and every other tool accepting a `scope`
434
+ arg) stored the row under the literal string `"project"` — but scope-filtered
435
+ reads derive the scope via `deriveProjectScope()`, which returns `"global"`
436
+ in global mode, so the memory was effectively lost (invisible to search,
437
+ promote, why, `memory_global_list`, ...; reachable only by passing
438
+ `scope="project"` explicitly).
439
+ - **New `resolveScope(scope, worktree)` helper** in `dist/scope.js` — collapses
440
+ any explicit scope to `"global"` in global mode and honors it in project
441
+ mode (falling back to the derived project scope when omitted). Applied to all
442
+ 30 scope-arg sites across `dist/tools/memory.js`, `dist/tools/episodic.js`,
443
+ and `dist/tools/feedback.js`, including `memory_clear`, which previously
444
+ called `clearScope(args.scope)` without any normalization.
445
+ - **Tests**: two new unit tests cover the collapse-to-global and
446
+ honor-in-project-mode behavior.
447
+
448
+ ### v1.3.6 (2026-09-06)
449
+
450
+ Compaction lock hardening — fixes the "Compaction commit failed; leaving N
451
+ rewritten fragment(s) in place for GC" warning reappearing on the TUI at
452
+ startup / first turn when two opencode instances share one store:
453
+
454
+ - **No more lock stealing during the owner's init window**: the owner creates
455
+ `.optimize.lock` with `open("wx")` and *then* writes its pid; a contender
456
+ reading in between saw an empty file, declared it stale, deleted it, and
457
+ created its own — so both processes "owned" the lock and raced `optimize()`
458
+ (the native LanceDB stderr line is uninterceptable by the plugin). The lock
459
+ now treats an empty file as "being initialized" for a short grace instead of
460
+ reclaiming it.
461
+ - **Contenders wait instead of giving up instantly**: when a live process holds
462
+ the lock, the second instance now polls up to 10s for it to finish (serializing
463
+ compaction across processes) before skipping this cycle and retrying next
464
+ interval, instead of racing it.
465
+ - **In-process guard set synchronously**: `maybeOptimizeAll` now sets
466
+ `optimizing = true` before any `await`, so overlapping calls in one process
467
+ (fire-and-forget write trigger + awaited explicit call on the first turn)
468
+ can no longer both run `optimize()` concurrently.
469
+ - **Tests**: two new unit tests cover the open→write TOCTOU (old lock returns
470
+ `true` and steals; new lock returns `false` and preserves ownership) and
471
+ stale-lock reclamation.
472
+
411
473
  ### v1.3.5 (2026-09-06)
412
474
 
413
475
  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.8";
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";
package/dist/store.d.ts CHANGED
@@ -49,6 +49,8 @@ export declare class MemoryStore {
49
49
  globalDiscountFactor?: number;
50
50
  }): Promise<SearchResult[]>;
51
51
  deleteById(id: string, scopes: string[]): Promise<boolean>;
52
+ deleteByIdRaw(id: string): Promise<boolean>;
53
+ deleteByIdForce(id: string): Promise<boolean>;
52
54
  softDeleteMemory(id: string, scopes: string[]): Promise<boolean>;
53
55
  updateMemoryScope(id: string, newScope: string, scopes: string[]): Promise<boolean>;
54
56
  readGlobalMemories(limit?: number): Promise<MemoryRecord[]>;
package/dist/store.js CHANGED
@@ -628,6 +628,28 @@ export class MemoryStore {
628
628
  this.notifyGraphRemoved(id);
629
629
  return true;
630
630
  }
631
+ // DELETE_BY_FORCE (1.3.8): like deleteById but sees rows the status-
632
+ // filtered reads hide (disabled/merged/digested). Fixes memory_forget
633
+ // force=true: the soft-delete path marks rows disabled, and the force
634
+ // path previously used deleteById, whose readByScopes filter excludes
635
+ // status='disabled' — so "Use force=true for permanent deletion" silently
636
+ // failed and left the hidden row on disk forever. Tries the exact-id raw
637
+ // delete first (fast path), then falls back to an unfiltered scan so
638
+ // prefix ids and hidden rows both work.
639
+ async deleteByIdForce(id) {
640
+ if (await this.deleteByIdRaw(id)) {
641
+ return true;
642
+ }
643
+ const table = this.requireTable();
644
+ const rows = await table.query().limit(100000).toArray();
645
+ const match = rows.find((row) => this.matchesId(row.id, id));
646
+ if (!match)
647
+ return false;
648
+ await table.delete(`id = '${escapeSql(match.id)}'`);
649
+ this.invalidateScope(match.scope);
650
+ this.notifyGraphRemoved(match.id);
651
+ return true;
652
+ }
631
653
  async softDeleteMemory(id, scopes) {
632
654
  const rows = await this.readByScopes(scopes);
633
655
  const match = rows.find((row) => this.matchesId(row.id, id));
@@ -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,10 +444,14 @@ 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
- const deleted = await state.store.deleteById(args.id, scopes);
450
+ // FORCE_DELETE_HIDDEN (1.3.8): was deleteById, whose
451
+ // readByScopes filter excludes disabled/merged rows — so
452
+ // force=true could never permanently delete a memory that
453
+ // was soft-deleted first.
454
+ const deleted = await state.store.deleteByIdForce(args.id);
450
455
  if (!deleted) {
451
456
  return `Memory ${args.id} not found in current scope.`;
452
457
  }
@@ -499,7 +504,7 @@ export function createMemoryTools(state) {
499
504
  await state.ensureInitialized();
500
505
  if (!state.initialized)
501
506
  return unavailableMessage(state.config.embedding.provider);
502
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
507
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
503
508
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
504
509
  const citation = await state.store.getCitation(args.id, scopes);
505
510
  if (!citation) {
@@ -535,7 +540,7 @@ export function createMemoryTools(state) {
535
540
  await state.ensureInitialized();
536
541
  if (!state.initialized)
537
542
  return unavailableMessage(state.config.embedding.provider);
538
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
543
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
539
544
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
540
545
  const result = await state.store.validateCitation(args.id, scopes);
541
546
  return JSON.stringify({
@@ -556,7 +561,7 @@ export function createMemoryTools(state) {
556
561
  await state.ensureInitialized();
557
562
  if (!state.initialized)
558
563
  return unavailableMessage(state.config.embedding.provider);
559
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
564
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
560
565
  const sinceTimestamp = Date.now() - (args.days ?? 7) * 24 * 60 * 60 * 1000;
561
566
  const memories = await state.store.listSince(activeScope, sinceTimestamp, 1000);
562
567
  if (memories.length === 0) {
@@ -598,7 +603,7 @@ ${recentSamples}
598
603
  await state.ensureInitialized();
599
604
  if (!state.initialized)
600
605
  return unavailableMessage(state.config.embedding.provider);
601
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
606
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
602
607
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
603
608
  const explanation = await state.store.explainMemory(args.id, scopes, activeScope, state.config.retrieval.recencyHalfLifeHours, state.config.globalDiscountFactor);
604
609
  if (!explanation) {
@@ -637,7 +642,7 @@ Explanation:
637
642
  if (!lastRecall) {
638
643
  return "No recent recall to explain. Use memory_search or wait for auto-recall first.";
639
644
  }
640
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
645
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
641
646
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
642
647
  const explanations = [];
643
648
  for (const result of lastRecall.results) {
@@ -697,7 +702,7 @@ ${explanations.join("\n")}`;
697
702
  if (!args.confirm) {
698
703
  return "Rejected: memory_scope_demote requires confirm=true.";
699
704
  }
700
- const projectScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
705
+ const projectScope = resolveScope(args.scope, context.directory || context.worktree);
701
706
  const globalExists = await state.store.hasMemory(args.id, ["global"]);
702
707
  if (!globalExists) {
703
708
  return `Memory ${args.id} not found in global scope or is not a global memory.`;
@@ -774,7 +779,7 @@ ${explanations.join("\n")}`;
774
779
  if (!args.confirm) {
775
780
  return "Rejected: memory_consolidate requires confirm=true.";
776
781
  }
777
- const targetScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
782
+ const targetScope = resolveScope(args.scope, context.directory || context.worktree);
778
783
  if (state.consolidationInProgress.get(targetScope)) {
779
784
  return JSON.stringify({ scope: targetScope, status: "already_in_progress", message: "Consolidation already in progress for this scope" });
780
785
  }
@@ -980,7 +985,7 @@ ${explanations.join("\n")}`;
980
985
  await state.ensureInitialized();
981
986
  if (!state.initialized)
982
987
  return unavailableMessage(state.config.embedding.provider);
983
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
988
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
984
989
  const dashboard = await state.store.getWeeklyEffectivenessSummary(scope, state.config.includeGlobalScope, args.days ?? 7);
985
990
  return JSON.stringify(dashboard, null, 2);
986
991
  },
@@ -995,7 +1000,7 @@ ${explanations.join("\n")}`;
995
1000
  await state.ensureInitialized();
996
1001
  if (!state.initialized)
997
1002
  return unavailableMessage(state.config.embedding.provider);
998
- const scope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1003
+ const scope = resolveScope(args.scope, context.directory || context.worktree);
999
1004
  const kpi = await state.store.getKpiSummary(scope, args.days ?? 30);
1000
1005
  return JSON.stringify(kpi, null, 2);
1001
1006
  },
@@ -1012,7 +1017,7 @@ ${explanations.join("\n")}`;
1012
1017
  await state.ensureInitialized();
1013
1018
  if (!state.initialized)
1014
1019
  return unavailableMessage(state.config.embedding.provider);
1015
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1020
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1016
1021
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1017
1022
  const records = await state.store.exportAllRecords(scopes);
1018
1023
  if (args.dryRun) {
@@ -1073,7 +1078,7 @@ ${explanations.join("\n")}`;
1073
1078
  if (!Array.isArray(payload?.memories)) {
1074
1079
  return JSON.stringify({ error: "Not a memory_export backup (missing memories array)" }, null, 2);
1075
1080
  }
1076
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1081
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1077
1082
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1078
1083
  const source = payload?.memories ?? [];
1079
1084
  let imported = 0;
@@ -1203,7 +1208,7 @@ ${explanations.join("\n")}`;
1203
1208
  if (summarizeCfg.enabled === false) {
1204
1209
  return JSON.stringify({ error: "Summarization disabled via config summarize.enabled=false" }, null, 2);
1205
1210
  }
1206
- const activeScope = args.scope ?? deriveProjectScope(context.directory || context.worktree);
1211
+ const activeScope = resolveScope(args.scope, context.directory || context.worktree);
1207
1212
  const scopes = buildScopeFilter(activeScope, state.config.includeGlobalScope);
1208
1213
  const minAgeDays = args.minAgeDays ?? summarizeCfg.minAgeDays;
1209
1214
  const minGroupSize = args.minGroupSize ?? summarizeCfg.minGroupSize;
@@ -1347,7 +1352,7 @@ ${explanations.join("\n")}`;
1347
1352
  return JSON.stringify({ error: "Memory retention disabled via config retention.memory.enabled=false" }, null, 2);
1348
1353
  }
1349
1354
  const result = await sweepExpiredMemories(state, {
1350
- scope: args.scope ?? deriveProjectScope(context.directory || context.worktree),
1355
+ scope: resolveScope(args.scope, context.directory || context.worktree),
1351
1356
  dryRun: args.dryRun === true,
1352
1357
  unusedDays: args.unusedDays,
1353
1358
  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.8",
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",