opencode-claude-memory 2.2.1 → 2.3.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.
- package/README.md +23 -4
- package/dist/config.d.ts +3 -0
- package/dist/config.js +4 -0
- package/dist/extraction/ExtractionCoordinator.js +9 -6
- package/dist/extraction/autodream.js +1 -1
- package/dist/store/MemoryStore.d.ts +4 -0
- package/dist/store/MemoryStore.js +43 -3
- package/dist/store/autoMemoryDirectory.d.ts +4 -0
- package/dist/store/autoMemoryDirectory.js +52 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,7 @@ The hook names below are OpenCode 1.x's; on 2.x the same steps run on the V2 plu
|
|
|
73
73
|
4. **Auto-dream** — after each extracted session the gate is evaluated (`autodream.minHours` since the last pass **and** `autodream.minSessions` extracted since). When it passes, `opencode-memory-dream` runs with all five memory tools. A lock file prevents two OpenCode processes from consolidating at once.
|
|
74
74
|
5. **Ignore memory** — "ignore memory" in a user message switches memory off for the rest of the session (no index, no recall); "use memory again" switches it back on.
|
|
75
75
|
|
|
76
|
-
State that is private to the plugin (watermarks, auto-dream gate, lock and, on OpenCode 2.x, the log file) lives
|
|
76
|
+
State that is private to the plugin (watermarks, auto-dream gate, lock and, on OpenCode 2.x, the log file) lives under `<CLAUDE_CONFIG_DIR>/opencode-memory/`, never inside the memory directory. The default location is `<project>/`; custom memory directories use the namespaces described below.
|
|
77
77
|
|
|
78
78
|
## 🔧 Configuration
|
|
79
79
|
|
|
@@ -125,9 +125,28 @@ All behaviour is configured through OpenCode's own configuration. There are no `
|
|
|
125
125
|
- Agent overrides live under `agents.<name>` and use 2.x agent fields (`model`, `steps`, `permissions`, …). The plugin's sandbox rules (`*` deny, then allow for the agent's memory tools) come first and your own `permissions` after them; 2.x applies the last matching rule, so your rule wins.
|
|
126
126
|
- The main agent needs no extra permission on 2.x's default `build` agent, which allows everything. If you restrict permissions (for example `*` → `ask` or `deny`), add `{ "action": "memory_*", "resource": "*", "effect": "allow" }` to your `permissions` so the memory tools are neither blocked nor prompted for.
|
|
127
127
|
|
|
128
|
+
**Custom memory directory.** Set `autoMemoryDirectory` in the plugin options to use a fixed memory directory, matching [Claude Code's setting](https://code.claude.com/docs/en/settings-reference#automemorydirectory). For example, on OpenCode 2.x:
|
|
129
|
+
|
|
130
|
+
```jsonc
|
|
131
|
+
{
|
|
132
|
+
"plugins": [
|
|
133
|
+
{ "package": "opencode-claude-memory", "options": { "autoMemoryDirectory": "~/shared-memory" } }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The same option works in the 1.x plugin options. Configure the same path as `autoMemoryDirectory` in Claude Code's settings to share this store between both tools; this plugin reads OpenCode options, not Claude Code settings files. OpenCode's plugin declaration precedence above still applies.
|
|
139
|
+
|
|
140
|
+
- The value is the **final memory directory**: `~/shared-memory/MEMORY.md` and its topic files. No project name or `memory/` suffix is appended. All projects configured with the same path share that store; it replaces their default memory directories rather than merging with them. Existing memories are not moved.
|
|
141
|
+
- Path handling matches Claude Code **2.1.292**: expand `~/` (also `~\`), normalise the path and Unicode (NFC), and retain one trailing separator. The directory is not resolved against a Git root. Relative or empty paths, filesystem roots, home-only `~/`, tilde paths escaping the home directory, NUL bytes and network/device roots disable the override and fall back to the default project directory. Whitespace is not trimmed and environment variables are not expanded. Non-string values remain configuration errors.
|
|
142
|
+
- Working directories and session/event handling retain their actual project scope. Watermarks, the auto-dream gate, trash and logs remain project-scoped and are also keyed by the target directory (`<project>/memory-stores/<directory-hash>/`), so switching stores does not reuse another store's watermarks. Within one `CLAUDE_CONFIG_DIR`, extraction and auto-dream targeting the same physical directory share a maintenance lock, including directory symlinks and overrides pointing to a default project store. Auto-dream thresholds remain per project.
|
|
143
|
+
- A symlink alias of this project's default memory directory retains its existing watermarks and auto-dream gate.
|
|
144
|
+
- The memory directory must not equal, contain or reside inside the plugin's private state root (`<CLAUDE_CONFIG_DIR>/opencode-memory/`), including through symlinks. Such a configuration is rejected before creating directories, so deleted-memory backups cannot be scanned as live memories.
|
|
145
|
+
- `readOnly: true` applies to a custom directory too: it is not created, memory writes are disabled, and no plugin state or logs are written.
|
|
146
|
+
|
|
128
147
|
**Models for the memory agents.** OpenCode Zen's free models (e.g. `opencode/big-pickle`) reject every request whose tool list lacks OpenCode's `shell` tool (HTTP 403, *"OpenCode's free tier can only be used from within OpenCode"*). The sandboxed extraction and auto-dream forks are exactly such requests on both 1.x and 2.x, and so is recall on 2.x. On a free Zen model, extraction, auto-dream and (on 2.x) recall therefore fail. Configure a paid model for `opencode-memory-extract`, `opencode-memory-dream` and `opencode-memory-recall` (e.g. `opencode/gpt-6-luna`); the main agent can stay on a free model.
|
|
129
148
|
|
|
130
|
-
**Logs.** On 1.x logs go to the OpenCode service log (`opencode` log directory, service `opencode-claude-memory`). 2.x gives plugins no log API, so the plugin writes
|
|
149
|
+
**Logs.** On 1.x logs go to the OpenCode service log (`opencode` log directory, service `opencode-claude-memory`). 2.x gives plugins no log API, so the plugin writes `opencode-memory.log` in its state directory instead (rotated at about 1 MB). This is `<CLAUDE_CONFIG_DIR>/opencode-memory/<project>/` by default, or the custom-store state namespace described above.
|
|
131
150
|
|
|
132
151
|
## 🆕 Differences on OpenCode 2.x
|
|
133
152
|
|
|
@@ -144,7 +163,7 @@ The V2 plugin API exposes less than V1, so a few behaviours differ:
|
|
|
144
163
|
|
|
145
164
|
| Aspect | Claude Code | This plugin |
|
|
146
165
|
|---|---|---|
|
|
147
|
-
| Memory directory | `~/.claude/projects/<sanitized canonical git root>/memory
|
|
166
|
+
| Memory directory | `~/.claude/projects/<sanitized canonical git root>/memory/`, or `autoMemoryDirectory` | same default layout and custom-directory path semantics (verified against Claude Code 2.1.292) |
|
|
148
167
|
| File format | Markdown + `name` (kebab-case slug) / `description` / `metadata.type` frontmatter | identical; older top-level `type:` still read; frontmatter parsed only within the first 30 lines, as in Claude Code |
|
|
149
168
|
| Taxonomy | `user`, `feedback`, `project`, `reference` | identical |
|
|
150
169
|
| `MEMORY.md` | one-line pointers, hand-organisable | read with the same truncation rules; written with minimal line-level edits, hand-written pointer lines are never replaced |
|
|
@@ -204,7 +223,7 @@ Everything the environment variables used to control now lives under `extract`,
|
|
|
204
223
|
|
|
205
224
|
**Do I need to migrate existing memory?** No. Existing Claude Code memory files are used as they are.
|
|
206
225
|
|
|
207
|
-
**Where is data stored?** `~/.claude/projects/<project>/memory/` (or `$CLAUDE_CONFIG_DIR/projects/...`). Plugin state lives
|
|
226
|
+
**Where is data stored?** `~/.claude/projects/<project>/memory/` (or `$CLAUDE_CONFIG_DIR/projects/...`), unless `autoMemoryDirectory` selects a custom store. Plugin state lives under `$CLAUDE_CONFIG_DIR/opencode-memory/`, scoped by project and, for custom stores, target directory. Memories the plugin deletes that are not purely its own (Claude Code's, another tool's, or one of its own that another tool has since edited) are copied to `trash/<timestamp>/` in that state directory first.
|
|
208
227
|
|
|
209
228
|
**Can I disable extraction, auto-dream or recall?** Yes — `extract.enabled`, `autodream.enabled`, `recall.enabled` in the plugin options.
|
|
210
229
|
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
export declare const MemoryOptionsSchema: z.ZodObject<{
|
|
3
|
+
autoMemoryDirectory: z.ZodOptional<z.ZodString>;
|
|
3
4
|
extract: z.ZodDefault<z.ZodObject<{
|
|
4
5
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
5
6
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
@@ -73,7 +74,9 @@ export declare const MemoryOptionsSchema: z.ZodObject<{
|
|
|
73
74
|
maxMemories: number;
|
|
74
75
|
};
|
|
75
76
|
readOnly: boolean;
|
|
77
|
+
autoMemoryDirectory?: string | undefined;
|
|
76
78
|
}, {
|
|
79
|
+
autoMemoryDirectory?: string | undefined;
|
|
77
80
|
extract?: {
|
|
78
81
|
enabled?: boolean | undefined;
|
|
79
82
|
timeoutMs?: number | undefined;
|
package/dist/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { resolveAutoMemoryDirectory } from "./store/autoMemoryDirectory.js";
|
|
4
5
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
5
6
|
const timeoutMs = (fallback) => z.number().int().positive().max(MAX_TIMER_DELAY_MS).default(fallback);
|
|
6
7
|
// Plugin options come from `opencode.json`: `"plugin": [["opencode-claude-memory", { ... }]]`.
|
|
@@ -8,6 +9,8 @@ const timeoutMs = (fallback) => z.number().int().positive().max(MAX_TIMER_DELAY_
|
|
|
8
9
|
// falling back to defaults.
|
|
9
10
|
export const MemoryOptionsSchema = z
|
|
10
11
|
.object({
|
|
12
|
+
// Final memory directory, with Claude Code's autoMemoryDirectory path semantics.
|
|
13
|
+
autoMemoryDirectory: z.string().optional(),
|
|
11
14
|
extract: z
|
|
12
15
|
.object({
|
|
13
16
|
enabled: z.boolean().default(true),
|
|
@@ -78,6 +81,7 @@ export function parseConfig(options, env = process.env, homeDir = homedir()) {
|
|
|
78
81
|
const parsed = parseMemoryOptions(options);
|
|
79
82
|
return {
|
|
80
83
|
...parsed,
|
|
84
|
+
autoMemoryDirectory: resolveAutoMemoryDirectory(parsed.autoMemoryDirectory, homeDir),
|
|
81
85
|
// Read-only switches off everything that writes, whatever the per-feature options say.
|
|
82
86
|
...(parsed.readOnly
|
|
83
87
|
? { extract: { ...parsed.extract, enabled: false }, autodream: { ...parsed.autodream, enabled: false } }
|
|
@@ -98,7 +98,7 @@ export class ExtractionCoordinator {
|
|
|
98
98
|
this.deps = deps;
|
|
99
99
|
this.now = deps.now ?? Date.now;
|
|
100
100
|
this.state = deps.state ?? new ExtractionStateStore(deps.store.stateDir, this.now);
|
|
101
|
-
this.lock = deps.lock ?? new MaintenanceLock(
|
|
101
|
+
this.lock = deps.lock ?? new MaintenanceLock(deps.store.maintenanceLockPath, this.now);
|
|
102
102
|
this.autodream = deps.host
|
|
103
103
|
? new AutoDream({ ...deps, host: deps.host, state: this.state, now: this.now, lock: this.lock })
|
|
104
104
|
: undefined;
|
|
@@ -192,11 +192,14 @@ export class ExtractionCoordinator {
|
|
|
192
192
|
if (!host)
|
|
193
193
|
return;
|
|
194
194
|
this.warnLegacyShellHook();
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
store.
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
// The old shell hook maintained the default project store, never a custom destination.
|
|
196
|
+
if (store.usesDefaultMemoryDirectory) {
|
|
197
|
+
migrateLegacyAutodreamState(this.state, `${store.claudeConfigDir}/opencode-memory`, [
|
|
198
|
+
store.gitRoot ?? store.memoryRoot,
|
|
199
|
+
store.memoryRoot,
|
|
200
|
+
store.canonicalRoot,
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
200
203
|
if (config.extract.catchUpLimit <= 0)
|
|
201
204
|
return;
|
|
202
205
|
let sessions;
|
|
@@ -21,7 +21,7 @@ export class AutoDream {
|
|
|
21
21
|
constructor(deps) {
|
|
22
22
|
this.deps = deps;
|
|
23
23
|
this.now = deps.now ?? Date.now;
|
|
24
|
-
this.lock = deps.lock ?? new MaintenanceLock(deps.
|
|
24
|
+
this.lock = deps.lock ?? new MaintenanceLock(deps.store.maintenanceLockPath, this.now);
|
|
25
25
|
}
|
|
26
26
|
// Called from the extraction coordinator's persisted update after a session was extracted.
|
|
27
27
|
static noteSession(state, sessionID) {
|
|
@@ -22,6 +22,8 @@ export type ListOptions = {
|
|
|
22
22
|
};
|
|
23
23
|
export type MemoryStoreOptions = {
|
|
24
24
|
claudeConfigDir: string;
|
|
25
|
+
autoMemoryDirectory?: string;
|
|
26
|
+
homeDir?: string;
|
|
25
27
|
readOnly?: boolean;
|
|
26
28
|
now?: () => Date;
|
|
27
29
|
};
|
|
@@ -32,8 +34,10 @@ export declare class MemoryStore {
|
|
|
32
34
|
readonly claudeConfigDir: string;
|
|
33
35
|
readonly projectDir: string;
|
|
34
36
|
readonly memoryDir: string;
|
|
37
|
+
readonly usesDefaultMemoryDirectory: boolean;
|
|
35
38
|
readonly entrypoint: string;
|
|
36
39
|
readonly stateDir: string;
|
|
40
|
+
readonly maintenanceLockPath: string;
|
|
37
41
|
readonly readOnly: boolean;
|
|
38
42
|
private readonly now;
|
|
39
43
|
constructor(memoryRoot: string, options: MemoryStoreOptions);
|
|
@@ -1,10 +1,29 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { cpSync, lstatSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync } from "node:fs";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
4
|
import { writeFileAtomicSync } from "../util/exclusiveFile.js";
|
|
5
|
+
import { resolveAutoMemoryDirectory } from "./autoMemoryDirectory.js";
|
|
4
6
|
import { buildFrontmatter, editFrontmatter, FRONTMATTER_MAX_LINES, frontmatterKeys, ORIGIN, parseFrontmatter, parseMemoryType, } from "./frontmatter.js";
|
|
5
7
|
import { buildIndexPointer, findIndexPointerLine, readIndexFile, removeIndexLine, upsertIndexLine, } from "./indexFile.js";
|
|
6
8
|
import { ENTRYPOINT_NAME, findCanonicalGitRoot, findGitRoot, MAX_MEMORY_FILE_BYTES, resolveMemoryFilePath, sanitizePath, } from "./paths.js";
|
|
7
9
|
import { formatMemoryManifest, nameFromFilename, readMemoryEntry, scanMemoryFiles, } from "./scan.js";
|
|
10
|
+
function realDirectory(directory) {
|
|
11
|
+
const absolute = resolve(directory);
|
|
12
|
+
try {
|
|
13
|
+
// Filesystems such as ext4 can keep canonically equivalent Unicode names distinct.
|
|
14
|
+
return realpathSync.native(absolute);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Resolve existing ancestors too: read-only stores and private state may not exist yet,
|
|
18
|
+
// but their parents can still contain symlinks.
|
|
19
|
+
const parent = dirname(absolute);
|
|
20
|
+
return parent === absolute ? absolute : join(realDirectory(parent), basename(absolute));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function containsDirectory(root, directory) {
|
|
24
|
+
const fromRoot = relative(root, directory);
|
|
25
|
+
return fromRoot === "" || (!isAbsolute(fromRoot) && fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`));
|
|
26
|
+
}
|
|
8
27
|
// Owns the resolved memory paths for one project. Path resolution (git root, worktree canonical
|
|
9
28
|
// root, sanitised project key) happens exactly once, in the constructor.
|
|
10
29
|
export class MemoryStore {
|
|
@@ -14,10 +33,13 @@ export class MemoryStore {
|
|
|
14
33
|
claudeConfigDir;
|
|
15
34
|
projectDir;
|
|
16
35
|
memoryDir;
|
|
36
|
+
usesDefaultMemoryDirectory;
|
|
17
37
|
entrypoint;
|
|
18
38
|
// Plugin-private state (extraction watermarks, auto-dream gate) lives next to, not inside, the
|
|
19
39
|
// Claude Code project directory so Claude Code never sees it.
|
|
20
40
|
stateDir;
|
|
41
|
+
// Shared by every project targeting this memory directory; watermarks stay project-scoped.
|
|
42
|
+
maintenanceLockPath;
|
|
21
43
|
readOnly;
|
|
22
44
|
now;
|
|
23
45
|
constructor(memoryRoot, options) {
|
|
@@ -29,11 +51,29 @@ export class MemoryStore {
|
|
|
29
51
|
this.now = options.now ?? (() => new Date());
|
|
30
52
|
const projectKey = sanitizePath(this.canonicalRoot);
|
|
31
53
|
this.projectDir = join(this.claudeConfigDir, "projects", projectKey);
|
|
32
|
-
|
|
54
|
+
const defaultMemoryDir = join(this.projectDir, "memory");
|
|
55
|
+
this.memoryDir = resolveAutoMemoryDirectory(options.autoMemoryDirectory, options.homeDir ?? "") ?? defaultMemoryDir;
|
|
33
56
|
this.entrypoint = join(this.memoryDir, ENTRYPOINT_NAME);
|
|
34
|
-
|
|
57
|
+
const directory = realDirectory(this.memoryDir);
|
|
58
|
+
const privateRoot = join(this.claudeConfigDir, "opencode-memory");
|
|
59
|
+
const privateDirectory = realDirectory(privateRoot);
|
|
60
|
+
if (containsDirectory(directory, privateDirectory) || containsDirectory(privateDirectory, directory)) {
|
|
61
|
+
throw new Error(`Memory directory must not overlap the plugin state directory: ${privateRoot}`);
|
|
62
|
+
}
|
|
35
63
|
if (!this.readOnly)
|
|
36
64
|
mkdirSync(this.memoryDir, { recursive: true });
|
|
65
|
+
this.usesDefaultMemoryDirectory = directory === realDirectory(defaultMemoryDir);
|
|
66
|
+
const key = createHash("sha256").update(directory).digest("hex");
|
|
67
|
+
const projectStateDir = join(privateRoot, projectKey);
|
|
68
|
+
this.stateDir = this.usesDefaultMemoryDirectory ? projectStateDir : join(projectStateDir, "memory-stores", key);
|
|
69
|
+
// Recognise the default Claude directory layout too, so an explicit override pointing to
|
|
70
|
+
// another project's default store shares its existing maintenance lock.
|
|
71
|
+
const fromProjects = relative(realDirectory(join(this.claudeConfigDir, "projects")), directory);
|
|
72
|
+
const parts = fromProjects.split(sep);
|
|
73
|
+
this.maintenanceLockPath =
|
|
74
|
+
!isAbsolute(fromProjects) && parts.length === 2 && parts[0] !== ".." && parts[1] === "memory"
|
|
75
|
+
? join(privateRoot, parts[0] ?? "", "maintenance.lock")
|
|
76
|
+
: join(privateRoot, "memory-stores", key, "maintenance.lock");
|
|
37
77
|
}
|
|
38
78
|
scan() {
|
|
39
79
|
return scanMemoryFiles(this.memoryDir);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
type DirectoryPaths = Pick<typeof path, "isAbsolute" | "join" | "normalize" | "sep">;
|
|
3
|
+
export declare function resolveAutoMemoryDirectory(value: string | undefined, homeDir: string, paths?: DirectoryPaths): string | undefined;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
// Matches Claude Code 2.1.292's custom auto-memory directory resolver: expand only ~/ (or ~\),
|
|
3
|
+
// normalise lexically, reject unsafe roots, then return an NFC path with one trailing separator.
|
|
4
|
+
// No git discovery, realpath, cwd-relative resolution, whitespace trimming or environment expansion.
|
|
5
|
+
// An invalid string disables the override; it does not make the plugin fail to load.
|
|
6
|
+
export function resolveAutoMemoryDirectory(value, homeDir, paths = path) {
|
|
7
|
+
if (!value)
|
|
8
|
+
return undefined;
|
|
9
|
+
let directory = value;
|
|
10
|
+
if (directory.startsWith("~/") || directory.startsWith("~\\")) {
|
|
11
|
+
const suffix = directory.slice(2);
|
|
12
|
+
const relative = paths.normalize(suffix || ".");
|
|
13
|
+
if (relative === "." ||
|
|
14
|
+
relative === ".." ||
|
|
15
|
+
relative.startsWith(`..${paths.sep}`) ||
|
|
16
|
+
relative.startsWith("../") ||
|
|
17
|
+
relative.startsWith("..\\")) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
directory = paths.join(homeDir, suffix);
|
|
21
|
+
}
|
|
22
|
+
const normalized = paths.normalize(directory).replace(/[/\\]+$/, "");
|
|
23
|
+
if (!paths.isAbsolute(normalized) ||
|
|
24
|
+
normalized.length < 3 ||
|
|
25
|
+
/^[A-Za-z]:$/.test(normalized) ||
|
|
26
|
+
normalized.includes("\0") ||
|
|
27
|
+
isNetworkOrDevicePath(normalized)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
return `${normalized}${paths.sep}`.normalize("NFC");
|
|
31
|
+
}
|
|
32
|
+
// Claude Code also rejects UNC/NT namespaces, POSIX network automounts and macOS filesystem
|
|
33
|
+
// pseudo-paths. These checks apply on every platform, after the platform's path normalisation.
|
|
34
|
+
function isNetworkOrDevicePath(directory) {
|
|
35
|
+
const ntNamespace = /^[\\/]\?\?[\\/]/;
|
|
36
|
+
if (/^[\\/]{2}/.test(directory) || ntNamespace.test(directory))
|
|
37
|
+
return true;
|
|
38
|
+
if (directory.includes("??") && ntNamespace.test(path.win32.normalize(directory)))
|
|
39
|
+
return true;
|
|
40
|
+
if (!directory.startsWith("/"))
|
|
41
|
+
return false;
|
|
42
|
+
const parts = directory.split("/").filter(Boolean);
|
|
43
|
+
const first = parts[0] ?? "";
|
|
44
|
+
if (first.toLowerCase() === "network" || /^\.(vol|file|nofollow|resolve)$/i.test(first))
|
|
45
|
+
return true;
|
|
46
|
+
const fold = (part) => part
|
|
47
|
+
.replace(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/g, "")
|
|
48
|
+
.toUpperCase()
|
|
49
|
+
.toLowerCase();
|
|
50
|
+
return ((parts.length >= 2 && fold(first) === "net") ||
|
|
51
|
+
(parts.length >= 3 && fold(first) === "network" && fold(parts[1] ?? "") === "servers"));
|
|
52
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-claude-memory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Claude Code memory: persistent, local-first shared memory with Claude Code-compatible Markdown files, LLM recall, in-process auto extraction, and auto-dream consolidation",
|
|
6
6
|
"main": "dist/index.js",
|