@mathew-cf/opencode-memory 0.2.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/LICENSE +201 -0
- package/README.md +126 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/constants.d.ts +31 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/hooks/guard.d.ts +78 -0
- package/dist/hooks/guard.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13555 -0
- package/dist/lib/db.d.ts +32 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/frontmatter.d.ts +49 -0
- package/dist/lib/frontmatter.d.ts.map +1 -0
- package/dist/lib/paths.d.ts +48 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/rag.d.ts +79 -0
- package/dist/lib/rag.d.ts.map +1 -0
- package/dist/lib/ripgrep.d.ts +32 -0
- package/dist/lib/ripgrep.d.ts.map +1 -0
- package/dist/lib/search-terms.d.ts +61 -0
- package/dist/lib/search-terms.d.ts.map +1 -0
- package/dist/tools/memory.d.ts +84 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/session.d.ts +103 -0
- package/dist/tools/session.d.ts.map +1 -0
- package/package.json +47 -0
- package/skills/opencode-memory/SKILL.md +295 -0
package/dist/lib/db.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for locating the OpenCode session database and running read-only
|
|
3
|
+
* SQL queries against it.
|
|
4
|
+
*
|
|
5
|
+
* The session tools wrap these helpers so they can be pointed at a temp DB
|
|
6
|
+
* during tests by setting `$OPENCODE_DB`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the path to the OpenCode SQLite database.
|
|
10
|
+
*
|
|
11
|
+
* OpenCode uses `xdg-basedir@5.1.0` which joins `os.homedir()` with
|
|
12
|
+
* `.local/share` on every platform (including Windows, where it resolves
|
|
13
|
+
* under `%USERPROFILE%`). So once HOME resolves correctly, the layout is
|
|
14
|
+
* identical cross-platform.
|
|
15
|
+
*
|
|
16
|
+
* Tests and power users can override the path with `$OPENCODE_DB`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveDbPath(env?: NodeJS.ProcessEnv): string;
|
|
19
|
+
/**
|
|
20
|
+
* Escape a value for use in a SQLite single-quoted string literal.
|
|
21
|
+
* Exported for tests — the exact quoting rules matter for ensuring we
|
|
22
|
+
* don't construct SQL injection vectors when building queries from user
|
|
23
|
+
* input.
|
|
24
|
+
*/
|
|
25
|
+
export declare function sqlStr(s: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* Run a SQLite statement against the given db and return the parsed JSON
|
|
28
|
+
* result. Returns an empty array on any error — callers then present a
|
|
29
|
+
* graceful "no results" response instead of propagating an exception.
|
|
30
|
+
*/
|
|
31
|
+
export declare function querySqlite<T = unknown>(dbPath: string, sql: string): Promise<T[]>;
|
|
32
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG1E;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAExC;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,CAAC,EAAE,CAAC,CAWd"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal YAML frontmatter parser/updater.
|
|
3
|
+
*
|
|
4
|
+
* Memory files use a small, opinionated frontmatter schema (title, tags,
|
|
5
|
+
* summary, importance, updated, last_accessed, access_count, related). A full
|
|
6
|
+
* YAML parser would be overkill — this hand-rolled parser understands just
|
|
7
|
+
* enough to feed the search ranker and update the access-tracking fields.
|
|
8
|
+
*
|
|
9
|
+
* Keeping it simple also means the parser behaves predictably on broken or
|
|
10
|
+
* partial frontmatter: unknown keys are ignored, malformed arrays become
|
|
11
|
+
* empty arrays, etc. Tests exercise those failure modes directly.
|
|
12
|
+
*/
|
|
13
|
+
export interface FrontMatter {
|
|
14
|
+
title?: string;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
summary?: string;
|
|
17
|
+
importance?: string;
|
|
18
|
+
updated?: string;
|
|
19
|
+
related?: string[];
|
|
20
|
+
last_accessed?: string;
|
|
21
|
+
access_count?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Split a markdown file into `{meta, body}`. If no frontmatter is present,
|
|
25
|
+
* `meta` is empty and the entire input becomes `body` — callers still get
|
|
26
|
+
* something usable rather than a thrown error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseFrontmatter(content: string): {
|
|
29
|
+
meta: FrontMatter;
|
|
30
|
+
body: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Update or insert the `last_accessed` and `access_count` fields in a YAML
|
|
34
|
+
* block. Returns the new YAML string. The caller is responsible for gluing
|
|
35
|
+
* it back onto the body with the `---` sentinels.
|
|
36
|
+
*
|
|
37
|
+
* Kept pure so tests can verify the exact text transformation without
|
|
38
|
+
* touching the filesystem.
|
|
39
|
+
*/
|
|
40
|
+
export declare function bumpAccessFields(yaml: string, dateStr: string): {
|
|
41
|
+
yaml: string;
|
|
42
|
+
newCount: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Current date as `YYYY-MM-DD` in UTC. Exposed so tests can compare against
|
|
46
|
+
* the exact string format written to memory files.
|
|
47
|
+
*/
|
|
48
|
+
export declare function todayISO(): string;
|
|
49
|
+
//# sourceMappingURL=frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/frontmatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IACjD,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAoCA;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAmBpC;AAED;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,MAAM,CAEjC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform path helpers.
|
|
3
|
+
*
|
|
4
|
+
* The memory and session tools were originally written for macOS/Linux where
|
|
5
|
+
* paths use forward slashes and process.env.HOME is always set. On Windows,
|
|
6
|
+
* both assumptions break. These helpers centralize the compatibility shim so
|
|
7
|
+
* the rest of the plugin can assume a single canonical path shape.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the user's home directory across platforms.
|
|
11
|
+
*
|
|
12
|
+
* Unix conventionally exposes the home via $HOME; Windows exposes it via
|
|
13
|
+
* %USERPROFILE%. When both are set, HOME wins so explicit overrides work
|
|
14
|
+
* on either platform. Backslashes in the resolved value are normalized to
|
|
15
|
+
* forward slashes so downstream string operations behave uniformly.
|
|
16
|
+
*
|
|
17
|
+
* Empty-string values are treated as unset (so an inherited-but-blank
|
|
18
|
+
* HOME on Windows falls through to USERPROFILE). Throws if neither
|
|
19
|
+
* variable resolves to a non-empty string — silently returning an empty
|
|
20
|
+
* path would cause every downstream file operation to resolve against
|
|
21
|
+
* the filesystem root, which is both surprising and extremely hard to
|
|
22
|
+
* debug.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveHome(env?: NodeJS.ProcessEnv): string;
|
|
25
|
+
/**
|
|
26
|
+
* Normalize a filesystem path to use forward slashes.
|
|
27
|
+
*
|
|
28
|
+
* On Windows, ripgrep emits paths with backslashes even when invoked with
|
|
29
|
+
* forward-slash arguments. When our code strips a forward-slash MEMORY_DIR
|
|
30
|
+
* prefix from a backslash-separated rg output line, the replace silently
|
|
31
|
+
* fails and the prefix is left in place — which breaks every downstream
|
|
32
|
+
* file read. Normalizing rg output through this helper restores the
|
|
33
|
+
* invariant that paths handled inside the tool always use /.
|
|
34
|
+
*
|
|
35
|
+
* On macOS/Linux this is a no-op: paths already use forward slashes.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normPath(p: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the root memory directory. Honors `$MEMORY_DIR` (used by tests
|
|
40
|
+
* and by users who keep their knowledge base in a non-default location),
|
|
41
|
+
* falling back to `$HOME/opencode-memory`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveMemoryDir(env?: NodeJS.ProcessEnv): string;
|
|
44
|
+
/**
|
|
45
|
+
* Convenience: the rag index directory for a given memory root.
|
|
46
|
+
*/
|
|
47
|
+
export declare function ragIndexDir(memoryDir: string): string;
|
|
48
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/lib/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CASxE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAErD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrappers around the `rag` CLI (https://github.com/mathew-cf/rag-cli).
|
|
3
|
+
*
|
|
4
|
+
* `@mathew-cf/rag-cli` is declared as a runtime dependency of this plugin,
|
|
5
|
+
* so its JS shim is always available in `node_modules/@mathew-cf/rag-cli/
|
|
6
|
+
* bin/rag.js` after installation. The shim handles platform detection and
|
|
7
|
+
* execs the platform-specific prebuilt binary published alongside it.
|
|
8
|
+
*
|
|
9
|
+
* We resolve the shim's absolute path via `require.resolve` at call time
|
|
10
|
+
* rather than trusting `$PATH`, because:
|
|
11
|
+
* - opencode installs plugins into a cache dir whose `node_modules/.bin`
|
|
12
|
+
* is NOT on $PATH when the plugin's code runs.
|
|
13
|
+
* - An absolute path also fails loudly if the dep is missing, which is
|
|
14
|
+
* easier to diagnose than a confusing "command not found".
|
|
15
|
+
*
|
|
16
|
+
* Semantic search is still optional: if resolution fails for any reason
|
|
17
|
+
* (broken install, unusual layout, native binary missing for the host
|
|
18
|
+
* platform), every public helper here degrades gracefully. Callers see
|
|
19
|
+
* `null` from `resolveRagBinary()` and fall back to keyword-only search.
|
|
20
|
+
*/
|
|
21
|
+
export interface RagStatus {
|
|
22
|
+
/** Absolute path to the JS shim, if resolvable. `null` otherwise. */
|
|
23
|
+
shimPath: string | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Locate the rag shim via node module resolution. Returns `null` when the
|
|
27
|
+
* package isn't installed (usually indicates the user added the plugin
|
|
28
|
+
* via a non-standard loader that skipped optionalDependencies or the
|
|
29
|
+
* rag-cli package failed to install for their platform).
|
|
30
|
+
*
|
|
31
|
+
* Exported so tests can assert the resolution behaviour directly.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveRagBinary(): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Probe once for the installation status. Kept as a separate function
|
|
36
|
+
* (even though it's currently a thin wrapper) so future logic around
|
|
37
|
+
* caching, version checks, or alternate lookup paths has a single home.
|
|
38
|
+
*/
|
|
39
|
+
export declare function probeRag(): RagStatus;
|
|
40
|
+
/** True iff the shim is resolvable. */
|
|
41
|
+
export declare function ragAvailable(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Human-readable guidance for when `rag` can't be resolved. Called from
|
|
44
|
+
* error paths and from `memory_setup` so the message lives in one place.
|
|
45
|
+
*
|
|
46
|
+
* With rag-cli as a declared dependency, the expected remedy is a
|
|
47
|
+
* reinstall — not a separate `cargo install` dance.
|
|
48
|
+
*/
|
|
49
|
+
export declare function installGuidance(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Run `rag search` against an index. Returns the raw JSON text so callers
|
|
52
|
+
* can parse it themselves. Any failure (missing shim, missing index,
|
|
53
|
+
* parse error upstream) resolves to the empty string — degrading
|
|
54
|
+
* gracefully rather than propagating shell exceptions.
|
|
55
|
+
*/
|
|
56
|
+
export declare function ragSearch(args: {
|
|
57
|
+
query: string;
|
|
58
|
+
indexDir: string;
|
|
59
|
+
topK?: number;
|
|
60
|
+
}): Promise<string>;
|
|
61
|
+
/**
|
|
62
|
+
* Spawn `rag index` as a detached background process. The caller does
|
|
63
|
+
* not block on the index build because it can be slow on large corpora
|
|
64
|
+
* and users don't want their save operations to stall.
|
|
65
|
+
*
|
|
66
|
+
* Returns `true` if we kicked off an index build, `false` if the shim
|
|
67
|
+
* couldn't be resolved.
|
|
68
|
+
*/
|
|
69
|
+
export declare function spawnRagIndex(args: {
|
|
70
|
+
memoryDir: string;
|
|
71
|
+
indexDir: string;
|
|
72
|
+
}): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Pre-download the embedding model by running `rag download`. Returns a
|
|
75
|
+
* human-readable status string — either the command's output or the
|
|
76
|
+
* installation guidance if the shim isn't resolvable.
|
|
77
|
+
*/
|
|
78
|
+
export declare function downloadModel(): Promise<string>;
|
|
79
|
+
//# sourceMappingURL=rag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rag.d.ts","sourceRoot":"","sources":["../../src/lib/rag.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,MAAM,WAAW,SAAS;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAMhD;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,IAAI,SAAS,CAEpC;AAED,uCAAuC;AACvC,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAoBxC;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,CAOlB;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAQV;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAUrD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper around the `@vscode/ripgrep` package.
|
|
3
|
+
*
|
|
4
|
+
* `@vscode/ripgrep` is a required dependency of this plugin. On install,
|
|
5
|
+
* its postinstall script downloads the platform's prebuilt `rg` binary
|
|
6
|
+
* and exposes its absolute path via `rgPath`. We import it via a dynamic
|
|
7
|
+
* `require` so tests (and code paths where the dep happens to be missing)
|
|
8
|
+
* can still execute — returning `null` from `resolveRgBinary` so callers
|
|
9
|
+
* surface a clean error rather than crashing on import.
|
|
10
|
+
*
|
|
11
|
+
* We resolve at call time rather than at module-top-level so a broken
|
|
12
|
+
* install doesn't prevent the plugin from loading at all. `memory_search`
|
|
13
|
+
* will then fall through to the "no results" branch with a helpful message.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the absolute path to the `rg` binary shipped by the
|
|
17
|
+
* `@vscode/ripgrep` package. Returns `null` if the package isn't
|
|
18
|
+
* installed (unusual — should never happen in practice because it's
|
|
19
|
+
* a required dep) or if its published layout changes.
|
|
20
|
+
*
|
|
21
|
+
* Exported for tests and for `memory_setup`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveRgBinary(): string | null;
|
|
24
|
+
/** True iff a usable `rg` binary is resolvable. */
|
|
25
|
+
export declare function rgAvailable(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Human-readable guidance for when ripgrep can't be resolved. Mirrors the
|
|
28
|
+
* shape of `rag.installGuidance()` so `memory_setup` can surface either
|
|
29
|
+
* message consistently.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rgInstallGuidance(): string;
|
|
32
|
+
//# sourceMappingURL=ripgrep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ripgrep.d.ts","sourceRoot":"","sources":["../../src/lib/ripgrep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAY/C;AAED,mDAAmD;AACnD,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAmB1C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search-term tokenization and scoring helpers.
|
|
3
|
+
*
|
|
4
|
+
* All the logic in this file is pure — no filesystem, no shell, no
|
|
5
|
+
* timestamps. That lets tests pin down ranking behaviour deterministically
|
|
6
|
+
* without setting up a temp directory or spawning `rg`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Split a query into individual search terms.
|
|
10
|
+
* - Quoted phrases become a single term (with the quotes stripped).
|
|
11
|
+
* - Whitespace-separated words become individual terms.
|
|
12
|
+
* - Very short words (<2 chars) and common stop words are filtered out.
|
|
13
|
+
*
|
|
14
|
+
* The original casing is preserved in the return value; downstream callers
|
|
15
|
+
* lowercase when they need to compare. That's intentional: a future ranker
|
|
16
|
+
* might want to treat `SQL` differently from `sql`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseSearchTerms(query: string): string[];
|
|
19
|
+
/**
|
|
20
|
+
* Count how many of the given search terms appear anywhere in the text
|
|
21
|
+
* (case-insensitive). Used as one of the ranking signals in the memory
|
|
22
|
+
* search scorer.
|
|
23
|
+
*/
|
|
24
|
+
export declare function countTermMatches(text: string, terms: string[]): number;
|
|
25
|
+
/**
|
|
26
|
+
* Structured inputs for the memory ranker. Kept as a record of primitives
|
|
27
|
+
* so callers can fill in whatever subset of signals they have — e.g. tests
|
|
28
|
+
* can exercise just the importance weighting without constructing a full
|
|
29
|
+
* ripgrep/rag result.
|
|
30
|
+
*/
|
|
31
|
+
export interface ScoreInputs {
|
|
32
|
+
/** True if ripgrep found a keyword match in this file. */
|
|
33
|
+
rgMatch: boolean;
|
|
34
|
+
/** Cosine similarity from the semantic index, if available. */
|
|
35
|
+
ragScore?: number;
|
|
36
|
+
/** Per-file count of how many search terms appeared in the body. */
|
|
37
|
+
termMatches: number;
|
|
38
|
+
/** Total number of search terms in the query. */
|
|
39
|
+
totalTerms: number;
|
|
40
|
+
/** Frontmatter tags (used for tag-match bonus). */
|
|
41
|
+
tags?: string[];
|
|
42
|
+
/** Relative path (used for filename-match bonus). */
|
|
43
|
+
path: string;
|
|
44
|
+
/** Importance bucket: `high`, `medium`, or `low`. */
|
|
45
|
+
importance?: string;
|
|
46
|
+
/** Historical access count (frequently-used memories get a bump). */
|
|
47
|
+
accessCount?: number;
|
|
48
|
+
/** The original search terms. */
|
|
49
|
+
terms: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Deterministic scorer for a single candidate memory file. The weights were
|
|
53
|
+
* tuned against a real corpus, but the key insight is that they're all
|
|
54
|
+
* bounded — no single signal can dominate. `rg` gives coarse recall; `rag`
|
|
55
|
+
* gives semantic discrimination; the metadata bonuses reward curation.
|
|
56
|
+
*
|
|
57
|
+
* Exported so tests can verify the exact ranking under mixed signals
|
|
58
|
+
* without going through the full `memory_search` tool.
|
|
59
|
+
*/
|
|
60
|
+
export declare function scoreCandidate(input: ScoreInputs): number;
|
|
61
|
+
//# sourceMappingURL=search-terms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-terms.d.ts","sourceRoot":"","sources":["../../src/lib/search-terms.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAcxD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAItE;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oEAAoE;IACpE,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAgDzD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory tools: search, list, save, access.
|
|
3
|
+
*
|
|
4
|
+
* The memory root is resolved lazily via `resolveMemoryDir()` so that
|
|
5
|
+
* tests can point the whole plugin at a temp directory by setting
|
|
6
|
+
* `$MEMORY_DIR` before importing.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Build a ripgrep argument list for OR-matching multiple terms.
|
|
10
|
+
* Kept as a pure function so tests can verify the exact args shape.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildRgArgs(terms: string[]): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Parse the output of a `rag search --json` invocation. Returns an empty
|
|
15
|
+
* array on any failure. Exported for tests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function parseRagHits(ragText: string): Array<{
|
|
18
|
+
source: string;
|
|
19
|
+
score: number;
|
|
20
|
+
text: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Relative path from the memory root, with backslashes normalized.
|
|
24
|
+
* Exposed for tests that need to assert the exact key written into the
|
|
25
|
+
* result map.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toRelPath(memoryDir: string, absPath: string): string;
|
|
28
|
+
export declare const search: {
|
|
29
|
+
description: string;
|
|
30
|
+
args: {
|
|
31
|
+
query: import("zod").ZodString;
|
|
32
|
+
category: import("zod").ZodOptional<import("zod").ZodString>;
|
|
33
|
+
};
|
|
34
|
+
execute(args: {
|
|
35
|
+
query: string;
|
|
36
|
+
category?: string | undefined;
|
|
37
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Pure entry point for memory search. Exposed separately from the `tool()`
|
|
41
|
+
* wrapper so tests can call it with plain arguments and assert against the
|
|
42
|
+
* returned string without constructing a full tool context.
|
|
43
|
+
*/
|
|
44
|
+
export declare function runSearch(input: {
|
|
45
|
+
query: string;
|
|
46
|
+
category?: string;
|
|
47
|
+
}): Promise<string>;
|
|
48
|
+
export declare const list: {
|
|
49
|
+
description: string;
|
|
50
|
+
args: {
|
|
51
|
+
category: import("zod").ZodOptional<import("zod").ZodString>;
|
|
52
|
+
};
|
|
53
|
+
execute(args: {
|
|
54
|
+
category?: string | undefined;
|
|
55
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
56
|
+
};
|
|
57
|
+
export declare function runList(input: {
|
|
58
|
+
category?: string;
|
|
59
|
+
}): Promise<string>;
|
|
60
|
+
export declare const access: {
|
|
61
|
+
description: string;
|
|
62
|
+
args: {
|
|
63
|
+
path: import("zod").ZodString;
|
|
64
|
+
};
|
|
65
|
+
execute(args: {
|
|
66
|
+
path: string;
|
|
67
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
68
|
+
};
|
|
69
|
+
export declare function runAccess(input: {
|
|
70
|
+
path: string;
|
|
71
|
+
}): Promise<string>;
|
|
72
|
+
export declare const save: {
|
|
73
|
+
description: string;
|
|
74
|
+
args: {};
|
|
75
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
76
|
+
};
|
|
77
|
+
export declare function runSave(): Promise<string>;
|
|
78
|
+
export declare const setup: {
|
|
79
|
+
description: string;
|
|
80
|
+
args: {};
|
|
81
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
82
|
+
};
|
|
83
|
+
export declare function runSetup(): Promise<string>;
|
|
84
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/tools/memory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8BH;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAgBrD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,GACd,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAexD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpE;AAID,eAAO,MAAM,MAAM;;;;;;;;;;CAgCjB,CAAC;AAEH;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,MAAM,CAAC,CA+OlB;AAED,eAAO,MAAM,IAAI;;;;;;;;CAef,CAAC;AAEH,wBAAsB,OAAO,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAoE3E;AAED,eAAO,MAAM,MAAM;;;;;;;;CAgBjB,CAAC;AAEH,wBAAsB,SAAS,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBxE;AAED,eAAO,MAAM,IAAI;;;;CAkCf,CAAC;AAEH,wBAAsB,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CA+B/C;AAED,eAAO,MAAM,KAAK;;;;CAShB,CAAC;AAEH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAwBhD"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session tools: search, list, read.
|
|
3
|
+
*
|
|
4
|
+
* These read from the OpenCode SQLite database (WAL mode, safe to query
|
|
5
|
+
* concurrently while OpenCode is running). The database path is resolved
|
|
6
|
+
* lazily so tests can override it with `$OPENCODE_DB`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Build an `<col> LIKE <pattern>` OR clause. When there's a single term we
|
|
10
|
+
* return a bare expression; multi-term queries get parenthesized so they
|
|
11
|
+
* can be composed into larger `WHERE` clauses without operator-precedence
|
|
12
|
+
* surprises.
|
|
13
|
+
*/
|
|
14
|
+
export declare function likeOr(col: string, patterns: string[]): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build a `term_hits` expression that counts how many patterns match —
|
|
17
|
+
* used as a ranking signal so rows matching more terms surface first.
|
|
18
|
+
*/
|
|
19
|
+
export declare function termHits(col: string, patterns: string[]): string;
|
|
20
|
+
export interface SessionSearchRow {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string | null;
|
|
23
|
+
directory: string;
|
|
24
|
+
updated: string;
|
|
25
|
+
snippet: string | null;
|
|
26
|
+
match_offset: number | null;
|
|
27
|
+
term_hits: number;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionListRow {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string | null;
|
|
32
|
+
directory: string;
|
|
33
|
+
created: string;
|
|
34
|
+
updated: string;
|
|
35
|
+
}
|
|
36
|
+
export declare const search: {
|
|
37
|
+
description: string;
|
|
38
|
+
args: {
|
|
39
|
+
query: import("zod").ZodString;
|
|
40
|
+
limit: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
41
|
+
directory: import("zod").ZodOptional<import("zod").ZodString>;
|
|
42
|
+
};
|
|
43
|
+
execute(args: {
|
|
44
|
+
query: string;
|
|
45
|
+
limit?: number | undefined;
|
|
46
|
+
directory?: string | undefined;
|
|
47
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
48
|
+
};
|
|
49
|
+
export declare function runSessionSearch(input: {
|
|
50
|
+
query: string;
|
|
51
|
+
limit?: number;
|
|
52
|
+
directory?: string;
|
|
53
|
+
currentSessionId?: string;
|
|
54
|
+
}): Promise<string>;
|
|
55
|
+
export declare const list: {
|
|
56
|
+
description: string;
|
|
57
|
+
args: {
|
|
58
|
+
from: import("zod").ZodOptional<import("zod").ZodString>;
|
|
59
|
+
to: import("zod").ZodOptional<import("zod").ZodString>;
|
|
60
|
+
directory: import("zod").ZodOptional<import("zod").ZodString>;
|
|
61
|
+
limit: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
62
|
+
};
|
|
63
|
+
execute(args: {
|
|
64
|
+
from?: string | undefined;
|
|
65
|
+
to?: string | undefined;
|
|
66
|
+
directory?: string | undefined;
|
|
67
|
+
limit?: number | undefined;
|
|
68
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
69
|
+
};
|
|
70
|
+
export declare function runSessionList(input: {
|
|
71
|
+
from?: string;
|
|
72
|
+
to?: string;
|
|
73
|
+
directory?: string;
|
|
74
|
+
limit?: number;
|
|
75
|
+
currentSessionId?: string;
|
|
76
|
+
}): Promise<string>;
|
|
77
|
+
export declare const read: {
|
|
78
|
+
description: string;
|
|
79
|
+
args: {
|
|
80
|
+
session_id: import("zod").ZodString;
|
|
81
|
+
limit: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
82
|
+
offset: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
83
|
+
role: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
84
|
+
all: "all";
|
|
85
|
+
user: "user";
|
|
86
|
+
assistant: "assistant";
|
|
87
|
+
}>>;
|
|
88
|
+
};
|
|
89
|
+
execute(args: {
|
|
90
|
+
session_id: string;
|
|
91
|
+
limit?: number | undefined;
|
|
92
|
+
offset?: number | undefined;
|
|
93
|
+
role?: "all" | "user" | "assistant" | undefined;
|
|
94
|
+
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
95
|
+
};
|
|
96
|
+
export declare function runSessionRead(input: {
|
|
97
|
+
sessionId: string;
|
|
98
|
+
limit?: number;
|
|
99
|
+
offset?: number;
|
|
100
|
+
role?: "all" | "user" | "assistant";
|
|
101
|
+
currentSessionId?: string;
|
|
102
|
+
}): Promise<string>;
|
|
103
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAI9D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAIhE;AAID,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,eAAO,MAAM,MAAM;;;;;;;;;;;;CA2BjB,CAAC;AAEH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,MAAM,CAAC,CAoIlB;AAED,eAAO,MAAM,IAAI;;;;;;;;;;;;;;CAqCf,CAAC;AAEH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,MAAM,CAAC,CAqDlB;AAED,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;CAiCf,CAAC;AAEH,wBAAsB,cAAc,CAAC,KAAK,EAAE;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,WAAW,CAAC;IACpC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GAAG,OAAO,CAAC,MAAM,CAAC,CA0ElB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mathew-cf/opencode-memory",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Persistent cross-session memory for OpenCode \u2014 a hybrid keyword + semantic search layer over a local markdown knowledge base, plus session history tools and guardrails that nudge agents to use them.",
|
|
5
|
+
"author": "mat",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/mathew-cf/opencode-memory"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"skills"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "bun build src/index.ts --outdir dist --target node && tsc --emitDeclarationOnly",
|
|
20
|
+
"test": "bun test",
|
|
21
|
+
"prepublishOnly": "bun run build",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"sync-version": "bun scripts/sync-version.ts",
|
|
24
|
+
"version": "bun run sync-version && git add README.md"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"opencode",
|
|
28
|
+
"opencode-plugin",
|
|
29
|
+
"memory",
|
|
30
|
+
"rag",
|
|
31
|
+
"semantic-search",
|
|
32
|
+
"persistence"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@mathew-cf/rag-cli": "^0.4.0",
|
|
36
|
+
"@vscode/ripgrep": "^1.17.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@opencode-ai/plugin": "^1.1.49",
|
|
40
|
+
"@types/bun": "^1.3.12",
|
|
41
|
+
"@types/node": "^22.0.0",
|
|
42
|
+
"typescript": "^5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@opencode-ai/plugin": ">=1.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|