@pcircle/memesh 4.5.0 → 4.5.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/.mcp.json +1 -1
- package/README.de.md +2 -5
- package/README.es.md +5 -8
- package/README.fr.md +3 -6
- package/README.ja.md +5 -8
- package/README.ko.md +5 -8
- package/README.md +28 -10
- package/README.pt.md +3 -6
- package/README.th.md +3 -6
- package/README.vi.md +3 -6
- package/README.zh-CN.md +5 -8
- package/README.zh-TW.md +5 -8
- package/dashboard/dist/index.html +9 -9
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +2 -2
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +3 -3
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/config.d.ts +0 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +2 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/demo.d.ts +2 -2
- package/dist/core/demo.d.ts.map +1 -1
- package/dist/core/demo.js.map +1 -1
- package/dist/core/doctor.d.ts +1 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +191 -47
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +15 -7
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +310 -35
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +2 -1
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +22 -1
- 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/graph.d.ts +4 -4
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js.map +1 -1
- package/dist/core/install-hooks.d.ts +1 -0
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +0 -0
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +4 -4
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +1 -1
- package/dist/core/kg-backfill.js.map +1 -1
- package/dist/core/lesson-engine.d.ts +1 -0
- package/dist/core/lesson-engine.d.ts.map +1 -1
- package/dist/core/lesson-engine.js +1 -0
- package/dist/core/lesson-engine.js.map +1 -1
- package/dist/core/lifecycle.d.ts +4 -4
- package/dist/core/lifecycle.d.ts.map +1 -1
- package/dist/core/lifecycle.js +1 -1
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +3 -6
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +4 -4
- package/dist/core/llm-telemetry.d.ts.map +1 -1
- package/dist/core/llm-telemetry.js +1 -1
- package/dist/core/llm-telemetry.js.map +1 -1
- package/dist/core/memory-tool.d.ts.map +1 -1
- package/dist/core/memory-tool.js +4 -0
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +22 -7
- package/dist/core/operations.js.map +1 -1
- package/dist/core/paths.d.ts +3 -0
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +67 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/patterns.d.ts +2 -2
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/project-tags.d.ts +3 -3
- package/dist/core/project-tags.d.ts.map +1 -1
- package/dist/core/project-tags.js.map +1 -1
- package/dist/core/projects.d.ts +2 -2
- package/dist/core/projects.d.ts.map +1 -1
- package/dist/core/projects.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +0 -34
- package/dist/core/schema-export.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +45 -4
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/stats.d.ts +2 -2
- package/dist/core/stats.d.ts.map +1 -1
- package/dist/core/stats.js.map +1 -1
- package/dist/core/transcript-extractor.d.ts +5 -6
- package/dist/core/transcript-extractor.d.ts.map +1 -1
- package/dist/core/transcript-extractor.js +4 -24
- package/dist/core/transcript-extractor.js.map +1 -1
- package/dist/core/transcript-source.d.ts.map +1 -1
- package/dist/core/transcript-source.js.map +1 -1
- package/dist/core/types.d.ts +17 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +155 -17
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +3 -2
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +19 -10
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/mcp/server.js +2 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills-manifest.json +28 -33
- package/dist/storage/conflicts.d.ts +3 -3
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/fts-index.d.ts +4 -4
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +64 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/storage/vector-index.d.ts +3 -0
- package/dist/storage/vector-index.d.ts.map +1 -0
- package/dist/storage/vector-index.js +7 -0
- package/dist/storage/vector-index.js.map +1 -0
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +126 -84
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/retired-routes.d.ts.map +1 -1
- package/dist/transports/http/retired-routes.js +1 -0
- package/dist/transports/http/retired-routes.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +48 -29
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +4 -96
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +24 -58
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +10 -30
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +5 -29
- package/dist/transports/schemas.js.map +1 -1
- package/hooks/hooks.json +0 -10
- package/package.json +6 -10
- package/scripts/hooks/_generated/core-paths.js +67 -1
- package/scripts/hooks/_generated/sqlite.js +71 -0
- package/scripts/hooks/_shared.js +286 -195
- package/scripts/hooks/post-commit.js +52 -10
- package/scripts/hooks/pre-compact.js +10 -7
- package/scripts/hooks/pre-edit-recall.js +4 -8
- package/scripts/hooks/session-start.js +95 -47
- package/scripts/hooks/session-summary.js +120 -52
- package/scripts/hooks/user-prompt-intent.js +3 -2
- package/skills/memesh/SKILL.md +1 -2
- package/dist/core/skill-usage-log.d.ts +0 -11
- package/dist/core/skill-usage-log.d.ts.map +0 -1
- package/dist/core/skill-usage-log.js +0 -125
- package/dist/core/skill-usage-log.js.map +0 -1
- package/dist/core/verifier.d.ts +0 -40
- package/dist/core/verifier.d.ts.map +0 -1
- package/dist/core/verifier.js +0 -206
- package/dist/core/verifier.js.map +0 -1
- package/dist/mcp/launcher.d.ts +0 -3
- package/dist/mcp/launcher.d.ts.map +0 -1
- package/dist/mcp/launcher.js +0 -37
- package/dist/mcp/launcher.js.map +0 -1
- package/scripts/hooks/pre-bash-orchestration-nudge.js +0 -155
- package/scripts/postinstall-rebuild.mjs +0 -41
- package/skills/agentic-orchestration/SKILL.md +0 -399
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { execFileSync } from 'child_process';
|
|
4
|
-
import { captureEntity, getProjectName, openHookDb } from './_shared.js';
|
|
4
|
+
import { AUTO_CAPTURE_TAG, captureEntity, getProjectName, isAutoCaptureEnabled, openHookDb, recordHookRun } from './_shared.js';
|
|
5
5
|
|
|
6
6
|
let input = '';
|
|
7
7
|
process.stdin.setEncoding('utf8');
|
|
8
8
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
9
9
|
process.stdin.on('end', () => {
|
|
10
10
|
try {
|
|
11
|
+
// Opt-out check (env > config > default-on). This hook skipped it for
|
|
12
|
+
// years while its two siblings honoured it — with capture disabled it
|
|
13
|
+
// kept writing commit entities AND stamping the heartbeat, which made
|
|
14
|
+
// doctor's "capture is off, hook silence is expected" message false.
|
|
15
|
+
if (!isAutoCaptureEnabled(process.env)) return exit0();
|
|
16
|
+
|
|
11
17
|
const data = JSON.parse(input);
|
|
12
18
|
|
|
13
19
|
// tool_name absent is a schema-flip signal (Claude Code has done
|
|
@@ -50,6 +56,20 @@ process.stdin.on('end', () => {
|
|
|
50
56
|
const commitMatch = toolOutput.match(/\[[\w/.-]+(?: \([\w -]+\))? ([a-f0-9]{7,})\] (.+)/);
|
|
51
57
|
if (!commitMatch) return exit0();
|
|
52
58
|
|
|
59
|
+
// The OUTPUT looking like a commit is not evidence that a commit happened.
|
|
60
|
+
// This hook stopped at the regex above, so any Bash output containing a
|
|
61
|
+
// commit-shaped line produced a permanent memory. Measured: a payload whose
|
|
62
|
+
// command was `cat docs/release-notes.md` wrote entity `commit-9f3c2a1`
|
|
63
|
+
// for a hash `git cat-file -t` rejects as "Not a valid object name".
|
|
64
|
+
// Reading a changelog, tailing a build log, or quoting a commit line was
|
|
65
|
+
// enough — and the fake then surfaced through session-start and
|
|
66
|
+
// pre-edit-recall as if it had happened.
|
|
67
|
+
const issuedCommand = typeof data.tool_input?.command === 'string' ? data.tool_input.command : '';
|
|
68
|
+
if (!/\bgit\b[^|;&]*\bcommit\b/.test(issuedCommand)) {
|
|
69
|
+
try { process.stderr.write(`[memesh post-commit] output looks like a commit but the command was not a git commit; skipping ${commitMatch[1]}\n`); } catch {}
|
|
70
|
+
return exit0();
|
|
71
|
+
}
|
|
72
|
+
|
|
53
73
|
const branchMatch = commitMatch[0].match(/^\[([^\s]+)\s/);
|
|
54
74
|
const branch = branchMatch ? branchMatch[1] : 'unknown';
|
|
55
75
|
|
|
@@ -66,17 +86,28 @@ process.stdin.on('end', () => {
|
|
|
66
86
|
try { process.stderr.write(`[memesh post-commit] data.cwd absent — cannot resolve project / repo; skipping commit ${commitHash}\n`); } catch {}
|
|
67
87
|
return exit0();
|
|
68
88
|
}
|
|
89
|
+
// And the commit has to actually be in THIS repository.
|
|
90
|
+
//
|
|
91
|
+
// `cat-file -e <hash>^{commit}` answers exactly one question — does this
|
|
92
|
+
// resolve to a commit object here — in milliseconds, before anything is
|
|
93
|
+
// written. Deliberately separate from the `git show` below, whose failure
|
|
94
|
+
// (git absent, timeout on a huge diff) says nothing about the commit and
|
|
95
|
+
// must NOT veto.
|
|
96
|
+
try {
|
|
97
|
+
execFileSync('git', ['-C', data.cwd, 'cat-file', '-e', `${commitHash}^{commit}`], {
|
|
98
|
+
timeout: 5000,
|
|
99
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
100
|
+
});
|
|
101
|
+
} catch {
|
|
102
|
+
try { process.stderr.write(`[memesh post-commit] ${commitHash} is not a commit in ${data.cwd}; nothing written\n`); } catch {}
|
|
103
|
+
return exit0();
|
|
104
|
+
}
|
|
105
|
+
|
|
69
106
|
const projectName = getProjectName(data.cwd);
|
|
70
107
|
|
|
71
108
|
// Open DB via shared helper — applies SCHEMA_SQL + status migration.
|
|
72
109
|
// Pass fts:true so the FTS5 entity-search index is also available.
|
|
73
|
-
|
|
74
|
-
// (e.g. plugin-marketplace cache install with no node_modules); in
|
|
75
|
-
// that case silently skip — a sibling registered hook copy with
|
|
76
|
-
// proper deps still records the commit.
|
|
77
|
-
const handle = openHookDb(process.env, { fts: true });
|
|
78
|
-
if (!handle) return;
|
|
79
|
-
const { db } = handle;
|
|
110
|
+
const { db } = openHookDb(process.env, { fts: true });
|
|
80
111
|
try {
|
|
81
112
|
const entityName = `commit-${commitHash}`;
|
|
82
113
|
|
|
@@ -103,12 +134,23 @@ process.stdin.on('end', () => {
|
|
|
103
134
|
}
|
|
104
135
|
|
|
105
136
|
// Shared write dance — upsert entity + observations + tags AND reindex FTS.
|
|
106
|
-
|
|
137
|
+
//
|
|
138
|
+
// `source:auto-capture` is the provenance marker every capture hook
|
|
139
|
+
// writes (session-summary, pre-compact and the extractor already did;
|
|
140
|
+
// this one did not). `memesh doctor` counts it to answer "is the
|
|
141
|
+
// auto-capture loop alive" — a question it used to answer from entity
|
|
142
|
+
// TYPE, which a hand-typed `memesh learn` satisfied all by itself.
|
|
143
|
+
const written = captureEntity(db, {
|
|
107
144
|
name: entityName,
|
|
108
145
|
type: 'commit',
|
|
109
146
|
observations,
|
|
110
|
-
tags: [`project:${projectName}`],
|
|
147
|
+
tags: [AUTO_CAPTURE_TAG, `project:${projectName}`],
|
|
111
148
|
});
|
|
149
|
+
|
|
150
|
+
// Heartbeat AFTER capture, so the stamp certifies "the capture loop
|
|
151
|
+
// completed", not "a database handle existed". A throw above skips it,
|
|
152
|
+
// and so does a null return (the write did not land).
|
|
153
|
+
if (written) recordHookRun(db, 'post-commit');
|
|
112
154
|
} finally {
|
|
113
155
|
db.close();
|
|
114
156
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { basename } from 'path';
|
|
4
4
|
import { existsSync, readFileSync } from 'fs';
|
|
5
|
-
import { captureEntity, getProjectName, isAutoCaptureEnabled, openHookDb } from './_shared.js';
|
|
5
|
+
import { AUTO_CAPTURE_TAG, captureEntity, getProjectName, isAutoCaptureEnabled, openHookDb, recordHookRun } from './_shared.js';
|
|
6
6
|
|
|
7
7
|
// Timeout guard: always exit within 10 seconds
|
|
8
8
|
const TIMEOUT_MS = 10000;
|
|
@@ -101,11 +101,7 @@ process.stdin.on('end', () => {
|
|
|
101
101
|
|
|
102
102
|
// Open DB via shared helper — applies SCHEMA_SQL + status migration.
|
|
103
103
|
// FTS5 needed for the entity-search index updates below.
|
|
104
|
-
|
|
105
|
-
// silently skip in that case (sibling registered copy handles it).
|
|
106
|
-
const handle = openHookDb(process.env, { fts: true });
|
|
107
|
-
if (!handle) return;
|
|
108
|
-
const { db } = handle;
|
|
104
|
+
const { db } = openHookDb(process.env, { fts: true });
|
|
109
105
|
let written = null;
|
|
110
106
|
try {
|
|
111
107
|
// Shared write dance — upsert entity + observations + tags AND reindex FTS
|
|
@@ -114,8 +110,15 @@ process.stdin.on('end', () => {
|
|
|
114
110
|
name: entityName,
|
|
115
111
|
type: 'session-summary',
|
|
116
112
|
observations: obsLines,
|
|
117
|
-
tags: [
|
|
113
|
+
tags: [AUTO_CAPTURE_TAG, 'urgency:pre-compact', `project:${projectName}`],
|
|
118
114
|
});
|
|
115
|
+
|
|
116
|
+
// Heartbeat AFTER capture, so the stamp certifies "the capture loop
|
|
117
|
+
// completed", not "a database handle existed". A throw above skips it,
|
|
118
|
+
// and so does a null return — captureEntity's null means the write did
|
|
119
|
+
// not land, and this very hook tells the user "could not save" below;
|
|
120
|
+
// stamping would say "alive" to doctor about the same failed run.
|
|
121
|
+
if (written) recordHookRun(db, 'pre-compact');
|
|
119
122
|
} finally {
|
|
120
123
|
db.close();
|
|
121
124
|
}
|
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
getMemeshDirFromDbPath,
|
|
14
14
|
getProjectName,
|
|
15
15
|
isTrustedForAutoContext,
|
|
16
|
-
tryRequireBetterSqlite,
|
|
17
16
|
writePrivateJson,
|
|
18
17
|
hookMatchExpression,
|
|
19
18
|
} from './_shared.js';
|
|
19
|
+
import { MemeshDatabase } from './_generated/sqlite.js';
|
|
20
20
|
|
|
21
21
|
const dbPath = getDbPath();
|
|
22
22
|
const memeshDir = getMemeshDirFromDbPath();
|
|
@@ -64,13 +64,9 @@ process.stdin.on('end', () => {
|
|
|
64
64
|
|
|
65
65
|
if (!existsSync(dbPath)) return pass();
|
|
66
66
|
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
// recall context.
|
|
71
|
-
const Database = tryRequireBetterSqlite();
|
|
72
|
-
if (!Database) return pass();
|
|
73
|
-
const db = new Database(dbPath, { readonly: true });
|
|
67
|
+
// `readOnly`, not `readonly`: node:sqlite ignores the lowercase spelling
|
|
68
|
+
// and hands back a WRITABLE handle. This hook only reads.
|
|
69
|
+
const db = new MemeshDatabase(dbPath, { readOnly: true });
|
|
74
70
|
try {
|
|
75
71
|
|
|
76
72
|
// Check if entities table exists
|
|
@@ -5,7 +5,7 @@ import { spawn } from 'child_process';
|
|
|
5
5
|
import { homedir } from 'os';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { pathToFileURL } from 'url';
|
|
8
|
-
import { existsSync, readFileSync, unlinkSync,
|
|
8
|
+
import { existsSync, readFileSync, unlinkSync, mkdirSync, accessSync, constants as fsConstants } from 'fs';
|
|
9
9
|
import {
|
|
10
10
|
buildReferenceContext,
|
|
11
11
|
ensurePrivateDir,
|
|
@@ -13,14 +13,13 @@ import {
|
|
|
13
13
|
getMemeshDirFromDbPath,
|
|
14
14
|
getProjectName,
|
|
15
15
|
importFromPluginRoot,
|
|
16
|
-
isAgenticOrchestrationEnabled,
|
|
17
16
|
isTrustedForAutoContext,
|
|
18
17
|
readUpdateCheckCache,
|
|
19
18
|
resolvePluginRoot,
|
|
20
19
|
resolveSessionLimit,
|
|
21
|
-
tryRequireBetterSqlite,
|
|
22
20
|
writePrivateJson,
|
|
23
21
|
} from './_shared.js';
|
|
22
|
+
import { MemeshDatabase } from './_generated/sqlite.js';
|
|
24
23
|
|
|
25
24
|
const require = createRequire(import.meta.url);
|
|
26
25
|
|
|
@@ -44,7 +43,6 @@ try {
|
|
|
44
43
|
const dbPath = getDbPath();
|
|
45
44
|
const memeshDir = getMemeshDirFromDbPath();
|
|
46
45
|
const throttlePath = join(memeshDir, 'session-recalled-files.json');
|
|
47
|
-
const nudgeFlagsDir = join(memeshDir, 'agent-nudge-flags');
|
|
48
46
|
|
|
49
47
|
/**
|
|
50
48
|
* Build the strong deprecation warning lines to prepend to the
|
|
@@ -430,6 +428,48 @@ function runPostBannerUpdateTasks() {
|
|
|
430
428
|
}
|
|
431
429
|
}
|
|
432
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Can the capture hooks actually write? Returns the offending path, or null.
|
|
433
|
+
*
|
|
434
|
+
* Probes the two things a capture hook needs and nothing else: the memesh
|
|
435
|
+
* directory has to exist and be writable, and where the database file already
|
|
436
|
+
* exists, that file has to be writable too. The second half matters on its
|
|
437
|
+
* own — a writable directory holding a read-only database is a state the old
|
|
438
|
+
* mkdir-only probe called healthy, and it is exactly what a botched `sudo`
|
|
439
|
+
* leaves behind.
|
|
440
|
+
*
|
|
441
|
+
* `accessSync(W_OK)` rather than opening a handle: this is the SessionStart
|
|
442
|
+
* hot path, and a read-write open would run the whole migration chain here
|
|
443
|
+
* just to answer a permissions question. It is one syscall and it fails in
|
|
444
|
+
* the same cases EACCES would.
|
|
445
|
+
*
|
|
446
|
+
* Deliberately not detected: a full disk. No cheap probe finds it, and
|
|
447
|
+
* claiming otherwise would be worse than the honest gap.
|
|
448
|
+
*/
|
|
449
|
+
function captureTargetUnwritable() {
|
|
450
|
+
try {
|
|
451
|
+
mkdirSync(memeshDir, { recursive: true });
|
|
452
|
+
accessSync(memeshDir, fsConstants.W_OK);
|
|
453
|
+
} catch {
|
|
454
|
+
return memeshDir;
|
|
455
|
+
}
|
|
456
|
+
// The WAL/SHM sidecars are probed too: an interrupted `sudo` run leaves a
|
|
457
|
+
// user-owned database next to root-owned `-wal`/`-shm` files, and SQLite
|
|
458
|
+
// then fails every write with EACCES while the db file itself probes
|
|
459
|
+
// writable — the most common botched-sudo residue, and exactly the state
|
|
460
|
+
// the db-file probe alone called healthy.
|
|
461
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
462
|
+
const p = `${dbPath}${suffix}`;
|
|
463
|
+
if (!existsSync(p)) continue;
|
|
464
|
+
try {
|
|
465
|
+
accessSync(p, fsConstants.W_OK);
|
|
466
|
+
} catch {
|
|
467
|
+
return p;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
433
473
|
/**
|
|
434
474
|
* Build a "base message + optional deprecation banner" combined
|
|
435
475
|
* single-line systemMessage payload. Keeps stdout a single JSON
|
|
@@ -466,6 +506,14 @@ let input = '';
|
|
|
466
506
|
process.stdin.setEncoding('utf8');
|
|
467
507
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
468
508
|
process.stdin.on('end', async () => {
|
|
509
|
+
// Hoisted above both try blocks: the recall-failure catch below must be
|
|
510
|
+
// able to lead with this warning too, or a capture-dead session that ALSO
|
|
511
|
+
// hits a recall error silently drops the more important half of the story.
|
|
512
|
+
let captureWarning = null;
|
|
513
|
+
const withCaptureWarning = (msg) => {
|
|
514
|
+
if (!captureWarning) return msg;
|
|
515
|
+
return `${captureWarning}\n${msg.replace(/^◉ MeMesh ready · /, '◉ MeMesh · ')}`;
|
|
516
|
+
};
|
|
469
517
|
try {
|
|
470
518
|
try {
|
|
471
519
|
const data = JSON.parse(input);
|
|
@@ -479,38 +527,57 @@ process.stdin.on('end', async () => {
|
|
|
479
527
|
} catch {
|
|
480
528
|
// Non-critical
|
|
481
529
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
530
|
+
|
|
531
|
+
// Every banner below this line is a PROMISE that memories will be saved,
|
|
532
|
+
// so check that it can be kept before making any of them.
|
|
533
|
+
//
|
|
534
|
+
// This probe used to live INSIDE the `!existsSync(dbPath)` branch below,
|
|
535
|
+
// which meant it only ever ran before the database existed — while the
|
|
536
|
+
// failure it detects has nothing to do with first runs. A `~/.memesh` that
|
|
537
|
+
// became unwritable later (permissions changed, a read-only mount, a
|
|
538
|
+
// directory that changed owner) produced the cheerful green count banner
|
|
539
|
+
// on every session, forever, while every capture hook failed with EACCES.
|
|
540
|
+
// Fixing the first-run case and leaving the steady-state case is how a
|
|
541
|
+
// detector ends up covering the one day the bug is least likely to happen.
|
|
542
|
+
// The warning does NOT return: this hook's other job is recall, which
|
|
543
|
+
// opens the database read-only and works fine on an unwritable target.
|
|
544
|
+
// Returning here turned "capture is off" into "your memory is gone" —
|
|
545
|
+
// every existing memory silently withheld exactly when the user needs
|
|
546
|
+
// the context to notice something is wrong. Warn, then keep reading.
|
|
547
|
+
const unwritable = captureTargetUnwritable();
|
|
548
|
+
// "ready" is a promise about capture — when the warning is present,
|
|
549
|
+
// withCaptureWarning (hoisted above) demotes it instead of contradicting
|
|
550
|
+
// it one line later.
|
|
551
|
+
captureWarning = unwritable
|
|
552
|
+
? `◉ MeMesh cannot write to ${unwritable} — memories will NOT be saved this session (recall still works). Run 'memesh doctor'.`
|
|
553
|
+
: null;
|
|
489
554
|
|
|
490
555
|
if (!existsSync(dbPath)) {
|
|
491
556
|
// Combine deprecation banner (if any) into the same
|
|
492
557
|
// systemMessage so stdout stays a single JSON document. Outer
|
|
493
558
|
// finally runs runPostBannerUpdateTasks().
|
|
494
|
-
|
|
559
|
+
// With no database there is nothing to recall either — the warning IS
|
|
560
|
+
// the whole truth, and "memories will be created as you work" would
|
|
561
|
+
// contradict it one line later.
|
|
562
|
+
output(combineWithBanner(captureWarning ?? '◉ MeMesh ready · no database yet, memories will be created as you work'));
|
|
495
563
|
return;
|
|
496
564
|
}
|
|
497
565
|
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
566
|
+
// `readOnly`, not `readonly`: node:sqlite ignores the lowercase spelling
|
|
567
|
+
// and hands back a WRITABLE handle. This hook only reads.
|
|
568
|
+
//
|
|
569
|
+
// No `journal_mode = WAL` here. Setting it is a write, so a read-only
|
|
570
|
+
// connection refuses it — and it was never doing anything: the mode is a
|
|
571
|
+
// property of the database file that the writing side already set, and a
|
|
572
|
+
// reader opens a WAL database perfectly well without asking for it.
|
|
573
|
+
const db = new MemeshDatabase(dbPath, { readOnly: true });
|
|
505
574
|
try {
|
|
506
|
-
db.pragma('journal_mode = WAL');
|
|
507
|
-
|
|
508
575
|
// Check if tables exist (db may exist but be empty)
|
|
509
576
|
const tableCheck = db.prepare(
|
|
510
577
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='entities'"
|
|
511
578
|
).get();
|
|
512
579
|
if (!tableCheck) {
|
|
513
|
-
output(combineWithBanner('◉ MeMesh ready · database initialised but no memories stored yet'));
|
|
580
|
+
output(combineWithBanner(captureWarning ?? '◉ MeMesh ready · database initialised but no memories stored yet'));
|
|
514
581
|
return;
|
|
515
582
|
}
|
|
516
583
|
|
|
@@ -545,11 +612,10 @@ process.stdin.on('end', async () => {
|
|
|
545
612
|
// Functions:
|
|
546
613
|
// - frequency: log(c+1) / log(max(maxAccess,1) + 1) (matches frequencyScore)
|
|
547
614
|
// - recency: exp(-(now - lastAccessed_days) / 30) (matches recencyScore)
|
|
548
|
-
// SQLite >= 3.35 with -DSQLITE_ENABLE_MATH_FUNCTIONS provides exp/log
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
// rather than throwing.
|
|
615
|
+
// SQLite >= 3.35 with -DSQLITE_ENABLE_MATH_FUNCTIONS provides exp/log,
|
|
616
|
+
// which Node's bundled SQLite has. We probe once per process and fall
|
|
617
|
+
// back to the legacy linear/rational forms if a stripped-down build is
|
|
618
|
+
// detected, so ranking degrades gracefully rather than throwing.
|
|
553
619
|
// Test-only seam: force the legacy linear/rational fallback so the
|
|
554
620
|
// pre-math-functions code path is reachable in CI on builds where
|
|
555
621
|
// exp/log ARE available. Production callers never set this.
|
|
@@ -842,24 +908,6 @@ process.stdin.on('end', async () => {
|
|
|
842
908
|
try { process.stderr.write(`[memesh session-start] sessions-write: ${err?.message || err}\n`); } catch {}
|
|
843
909
|
}
|
|
844
910
|
|
|
845
|
-
// --- Agentic-orchestration mode (opt-in) ---
|
|
846
|
-
// Banner kept short. Telemetry write preserved for protocol validation.
|
|
847
|
-
if (isAgenticOrchestrationEnabled(process.env)) {
|
|
848
|
-
summary += '\n[AO opt-in: dispatch verifiable work as background agent · skill: agentic-orchestration]';
|
|
849
|
-
try {
|
|
850
|
-
const usagePath = join(homedir(), '.memesh', 'skill-usage.jsonl');
|
|
851
|
-
// Only { ts, event } — an earlier `payload: { cwd_hashed }` was never
|
|
852
|
-
// read by summariseSkillUsage (counts by event name only), so it was
|
|
853
|
-
// write-only privacy-adjacent data. Removed.
|
|
854
|
-
const line = JSON.stringify({
|
|
855
|
-
ts: new Date().toISOString(),
|
|
856
|
-
event: 'agentic_orchestration_banner_injected',
|
|
857
|
-
}) + '\n';
|
|
858
|
-
appendFileSync(usagePath, line);
|
|
859
|
-
try { chmodSync(usagePath, 0o600); } catch { /* non-POSIX */ }
|
|
860
|
-
} catch { /* swallow — telemetry must not break session-start */ }
|
|
861
|
-
}
|
|
862
|
-
|
|
863
911
|
// Deprecation banner (security advisory) — surfaced even with the
|
|
864
912
|
// short summary so flagged installs still warn on every session.
|
|
865
913
|
let installedVersion = null;
|
|
@@ -889,7 +937,7 @@ process.stdin.on('end', async () => {
|
|
|
889
937
|
? [...bannerLines.filter(l => l.length > 0), '', summary].join('\n')
|
|
890
938
|
: summary;
|
|
891
939
|
|
|
892
|
-
output(finalMessage, memoryContext);
|
|
940
|
+
output(withCaptureWarning(finalMessage), memoryContext);
|
|
893
941
|
} finally {
|
|
894
942
|
db.close();
|
|
895
943
|
}
|
|
@@ -921,7 +969,7 @@ process.stdin.on('end', async () => {
|
|
|
921
969
|
// Hooks must never crash Claude Code — but report honestly.
|
|
922
970
|
// Inner catch so the outer finally can still run the post-
|
|
923
971
|
// banner update tasks even when the recall flow blew up.
|
|
924
|
-
console.log(JSON.stringify({ systemMessage: `MeMesh: memories not loaded this session (${err?.message || 'unknown error'}) — everything else works; run \`memesh doctor\` if this repeats.` }));
|
|
972
|
+
console.log(JSON.stringify({ systemMessage: withCaptureWarning(`MeMesh: memories not loaded this session (${err?.message || 'unknown error'}) — everything else works; run \`memesh doctor\` if this repeats.`) }));
|
|
925
973
|
}
|
|
926
974
|
} finally {
|
|
927
975
|
// ── Auto-update + cache refresh ──────────────────────────────
|