@pcircle/memesh 4.0.1 → 4.0.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.
@@ -2,13 +2,23 @@
2
2
 
3
3
  import { createRequire } from 'module';
4
4
  import { homedir } from 'os';
5
- import { join, basename } from 'path';
6
- import { existsSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
7
- import { dirname } from 'path';
5
+ import { dirname, join, basename } from 'path';
6
+ import { existsSync, unlinkSync } from 'fs';
8
7
  import { fileURLToPath } from 'url';
8
+ import {
9
+ buildReferenceContext,
10
+ ensurePrivateDir,
11
+ getMemeshDir,
12
+ isTrustedForAutoContext,
13
+ writePrivateJson,
14
+ } from './_shared.js';
9
15
 
10
16
  const require = createRequire(import.meta.url);
11
17
 
18
+ const dbPath = process.env.MEMESH_DB_PATH || join(homedir(), '.memesh', 'knowledge-graph.db');
19
+ const memeshDir = getMemeshDir(process.env);
20
+ const throttlePath = join(memeshDir, 'session-recalled-files.json');
21
+
12
22
  let input = '';
13
23
  process.stdin.setEncoding('utf8');
14
24
  process.stdin.on('data', (chunk) => { input += chunk; });
@@ -19,7 +29,6 @@ process.stdin.on('end', async () => {
19
29
 
20
30
  // Clear pre-edit recall throttle from previous session
21
31
  try {
22
- const throttlePath = join(homedir(), '.memesh', 'session-recalled-files.json');
23
32
  if (existsSync(throttlePath)) {
24
33
  unlinkSync(throttlePath);
25
34
  }
@@ -27,8 +36,6 @@ process.stdin.on('end', async () => {
27
36
  // Non-critical
28
37
  }
29
38
 
30
- // Find database
31
- const dbPath = process.env.MEMESH_DB_PATH || join(homedir(), '.memesh', 'knowledge-graph.db');
32
39
  if (!existsSync(dbPath)) {
33
40
  output('MeMesh: No database found. Memories will be created as you work.');
34
41
  return;
@@ -85,14 +92,16 @@ process.stdin.on('end', async () => {
85
92
  // Query project-specific top-N entities by relevance score
86
93
  const projectTag = `project:${projectName}`;
87
94
  const projectEntities = db.prepare(`
88
- SELECT DISTINCT e.id, e.name, e.type, e.created_at
95
+ SELECT DISTINCT e.id, e.name, e.type, e.created_at, e.metadata
89
96
  FROM entities e
90
97
  JOIN tags t ON t.entity_id = e.id
91
98
  WHERE t.tag = ?
92
99
  ${statusFilter}
93
100
  ${scoringOrderBy}
94
101
  LIMIT ?
95
- `).all(projectTag, sessionLimit);
102
+ `).all(projectTag, sessionLimit * 3)
103
+ .filter(entity => isTrustedForAutoContext(entity.metadata))
104
+ .slice(0, sessionLimit);
96
105
 
97
106
  // Fetch the first observation for each entity (for concise summary)
98
107
  const getFirstObservation = db.prepare(
@@ -101,12 +110,14 @@ process.stdin.on('end', async () => {
101
110
 
102
111
  // Query global recent/top entities (exclude project-tagged ones for this project)
103
112
  const recentEntities = db.prepare(`
104
- SELECT id, name, type, created_at
113
+ SELECT id, name, type, created_at, metadata
105
114
  FROM entities
106
115
  ${recentStatusFilter}
107
116
  ${recentScoringOrderBy}
108
- LIMIT 5
109
- `).all();
117
+ LIMIT 15
118
+ `).all()
119
+ .filter(entity => isTrustedForAutoContext(entity.metadata))
120
+ .slice(0, 5);
110
121
 
111
122
  // Format entity as concise bullet: "• name (type): first observation (truncated)"
112
123
  function formatEntity(entity) {
@@ -144,7 +155,7 @@ process.stdin.on('end', async () => {
144
155
  // --- Proactive lesson warnings ---
145
156
  try {
146
157
  const lessonEntities = db.prepare(`
147
- SELECT DISTINCT e.id, e.name, e.confidence
158
+ SELECT DISTINCT e.id, e.name, e.confidence, e.metadata
148
159
  FROM entities e
149
160
  JOIN tags t ON t.entity_id = e.id
150
161
  WHERE e.type = 'lesson_learned'
@@ -152,8 +163,8 @@ process.stdin.on('end', async () => {
152
163
  AND t.tag = ?
153
164
  ORDER BY CASE WHEN e.confidence IS NULL THEN 0.5 ELSE e.confidence END DESC,
154
165
  CASE WHEN e.access_count IS NULL THEN 0 ELSE e.access_count END DESC
155
- LIMIT 5
156
- `).all(projectTag);
166
+ LIMIT 15
167
+ `).all(projectTag).filter(entity => isTrustedForAutoContext(entity.metadata));
157
168
 
158
169
  if (lessonEntities.length > 0) {
159
170
  memorySummary += '\n\n⚠️ Known lessons for this project:\n';
@@ -188,23 +199,21 @@ process.stdin.on('end', async () => {
188
199
  });
189
200
 
190
201
  if (allInjected.length > 0) {
191
- const memeshDir = join(homedir(), '.memesh');
192
202
  const sessionsDir = join(memeshDir, 'sessions');
193
- if (!existsSync(sessionsDir)) mkdirSync(sessionsDir, { recursive: true });
203
+ ensurePrivateDir(sessionsDir);
194
204
 
195
205
  // FIX: Use session-scoped file with unique ID (pid + timestamp)
196
206
  const sessionId = `${process.pid}-${Date.now()}`;
197
- writeFileSync(
207
+ writePrivateJson(
198
208
  join(sessionsDir, `${sessionId}.json`),
199
- JSON.stringify({
209
+ {
200
210
  injectedAt: new Date().toISOString(),
201
211
  project: projectName,
202
212
  entityIds: allInjected.map(e => e.id),
203
213
  entityNames: allInjected.map(e => e.name),
204
214
  // FIX: Save injected context text to exclude from hit detection
205
215
  injectedContext: memorySummary,
206
- }),
207
- 'utf8'
216
+ }
208
217
  );
209
218
 
210
219
  // Clean up old session files (>24h)
@@ -229,7 +238,7 @@ process.stdin.on('end', async () => {
229
238
  suppressOutput: true,
230
239
  hookSpecificOutput: {
231
240
  hookEventName: 'SessionStart',
232
- additionalContext: memorySummary,
241
+ additionalContext: buildReferenceContext(memorySummary.split('\n')),
233
242
  },
234
243
  };
235
244
  console.log(JSON.stringify(hookOutput));
@@ -7,8 +7,9 @@
7
7
  import { createRequire } from 'module';
8
8
  import { homedir } from 'os';
9
9
  import { join, basename, dirname } from 'path';
10
- import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
10
+ import { existsSync, mkdirSync, readFileSync } from 'fs';
11
11
  import { fileURLToPath } from 'url';
12
+ import { getMemeshDir } from './_shared.js';
12
13
 
13
14
  const require = createRequire(import.meta.url);
14
15
 
@@ -152,9 +153,7 @@ process.stdin.on('end', async () => {
152
153
 
153
154
  // Open DB
154
155
  const dbPath = process.env.MEMESH_DB_PATH || join(homedir(), '.memesh', 'knowledge-graph.db');
155
- const dbDir = process.env.MEMESH_DB_PATH
156
- ? join(process.env.MEMESH_DB_PATH, '..')
157
- : join(homedir(), '.memesh');
156
+ const dbDir = getMemeshDir(process.env);
158
157
  if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
159
158
 
160
159
  const Database = require('better-sqlite3');
@@ -244,7 +243,7 @@ process.stdin.on('end', async () => {
244
243
  // their names appear in the transcript, update hits/misses.
245
244
  try {
246
245
  // FIX: Find the most recent session file for this project (within last hour)
247
- const sessionsDir = join(homedir(), '.memesh', 'sessions');
246
+ const sessionsDir = join(getMemeshDir(process.env), 'sessions');
248
247
  let injectedData = null;
249
248
 
250
249
  if (existsSync(sessionsDir)) {
@@ -116,6 +116,14 @@ memesh status # version, search level, embeddings
116
116
  memesh config list # current configuration
117
117
  ```
118
118
 
119
+ ### Regenerate embeddings after provider change
120
+ ```bash
121
+ memesh reindex # rebuild all embeddings
122
+ memesh reindex --namespace personal # reindex only one namespace
123
+ memesh reindex --json # structured progress output
124
+ ```
125
+ Use this when you change embedding provider (e.g., Ollama → OpenAI) or dimension. The database auto-drops old embeddings on provider change, but you need to run `reindex` to regenerate them for existing memories.
126
+
119
127
  ## MCP-Only Features
120
128
 
121
129
  These require MCP tools or the HTTP API (`memesh serve` + REST calls):