@devxiyang/agent-memo 0.0.1

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.
Files changed (42) hide show
  1. package/README.md +193 -0
  2. package/dist/core/memo/constants.d.ts +6 -0
  3. package/dist/core/memo/constants.d.ts.map +1 -0
  4. package/dist/core/memo/constants.js +6 -0
  5. package/dist/core/memo/constants.js.map +1 -0
  6. package/dist/core/memo/file-fs.d.ts +39 -0
  7. package/dist/core/memo/file-fs.d.ts.map +1 -0
  8. package/dist/core/memo/file-fs.js +259 -0
  9. package/dist/core/memo/file-fs.js.map +1 -0
  10. package/dist/core/memo/index.d.ts +7 -0
  11. package/dist/core/memo/index.d.ts.map +1 -0
  12. package/dist/core/memo/index.js +7 -0
  13. package/dist/core/memo/index.js.map +1 -0
  14. package/dist/core/memo/memo.d.ts +1 -0
  15. package/dist/core/memo/memo.d.ts.map +1 -0
  16. package/dist/core/memo/memo.js +3 -0
  17. package/dist/core/memo/memo.js.map +1 -0
  18. package/dist/core/memo/mime.d.ts +10 -0
  19. package/dist/core/memo/mime.d.ts.map +1 -0
  20. package/dist/core/memo/mime.js +13 -0
  21. package/dist/core/memo/mime.js.map +1 -0
  22. package/dist/core/memo/search/index.d.ts +3 -0
  23. package/dist/core/memo/search/index.d.ts.map +1 -0
  24. package/dist/core/memo/search/index.js +3 -0
  25. package/dist/core/memo/search/index.js.map +1 -0
  26. package/dist/core/memo/search/ripgrep.d.ts +11 -0
  27. package/dist/core/memo/search/ripgrep.d.ts.map +1 -0
  28. package/dist/core/memo/search/ripgrep.js +71 -0
  29. package/dist/core/memo/search/ripgrep.js.map +1 -0
  30. package/dist/core/memo/search/text.d.ts +10 -0
  31. package/dist/core/memo/search/text.d.ts.map +1 -0
  32. package/dist/core/memo/search/text.js +63 -0
  33. package/dist/core/memo/search/text.js.map +1 -0
  34. package/dist/core/memo/types.d.ts +113 -0
  35. package/dist/core/memo/types.d.ts.map +1 -0
  36. package/dist/core/memo/types.js +3 -0
  37. package/dist/core/memo/types.js.map +1 -0
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +2 -0
  41. package/dist/index.js.map +1 -0
  42. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # agent.memo
2
+
3
+ Agent memory SDK based on a filesystem paradigm. Organizes memories, resources and skills as a hierarchical virtual filesystem with `memo://` URIs — inspired by [OpenViking](https://github.com/volcengine/OpenViking).
4
+
5
+ ## Concepts
6
+
7
+ Every directory carries up to four meta files managed by the **caller** (typically via LLM):
8
+
9
+ ```
10
+ {workspace}/user/memories/preferences/
11
+ ├── .abstract.md # L0 ~100 tokens, one-sentence summary
12
+ ├── .overview.md # L1 ~1-2k tokens, navigational overview
13
+ ├── .relations.json # links to other nodes
14
+ ├── .meta.json # arbitrary structured metadata
15
+ └── coding.md # L2 actual content
16
+ ```
17
+
18
+ **Tiered context loading:**
19
+
20
+ | Tier | Source | Cost |
21
+ |------|--------|------|
22
+ | 0 | `.abstract.md` | cheap, always-on |
23
+ | 1 | `.overview.md` | medium |
24
+ | 2 | full file content | full |
25
+
26
+ **URI scheme:** `memo://path/to/node` maps directly to `{workspace}/path/to/node`.
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install @devxiyang/agent-memo
32
+ ```
33
+
34
+ > `RipgrepSearchBackend` (default) uses a bundled `rg` binary via `@vscode/ripgrep` — no manual installation needed.
35
+
36
+ ## Quick Start
37
+
38
+ ```typescript
39
+ import { FileMemo, type Memo, type MemoLifecycle } from '@devxiyang/agent-memo'
40
+
41
+ await using memo = new FileMemo({
42
+ workspace: '/path/to/workspace',
43
+ hooks: {
44
+ onWritten: async (uri, parentDirs) => {
45
+ // Called after any file write or update (SDK or external tool)
46
+ // Regenerate .abstract.md for affected directories
47
+ for (const dir of parentDirs) {
48
+ const abstract = await myLLM.summarize(await memo.ls(dir))
49
+ await memo.writeMeta(dir, { abstract })
50
+ }
51
+ },
52
+ onError: (err, event) => console.error(`[memo:${event}]`, err),
53
+ },
54
+ })
55
+
56
+ // Write content
57
+ await memo.write('memo://user/memories/preferences/coding.md', 'Prefers TypeScript strict mode')
58
+
59
+ // List directory (includes mimeType for files, abstract for subdirs)
60
+ const nodes = await memo.ls('memo://user/memories/')
61
+
62
+ // Search
63
+ const results = await memo.search('TypeScript', {
64
+ scope: ['memo://user/memories/'],
65
+ limit: 10,
66
+ })
67
+
68
+ // Build LLM context
69
+ const systemPrompt = await memo.context(['memo://user/memories/'], 0) // L0, cheap
70
+ const detail = await memo.context(['memo://user/memories/preferences/coding.md'], 2) // full
71
+ ```
72
+
73
+ ## Binary Files
74
+
75
+ For binary files (images, PDFs, audio), use `toPath()` to get the physical path and write directly — the filesystem watcher picks it up automatically:
76
+
77
+ ```typescript
78
+ const path = memo.toPath('memo://user/memories/photos/avatar.jpg')
79
+ await sharp(imageBuffer).toFile(path)
80
+ // onWrite hook fires automatically, caller generates .abstract.md via VLM
81
+ ```
82
+
83
+ ## API
84
+
85
+ ### `new FileMemo(options: FileMemoOptions)`
86
+
87
+ | Option | Type | Default | Description |
88
+ |--------|------|---------|-------------|
89
+ | `workspace` | `string` | required | Root directory |
90
+ | `hooks` | `MemoHooks` | — | Lifecycle hooks |
91
+ | `search` | `SearchBackend` | `RipgrepSearchBackend` | Search implementation |
92
+ | `mime` | `MimeDetector` | `MagicMimeDetector` | MIME type detection |
93
+ | `watch` | `boolean` | `true` | Auto-start filesystem watcher |
94
+
95
+ ### `Memo` interface
96
+
97
+ ```typescript
98
+ // Content
99
+ read(uri): Promise<string | null>
100
+ write(uri, content): Promise<void>
101
+ delete(uri): Promise<void>
102
+
103
+ // Directory
104
+ ls(uri): Promise<MemoNode[]>
105
+ mkdir(uri): Promise<void>
106
+ rmdir(uri, recursive?): Promise<void>
107
+
108
+ // Meta (caller manages content)
109
+ readMeta(uri): Promise<DirMeta>
110
+ writeMeta(uri, meta): Promise<void>
111
+
112
+ // Relations
113
+ link(uri, targets, reason?): Promise<void>
114
+ unlink(uri, target): Promise<void>
115
+ relations(uri): Promise<Relation[]>
116
+
117
+ // Search
118
+ search(query, options?): Promise<SearchResult[]>
119
+
120
+ // LLM context assembly
121
+ context(uris, tier: 0 | 1 | 2): Promise<string>
122
+ ```
123
+
124
+ ### `MemoLifecycle` interface
125
+
126
+ ```typescript
127
+ toPath(uri): string // get physical path for binary access
128
+ watch(): void // start watcher
129
+ unwatch(): Promise<void> // stop watcher
130
+ [Symbol.asyncDispose]() // used by `await using`
131
+ ```
132
+
133
+ ### Hooks
134
+
135
+ ```typescript
136
+ interface MemoHooks {
137
+ onWritten?(uri, parentDirs): void | Promise<void>
138
+ onDeleted?(uri, parentDirs): void | Promise<void>
139
+ onLinked?(uri, targets): void | Promise<void>
140
+ onUnlinked?(uri, target): void | Promise<void>
141
+ onError?(err, event): void
142
+ }
143
+ ```
144
+
145
+ All hooks are **fire-and-forget** — the SDK does not await them. Errors are forwarded to `onError`.
146
+
147
+ ### Search Backends
148
+
149
+ ```typescript
150
+ // Default — bundled rg binary, fast
151
+ new RipgrepSearchBackend()
152
+
153
+ // Fallback — pure JS, no system dependencies
154
+ new SimpleSearchBackend()
155
+ ```
156
+
157
+ ### MIME Detector
158
+
159
+ ```typescript
160
+ // Default — magic bytes detection via file-type
161
+ new MagicMimeDetector()
162
+
163
+ // Custom
164
+ class MyDetector implements MimeDetector {
165
+ async detect(path: string): Promise<string | undefined> { ... }
166
+ }
167
+ ```
168
+
169
+ ## Filesystem Structure
170
+
171
+ ```
172
+ {workspace}/
173
+ ├── user/
174
+ │ └── memories/
175
+ │ ├── .abstract.md
176
+ │ ├── .overview.md
177
+ │ ├── .relations.json
178
+ │ ├── preferences/
179
+ │ │ ├── .abstract.md
180
+ │ │ └── coding.md
181
+ │ └── photos/
182
+ │ └── avatar.jpg
183
+ ├── agent/
184
+ │ └── memories/
185
+ │ ├── cases/
186
+ │ └── patterns/
187
+ └── session/
188
+ └── {session-id}/
189
+ ```
190
+
191
+ ## License
192
+
193
+ MIT
@@ -0,0 +1,6 @@
1
+ export declare const META_ABSTRACT = ".abstract.md";
2
+ export declare const META_OVERVIEW = ".overview.md";
3
+ export declare const META_RELATIONS = ".relations.json";
4
+ export declare const META_META = ".meta.json";
5
+ export declare const META_FILES: Set<string>;
6
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/memo/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,iBAAkB,CAAA;AAC5C,eAAO,MAAM,aAAa,iBAAkB,CAAA;AAC5C,eAAO,MAAM,cAAc,oBAAoB,CAAA;AAC/C,eAAO,MAAM,SAAS,eAAoB,CAAA;AAE1C,eAAO,MAAM,UAAU,aAAqE,CAAA"}
@@ -0,0 +1,6 @@
1
+ export const META_ABSTRACT = '.abstract.md';
2
+ export const META_OVERVIEW = '.overview.md';
3
+ export const META_RELATIONS = '.relations.json';
4
+ export const META_META = '.meta.json';
5
+ export const META_FILES = new Set([META_ABSTRACT, META_OVERVIEW, META_RELATIONS, META_META]);
6
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/core/memo/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,aAAa,GAAI,cAAc,CAAA;AAC5C,MAAM,CAAC,MAAM,aAAa,GAAI,cAAc,CAAA;AAC5C,MAAM,CAAC,MAAM,cAAc,GAAG,iBAAiB,CAAA;AAC/C,MAAM,CAAC,MAAM,SAAS,GAAQ,YAAY,CAAA;AAE1C,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC,CAAA"}
@@ -0,0 +1,39 @@
1
+ import type { Memo, MemoLifecycle, MemoUri, MemoNode, DirMeta, Relation, SearchOptions, SearchResult, FileMemoOptions } from './types.js';
2
+ export declare class FileMemo implements Memo, MemoLifecycle {
3
+ private readonly options;
4
+ private readonly hooks;
5
+ private readonly backend;
6
+ private readonly mime;
7
+ private readonly index;
8
+ private watcher;
9
+ constructor(options: FileMemoOptions);
10
+ private get workspace();
11
+ private absToUri;
12
+ private fire;
13
+ private _indexFile;
14
+ /** Convert a memo URI to an absolute filesystem path (for binary file access). */
15
+ toPath(uri: MemoUri): string;
16
+ /**
17
+ * Start watching the workspace with chokidar.
18
+ * On startup: scans existing files to build the index (no hooks fired).
19
+ * Afterwards: all filesystem changes update the index and fire hooks.
20
+ * Meta files are excluded to prevent infinite loops.
21
+ */
22
+ watch(): void;
23
+ unwatch(): Promise<void>;
24
+ [Symbol.asyncDispose](): Promise<void>;
25
+ read(uri: MemoUri): Promise<string | null>;
26
+ write(uri: MemoUri, content: string): Promise<void>;
27
+ delete(uri: MemoUri): Promise<void>;
28
+ ls(uri: MemoUri): Promise<MemoNode[]>;
29
+ mkdir(uri: MemoUri): Promise<void>;
30
+ rmdir(uri: MemoUri, recursive?: boolean): Promise<void>;
31
+ readMeta(uri: MemoUri): Promise<DirMeta>;
32
+ writeMeta(uri: MemoUri, meta: Partial<DirMeta>): Promise<void>;
33
+ relations(uri: MemoUri): Promise<Relation[]>;
34
+ link(uri: MemoUri, targets: MemoUri[], reason?: string): Promise<void>;
35
+ unlink(uri: MemoUri, target: MemoUri): Promise<void>;
36
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
37
+ context(uris: MemoUri[], tier: 0 | 1 | 2): Promise<string>;
38
+ }
39
+ //# sourceMappingURL=file-fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-fs.d.ts","sourceRoot":"","sources":["../../../src/core/memo/file-fs.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,IAAI,EACJ,aAAa,EACb,OAAO,EACP,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,aAAa,EACb,YAAY,EAKZ,eAAe,EAChB,MAAM,YAAY,CAAA;AA+BnB,qBAAa,QAAS,YAAW,IAAI,EAAE,aAAa;IAOtC,OAAO,CAAC,QAAQ,CAAC,OAAO;IANpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAiB;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,OAAO,CAAkC;gBAEpB,OAAO,EAAE,eAAe;IAWrD,OAAO,KAAK,SAAS,GAAoC;IAEzD,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,IAAI;YAME,UAAU;IAUxB,kFAAkF;IAClF,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM;IAI5B;;;;;OAKG;IACH,KAAK,IAAI,IAAI;IAiDP,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAMxB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtC,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQ1C,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASnD,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IASnC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAiCrC,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAcxC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9D,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAS5C,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBtE,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAapD,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAMvE,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;CAqBjE"}
@@ -0,0 +1,259 @@
1
+ import { readFile, writeFile, mkdir, rm, readdir, stat } from 'node:fs/promises';
2
+ import { join, dirname, basename } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import chokidar from 'chokidar';
5
+ import { META_ABSTRACT, META_OVERVIEW, META_RELATIONS, META_META, META_FILES } from './constants.js';
6
+ import { RipgrepSearchBackend } from './search/index.js';
7
+ import { MagicMimeDetector } from './mime.js';
8
+ // ─── URI helpers ──────────────────────────────────────────────────────────────
9
+ function uriToRel(uri) {
10
+ if (!uri.startsWith('memo://'))
11
+ throw new Error(`Invalid memo URI: "${uri}"`);
12
+ return uri.slice('memo://'.length);
13
+ }
14
+ function parentDirs(uri) {
15
+ const parts = uriToRel(uri).split('/').filter(Boolean);
16
+ const dirs = [];
17
+ for (let i = parts.length - 1; i > 0; i--) {
18
+ dirs.push(`memo://${parts.slice(0, i).join('/')}`);
19
+ }
20
+ return dirs;
21
+ }
22
+ // ─── FileMemo ───────────────────────────────────────────────────────────────
23
+ export class FileMemo {
24
+ options;
25
+ hooks;
26
+ backend;
27
+ mime;
28
+ index = new Map();
29
+ watcher = null;
30
+ constructor(options) {
31
+ this.options = options;
32
+ this.hooks = options.hooks ?? {};
33
+ this.backend = options.search ?? new RipgrepSearchBackend();
34
+ this.mime = options.mime ?? new MagicMimeDetector();
35
+ this.backend.init(options.workspace);
36
+ if (options.watch !== false)
37
+ this.watch();
38
+ }
39
+ // ── Internal helpers ───────────────────────────────────────────────────────
40
+ get workspace() { return this.options.workspace; }
41
+ absToUri(absPath) {
42
+ const rel = absPath.slice(this.workspace.length).replace(/^[/\\]/, '');
43
+ return `memo://${rel}`;
44
+ }
45
+ fire(result, event) {
46
+ if (result instanceof Promise) {
47
+ result.catch(err => this.hooks.onError?.(err, event));
48
+ }
49
+ }
50
+ async _indexFile(absPath) {
51
+ const [s, mimeType] = await Promise.all([
52
+ stat(absPath).catch(() => null),
53
+ this.mime.detect(absPath).catch(() => undefined),
54
+ ]);
55
+ return { mimeType, size: s?.size ?? 0, modTime: s?.mtimeMs ?? 0 };
56
+ }
57
+ // ── MemoLifecycle ────────────────────────────────────────────────────────
58
+ /** Convert a memo URI to an absolute filesystem path (for binary file access). */
59
+ toPath(uri) {
60
+ return join(this.workspace, uriToRel(uri));
61
+ }
62
+ /**
63
+ * Start watching the workspace with chokidar.
64
+ * On startup: scans existing files to build the index (no hooks fired).
65
+ * Afterwards: all filesystem changes update the index and fire hooks.
66
+ * Meta files are excluded to prevent infinite loops.
67
+ */
68
+ watch() {
69
+ if (this.watcher)
70
+ return;
71
+ let ready = false;
72
+ this.watcher = chokidar.watch(this.workspace, {
73
+ ignoreInitial: false,
74
+ ignored: (p) => META_FILES.has(basename(p)),
75
+ });
76
+ this.watcher.on('add', async (p) => {
77
+ const entry = await this._indexFile(p);
78
+ this.index.set(p, entry);
79
+ if (ready) {
80
+ const uri = this.absToUri(p);
81
+ this.fire(this.hooks.onWritten?.(uri, parentDirs(uri)), 'write');
82
+ }
83
+ });
84
+ this.watcher.on('change', async (p) => {
85
+ const entry = await this._indexFile(p);
86
+ this.index.set(p, entry);
87
+ if (ready) {
88
+ const uri = this.absToUri(p);
89
+ this.fire(this.hooks.onWritten?.(uri, parentDirs(uri)), 'write');
90
+ }
91
+ });
92
+ this.watcher.on('unlink', (p) => {
93
+ this.index.delete(p);
94
+ if (ready) {
95
+ const uri = this.absToUri(p);
96
+ this.fire(this.hooks.onDeleted?.(uri, parentDirs(uri)), 'delete');
97
+ }
98
+ });
99
+ this.watcher.on('unlinkDir', (p) => {
100
+ for (const key of this.index.keys()) {
101
+ if (key.startsWith(p))
102
+ this.index.delete(key);
103
+ }
104
+ if (ready) {
105
+ const uri = this.absToUri(p);
106
+ this.fire(this.hooks.onDeleted?.(uri, parentDirs(uri)), 'delete');
107
+ }
108
+ });
109
+ this.watcher.on('ready', () => { ready = true; });
110
+ }
111
+ async unwatch() {
112
+ await this.watcher?.close();
113
+ this.watcher = null;
114
+ this.index.clear();
115
+ }
116
+ async [Symbol.asyncDispose]() {
117
+ await this.unwatch();
118
+ }
119
+ // ── Content files ──────────────────────────────────────────────────────────
120
+ async read(uri) {
121
+ try {
122
+ return await readFile(this.toPath(uri), 'utf-8');
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ async write(uri, content) {
129
+ const p = this.toPath(uri);
130
+ await mkdir(dirname(p), { recursive: true });
131
+ await writeFile(p, content, 'utf-8');
132
+ if (!this.watcher) {
133
+ this.fire(this.hooks.onWritten?.(uri, parentDirs(uri)), 'write');
134
+ }
135
+ }
136
+ async delete(uri) {
137
+ await rm(this.toPath(uri), { force: true });
138
+ if (!this.watcher) {
139
+ this.fire(this.hooks.onDeleted?.(uri, parentDirs(uri)), 'delete');
140
+ }
141
+ }
142
+ // ── Directory ──────────────────────────────────────────────────────────────
143
+ async ls(uri) {
144
+ const p = this.toPath(uri);
145
+ let entries;
146
+ try {
147
+ entries = await readdir(p, { withFileTypes: true, encoding: 'utf8' });
148
+ }
149
+ catch {
150
+ return [];
151
+ }
152
+ return Promise.all(entries
153
+ .filter(e => !META_FILES.has(e.name))
154
+ .map(async (e) => {
155
+ const childPath = join(p, e.name);
156
+ const isDir = e.isDirectory();
157
+ const indexed = !isDir ? this.index.get(childPath) : undefined;
158
+ const s = indexed ? null : await stat(childPath).catch(() => null);
159
+ return {
160
+ uri: this.absToUri(childPath),
161
+ name: e.name,
162
+ isDir,
163
+ size: indexed?.size ?? s?.size ?? 0,
164
+ modTime: indexed?.modTime ?? s?.mtimeMs ?? 0,
165
+ mimeType: indexed?.mimeType,
166
+ abstract: isDir
167
+ ? await readFile(join(childPath, META_ABSTRACT), 'utf-8').catch(() => undefined)
168
+ : undefined,
169
+ };
170
+ }));
171
+ }
172
+ async mkdir(uri) {
173
+ await mkdir(this.toPath(uri), { recursive: true });
174
+ }
175
+ async rmdir(uri, recursive = false) {
176
+ await rm(this.toPath(uri), { recursive, force: true });
177
+ }
178
+ // ── Meta ───────────────────────────────────────────────────────────────────
179
+ async readMeta(uri) {
180
+ const p = this.toPath(uri);
181
+ const [abstract, overview, metaRaw] = await Promise.all([
182
+ readFile(join(p, META_ABSTRACT), 'utf-8').catch(() => undefined),
183
+ readFile(join(p, META_OVERVIEW), 'utf-8').catch(() => undefined),
184
+ readFile(join(p, META_META), 'utf-8').catch(() => undefined),
185
+ ]);
186
+ return {
187
+ abstract,
188
+ overview,
189
+ meta: metaRaw ? JSON.parse(metaRaw) : undefined,
190
+ };
191
+ }
192
+ async writeMeta(uri, meta) {
193
+ const p = this.toPath(uri);
194
+ await mkdir(p, { recursive: true });
195
+ const writes = [];
196
+ if (meta.abstract !== undefined)
197
+ writes.push(writeFile(join(p, META_ABSTRACT), meta.abstract, 'utf-8'));
198
+ if (meta.overview !== undefined)
199
+ writes.push(writeFile(join(p, META_OVERVIEW), meta.overview, 'utf-8'));
200
+ if (meta.meta !== undefined)
201
+ writes.push(writeFile(join(p, META_META), JSON.stringify(meta.meta, null, 2), 'utf-8'));
202
+ await Promise.all(writes);
203
+ }
204
+ // ── Relations ──────────────────────────────────────────────────────────────
205
+ async relations(uri) {
206
+ try {
207
+ const raw = await readFile(join(this.toPath(uri), META_RELATIONS), 'utf-8');
208
+ return JSON.parse(raw);
209
+ }
210
+ catch {
211
+ return [];
212
+ }
213
+ }
214
+ async link(uri, targets, reason) {
215
+ const p = this.toPath(uri);
216
+ await mkdir(p, { recursive: true });
217
+ const existing = await this.relations(uri);
218
+ existing.push({
219
+ id: randomUUID(),
220
+ uris: [uri, ...targets],
221
+ reason,
222
+ createdAt: new Date().toISOString(),
223
+ });
224
+ await writeFile(join(p, META_RELATIONS), JSON.stringify(existing, null, 2), 'utf-8');
225
+ if (!this.watcher) {
226
+ this.fire(this.hooks.onLinked?.(uri, targets), 'link');
227
+ }
228
+ }
229
+ async unlink(uri, target) {
230
+ const existing = await this.relations(uri);
231
+ const filtered = existing.filter(r => !r.uris.includes(target));
232
+ await writeFile(join(this.toPath(uri), META_RELATIONS), JSON.stringify(filtered, null, 2), 'utf-8');
233
+ this.fire(this.hooks.onUnlinked?.(uri, target), 'unlink');
234
+ }
235
+ // ── Search ─────────────────────────────────────────────────────────────────
236
+ async search(query, options) {
237
+ return this.backend.search(query, options);
238
+ }
239
+ // ── Context assembly ───────────────────────────────────────────────────────
240
+ async context(uris, tier) {
241
+ const parts = await Promise.all(uris.map(async (uri) => {
242
+ let body;
243
+ if (tier === 0) {
244
+ const m = await this.readMeta(uri);
245
+ body = m.abstract;
246
+ }
247
+ else if (tier === 1) {
248
+ const m = await this.readMeta(uri);
249
+ body = m.overview ?? m.abstract;
250
+ }
251
+ else {
252
+ body = (await this.read(uri)) ?? undefined;
253
+ }
254
+ return body ? `## ${uri}\n\n${body}` : null;
255
+ }));
256
+ return parts.filter(Boolean).join('\n\n---\n\n');
257
+ }
258
+ }
259
+ //# sourceMappingURL=file-fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-fs.js","sourceRoot":"","sources":["../../../src/core/memo/file-fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './constants.js';
3
+ export { FileMemo } from './file-fs.js';
4
+ export { MagicMimeDetector } from './mime.js';
5
+ export { SimpleSearchBackend } from './search/text.js';
6
+ export { RipgrepSearchBackend } from './search/ripgrep.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/memo/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAe,cAAc,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAQ,WAAW,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,7 @@
1
+ export * from './types.js';
2
+ export * from './constants.js';
3
+ export { FileMemo } from './file-fs.js';
4
+ export { MagicMimeDetector } from './mime.js';
5
+ export { SimpleSearchBackend } from './search/text.js';
6
+ export { RipgrepSearchBackend } from './search/ripgrep.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/memo/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAe,cAAc,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAQ,WAAW,CAAA;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=memo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.d.ts","sourceRoot":"","sources":["../../../src/core/memo/memo.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Superseded by FileMemoFS in ./file-fs.ts
3
+ //# sourceMappingURL=memo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memo.js","sourceRoot":"","sources":["../../../src/core/memo/memo.ts"],"names":[],"mappings":";AAAA,2CAA2C"}
@@ -0,0 +1,10 @@
1
+ import type { MimeDetector } from './types.js';
2
+ /**
3
+ * MIME type detector based on magic bytes (file-type).
4
+ * More accurate than extension-based detection since it reads the file header.
5
+ * Returns undefined for plain text files or unrecognised formats.
6
+ */
7
+ export declare class MagicMimeDetector implements MimeDetector {
8
+ detect(path: string): Promise<string | undefined>;
9
+ }
10
+ //# sourceMappingURL=mime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../../../src/core/memo/mime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;GAIG;AACH,qBAAa,iBAAkB,YAAW,YAAY;IAC9C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAIxD"}
@@ -0,0 +1,13 @@
1
+ import { fileTypeFromFile } from 'file-type';
2
+ /**
3
+ * MIME type detector based on magic bytes (file-type).
4
+ * More accurate than extension-based detection since it reads the file header.
5
+ * Returns undefined for plain text files or unrecognised formats.
6
+ */
7
+ export class MagicMimeDetector {
8
+ async detect(path) {
9
+ const result = await fileTypeFromFile(path);
10
+ return result?.mime;
11
+ }
12
+ }
13
+ //# sourceMappingURL=mime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime.js","sourceRoot":"","sources":["../../../src/core/memo/mime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;GAIG;AACH,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,MAAM,CAAC,IAAY;QACvB,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAA;QAC3C,OAAO,MAAM,EAAE,IAAI,CAAA;IACrB,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ export { SimpleSearchBackend } from './text.js';
2
+ export { RipgrepSearchBackend } from './ripgrep.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/core/memo/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAS,WAAW,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,3 @@
1
+ export { SimpleSearchBackend } from './text.js';
2
+ export { RipgrepSearchBackend } from './ripgrep.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/core/memo/search/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAS,WAAW,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA"}
@@ -0,0 +1,11 @@
1
+ import type { SearchBackend, SearchOptions, SearchResult } from '../types.js';
2
+ export declare class RipgrepSearchBackend implements SearchBackend {
3
+ private readonly rgBin;
4
+ private workspace;
5
+ constructor(rgBin?: string);
6
+ init(workspace: string): void;
7
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
8
+ private _toPath;
9
+ private _toUri;
10
+ }
11
+ //# sourceMappingURL=ripgrep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ripgrep.d.ts","sourceRoot":"","sources":["../../../../src/core/memo/search/ripgrep.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAW,MAAM,aAAa,CAAA;AActF,qBAAa,oBAAqB,YAAW,aAAa;IAG5C,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,SAAS,CAAK;gBAEO,KAAK,SAAS;IAE3C,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIvB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAkD7E,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;CAIf"}
@@ -0,0 +1,71 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ import { rgPath } from '@vscode/ripgrep';
4
+ import { META_FILES } from '../constants.js';
5
+ export class RipgrepSearchBackend {
6
+ rgBin;
7
+ workspace = '';
8
+ constructor(rgBin = rgPath) {
9
+ this.rgBin = rgBin;
10
+ }
11
+ init(workspace) {
12
+ this.workspace = workspace;
13
+ }
14
+ async search(query, options) {
15
+ const limit = options?.limit ?? 20;
16
+ const paths = options?.scope?.map(u => this._toPath(u)) ?? [this.workspace];
17
+ const args = [
18
+ query,
19
+ ...paths,
20
+ '--json',
21
+ '--max-count', '1',
22
+ ...[...META_FILES].flatMap(f => ['--glob', `!${f}`]),
23
+ ];
24
+ return new Promise((resolve, reject) => {
25
+ const child = spawn(this.rgBin, args);
26
+ const results = [];
27
+ let buffer = '';
28
+ child.stderr.resume(); // drain stderr to prevent buffer blocking
29
+ child.stdout.on('data', (chunk) => {
30
+ buffer += chunk.toString('utf-8');
31
+ const lines = buffer.split('\n');
32
+ buffer = lines.pop() ?? '';
33
+ for (const line of lines) {
34
+ if (!line.trim() || results.length >= limit)
35
+ continue;
36
+ try {
37
+ const msg = JSON.parse(line);
38
+ if (msg.type !== 'match')
39
+ continue;
40
+ results.push({
41
+ uri: this._toUri(msg.data.path.text),
42
+ snippet: msg.data.lines.text.trim(),
43
+ });
44
+ }
45
+ catch { /* skip malformed lines */ }
46
+ }
47
+ if (results.length >= limit)
48
+ child.kill();
49
+ });
50
+ child.on('close', () => resolve(results));
51
+ child.on('error', err => {
52
+ if (err.code === 'ENOENT') {
53
+ reject(new Error(`ripgrep binary not found: "${this.rgBin}". Try reinstalling @vscode/ripgrep.`));
54
+ }
55
+ else {
56
+ reject(err);
57
+ }
58
+ });
59
+ });
60
+ }
61
+ _toPath(uri) {
62
+ if (!uri.startsWith('memo://'))
63
+ throw new Error(`Invalid memo URI: "${uri}"`);
64
+ return join(this.workspace, uri.slice('memo://'.length));
65
+ }
66
+ _toUri(absPath) {
67
+ const rel = absPath.slice(this.workspace.length).replace(/^[/\\]/, '');
68
+ return `memo://${rel}`;
69
+ }
70
+ }
71
+ //# sourceMappingURL=ripgrep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ripgrep.js","sourceRoot":"","sources":["../../../../src/core/memo/search/ripgrep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SearchBackend, SearchOptions, SearchResult } from '../types.js';
2
+ export declare class SimpleSearchBackend implements SearchBackend {
3
+ private workspace;
4
+ init(workspace: string): void;
5
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
6
+ private _searchDir;
7
+ private _toPath;
8
+ private _toUri;
9
+ }
10
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../../src/core/memo/search/text.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAW,MAAM,aAAa,CAAA;AAGtF,qBAAa,mBAAoB,YAAW,aAAa;IACvD,OAAO,CAAC,SAAS,CAAK;IAEtB,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIvB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;YAY/D,UAAU;IAoCxB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,MAAM;CAIf"}
@@ -0,0 +1,63 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { META_FILES } from '../constants.js';
4
+ export class SimpleSearchBackend {
5
+ workspace = '';
6
+ init(workspace) {
7
+ this.workspace = workspace;
8
+ }
9
+ async search(query, options) {
10
+ const limit = options?.limit ?? 20;
11
+ const roots = options?.scope?.map(u => this._toPath(u)) ?? [this.workspace];
12
+ const results = [];
13
+ for (const root of roots) {
14
+ await this._searchDir(root, query, results, limit);
15
+ if (results.length >= limit)
16
+ break;
17
+ }
18
+ return results;
19
+ }
20
+ async _searchDir(dir, query, results, limit) {
21
+ if (results.length >= limit)
22
+ return;
23
+ let entries;
24
+ try {
25
+ entries = await readdir(dir, { withFileTypes: true, encoding: 'utf8' });
26
+ }
27
+ catch {
28
+ return;
29
+ }
30
+ for (const e of entries) {
31
+ if (results.length >= limit)
32
+ return;
33
+ const childPath = join(dir, e.name);
34
+ if (e.isDirectory()) {
35
+ await this._searchDir(childPath, query, results, limit);
36
+ }
37
+ else if (!META_FILES.has(e.name)) {
38
+ try {
39
+ const content = await readFile(childPath, 'utf-8');
40
+ const idx = content.toLowerCase().indexOf(query.toLowerCase());
41
+ if (idx === -1)
42
+ continue;
43
+ const start = Math.max(0, idx - 60);
44
+ const end = Math.min(content.length, idx + query.length + 60);
45
+ results.push({ uri: this._toUri(childPath), snippet: content.slice(start, end) });
46
+ }
47
+ catch {
48
+ // skip binary / unreadable files
49
+ }
50
+ }
51
+ }
52
+ }
53
+ _toPath(uri) {
54
+ if (!uri.startsWith('memo://'))
55
+ throw new Error(`Invalid memo URI: "${uri}"`);
56
+ return join(this.workspace, uri.slice('memo://'.length));
57
+ }
58
+ _toUri(absPath) {
59
+ const rel = absPath.slice(this.workspace.length).replace(/^[/\\]/, '');
60
+ return `memo://${rel}`;
61
+ }
62
+ }
63
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../../../src/core/memo/search/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,113 @@
1
+ /** memo://user/memories/preferences/coding */
2
+ export type MemoUri = string;
3
+ export type MemoNode = {
4
+ uri: MemoUri;
5
+ name: string;
6
+ isDir: boolean;
7
+ size: number;
8
+ modTime: number;
9
+ mimeType?: string;
10
+ abstract?: string;
11
+ };
12
+ /**
13
+ * Every directory can carry three meta files:
14
+ * - .abstract.md L0 ~100 tokens, one-sentence summary
15
+ * - .overview.md L1 ~1-2k tokens, navigational overview
16
+ * - .meta.json arbitrary structured metadata
17
+ *
18
+ * Content is always written by the caller; the SDK only stores and retrieves.
19
+ */
20
+ export type DirMeta = {
21
+ abstract?: string;
22
+ overview?: string;
23
+ meta?: Record<string, unknown>;
24
+ };
25
+ export type Relation = {
26
+ id: string;
27
+ uris: MemoUri[];
28
+ reason?: string;
29
+ createdAt: string;
30
+ };
31
+ export interface MimeDetector {
32
+ detect(path: string): Promise<string | undefined>;
33
+ }
34
+ export type SearchOptions = {
35
+ /** Scope search to these directory URIs. Defaults to entire workspace. */
36
+ scope?: MemoUri[];
37
+ limit?: number;
38
+ };
39
+ export type SearchResult = {
40
+ uri: MemoUri;
41
+ snippet: string;
42
+ };
43
+ export interface SearchBackend {
44
+ /** Called by FileMemo after construction to provide the workspace path. */
45
+ init(workspace: string): void;
46
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
47
+ }
48
+ export type HookEvent = 'write' | 'delete' | 'link' | 'unlink';
49
+ /**
50
+ * All hooks are fire-and-forget: the SDK does not await them.
51
+ * Promise rejections are forwarded to `onError` if provided.
52
+ */
53
+ export interface MemoHooks {
54
+ /** Called after a content file is written or created. `parentDirs` lists all ancestor directory URIs. */
55
+ onWritten?: (uri: MemoUri, parentDirs: MemoUri[]) => void | Promise<void>;
56
+ /** Called after a content file or directory is deleted. */
57
+ onDeleted?: (uri: MemoUri, parentDirs: MemoUri[]) => void | Promise<void>;
58
+ /** Called after a relation is created. */
59
+ onLinked?: (uri: MemoUri, targets: MemoUri[]) => void | Promise<void>;
60
+ /** Called after a relation is removed. */
61
+ onUnlinked?: (uri: MemoUri, target: MemoUri) => void | Promise<void>;
62
+ /** Catches errors thrown inside any hook. */
63
+ onError?: (err: unknown, event: HookEvent) => void;
64
+ }
65
+ export interface FileMemoOptions {
66
+ workspace: string;
67
+ hooks?: MemoHooks;
68
+ /** Search backend. Defaults to RipgrepSearchBackend. */
69
+ search?: SearchBackend;
70
+ /** MIME type detector. Defaults to MagicMimeDetector (magic bytes). */
71
+ mime?: MimeDetector;
72
+ /**
73
+ * Auto-start filesystem watcher on construction. Default: true.
74
+ * When enabled, hooks fire for ALL filesystem changes and the in-memory
75
+ * index is kept up-to-date automatically.
76
+ */
77
+ watch?: boolean;
78
+ }
79
+ /**
80
+ * Optional lifecycle interface for implementations that support filesystem
81
+ * watching and direct path access. Separate from Memo so that consumers
82
+ * can depend only on the storage primitives they need.
83
+ */
84
+ export interface MemoLifecycle extends AsyncDisposable {
85
+ /** Convert a memo URI to an absolute filesystem path (for binary file access). */
86
+ toPath(uri: MemoUri): string;
87
+ /** Start watching the workspace; hooks fire for all filesystem changes. */
88
+ watch(): void;
89
+ /** Stop watching and release resources. */
90
+ unwatch(): Promise<void>;
91
+ }
92
+ export interface Memo {
93
+ read(uri: MemoUri): Promise<string | null>;
94
+ write(uri: MemoUri, content: string): Promise<void>;
95
+ delete(uri: MemoUri): Promise<void>;
96
+ ls(uri: MemoUri): Promise<MemoNode[]>;
97
+ mkdir(uri: MemoUri): Promise<void>;
98
+ rmdir(uri: MemoUri, recursive?: boolean): Promise<void>;
99
+ readMeta(uri: MemoUri): Promise<DirMeta>;
100
+ writeMeta(uri: MemoUri, meta: Partial<DirMeta>): Promise<void>;
101
+ link(uri: MemoUri, targets: MemoUri[], reason?: string): Promise<void>;
102
+ unlink(uri: MemoUri, target: MemoUri): Promise<void>;
103
+ relations(uri: MemoUri): Promise<Relation[]>;
104
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
105
+ /**
106
+ * Build a context string from a list of URIs at the given tier.
107
+ * tier 0 → .abstract.md (L0)
108
+ * tier 1 → .overview.md (L1), falls back to .abstract.md
109
+ * tier 2 → full file content (L2)
110
+ */
111
+ context(uris: MemoUri[], tier: 0 | 1 | 2): Promise<string>;
112
+ }
113
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/memo/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ // ─── URI ──────────────────────────────────────────────────────────────────────
2
+ export {};
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/memo/types.ts"],"names":[],"mappings":"AAAA,iFAAiF"}
@@ -0,0 +1,2 @@
1
+ export * from './core/memo/index.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './core/memo/index.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@devxiyang/agent-memo",
3
+ "version": "0.0.1",
4
+ "description": "Agent memory and memo SDK",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/devxiyang/agent.memo.git"
9
+ },
10
+ "keywords": [
11
+ "agent",
12
+ "memory",
13
+ "memo",
14
+ "llm",
15
+ "typescript"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./memo": {
26
+ "types": "./dist/core/memo/index.d.ts",
27
+ "import": "./dist/core/memo/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json",
35
+ "clean": "rm -rf dist",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.11.0",
42
+ "typescript": "^5.8.0",
43
+ "vitest": "^4.0.18"
44
+ },
45
+ "dependencies": {
46
+ "@vscode/ripgrep": ">=1.17.0",
47
+ "chokidar": ">=5.0.0",
48
+ "file-type": ">=21.0.0"
49
+ }
50
+ }