memorix 1.9.0 → 1.9.2
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/CHANGELOG.md +39 -0
- package/README.md +22 -5
- package/README.zh-CN.md +9 -5
- package/dist/cli/index.js +2635 -1942
- package/dist/index.js +2445 -1766
- package/dist/maintenance-runner.js +1696 -1036
- package/dist/media-audio-runner.js +2197 -1537
- package/dist/media-video-runner.js +2195 -1535
- package/dist/memcode-runtime/CHANGELOG.md +39 -0
- package/dist/sdk.js +2507 -1764
- package/dist/vector-backfill-runner.js +2193 -1533
- package/docs/CONFIGURATION.md +22 -0
- package/docs/DEVELOPMENT.md +2 -2
- package/docs/INTEGRATIONS.md +4 -0
- package/docs/PERFORMANCE.md +77 -20
- package/docs/README.md +1 -1
- package/docs/SETUP.md +5 -0
- package/package.json +2 -2
- package/plugins/antigravity/memorix/plugin.json +1 -1
- package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
- package/plugins/codebuddy/memorix/.codebuddy-plugin/plugin.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/copilot/memorix/plugin.json +1 -1
- package/plugins/gemini/memorix/gemini-extension.json +1 -1
- package/plugins/hermes/memorix/plugin.yaml +1 -1
- package/plugins/omp/memorix/package.json +1 -1
- package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
- package/plugins/pi/memorix/package.json +1 -1
- package/server.json +2 -2
- package/src/cli/commands/memory.ts +2 -1
- package/src/cli/default-mode.ts +15 -0
- package/src/cli/index.ts +10 -1
- package/src/codegraph/lite-provider.ts +10 -1
- package/src/compact/engine.ts +14 -4
- package/src/dashboard/maintenance.ts +11 -2
- package/src/memory/consolidation.ts +15 -6
- package/src/memory/freshness.ts +7 -4
- package/src/memory/observations.ts +118 -6
- package/src/runtime/vector-backfill-runner.ts +1 -1
- package/src/sdk.ts +13 -6
- package/src/search/semantic-index.ts +340 -0
- package/src/server.ts +1 -1
- package/src/store/obs-store.ts +54 -2
- package/src/store/orama-store.ts +300 -12
- package/src/store/sqlite-db.ts +6 -0
- package/src/store/sqlite-fts.ts +164 -0
- package/src/store/sqlite-store.ts +83 -6
- package/assets/star-history-dark.svg +0 -30
- package/assets/star-history-light.svg +0 -30
- package/assets/star-history.json +0 -712
package/docs/CONFIGURATION.md
CHANGED
|
@@ -195,6 +195,28 @@ OpenAI-only default. The equivalent env-var form is
|
|
|
195
195
|
`MEMORIX_EMBEDDING=api`, `MEMORIX_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1`,
|
|
196
196
|
`MEMORIX_EMBEDDING_MODEL=qwen/qwen3-embedding-8b`.
|
|
197
197
|
|
|
198
|
+
### Large-store retrieval
|
|
199
|
+
|
|
200
|
+
Memorix does not cap the number of durable observations to make search fast.
|
|
201
|
+
For a large store, the runtime keeps the complete records in SQLite and uses
|
|
202
|
+
derived indexes for bounded candidate retrieval. The optional LanceDB package
|
|
203
|
+
is installed with the package's optional dependencies and loaded only at
|
|
204
|
+
runtime; if its native binary is unavailable, SQLite FTS5 and the Orama
|
|
205
|
+
fallback continue to work.
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
MEMORIX_ORAMA_HYDRATION_THRESHOLD=10000 # working-set switch, not a quota
|
|
209
|
+
MEMORIX_SEMANTIC_INDEX=auto # auto | off | orama
|
|
210
|
+
MEMORIX_SEMANTIC_INDEX_THRESHOLD=10000 # train ANN index after this many vectors
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`MEMORIX_SEMANTIC_INDEX=auto` enables the local LanceDB shadow index when the
|
|
214
|
+
optional native package is available. It stores vector IDs and filter metadata,
|
|
215
|
+
while full memory text remains in SQLite. The semantic index uses HNSW with
|
|
216
|
+
scalar quantization after its threshold is reached; before that, its table is
|
|
217
|
+
still searchable without a training step. The derived index can be deleted and
|
|
218
|
+
recreated by the background vector backfill without affecting durable memories.
|
|
219
|
+
|
|
198
220
|
### `[rerank]`
|
|
199
221
|
|
|
200
222
|
Optional HTTP rerank for thorough search. Off by default.
|
package/docs/DEVELOPMENT.md
CHANGED
|
@@ -8,12 +8,12 @@ Memorix is a TypeScript project built around:
|
|
|
8
8
|
- memcode bundled terminal-agent runtime
|
|
9
9
|
- CLI workflows
|
|
10
10
|
- SQLite canonical persistence with compatibility/fallback layers
|
|
11
|
-
- Orama
|
|
11
|
+
- layered retrieval: SQLite FTS5, optional LanceDB semantic index, and Orama fallback
|
|
12
12
|
- dashboard and HTTP service
|
|
13
13
|
|
|
14
14
|
## Current Development Baseline
|
|
15
15
|
|
|
16
|
-
The current release line is **1.9.
|
|
16
|
+
The current release line is **1.9.1** and package metadata is kept synchronized
|
|
17
17
|
with the root package and MCP Registry manifest.
|
|
18
18
|
|
|
19
19
|
Contributors should assume the following areas are part of the current release line:
|
package/docs/INTEGRATIONS.md
CHANGED
|
@@ -122,6 +122,10 @@ If an agent only needs a raw stdio MCP entry:
|
|
|
122
122
|
|
|
123
123
|
Avoid `npx` in persistent MCP configs. Use the globally installed `memorix` binary so startup is predictable.
|
|
124
124
|
|
|
125
|
+
For npm-based MCP clients or registry testers, the complete package command is
|
|
126
|
+
`npx -y memorix serve`. Do not omit `serve`: bare `memorix` is the interactive
|
|
127
|
+
memcode entry in a human terminal.
|
|
128
|
+
|
|
125
129
|
For HTTP mode:
|
|
126
130
|
|
|
127
131
|
```bash
|
package/docs/PERFORMANCE.md
CHANGED
|
@@ -12,7 +12,10 @@ Memorix is designed to be light for everyday memory use and explicit about heavi
|
|
|
12
12
|
| `memorix` | Interactive terminal UI | Human operator workbench |
|
|
13
13
|
| `memorix orchestrate` | Supervisor plus spawned CLI agent workers | Orchestrated subagent loops |
|
|
14
14
|
|
|
15
|
-
The default memory path uses local SQLite as the canonical store
|
|
15
|
+
The default memory path uses local SQLite as the canonical store. Orama remains
|
|
16
|
+
the in-process compatibility/semantic fallback; large corpora use SQLite FTS5
|
|
17
|
+
and, when available, the local LanceDB semantic shadow index. No cloud service
|
|
18
|
+
is required.
|
|
16
19
|
|
|
17
20
|
## Resource Safety Model
|
|
18
21
|
|
|
@@ -66,11 +69,11 @@ project-scoped tools (`memorix_project_context`, `memorix_context_pack`,
|
|
|
66
69
|
CodeGraph Memory directly, so a new agent can get a useful brief without
|
|
67
70
|
waiting for unrelated historical records to hydrate into Orama.
|
|
68
71
|
|
|
69
|
-
Search
|
|
70
|
-
|
|
71
|
-
Memory refresh are queued durably and run in an isolated child process.
|
|
72
|
-
backfill
|
|
73
|
-
|
|
72
|
+
Search and ordinary writes use the durable SQLite path directly when the
|
|
73
|
+
persistent indexes are available. Corpus-scale retention, consolidation, and
|
|
74
|
+
Code Memory refresh are queued durably and run in an isolated child process.
|
|
75
|
+
Vector backfill updates the persistent semantic shadow index and keeps Orama as
|
|
76
|
+
the compatibility fallback.
|
|
74
77
|
|
|
75
78
|
## What Is Lightweight
|
|
76
79
|
|
|
@@ -105,6 +108,9 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
105
108
|
| `MEMORIX_RERANK_PROVIDER` | `off` | Set `http` to enable optional HTTP rerank |
|
|
106
109
|
| `MEMORIX_RERANK_BASE_URL` | `[memory.llm].base_url` | Compatible `/rerank` API root (path `/rerank` is appended) |
|
|
107
110
|
| `MEMORIX_EMBEDDING_CACHE_MAX_BYTES` | `268435456` | Disposable in-process API-vector cache budget; raise for large warm caches, or lower when the host is memory constrained |
|
|
111
|
+
| `MEMORIX_ORAMA_HYDRATION_THRESHOLD` | `10000` | Above this durable corpus size, skip full Orama hydration and use persistent SQLite/semantic indexes; this is a working-set threshold, not a memory quota |
|
|
112
|
+
| `MEMORIX_SEMANTIC_INDEX` | `auto` | `auto` uses the optional local LanceDB shadow index when installed; `off` or `orama` keeps the legacy semantic path |
|
|
113
|
+
| `MEMORIX_SEMANTIC_INDEX_THRESHOLD` | `10000` | Minimum vectors before the optional HNSW/SQ index is trained; vectors remain searchable before training |
|
|
108
114
|
| `memorix memory search --quality fast` | n/a | Force a fully local retrieval path for a latency-sensitive call |
|
|
109
115
|
| `npm run benchmark:retrieval -- --records 1000 --runs 100` | n/a | Reproduce hot in-process lexical retrieval latency; not an end-to-end claim |
|
|
110
116
|
| `npm run gate:large-store -- --records 40000` | n/a | Exercise SDK, HTTP MCP, hook persistence, cache integrity, and reopen behavior against a large isolated store |
|
|
@@ -130,6 +136,10 @@ On the release development machine used for this check, the healthy HTTP service
|
|
|
130
136
|
rising `rss` with stable heap, or rising V8 `heapUsed`; those indicate
|
|
131
137
|
different classes of problem and should not be “fixed” by lowering the
|
|
132
138
|
memory corpus limit.
|
|
139
|
+
- When a corpus crosses `MEMORIX_ORAMA_HYDRATION_THRESHOLD`, the complete
|
|
140
|
+
durable corpus remains in SQLite. Only the per-query candidate set is loaded
|
|
141
|
+
into the agent process. Management actions such as export, retention, and
|
|
142
|
+
detail views may intentionally load the requested full/project slice.
|
|
133
143
|
|
|
134
144
|
## Large-Store Release Gate
|
|
135
145
|
|
|
@@ -143,22 +153,69 @@ report fails when an interactive path exceeds its release budget. It also
|
|
|
143
153
|
verifies that the personal hook candidate remains durable on disk without
|
|
144
154
|
becoming visible to an unbound SDK reader.
|
|
145
155
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
search number is intentionally a cold
|
|
149
|
-
|
|
156
|
+
Small stores may use bounded Orama batches. Large stores skip that hydration and
|
|
157
|
+
use the persistent FTS5 path immediately; the optional semantic shadow index is
|
|
158
|
+
opened independently. The MCP search number is intentionally a cold
|
|
159
|
+
control-plane measurement and includes only the work still owed by the selected
|
|
160
|
+
derived indexes.
|
|
150
161
|
|
|
151
162
|
Results are machine-specific and should be compared on the same OS and Node
|
|
152
163
|
version. The release gate is an integrity and regression check, not a public
|
|
153
164
|
latency guarantee.
|
|
154
165
|
|
|
155
|
-
##
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
## 1.9.1 Large-Scale Retrieval Contract
|
|
167
|
+
|
|
168
|
+
The 1.9.1 line improves retrieval latency without shrinking the user's durable
|
|
169
|
+
memory corpus. SQLite remains the source of truth. Search indexes are derived,
|
|
170
|
+
rebuildable working structures, following the same boundary used by mature
|
|
171
|
+
memory systems that keep human-readable or durable records separate from their
|
|
172
|
+
vector/lexical indexes.
|
|
173
|
+
|
|
174
|
+
The release contract is:
|
|
175
|
+
|
|
176
|
+
1. A normal lexical or exact-identifier query must be able to use a persistent
|
|
177
|
+
SQLite FTS5 index and fetch only bounded candidates. It must not require
|
|
178
|
+
hydrating every observation into Orama first.
|
|
179
|
+
2. FTS5 must be feature-detected. Older or degraded SQLite runtimes fall back
|
|
180
|
+
to the existing Orama path without losing data or changing write semantics.
|
|
181
|
+
3. Inserts, updates, deletes, imports, and sync applies must keep the derived
|
|
182
|
+
lexical index current. A rebuild command/test path must repair it after an
|
|
183
|
+
interrupted migration or manual database recovery.
|
|
184
|
+
4. Candidate generation, graph/code references, and optional reranking must be
|
|
185
|
+
separately measurable. Reranking is applied only to a small candidate set;
|
|
186
|
+
it must never scan or send the whole corpus to a provider.
|
|
187
|
+
5. No durable observation count, retention rule, or user-facing memory quota is
|
|
188
|
+
introduced by this work. Limits apply only to per-query candidates, caches,
|
|
189
|
+
and concurrent maintenance work.
|
|
190
|
+
6. Large-store acceptance must report cold start, warm search, MCP search, RSS,
|
|
191
|
+
heap, index mode, result integrity, and recall of planted records at 1k,
|
|
192
|
+
10k, 40k, and 100k records. A slower machine is evidence to report, not a
|
|
193
|
+
reason to quietly lower the corpus size.
|
|
194
|
+
|
|
195
|
+
The implementation target is SQLite FTS5 plus an optional local LanceDB shadow
|
|
196
|
+
index. LanceDB stores only vector IDs and filter metadata, and is loaded at
|
|
197
|
+
runtime so the main package still builds on platforms where its native binary
|
|
198
|
+
is unavailable. HNSW with scalar quantization is trained only after the vector
|
|
199
|
+
table crosses the semantic-index threshold; before that, the table remains
|
|
200
|
+
queryable without paying a large training spike. Orama remains the compatibility
|
|
201
|
+
fallback. A server-only vector database is not a default dependency for the
|
|
202
|
+
local-first product.
|
|
203
|
+
|
|
204
|
+
The retrieval shape is:
|
|
205
|
+
|
|
206
|
+
```text
|
|
207
|
+
SQLite observations (durable truth)
|
|
208
|
+
-> FTS5 exact/lexical candidates
|
|
209
|
+
-> optional vector candidates
|
|
210
|
+
-> optional CodeGraph/knowledge candidates
|
|
211
|
+
-> rank fusion
|
|
212
|
+
-> optional rerank on the short list
|
|
213
|
+
-> compact result IDs, then detail on demand
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Measured Windows/Node 22 evidence for this implementation: 1k, 10k, 40k, and
|
|
217
|
+
100k records all passed the large-store gate. At 100k, peak RSS was about
|
|
218
|
+
419MB, SDK reopen was about 131ms, and the cold HTTP MCP search was about
|
|
219
|
+
296ms. These are same-machine acceptance numbers, not universal latency
|
|
220
|
+
promises. The release is complete only when the measurements and fallback
|
|
221
|
+
behavior remain recorded alongside the implementation.
|
package/docs/README.md
CHANGED
|
@@ -125,7 +125,7 @@ Historical/deep-reference documents may describe older designs. If they conflict
|
|
|
125
125
|
|
|
126
126
|
## Current Product Line
|
|
127
127
|
|
|
128
|
-
The current product line is **1.9.
|
|
128
|
+
The current product line is **1.9.1**. Multi-device store sync is opt-in and
|
|
129
129
|
keeps local SQLite canonical; existing users do not upload anything until they
|
|
130
130
|
configure a relay provider explicitly.
|
|
131
131
|
The authoritative acceptance contract for the current implementation is
|
package/docs/SETUP.md
CHANGED
|
@@ -170,6 +170,11 @@ Generic stdio MCP config:
|
|
|
170
170
|
|
|
171
171
|
Avoid `npx` in persistent MCP configs. Use the globally installed `memorix` binary so startup is predictable.
|
|
172
172
|
|
|
173
|
+
When an npm-based MCP client or registry tester must launch the package
|
|
174
|
+
directly, use `npx -y memorix serve`. The explicit `serve` argument selects
|
|
175
|
+
the stdio MCP server; a human running bare `memorix` in a terminal gets the
|
|
176
|
+
bundled memcode agent instead.
|
|
177
|
+
|
|
173
178
|
### Option C: memcode terminal agent
|
|
174
179
|
|
|
175
180
|
```bash
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorix",
|
|
3
3
|
"mcpName": "io.github.AVIDS2/memorix",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.2",
|
|
5
5
|
"description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -77,7 +77,6 @@
|
|
|
77
77
|
"benchmark:codegraph:scale": "node scripts/benchmark-codegraph-scale.mjs",
|
|
78
78
|
"benchmark:retrieval": "npm run build && node scripts/benchmark-retrieval.mjs",
|
|
79
79
|
"gate:large-store": "npm run build && node scripts/large-store-gate.mjs",
|
|
80
|
-
"generate:star-history": "node scripts/generate-star-history.mjs",
|
|
81
80
|
"sync:mcp-registry": "node scripts/sync-mcp-registry-manifest.mjs",
|
|
82
81
|
"check:mcp-registry": "node scripts/check-mcp-registry-manifest.mjs",
|
|
83
82
|
"sync:plugin-release": "node scripts/sync-plugin-release-manifests.mjs",
|
|
@@ -155,6 +154,7 @@
|
|
|
155
154
|
"zod": "^3.24.0"
|
|
156
155
|
},
|
|
157
156
|
"optionalDependencies": {
|
|
157
|
+
"@lancedb/lancedb": "^0.38.0",
|
|
158
158
|
"better-sqlite3": "^12.11.1"
|
|
159
159
|
},
|
|
160
160
|
"overrides": {
|
package/server.json
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/AVIDS2/memorix",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "1.9.
|
|
10
|
+
"version": "1.9.2",
|
|
11
11
|
"websiteUrl": "https://mem.rglens.com",
|
|
12
12
|
"icons": [
|
|
13
13
|
{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"registryType": "npm",
|
|
24
24
|
"identifier": "memorix",
|
|
25
|
-
"version": "1.9.
|
|
25
|
+
"version": "1.9.2",
|
|
26
26
|
"runtimeHint": "npx",
|
|
27
27
|
"packageArguments": [
|
|
28
28
|
{
|
|
@@ -280,7 +280,8 @@ export default defineCommand({
|
|
|
280
280
|
case 'deduplicate': {
|
|
281
281
|
const query = asStringArg(args.query);
|
|
282
282
|
const dryRun = !!args.dryRun || !!args['dry-run'];
|
|
283
|
-
const { isLLMEnabled } = await import('../../llm/provider.js');
|
|
283
|
+
const { initLLM, isLLMEnabled } = await import('../../llm/provider.js');
|
|
284
|
+
initLLM({ scope: 'memory', projectRoot: project.rootPath });
|
|
284
285
|
if (!isLLMEnabled()) {
|
|
285
286
|
emitResult(
|
|
286
287
|
{ project, available: false, usedLLM: false },
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface CliInteractivity {
|
|
2
|
+
stdinIsTTY?: boolean;
|
|
3
|
+
stdoutIsTTY?: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MCP hosts start package commands with piped stdio, while a human terminal
|
|
8
|
+
* has TTYs on both streams. Keep the friendly memcode default for humans and
|
|
9
|
+
* make the package safe for registry probes and generic MCP launchers.
|
|
10
|
+
*/
|
|
11
|
+
export function shouldDefaultToMcp(options: CliInteractivity = {}): boolean {
|
|
12
|
+
const stdinIsTTY = options.stdinIsTTY ?? process.stdin.isTTY;
|
|
13
|
+
const stdoutIsTTY = options.stdoutIsTTY ?? process.stdout.isTTY;
|
|
14
|
+
return stdinIsTTY !== true || stdoutIsTTY !== true;
|
|
15
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { importBundledMemcode } from './memcode-bootstrap.js';
|
|
|
20
20
|
import { installCliPipeErrorGuard } from './pipe-errors.js';
|
|
21
21
|
import { normalizeCliInvocation } from './invocation.js';
|
|
22
22
|
import { printCliGuideForHelp, renderCliGuide } from './command-guide.js';
|
|
23
|
+
import { shouldDefaultToMcp } from './default-mode.js';
|
|
23
24
|
|
|
24
25
|
installCliPipeErrorGuard();
|
|
25
26
|
|
|
@@ -327,8 +328,16 @@ const main = defineCommand({
|
|
|
327
328
|
'background', 'bg', 'bs', 'doctor', 'repair', 'dashboard', 'cleanup', 'purge', 'uninstall', 'orchestrate'];
|
|
328
329
|
if (firstArg && knownSubs.includes(firstArg)) return;
|
|
329
330
|
|
|
330
|
-
// No subcommand provided —
|
|
331
|
+
// No subcommand provided — keep the interactive memcode default for a
|
|
332
|
+
// human terminal, but make piped package launches an MCP server. Generic
|
|
333
|
+
// MCP directories and clients commonly execute `npx memorix` without
|
|
334
|
+
// reading server.json's package arguments.
|
|
331
335
|
if (!firstArg) {
|
|
336
|
+
if (shouldDefaultToMcp()) {
|
|
337
|
+
const serve = await import('./commands/serve.js');
|
|
338
|
+
await serve.default.run?.({ args: {}, rawArgs: [], cmd: serve.default } as any);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
332
341
|
try {
|
|
333
342
|
const { runCli } = await importBundledMemcode();
|
|
334
343
|
await runCli(process.argv.slice(2));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readdirSync, readFileSync, statSync, type Dirent } from 'node:fs';
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync, type Dirent } from 'node:fs';
|
|
3
3
|
import { join, relative } from 'node:path';
|
|
4
4
|
import type { CodeEdge, CodeFile, CodeStateSnapshot, CodeSymbol } from './types.js';
|
|
5
5
|
import { collectCodeStateSnapshot } from './code-state.js';
|
|
@@ -210,6 +210,14 @@ function resolveMaxFileBytes(value: number | undefined): number {
|
|
|
210
210
|
return Math.max(1, Math.floor(value!));
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
function isPythonVirtualenvDir(dir: string): boolean {
|
|
214
|
+
// PEP 405 virtualenvs place a `pyvenv.cfg` at their root regardless of the
|
|
215
|
+
// directory name (`.venv`, `.venv-leann`, `env`, uv/poetry envs, ...). Their
|
|
216
|
+
// `site-packages` trees are large and generated; descending into one can stall
|
|
217
|
+
// `codegraph refresh`, so skip virtualenvs by this marker rather than by name.
|
|
218
|
+
return existsSync(join(dir, 'pyvenv.cfg'));
|
|
219
|
+
}
|
|
220
|
+
|
|
213
221
|
function walk(root: string, exclude: string[], maxFiles: number): string[] {
|
|
214
222
|
const out: string[] = [];
|
|
215
223
|
const visit = (dir: string) => {
|
|
@@ -228,6 +236,7 @@ function walk(root: string, exclude: string[], maxFiles: number): string[] {
|
|
|
228
236
|
if (entry.isDirectory()) {
|
|
229
237
|
if (entry.name === '.git' || entry.name === '.worktrees' || entry.name === '.tmp' || entry.name === 'node_modules') continue;
|
|
230
238
|
if (rel === '.claude/worktrees' || rel.startsWith('.claude/worktrees/')) continue;
|
|
239
|
+
if (isPythonVirtualenvDir(abs)) continue;
|
|
231
240
|
visit(abs);
|
|
232
241
|
if (out.length >= maxFiles) return;
|
|
233
242
|
continue;
|
package/src/compact/engine.ts
CHANGED
|
@@ -71,7 +71,7 @@ export async function compactSearch(options: SearchOptions, surface: RetrievalSu
|
|
|
71
71
|
formatted: string;
|
|
72
72
|
totalTokens: number;
|
|
73
73
|
}> {
|
|
74
|
-
await ensureFreshIndex();
|
|
74
|
+
await ensureFreshIndex({ loadCorpus: false });
|
|
75
75
|
const searchOptions = { ...options, query: normalizeMemoryBrowseQuery(options.query) };
|
|
76
76
|
let entries = (await searchObservations(searchOptions)).map((entry) =>
|
|
77
77
|
entry.projectId || !options.projectId ? entry : { ...entry, projectId: options.projectId },
|
|
@@ -102,9 +102,19 @@ export async function compactSearch(options: SearchOptions, surface: RetrievalSu
|
|
|
102
102
|
let formatted = formatIndexTable(entries, searchOptions.query, !options.projectId, surface);
|
|
103
103
|
|
|
104
104
|
if (entries.length === 0 && options.projectId) {
|
|
105
|
-
const allObservations = filterReadableObservations(getAllObservations(), options.reader);
|
|
106
105
|
const projectAliases = new Set(await resolveAliases(options.projectId).catch(() => [options.projectId]));
|
|
107
|
-
|
|
106
|
+
let projectHasStoredMemory = false;
|
|
107
|
+
try {
|
|
108
|
+
const store = getObservationStore();
|
|
109
|
+
projectHasStoredMemory = (await Promise.all(
|
|
110
|
+
[...projectAliases].filter((projectId): projectId is string => Boolean(projectId)).map((projectId) => store.countByProject(projectId, { status: 'active' })),
|
|
111
|
+
)).some((count) => count > 0);
|
|
112
|
+
} catch {
|
|
113
|
+
const allObservations = filterReadableObservations(getAllObservations(), options.reader);
|
|
114
|
+
projectHasStoredMemory = allObservations.some((observation) =>
|
|
115
|
+
observation.projectId !== undefined && projectAliases.has(observation.projectId),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
108
118
|
if (!projectHasStoredMemory) {
|
|
109
119
|
formatted =
|
|
110
120
|
options.reader
|
|
@@ -115,7 +125,7 @@ export async function compactSearch(options: SearchOptions, surface: RetrievalSu
|
|
|
115
125
|
`Memories will start appearing after observations, session summaries, hook captures, or git-memory are written.`;
|
|
116
126
|
} else {
|
|
117
127
|
formatted =
|
|
118
|
-
`No memories found
|
|
128
|
+
`No memories found for the current query.\n\n` +
|
|
119
129
|
`This project does have stored Memorix memories, but none matched the current query/filter. ` +
|
|
120
130
|
`Try a more specific topic, use /memory status for runtime diagnostics, or ask for recent memory if you want a broad overview.`;
|
|
121
131
|
}
|
|
@@ -221,6 +221,7 @@ export async function previewConsolidate(context: DashboardMaintenanceContext) {
|
|
|
221
221
|
const clusters = await findConsolidationCandidates(
|
|
222
222
|
context.projectRoot ?? context.dataDir,
|
|
223
223
|
context.projectId,
|
|
224
|
+
{ store: context.store },
|
|
224
225
|
);
|
|
225
226
|
const payload: ConsolidateTokenPayload = { clusterIds: normalizeClusters(clusters.map((cluster) => cluster.ids)) };
|
|
226
227
|
return {
|
|
@@ -248,11 +249,19 @@ export async function executeConsolidate(
|
|
|
248
249
|
const clusterIds = record.clusterIds.map((ids, index) => payloadIds(ids, `clusterIds[${index}]`));
|
|
249
250
|
const normalized: ConsolidateTokenPayload = { clusterIds: normalizeClusters(clusterIds) };
|
|
250
251
|
verify('consolidate', context.projectId, normalized, token);
|
|
251
|
-
const current = await findConsolidationCandidates(
|
|
252
|
+
const current = await findConsolidationCandidates(
|
|
253
|
+
context.projectRoot ?? context.dataDir,
|
|
254
|
+
context.projectId,
|
|
255
|
+
{ store: context.store },
|
|
256
|
+
);
|
|
252
257
|
if (JSON.stringify(normalized.clusterIds) !== JSON.stringify(normalizeClusters(current.map((cluster) => cluster.ids)))) {
|
|
253
258
|
throw new DashboardMaintenanceError('Memory changed after the preview. Refresh before consolidating.', 409);
|
|
254
259
|
}
|
|
255
|
-
const result = await executeConsolidation(
|
|
260
|
+
const result = await executeConsolidation(
|
|
261
|
+
context.projectRoot ?? context.dataDir,
|
|
262
|
+
context.projectId,
|
|
263
|
+
{ store: context.store },
|
|
264
|
+
);
|
|
256
265
|
return { action: 'consolidate', projectId: context.projectId, ...result };
|
|
257
266
|
}
|
|
258
267
|
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { Observation } from '../types.js';
|
|
19
|
-
import { getObservationStore } from '../store/obs-store.js';
|
|
19
|
+
import { getObservationStore, type ObservationStore } from '../store/obs-store.js';
|
|
20
20
|
import { isEligibleForAutomaticDelivery } from './admission.js';
|
|
21
21
|
import { resolveObservationVisibility } from './visibility.js';
|
|
22
22
|
|
|
@@ -108,6 +108,14 @@ interface ConsolidationPage {
|
|
|
108
108
|
nextCursor?: number;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
export interface ConsolidationOptions {
|
|
112
|
+
threshold?: number;
|
|
113
|
+
limit?: number;
|
|
114
|
+
afterId?: number;
|
|
115
|
+
/** Use a request-scoped store when running inside a multi-project host. */
|
|
116
|
+
store?: ObservationStore;
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
function clampBatchSize(limit: number | undefined): number {
|
|
112
120
|
if (!Number.isFinite(limit)) return MAX_BATCH_SIZE;
|
|
113
121
|
return Math.min(MAX_BATCH_SIZE, Math.max(1, Math.floor(limit!)));
|
|
@@ -120,10 +128,11 @@ function clampCursor(afterId: number | undefined): number {
|
|
|
120
128
|
|
|
121
129
|
async function loadConsolidationPage(
|
|
122
130
|
projectId: string,
|
|
123
|
-
options:
|
|
131
|
+
options: ConsolidationOptions,
|
|
124
132
|
): Promise<ConsolidationPage> {
|
|
125
133
|
const limit = clampBatchSize(options.limit);
|
|
126
|
-
const
|
|
134
|
+
const store = options.store ?? getObservationStore();
|
|
135
|
+
const page = await store.loadByProject(projectId, {
|
|
127
136
|
status: 'active',
|
|
128
137
|
afterId: clampCursor(options.afterId),
|
|
129
138
|
limit: limit + 1,
|
|
@@ -205,7 +214,7 @@ function findClusters(observations: Observation[], threshold: number): Consolida
|
|
|
205
214
|
export async function findConsolidationCandidates(
|
|
206
215
|
_projectDir: string,
|
|
207
216
|
projectId: string,
|
|
208
|
-
opts?:
|
|
217
|
+
opts?: ConsolidationOptions,
|
|
209
218
|
): Promise<ConsolidationCluster[]> {
|
|
210
219
|
const threshold = opts?.threshold ?? DEFAULT_SIMILARITY_THRESHOLD;
|
|
211
220
|
const page = await loadConsolidationPage(projectId, opts ?? {});
|
|
@@ -224,9 +233,9 @@ export async function findConsolidationCandidates(
|
|
|
224
233
|
export async function executeConsolidation(
|
|
225
234
|
_projectDir: string,
|
|
226
235
|
projectId: string,
|
|
227
|
-
opts?:
|
|
236
|
+
opts?: ConsolidationOptions,
|
|
228
237
|
): Promise<ConsolidationResult> {
|
|
229
|
-
const store = getObservationStore();
|
|
238
|
+
const store = opts?.store ?? getObservationStore();
|
|
230
239
|
const page = await loadConsolidationPage(projectId, opts ?? {});
|
|
231
240
|
const clusters = findClusters(page.observations, opts?.threshold ?? DEFAULT_SIMILARITY_THRESHOLD);
|
|
232
241
|
|
package/src/memory/freshness.ts
CHANGED
|
@@ -90,9 +90,9 @@ export async function reindexMiniSkills(): Promise<number> {
|
|
|
90
90
|
* Ensure both observations and mini-skills are fresh in the Orama index.
|
|
91
91
|
* Returns true if any data source was refreshed.
|
|
92
92
|
*/
|
|
93
|
-
export async function ensureFreshIndex(): Promise<boolean> {
|
|
93
|
+
export async function ensureFreshIndex(options: { loadCorpus?: boolean } = {}): Promise<boolean> {
|
|
94
94
|
let anyStale = false;
|
|
95
|
-
const obsStale = await ensureFreshObservations();
|
|
95
|
+
const obsStale = await ensureFreshObservations(options);
|
|
96
96
|
if (obsStale) anyStale = true;
|
|
97
97
|
const skillsStale = await ensureFreshMiniSkills();
|
|
98
98
|
if (skillsStale) anyStale = true;
|
|
@@ -108,8 +108,11 @@ export async function ensureFreshIndex(): Promise<boolean> {
|
|
|
108
108
|
*
|
|
109
109
|
* Phase 3a: replaces withFreshObservations() at all retrieval call sites.
|
|
110
110
|
*/
|
|
111
|
-
export async function withFreshIndex<T>(
|
|
112
|
-
|
|
111
|
+
export async function withFreshIndex<T>(
|
|
112
|
+
fn: () => T | Promise<T>,
|
|
113
|
+
options: { loadCorpus?: boolean } = {},
|
|
114
|
+
): Promise<T> {
|
|
115
|
+
await ensureFreshIndex(options);
|
|
113
116
|
return fn();
|
|
114
117
|
}
|
|
115
118
|
|