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 +63 -1
- package/dist/index.js +1 -1
- package/dist/scope.js +12 -0
- package/dist/store.d.ts +2 -0
- package/dist/store.js +22 -0
- package/dist/tools/episodic.js +6 -6
- package/dist/tools/feedback.js +5 -5
- package/dist/tools/memory.js +29 -24
- package/package.json +1 -1
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.
|
|
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.
|
|
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));
|
package/dist/tools/episodic.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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}`;
|
package/dist/tools/feedback.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
},
|
package/dist/tools/memory.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
231
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
// 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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",
|