@pcircle/memesh 4.1.7 → 4.2.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.de.md +48 -10
- package/README.es.md +82 -17
- package/README.fr.md +49 -11
- package/README.ja.md +83 -17
- package/README.ko.md +86 -20
- package/README.md +12 -7
- package/README.pt.md +48 -10
- package/README.th.md +46 -8
- package/README.vi.md +48 -10
- package/README.zh-CN.md +83 -17
- package/README.zh-TW.md +110 -44
- package/dashboard/dist/index.html +9 -9
- package/dist/cli/view-live.js +2 -2
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +15 -12
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +17 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +40 -0
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts +7 -2
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js +12 -4
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -11
- package/dist/core/config.js.map +1 -1
- package/dist/core/consolidator.d.ts.map +1 -1
- package/dist/core/consolidator.js +13 -4
- package/dist/core/consolidator.js.map +1 -1
- package/dist/core/digest-validator.d.ts +18 -0
- package/dist/core/digest-validator.d.ts.map +1 -0
- package/dist/core/digest-validator.js +79 -0
- package/dist/core/digest-validator.js.map +1 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +28 -11
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +8 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +68 -14
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +2 -2
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +2 -1
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts +6 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +10 -2
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +1 -7
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/install-id.d.ts.map +1 -1
- package/dist/core/install-id.js +2 -3
- package/dist/core/install-id.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +39 -0
- package/dist/core/kg-backfill.d.ts.map +1 -0
- package/dist/core/kg-backfill.js +362 -0
- package/dist/core/kg-backfill.js.map +1 -0
- package/dist/core/llm-client.d.ts +13 -0
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +63 -3
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +35 -0
- package/dist/core/llm-telemetry.d.ts.map +1 -0
- package/dist/core/llm-telemetry.js +96 -0
- package/dist/core/llm-telemetry.js.map +1 -0
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +3 -3
- package/dist/core/llm-validator.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +4 -35
- package/dist/core/operations.js.map +1 -1
- package/dist/core/paths.d.ts +6 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +27 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt-safety.d.ts.map +1 -1
- package/dist/core/prompt-safety.js.map +1 -1
- package/dist/core/scoring.d.ts +5 -0
- package/dist/core/scoring.d.ts.map +1 -1
- package/dist/core/scoring.js +8 -0
- package/dist/core/scoring.js.map +1 -1
- package/dist/core/serializer.js +1 -1
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/skill-usage-log.js +2 -2
- package/dist/core/skill-usage-log.js.map +1 -1
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/verifier.d.ts.map +1 -1
- package/dist/core/verifier.js +4 -4
- package/dist/core/verifier.js.map +1 -1
- package/dist/core/version-check.d.ts.map +1 -1
- package/dist/core/version-check.js +4 -3
- package/dist/core/version-check.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +71 -14
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +16 -16
- package/dist/storage/fts-index.js +1 -1
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/transports/cli/cli.js +128 -6
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +204 -25
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +1 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +1 -1
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/package.json +2 -2
- package/scripts/hooks/_shared.js +177 -14
- package/scripts/hooks/post-commit.js +50 -8
- package/scripts/hooks/pre-bash-orchestration-nudge.js +8 -3
- package/scripts/hooks/pre-compact.js +13 -16
- package/scripts/hooks/pre-edit-recall.js +28 -13
- package/scripts/hooks/session-start.js +194 -184
- package/scripts/hooks/session-summary.js +376 -41
- package/dist/core/query-expander.d.ts +0 -4
- package/dist/core/query-expander.d.ts.map +0 -1
- package/dist/core/query-expander.js +0 -53
- package/dist/core/query-expander.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pcircle/memesh",
|
|
3
|
-
"version": "4.1
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "MeMesh — Local memory for Claude Code and MCP coding agents. One SQLite file, zero cloud required.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"build": "tsc && node scripts/copy-cli-assets.mjs && node scripts/set-executable-bits.mjs && node scripts/generate-skills-manifest.mjs && npm run build:dashboard && node scripts/smoke-test.mjs",
|
|
25
|
+
"build": "node scripts/check-schema-drift.mjs && tsc && node scripts/copy-cli-assets.mjs && node scripts/set-executable-bits.mjs && node scripts/generate-skills-manifest.mjs && npm run build:dashboard && node scripts/smoke-test.mjs",
|
|
26
26
|
"build:dashboard": "node scripts/build-dashboard.mjs",
|
|
27
27
|
"test": "vitest",
|
|
28
28
|
"test:e2e-dashboard": "node scripts/dashboard-e2e-smoke.mjs",
|
package/scripts/hooks/_shared.js
CHANGED
|
@@ -2,11 +2,26 @@ import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync,
|
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { homedir } from 'os';
|
|
5
|
-
import { dirname, join } from 'path';
|
|
5
|
+
import { basename, dirname, join } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
9
|
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Path helpers — MIRROR of src/core/paths.ts
|
|
12
|
+
// =============================================================================
|
|
13
|
+
//
|
|
14
|
+
// Hooks cannot import from `dist/` (the F5 security boundary — `dist/` may
|
|
15
|
+
// be stale or absent at hook execution time), so the path-resolution logic
|
|
16
|
+
// is duplicated here. The contract MUST stay in lockstep with
|
|
17
|
+
// `src/core/paths.ts`. Any change to the function shapes / precedence
|
|
18
|
+
// rules in that file MUST be reflected here too.
|
|
19
|
+
//
|
|
20
|
+
// Unlike the schema duplication (which has a build-time diff guard via
|
|
21
|
+
// `scripts/check-schema-drift.mjs`), these helpers are short enough that
|
|
22
|
+
// human review at code-review time is sufficient. If they grow, add a
|
|
23
|
+
// programmatic guard.
|
|
24
|
+
|
|
10
25
|
/**
|
|
11
26
|
* Return the user's home directory, honoring HOME env var first.
|
|
12
27
|
*
|
|
@@ -17,10 +32,71 @@ const require = createRequire(import.meta.url);
|
|
|
17
32
|
* across platforms. Production users on Windows almost never set HOME,
|
|
18
33
|
* so this falls through to os.homedir() as before.
|
|
19
34
|
*
|
|
35
|
+
* Mirror of: src/core/paths.ts → homeDir()
|
|
36
|
+
*
|
|
20
37
|
* @returns {string}
|
|
21
38
|
*/
|
|
22
39
|
function homeDir() {
|
|
23
|
-
|
|
40
|
+
// Mirror src/core/paths.ts homeDir() — same three-step fallback to
|
|
41
|
+
// handle HOME="" environments. `os.homedir()` itself reads HOME on
|
|
42
|
+
// POSIX, so HOME="" makes it return "". `os.userInfo().homedir`
|
|
43
|
+
// reads pw_dir via getpwuid syscall, bypassing env vars entirely.
|
|
44
|
+
const home = process.env.HOME;
|
|
45
|
+
if (home && home.length > 0) return home;
|
|
46
|
+
const fromOs = homedir();
|
|
47
|
+
if (fromOs && fromOs.length > 0) return fromOs;
|
|
48
|
+
// userInfo is the final defence — re-import here to keep the
|
|
49
|
+
// top-of-file `import { homedir } from 'os'` line stable.
|
|
50
|
+
return require('os').userInfo().homedir;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the memesh data directory.
|
|
55
|
+
*
|
|
56
|
+
* Precedence: MEMESH_DIR env var > <home>/.memesh.
|
|
57
|
+
* Mirror of: src/core/paths.ts → memeshDir()
|
|
58
|
+
*
|
|
59
|
+
* No-arg to mirror the core helper exactly. Earlier drafts accepted a
|
|
60
|
+
* custom `env` parameter for symmetry with `getMemeshDirFromDbPath(env)`,
|
|
61
|
+
* but the inner `homeDir()` only ever read `process.env.HOME`, so a
|
|
62
|
+
* caller that passed `{HOME: '/tmp/x'}` would be silently ignored — a
|
|
63
|
+
* footgun. Tests redirect via `process.env.HOME`; that's the supported
|
|
64
|
+
* extension point.
|
|
65
|
+
*
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
export function memeshDir() {
|
|
69
|
+
return process.env.MEMESH_DIR ?? join(homeDir(), '.memesh');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the active memesh DB path.
|
|
74
|
+
*
|
|
75
|
+
* Precedence: MEMESH_DB_PATH env var > <memeshDir>/knowledge-graph.db.
|
|
76
|
+
* Mirror of: src/core/paths.ts → getDbPath() — no-arg, see memeshDir().
|
|
77
|
+
*
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export function getDbPath() {
|
|
81
|
+
return process.env.MEMESH_DB_PATH ?? join(memeshDir(), 'knowledge-graph.db');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Derive the project name from a working directory.
|
|
86
|
+
*
|
|
87
|
+
* Hooks historically used `basename(data.cwd || process.cwd())`. Core
|
|
88
|
+
* had two variants (`basename(context.cwd)` and `basename(process.cwd())`).
|
|
89
|
+
* This helper unifies the contract — explicit cwd wins, falls through to
|
|
90
|
+
* process.cwd() — matching the most permissive caller's behaviour.
|
|
91
|
+
*
|
|
92
|
+
* Mirror of: src/core/paths.ts → getProjectName()
|
|
93
|
+
*
|
|
94
|
+
* @param {string|null|undefined} [cwdInput]
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
export function getProjectName(cwdInput) {
|
|
98
|
+
const cwd = cwdInput && cwdInput.length > 0 ? cwdInput : process.cwd();
|
|
99
|
+
return basename(cwd);
|
|
24
100
|
}
|
|
25
101
|
|
|
26
102
|
/**
|
|
@@ -64,7 +140,7 @@ export function resolvePluginRoot(metaUrl) {
|
|
|
64
140
|
* @returns {Record<string, any>}
|
|
65
141
|
*/
|
|
66
142
|
export function readHookConfig(_env = process.env) {
|
|
67
|
-
const path = join(
|
|
143
|
+
const path = join(memeshDir(), 'config.json');
|
|
68
144
|
if (!existsSync(path)) return {};
|
|
69
145
|
try {
|
|
70
146
|
const raw = readFileSync(path, 'utf8');
|
|
@@ -210,6 +286,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_tags_entity_tag_unique ON tags(entity_id,
|
|
|
210
286
|
CREATE INDEX IF NOT EXISTS idx_observations_entity ON observations(entity_id);
|
|
211
287
|
CREATE INDEX IF NOT EXISTS idx_relations_from ON relations(from_entity_id);
|
|
212
288
|
CREATE INDEX IF NOT EXISTS idx_relations_to ON relations(to_entity_id);
|
|
289
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type_created ON entities(type, created_at);
|
|
213
290
|
`;
|
|
214
291
|
|
|
215
292
|
// FTS5 virtual table — separate so hooks that don't need it stay lean.
|
|
@@ -233,32 +310,118 @@ CREATE VIRTUAL TABLE IF NOT EXISTS entities_fts USING fts5(
|
|
|
233
310
|
* @param {boolean} [opts.fts=false] - Also create the FTS5 virtual table.
|
|
234
311
|
* @returns {{ db: any, dbPath: string }}
|
|
235
312
|
*/
|
|
313
|
+
// Cached lookup for the better-sqlite3 native module. Plugin-marketplace
|
|
314
|
+
// installs ship the tarball without node_modules, so the cache copy of the
|
|
315
|
+
// hook scripts cannot resolve the binding. tryRequireBetterSqlite() returns
|
|
316
|
+
// null in that scenario and lets each caller silent-skip — callers paired
|
|
317
|
+
// with a working dev/npm-global registration still produce output.
|
|
318
|
+
let _cachedDatabaseCtor;
|
|
319
|
+
export function tryRequireBetterSqlite() {
|
|
320
|
+
// Test-only seam: force the "native module unavailable" branch so
|
|
321
|
+
// tests can exercise the silent-skip path that plugin-marketplace
|
|
322
|
+
// cache installs hit. Production callers never set this var.
|
|
323
|
+
if (process.env.MEMESH_TEST_FORCE_MISSING_NATIVE === '1') return null;
|
|
324
|
+
if (_cachedDatabaseCtor !== undefined) return _cachedDatabaseCtor;
|
|
325
|
+
try {
|
|
326
|
+
_cachedDatabaseCtor = require('better-sqlite3');
|
|
327
|
+
} catch {
|
|
328
|
+
_cachedDatabaseCtor = null;
|
|
329
|
+
}
|
|
330
|
+
return _cachedDatabaseCtor;
|
|
331
|
+
}
|
|
332
|
+
|
|
236
333
|
export function openHookDb(env = process.env, opts = {}) {
|
|
237
|
-
const
|
|
238
|
-
|
|
334
|
+
const Database = tryRequireBetterSqlite();
|
|
335
|
+
if (!Database) return null;
|
|
336
|
+
|
|
337
|
+
// Path helpers read process.env directly (no-arg). The `env` parameter
|
|
338
|
+
// is kept on this signature for backward compatibility with callers
|
|
339
|
+
// and is consulted directly only for the DB-PATH dirname check below
|
|
340
|
+
// — that's the one place where a caller's custom env should win over
|
|
341
|
+
// process.env (e.g. the test harness redirects MEMESH_DB_PATH per test).
|
|
342
|
+
const dbPath = env.MEMESH_DB_PATH ?? getDbPath();
|
|
343
|
+
const dbDir = env.MEMESH_DB_PATH ? dirname(env.MEMESH_DB_PATH) : memeshDir();
|
|
239
344
|
if (!existsSync(dbDir)) mkdirSync(dbDir, { recursive: true });
|
|
240
345
|
|
|
241
|
-
const Database = require('better-sqlite3');
|
|
242
346
|
const db = new Database(dbPath);
|
|
243
347
|
db.pragma('journal_mode = WAL');
|
|
244
348
|
db.pragma('foreign_keys = ON');
|
|
245
349
|
db.exec(SCHEMA_SQL);
|
|
246
350
|
if (opts.fts) db.exec(FTS_SQL);
|
|
247
351
|
|
|
352
|
+
// Apply the full migration chain — keep in lockstep with src/db.ts.
|
|
353
|
+
// Conditional ALTER TABLE blocks ARE idempotent within a single process,
|
|
354
|
+
// but two hook processes can race: each reads `colNames` from its own
|
|
355
|
+
// PRAGMA snapshot, so both see "column missing" and both run ALTER —
|
|
356
|
+
// the second one throws SQLITE_ERROR: duplicate column name. Each ALTER
|
|
357
|
+
// is wrapped in safeAlter() which treats that specific error as the
|
|
358
|
+
// expected no-op outcome (a peer beat us to it). Any other error
|
|
359
|
+
// re-throws so we don't paper over real bugs.
|
|
360
|
+
//
|
|
361
|
+
// Earlier this helper applied ONLY the v2.11->v2.12 status migration.
|
|
362
|
+
// That left a hook-only-touched DB at v2.12 even though core was at
|
|
363
|
+
// v4.0+, so session-start fell back to `ORDER BY id DESC` (degraded
|
|
364
|
+
// ranking) until the CLI/MCP/HTTP first opened the DB and finished
|
|
365
|
+
// the chain. Backfilled here so write-path hooks produce the same
|
|
366
|
+
// schema state as core.
|
|
367
|
+
const safeAlter = (sql) => {
|
|
368
|
+
try {
|
|
369
|
+
db.exec(sql);
|
|
370
|
+
} catch (e) {
|
|
371
|
+
if (!/duplicate column name/i.test(e?.message || '')) throw e;
|
|
372
|
+
// Peer hook process won the race; column already exists. Idempotent.
|
|
373
|
+
}
|
|
374
|
+
};
|
|
248
375
|
const cols = db.prepare("PRAGMA table_info(entities)").all();
|
|
249
|
-
|
|
250
|
-
|
|
376
|
+
const colNames = new Set(cols.map((c) => c.name));
|
|
377
|
+
|
|
378
|
+
// v2.11 -> v2.12: status
|
|
379
|
+
if (!colNames.has('status')) {
|
|
380
|
+
safeAlter("ALTER TABLE entities ADD COLUMN status TEXT NOT NULL DEFAULT 'active'");
|
|
251
381
|
db.exec("CREATE INDEX IF NOT EXISTS idx_entities_status ON entities(status)");
|
|
252
382
|
}
|
|
253
383
|
|
|
384
|
+
// v2.14 -> v2.15: scoring + temporal-validity columns
|
|
385
|
+
if (!colNames.has('access_count')) {
|
|
386
|
+
safeAlter("ALTER TABLE entities ADD COLUMN access_count INTEGER DEFAULT 0");
|
|
387
|
+
safeAlter("ALTER TABLE entities ADD COLUMN last_accessed_at TIMESTAMP");
|
|
388
|
+
safeAlter("ALTER TABLE entities ADD COLUMN confidence REAL DEFAULT 1.0");
|
|
389
|
+
safeAlter("ALTER TABLE entities ADD COLUMN valid_from TIMESTAMP");
|
|
390
|
+
safeAlter("ALTER TABLE entities ADD COLUMN valid_until TIMESTAMP");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// v3.0.0-rc -> v3.0.0: namespace
|
|
394
|
+
if (!colNames.has('namespace')) {
|
|
395
|
+
safeAlter("ALTER TABLE entities ADD COLUMN namespace TEXT DEFAULT 'personal'");
|
|
396
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_entities_namespace ON entities(namespace)");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// v4.0.0: recall effectiveness counters
|
|
400
|
+
if (!colNames.has('recall_hits')) {
|
|
401
|
+
safeAlter("ALTER TABLE entities ADD COLUMN recall_hits INTEGER DEFAULT 0");
|
|
402
|
+
safeAlter("ALTER TABLE entities ADD COLUMN recall_misses INTEGER DEFAULT 0");
|
|
403
|
+
}
|
|
404
|
+
|
|
254
405
|
return { db, dbPath };
|
|
255
406
|
}
|
|
256
407
|
|
|
257
408
|
const PRIVATE_DIR_MODE = 0o700;
|
|
258
409
|
const PRIVATE_FILE_MODE = 0o600;
|
|
259
410
|
|
|
260
|
-
|
|
261
|
-
|
|
411
|
+
/**
|
|
412
|
+
* Resolve the directory containing the active DB file.
|
|
413
|
+
*
|
|
414
|
+
* When MEMESH_DB_PATH is set, returns its parent directory (used for
|
|
415
|
+
* sibling files next to the DB). Otherwise returns memeshDir().
|
|
416
|
+
* Mirror of: src/core/paths.ts → getMemeshDirFromDbPath()
|
|
417
|
+
*
|
|
418
|
+
* Renamed from the legacy `getMemeshDir` to match the core helper —
|
|
419
|
+
* sibling helper `memeshDir()` returns the GLOBAL data directory, so a
|
|
420
|
+
* second function called `getMemeshDir` was confusing. Callers updated
|
|
421
|
+
* in lockstep.
|
|
422
|
+
*/
|
|
423
|
+
export function getMemeshDirFromDbPath() {
|
|
424
|
+
return process.env.MEMESH_DB_PATH ? dirname(process.env.MEMESH_DB_PATH) : memeshDir();
|
|
262
425
|
}
|
|
263
426
|
|
|
264
427
|
export function ensurePrivateDir(dirPath) {
|
|
@@ -332,7 +495,7 @@ export function readUpdateCheckCache(installedVersion) {
|
|
|
332
495
|
&& /^[0-9A-Za-z.+-]+$/.test(installedVersion)
|
|
333
496
|
? installedVersion
|
|
334
497
|
: 'unknown';
|
|
335
|
-
const cachePath = join(
|
|
498
|
+
const cachePath = join(memeshDir(), `update-check.${versionTag}.json`);
|
|
336
499
|
try {
|
|
337
500
|
if (!existsSync(cachePath)) return null;
|
|
338
501
|
const parsed = JSON.parse(readFileSync(cachePath, 'utf8'));
|
|
@@ -391,7 +554,7 @@ export function decideAutoUpdateHook(currentVersion, cache, policy) {
|
|
|
391
554
|
|
|
392
555
|
export function logAutoUpdate(line) {
|
|
393
556
|
try {
|
|
394
|
-
const dir =
|
|
557
|
+
const dir = getMemeshDirFromDbPath();
|
|
395
558
|
ensurePrivateDir(dir);
|
|
396
559
|
const path = join(dir, 'auto-update.log');
|
|
397
560
|
appendFileSync(path, `[${new Date().toISOString()}] ${line}\n`);
|
|
@@ -405,7 +568,7 @@ export const AUTO_UPDATE_LOCK_TTL_MS = 10 * 60 * 1000;
|
|
|
405
568
|
|
|
406
569
|
export function tryAcquireAutoUpdateLock(version) {
|
|
407
570
|
try {
|
|
408
|
-
const dir =
|
|
571
|
+
const dir = memeshDir();
|
|
409
572
|
ensurePrivateDir(dir);
|
|
410
573
|
const lockPath = join(dir, 'auto-update.lock');
|
|
411
574
|
const fs = require('fs');
|
|
@@ -490,7 +653,7 @@ export function spawnAutoUpdate(version, deprecationOverride, installChannelMod)
|
|
|
490
653
|
);
|
|
491
654
|
return { state: 'in-progress' };
|
|
492
655
|
}
|
|
493
|
-
const dir =
|
|
656
|
+
const dir = getMemeshDirFromDbPath();
|
|
494
657
|
ensurePrivateDir(dir);
|
|
495
658
|
const logPath = join(dir, 'auto-update.log');
|
|
496
659
|
let fd = -1;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execFileSync } from 'child_process';
|
|
4
|
-
import {
|
|
5
|
-
import { openHookDb } from './_shared.js';
|
|
4
|
+
import { getProjectName, openHookDb } from './_shared.js';
|
|
6
5
|
|
|
7
6
|
let input = '';
|
|
8
7
|
process.stdin.setEncoding('utf8');
|
|
@@ -11,11 +10,37 @@ process.stdin.on('end', () => {
|
|
|
11
10
|
try {
|
|
12
11
|
const data = JSON.parse(input);
|
|
13
12
|
|
|
14
|
-
//
|
|
13
|
+
// tool_name absent is a schema-flip signal (Claude Code has done
|
|
14
|
+
// tool_name renames historically — e.g. tool_use/tool_result
|
|
15
|
+
// nesting). Trace to surface the rename day-1 instead of months
|
|
16
|
+
// of silent dropout (the bug shape that bit `tool_output` and
|
|
17
|
+
// `was_in_agentic_loop`).
|
|
18
|
+
if (data.tool_name === undefined) {
|
|
19
|
+
try { process.stderr.write(`[memesh post-commit] tool_name absent in payload (keys: ${Object.keys(data).join(',')}); skipping\n`); } catch {}
|
|
20
|
+
return exit0();
|
|
21
|
+
}
|
|
15
22
|
if (data.tool_name !== 'Bash') return exit0();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
|
|
24
|
+
// Claude Code's PostToolUse hook payload has had two field-name shapes:
|
|
25
|
+
// legacy `tool_output: <string>` and current
|
|
26
|
+
// `tool_response: { stdout, stderr, interrupted, isError }`. Prefer the
|
|
27
|
+
// current shape; fall back to legacy so unit-test fixtures (which use
|
|
28
|
+
// tool_output) keep working. This block existed only as the legacy
|
|
29
|
+
// branch — once Claude Code unified on tool_response the hook silently
|
|
30
|
+
// stopped seeing any output and never wrote commit entities again.
|
|
31
|
+
const tr = data.tool_response;
|
|
32
|
+
let toolOutput = '';
|
|
33
|
+
if (typeof tr === 'string') {
|
|
34
|
+
toolOutput = tr;
|
|
35
|
+
} else if (tr && typeof tr === 'object') {
|
|
36
|
+
const stdout = typeof tr.stdout === 'string' ? tr.stdout : '';
|
|
37
|
+
const stderr = typeof tr.stderr === 'string' ? tr.stderr : '';
|
|
38
|
+
toolOutput = stdout + (stderr ? '\n' + stderr : '');
|
|
39
|
+
} else if (typeof data.tool_output === 'string') {
|
|
40
|
+
toolOutput = data.tool_output;
|
|
41
|
+
} else if (data.tool_output != null) {
|
|
42
|
+
toolOutput = JSON.stringify(data.tool_output);
|
|
43
|
+
}
|
|
19
44
|
|
|
20
45
|
// Detect git commit in output
|
|
21
46
|
// Pattern: [branch hash] commit message
|
|
@@ -27,11 +52,28 @@ process.stdin.on('end', () => {
|
|
|
27
52
|
|
|
28
53
|
const commitHash = commitMatch[1];
|
|
29
54
|
const commitMsg = commitMatch[2];
|
|
30
|
-
|
|
55
|
+
|
|
56
|
+
// `data.cwd` MUST be present for project-tag and `git show` below
|
|
57
|
+
// to be correct. If absent, falling through to process.cwd() (the
|
|
58
|
+
// hook process's launch dir, unspecified for PostToolUse Bash)
|
|
59
|
+
// would either run git show against a different repo (silent
|
|
60
|
+
// corruption) or write the wrong project tag. Skip + trace
|
|
61
|
+
// instead — better to miss one commit than to tag it wrong.
|
|
62
|
+
if (!data.cwd) {
|
|
63
|
+
try { process.stderr.write(`[memesh post-commit] data.cwd absent — cannot resolve project / repo; skipping commit ${commitHash}\n`); } catch {}
|
|
64
|
+
return exit0();
|
|
65
|
+
}
|
|
66
|
+
const projectName = getProjectName(data.cwd);
|
|
31
67
|
|
|
32
68
|
// Open DB via shared helper — applies SCHEMA_SQL + status migration.
|
|
33
69
|
// Pass fts:true so the FTS5 entity-search index is also available.
|
|
34
|
-
|
|
70
|
+
// Returns null when the better-sqlite3 native module is unavailable
|
|
71
|
+
// (e.g. plugin-marketplace cache install with no node_modules); in
|
|
72
|
+
// that case silently skip — a sibling registered hook copy with
|
|
73
|
+
// proper deps still records the commit.
|
|
74
|
+
const handle = openHookDb(process.env, { fts: true });
|
|
75
|
+
if (!handle) return;
|
|
76
|
+
const { db } = handle;
|
|
35
77
|
try {
|
|
36
78
|
const entityName = `commit-${commitHash}`;
|
|
37
79
|
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
import { join } from 'path';
|
|
15
15
|
import { openSync, closeSync } from 'fs';
|
|
16
|
-
import { ensurePrivateDir,
|
|
16
|
+
import { ensurePrivateDir, getMemeshDirFromDbPath, isAgenticOrchestrationEnabled } from './_shared.js';
|
|
17
17
|
|
|
18
|
-
const memeshDir =
|
|
18
|
+
const memeshDir = getMemeshDirFromDbPath();
|
|
19
19
|
const FLAGS_DIR = join(memeshDir, 'agent-nudge-flags');
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
@@ -83,7 +83,12 @@ process.stdin.on('end', () => {
|
|
|
83
83
|
if (!isAgenticOrchestrationEnabled(process.env)) return pass();
|
|
84
84
|
|
|
85
85
|
const data = JSON.parse(input);
|
|
86
|
-
|
|
86
|
+
if (!data.tool_input) {
|
|
87
|
+
// Schema-flip signal: trace so a rename surfaces day-1.
|
|
88
|
+
try { process.stderr.write(`[memesh pre-bash-nudge] tool_input absent (keys: ${Object.keys(data).join(',')}); skipping\n`); } catch {}
|
|
89
|
+
return pass();
|
|
90
|
+
}
|
|
91
|
+
const command = (data.tool_input.command ?? '').trim();
|
|
87
92
|
|
|
88
93
|
if (!command) return pass();
|
|
89
94
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { basename } from 'path';
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
|
-
import { isAutoCaptureEnabled, openHookDb } from './_shared.js';
|
|
5
|
+
import { getProjectName, isAutoCaptureEnabled, openHookDb } from './_shared.js';
|
|
6
6
|
|
|
7
7
|
// Timeout guard: always exit within 10 seconds
|
|
8
8
|
const TIMEOUT_MS = 10000;
|
|
@@ -27,7 +27,7 @@ process.stdin.on('end', () => {
|
|
|
27
27
|
const transcriptPath = data.transcript_path || '';
|
|
28
28
|
const cwd = data.cwd || process.cwd();
|
|
29
29
|
const reason = data.reason || 'auto';
|
|
30
|
-
const projectName =
|
|
30
|
+
const projectName = getProjectName(cwd);
|
|
31
31
|
|
|
32
32
|
// Parse transcript to gather insights
|
|
33
33
|
let toolCallCount = 0;
|
|
@@ -40,7 +40,12 @@ process.stdin.on('end', () => {
|
|
|
40
40
|
if (!line.trim()) continue;
|
|
41
41
|
try {
|
|
42
42
|
const entry = JSON.parse(line);
|
|
43
|
-
//
|
|
43
|
+
// Claude Code transcript format: {type:'assistant', message:{content:[{type:'tool_use',...}]}}
|
|
44
|
+
// The earlier legacy branch (entry.role === 'assistant', entry.content
|
|
45
|
+
// at top level) was confirmed dead code via real-transcript audit:
|
|
46
|
+
// no entry in production transcripts ever shipped that shape.
|
|
47
|
+
// Removed to avoid confusion between two parsers that read
|
|
48
|
+
// different fields for the same logical event.
|
|
44
49
|
if (entry.type === 'assistant' && Array.isArray(entry.message?.content)) {
|
|
45
50
|
for (const block of entry.message.content) {
|
|
46
51
|
if (block.type !== 'tool_use') continue;
|
|
@@ -52,18 +57,6 @@ process.stdin.on('end', () => {
|
|
|
52
57
|
}
|
|
53
58
|
}
|
|
54
59
|
}
|
|
55
|
-
// Legacy format: {role:'assistant', content:[{type:'tool_use',...}]}
|
|
56
|
-
if (entry.role === 'assistant' && Array.isArray(entry.content)) {
|
|
57
|
-
for (const block of entry.content) {
|
|
58
|
-
if (block.type !== 'tool_use') continue;
|
|
59
|
-
toolCallCount++;
|
|
60
|
-
const name = block.name || '';
|
|
61
|
-
if (name === 'Edit' || name === 'Write' || name === 'MultiEdit') {
|
|
62
|
-
const filePath = block.input?.file_path || block.input?.path || '';
|
|
63
|
-
if (filePath) editedFiles.add(basename(filePath));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
60
|
} catch {
|
|
68
61
|
// Skip malformed lines
|
|
69
62
|
}
|
|
@@ -84,7 +77,11 @@ process.stdin.on('end', () => {
|
|
|
84
77
|
|
|
85
78
|
// Open DB via shared helper — applies SCHEMA_SQL + status migration.
|
|
86
79
|
// FTS5 needed for the entity-search index updates below.
|
|
87
|
-
|
|
80
|
+
// Returns null on plugin-marketplace cache installs without node_modules;
|
|
81
|
+
// silently skip in that case (sibling registered copy handles it).
|
|
82
|
+
const handle = openHookDb(process.env, { fts: true });
|
|
83
|
+
if (!handle) return;
|
|
84
|
+
const { db } = handle;
|
|
88
85
|
try {
|
|
89
86
|
// Upsert entity
|
|
90
87
|
const insertResult = db.prepare('INSERT OR IGNORE INTO entities (name, type) VALUES (?, ?)').run(entityName, 'session-summary');
|
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
// When editing a file, checks if MeMesh has relevant memories
|
|
5
5
|
// and injects them as context. Throttled: max 1 recall per file per session.
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import { homedir } from 'os';
|
|
9
|
-
import { join, basename } from 'path';
|
|
7
|
+
import { basename, join } from 'path';
|
|
10
8
|
import { existsSync, readFileSync } from 'fs';
|
|
11
9
|
import {
|
|
12
10
|
buildReferenceContext,
|
|
13
11
|
ensurePrivateDir,
|
|
14
|
-
|
|
12
|
+
getDbPath,
|
|
13
|
+
getMemeshDirFromDbPath,
|
|
14
|
+
getProjectName,
|
|
15
15
|
isTrustedForAutoContext,
|
|
16
|
+
tryRequireBetterSqlite,
|
|
16
17
|
writePrivateJson,
|
|
17
18
|
} from './_shared.js';
|
|
18
19
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
const dbPath = process.env.MEMESH_DB_PATH || join(homedir(), '.memesh', 'knowledge-graph.db');
|
|
22
|
-
const memeshDir = getMemeshDir(process.env);
|
|
20
|
+
const dbPath = getDbPath();
|
|
21
|
+
const memeshDir = getMemeshDirFromDbPath();
|
|
23
22
|
const THROTTLE_FILE = join(memeshDir, 'session-recalled-files.json');
|
|
24
23
|
const MAX_RESULTS = 3;
|
|
25
24
|
|
|
@@ -29,7 +28,13 @@ process.stdin.on('data', (chunk) => { input += chunk; });
|
|
|
29
28
|
process.stdin.on('end', () => {
|
|
30
29
|
try {
|
|
31
30
|
const data = JSON.parse(input);
|
|
32
|
-
|
|
31
|
+
if (!data.tool_input) {
|
|
32
|
+
// Schema-flip signal — Claude Code has renamed `tool_input` for
|
|
33
|
+
// transcript blocks before. Trace so the rename surfaces day-1.
|
|
34
|
+
try { process.stderr.write(`[memesh pre-edit-recall] tool_input absent (keys: ${Object.keys(data).join(',')}); skipping\n`); } catch {}
|
|
35
|
+
return pass();
|
|
36
|
+
}
|
|
37
|
+
const toolInput = data.tool_input;
|
|
33
38
|
const filePath = toolInput.file_path || toolInput.path || '';
|
|
34
39
|
|
|
35
40
|
// Only process if we have a file path
|
|
@@ -38,7 +43,7 @@ process.stdin.on('end', () => {
|
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
// Get project name from cwd for project-scoped filtering
|
|
41
|
-
const projectName =
|
|
46
|
+
const projectName = getProjectName(data.cwd);
|
|
42
47
|
|
|
43
48
|
// Throttle: skip if we already recalled for this file
|
|
44
49
|
const fileKey = filePath.toLowerCase();
|
|
@@ -58,7 +63,12 @@ process.stdin.on('end', () => {
|
|
|
58
63
|
|
|
59
64
|
if (!existsSync(dbPath)) return pass();
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
// tryRequireBetterSqlite() returns null on plugin-marketplace cache
|
|
67
|
+
// installs that ship without node_modules; pass-through silently in
|
|
68
|
+
// that case so a sibling registered hook copy can still inject
|
|
69
|
+
// recall context.
|
|
70
|
+
const Database = tryRequireBetterSqlite();
|
|
71
|
+
if (!Database) return pass();
|
|
62
72
|
const db = new Database(dbPath, { readonly: true });
|
|
63
73
|
try {
|
|
64
74
|
|
|
@@ -155,8 +165,13 @@ process.stdin.on('end', () => {
|
|
|
155
165
|
} finally {
|
|
156
166
|
db.close();
|
|
157
167
|
}
|
|
158
|
-
} catch {
|
|
159
|
-
// Never crash Claude Code
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// Never crash Claude Code, but trace — peer hooks (post-commit,
|
|
170
|
+
// pre-compact, session-summary) all stderr-trace their outer
|
|
171
|
+
// catches. Without a trace here, a typo in any prepare statement
|
|
172
|
+
// would silently break continuous recall on every Edit/Write
|
|
173
|
+
// tool call indefinitely.
|
|
174
|
+
try { process.stderr.write(`[memesh pre-edit-recall] ${err?.message || err}\n`); } catch {}
|
|
160
175
|
pass();
|
|
161
176
|
}
|
|
162
177
|
});
|