@pcircle/memesh 4.1.2 → 4.1.4
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/.mcp.json +1 -3
- package/README.de.md +12 -1
- package/README.es.md +14 -2
- package/README.fr.md +14 -2
- package/README.ja.md +12 -1
- package/README.ko.md +12 -1
- package/README.md +17 -2
- package/README.pt.md +14 -2
- package/README.th.md +14 -2
- package/README.vi.md +14 -2
- package/README.zh-CN.md +12 -1
- package/README.zh-TW.md +12 -1
- package/dashboard/dist/index.html +13 -4
- package/dist/cli/view-live.d.ts +2 -0
- package/dist/cli/view-live.d.ts.map +1 -0
- package/dist/cli/view-live.js +2177 -0
- package/dist/cli/view-live.js.map +1 -0
- package/dist/cli/view.d.ts +0 -1
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +0 -2169
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +23 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +79 -0
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/config.d.ts +9 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +16 -9
- package/dist/core/config.js.map +1 -1
- package/dist/core/consolidator.d.ts.map +1 -1
- package/dist/core/consolidator.js +1 -0
- package/dist/core/consolidator.js.map +1 -1
- package/dist/core/demo.d.ts +9 -0
- package/dist/core/demo.d.ts.map +1 -0
- package/dist/core/demo.js +79 -0
- package/dist/core/demo.js.map +1 -0
- package/dist/core/doctor.d.ts +2 -1
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +198 -8
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +65 -0
- package/dist/core/dreamer.d.ts.map +1 -0
- package/dist/core/dreamer.js +477 -0
- package/dist/core/dreamer.js.map +1 -0
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +9 -5
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +44 -22
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/graph.d.ts +1 -0
- package/dist/core/graph.d.ts.map +1 -1
- package/dist/core/graph.js +8 -2
- package/dist/core/graph.js.map +1 -1
- package/dist/core/install-hooks.d.ts +41 -0
- package/dist/core/install-hooks.d.ts.map +1 -0
- package/dist/core/install-hooks.js +181 -0
- package/dist/core/install-hooks.js.map +1 -0
- package/dist/core/install-id.d.ts +9 -0
- package/dist/core/install-id.d.ts.map +1 -0
- package/dist/core/install-id.js +51 -0
- package/dist/core/install-id.js.map +1 -0
- package/dist/core/lesson-engine.d.ts +1 -5
- package/dist/core/lesson-engine.d.ts.map +1 -1
- package/dist/core/lesson-engine.js +13 -13
- package/dist/core/lesson-engine.js.map +1 -1
- package/dist/core/llm-validator.d.ts +16 -0
- package/dist/core/llm-validator.d.ts.map +1 -0
- package/dist/core/llm-validator.js +182 -0
- package/dist/core/llm-validator.js.map +1 -0
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +6 -2
- package/dist/core/operations.js.map +1 -1
- package/dist/core/projects.d.ts +14 -0
- package/dist/core/projects.d.ts.map +1 -0
- package/dist/core/projects.js +70 -0
- package/dist/core/projects.js.map +1 -0
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/scoring.d.ts +0 -4
- package/dist/core/scoring.d.ts.map +1 -1
- package/dist/core/scoring.js +3 -10
- package/dist/core/scoring.js.map +1 -1
- package/dist/core/serializer.d.ts.map +1 -1
- package/dist/core/serializer.js +2 -0
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/signal-scorer.d.ts +9 -0
- package/dist/core/signal-scorer.d.ts.map +1 -0
- package/dist/core/signal-scorer.js +70 -0
- package/dist/core/signal-scorer.js.map +1 -0
- package/dist/core/types.d.ts +2 -30
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/updater.d.ts +16 -0
- package/dist/core/updater.d.ts.map +1 -1
- package/dist/core/updater.js +85 -0
- package/dist/core/updater.js.map +1 -1
- package/dist/core/version-check.d.ts +2 -1
- package/dist/core/version-check.d.ts.map +1 -1
- package/dist/core/version-check.js +154 -30
- package/dist/core/version-check.js.map +1 -1
- package/dist/db.d.ts +7 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +91 -0
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +7 -3
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +43 -18
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +23 -18
- package/dist/storage/conflicts.d.ts +4 -1
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +5 -2
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/transports/cli/cli.js +393 -37
- 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 +167 -13
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/schemas.d.ts +2 -2
- package/hooks/hooks.json +12 -0
- package/package.json +16 -6
- package/plugin.json +1 -1
- package/scripts/hooks/_shared.js +257 -5
- package/scripts/hooks/pre-compact.js +19 -9
- package/scripts/hooks/session-start.js +364 -15
- package/scripts/hooks/session-summary.js +131 -29
- package/scripts/hooks/user-prompt-intent.js +159 -0
- package/skills/memesh/SKILL.md +9 -5
- package/skills/memesh-review/SKILL.md +24 -0
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
4
5
|
import { createHash } from 'crypto';
|
|
5
6
|
import { homedir } from 'os';
|
|
6
7
|
import { join, basename } from 'path';
|
|
7
|
-
import {
|
|
8
|
+
import { pathToFileURL } from 'url';
|
|
9
|
+
import { existsSync, readFileSync, unlinkSync, rmSync, appendFileSync, chmodSync } from 'fs';
|
|
8
10
|
import {
|
|
9
11
|
buildReferenceContext,
|
|
10
12
|
ensurePrivateDir,
|
|
11
13
|
getMemeshDir,
|
|
12
14
|
isAgenticOrchestrationEnabled,
|
|
13
15
|
isTrustedForAutoContext,
|
|
16
|
+
readUpdateCheckCache,
|
|
14
17
|
resolvePluginRoot,
|
|
15
18
|
resolveSessionLimit,
|
|
16
19
|
writePrivateJson,
|
|
@@ -18,16 +21,326 @@ import {
|
|
|
18
21
|
|
|
19
22
|
const require = createRequire(import.meta.url);
|
|
20
23
|
|
|
24
|
+
// Codex round 37: dist/core/install-channel.js is emitted as ESM
|
|
25
|
+
// (the project's tsconfig produces NodeNext modules). On Node 20.x
|
|
26
|
+
// `require()` against an ESM file throws ERR_REQUIRE_ESM, which
|
|
27
|
+
// silently downgraded all install-channel detection to 'unknown' on
|
|
28
|
+
// the supported floor. Pre-load the module via dynamic `import()`
|
|
29
|
+
// at hook startup using a top-level await — once at process init,
|
|
30
|
+
// not on every call. Falls back to null if the dist file is
|
|
31
|
+
// missing (source checkout pre-build) or fails to load.
|
|
32
|
+
let _installChannelMod = null;
|
|
33
|
+
try {
|
|
34
|
+
const _pluginRootForInit = resolvePluginRoot(import.meta.url);
|
|
35
|
+
const _modPath = join(_pluginRootForInit, 'dist/core/install-channel.js');
|
|
36
|
+
if (existsSync(_modPath)) {
|
|
37
|
+
_installChannelMod = await import(pathToFileURL(_modPath).href);
|
|
38
|
+
}
|
|
39
|
+
} catch { /* best-effort — fall through to 'unknown' channel */ }
|
|
40
|
+
|
|
21
41
|
const dbPath = process.env.MEMESH_DB_PATH || join(homedir(), '.memesh', 'knowledge-graph.db');
|
|
22
42
|
const memeshDir = getMemeshDir(process.env);
|
|
23
43
|
const throttlePath = join(memeshDir, 'session-recalled-files.json');
|
|
24
44
|
const nudgeFlagsDir = join(memeshDir, 'agent-nudge-flags');
|
|
25
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Build the strong deprecation warning lines to prepend to the
|
|
48
|
+
* session-start banner when the installed version has been flagged
|
|
49
|
+
* by maintainers (typically a security advisory). Returns an empty
|
|
50
|
+
* array when the cache says nothing to warn about.
|
|
51
|
+
*/
|
|
52
|
+
function buildDeprecationBanner(currentVersion, cache) {
|
|
53
|
+
if (!cache || cache.currentVersion !== currentVersion) return [];
|
|
54
|
+
const msg = cache.currentVersionDeprecation;
|
|
55
|
+
if (typeof msg !== 'string' || msg.length === 0) {
|
|
56
|
+
// Partial-failure state ONLY: the version lookup answered but
|
|
57
|
+
// the deprecation sub-call did not. checkSucceeded stays true
|
|
58
|
+
// exactly in that case. A full registry failure (offline,
|
|
59
|
+
// blocked) leaves checkSucceeded=false with a generic lastError,
|
|
60
|
+
// and we must not surface that as a security-style warning —
|
|
61
|
+
// it's just regular "couldn't reach npm". Gate the banner
|
|
62
|
+
// strictly on checkSucceeded=true + lastError populated.
|
|
63
|
+
if (
|
|
64
|
+
cache.checkSucceeded === true
|
|
65
|
+
&& typeof cache.lastError === 'string'
|
|
66
|
+
&& cache.lastError.length > 0
|
|
67
|
+
) {
|
|
68
|
+
return [
|
|
69
|
+
'',
|
|
70
|
+
`ℹ️ MeMesh deprecation status unknown for ${currentVersion}: ${cache.lastError}`,
|
|
71
|
+
` Run: memesh status (retry the lookup once back online)`,
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const lines = [
|
|
77
|
+
'',
|
|
78
|
+
`⚠️ MeMesh ${currentVersion} is DEPRECATED by maintainers.`,
|
|
79
|
+
` ${msg}`,
|
|
80
|
+
];
|
|
81
|
+
// Codex round 36: emit a remediation line for EVERY deprecation
|
|
82
|
+
// banner — including the cases where the cached `latestVersion`
|
|
83
|
+
// is null, equal to current, or stale. The previous gate omitted
|
|
84
|
+
// the action line whenever the cache didn't yet show a strictly-
|
|
85
|
+
// newer version, leaving users with a security warning and no
|
|
86
|
+
// follow-up step. doctor / CLI status / dashboard already point
|
|
87
|
+
// at `memesh update` (or channel equivalents) in those uncertain
|
|
88
|
+
// cases, and the session-start banner should match — `npm`
|
|
89
|
+
// resolves @latest at install time, so the command works even
|
|
90
|
+
// when our local cache is uncertain.
|
|
91
|
+
const knownUpgradeTarget = Boolean(
|
|
92
|
+
cache.latestVersion && cache.latestVersion !== currentVersion,
|
|
93
|
+
);
|
|
94
|
+
// Codex round 39: the SessionStart hook reads ONLY cached cache
|
|
95
|
+
// data — there's no fresh lookup happening on this code path.
|
|
96
|
+
// That means `freshness === 'fresh'` (the strict rule the
|
|
97
|
+
// dashboard / `memesh status` use to authoritatively say
|
|
98
|
+
// "no upgrade target yet") can never apply here. Round 38 used a
|
|
99
|
+
// 24h-window heuristic to fire the no-target message anyway, but
|
|
100
|
+
// codex correctly flagged that as suppressing the upgrade hint
|
|
101
|
+
// exactly when a security-advisory fix could ship within the
|
|
102
|
+
// window. Conservative remediation: always recommend
|
|
103
|
+
// `memesh update` (which is a harmless no-op when there's truly
|
|
104
|
+
// no target, and immediately applies a freshly-published fix
|
|
105
|
+
// when there is one). The "no target yet" message remains
|
|
106
|
+
// available in `memesh status` (fresh lookup) and the dashboard
|
|
107
|
+
// (after a Check now click).
|
|
108
|
+
// Tailor the remediation hint to the install channel. `memesh
|
|
109
|
+
// update` and `autoUpdate` only work for npm-global installs;
|
|
110
|
+
// pointing source-checkout / project-local users at those
|
|
111
|
+
// commands is misleading (especially when the deprecation is a
|
|
112
|
+
// security advisory). Detect the channel and suggest the
|
|
113
|
+
// remediation that actually applies.
|
|
114
|
+
let channel = 'unknown';
|
|
115
|
+
try {
|
|
116
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
117
|
+
channel = detectInstallChannelHook(pluginRoot);
|
|
118
|
+
} catch { /* best-effort — fall through to generic guidance */ }
|
|
119
|
+
|
|
120
|
+
if (channel === 'npm-global') {
|
|
121
|
+
lines.push(
|
|
122
|
+
knownUpgradeTarget
|
|
123
|
+
? ` Run: memesh update (or set autoUpdate: memesh config set autoUpdate patch)`
|
|
124
|
+
: ` Run: memesh update (resolves @latest — or set: memesh config set autoUpdate patch)`,
|
|
125
|
+
);
|
|
126
|
+
} else if (channel === 'source-checkout') {
|
|
127
|
+
lines.push(` Source checkout: pull and rebuild (\`git pull && npm install && npm run build\`).`);
|
|
128
|
+
} else if (channel === 'npm-local') {
|
|
129
|
+
// Codex round 30: the cached `latestVersion` may itself be
|
|
130
|
+
// stale (cache TTL is 24h and we're already showing a stale
|
|
131
|
+
// banner). Pinning a specific version risks installing an
|
|
132
|
+
// already-superseded build that's part of the same security
|
|
133
|
+
// advisory. `@latest` always resolves to the registry's
|
|
134
|
+
// current dist-tag at install time, which is the right
|
|
135
|
+
// remediation for a deprecation/security-advisory banner.
|
|
136
|
+
lines.push(
|
|
137
|
+
knownUpgradeTarget
|
|
138
|
+
? ` Project-local install: run \`npm install @pcircle/memesh@latest\` in this project (cached upgrade target was ${cache.latestVersion}).`
|
|
139
|
+
: ` Project-local install: run \`npm install @pcircle/memesh@latest\` in this project.`,
|
|
140
|
+
);
|
|
141
|
+
} else {
|
|
142
|
+
lines.push(
|
|
143
|
+
knownUpgradeTarget
|
|
144
|
+
? ` Upgrade via the install path you used: fetch the latest @pcircle/memesh from npm (cached upgrade target was ${cache.latestVersion}).`
|
|
145
|
+
: ` Upgrade via the install path you used: fetch the latest @pcircle/memesh from npm.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return lines;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Detect the install channel of the running memesh binary by
|
|
153
|
+
* delegating to src/core/install-channel.ts via the dist build. The
|
|
154
|
+
* core helper resolves `npm root -g` so it correctly classifies:
|
|
155
|
+
* - POSIX globals at the default prefix (`/usr/local/lib/...`)
|
|
156
|
+
* - Windows globals (`%AppData%\npm\...`)
|
|
157
|
+
* - Globals at custom prefixes set via `npm config set prefix`
|
|
158
|
+
* - Project-local deps under any directory name (no false-positive
|
|
159
|
+
* 'npm-global' from a path that merely contains `lib`)
|
|
160
|
+
*
|
|
161
|
+
* Earlier hook-side regex heuristics agreed with the core logic on
|
|
162
|
+
* the common cases but disagreed on custom prefixes (false negative
|
|
163
|
+
* → auto-update silently broken) and on project-local deps living
|
|
164
|
+
* under `lib/node_modules/...` (false positive → spawning a global
|
|
165
|
+
* `npm install -g` while the active copy is the local one). Using
|
|
166
|
+
* the dist module here keeps the hook in lockstep with whatever
|
|
167
|
+
* `memesh status` says, paying the one-time `npm root -g` cost
|
|
168
|
+
* (~50-200ms) only on auto-update decision.
|
|
169
|
+
*
|
|
170
|
+
* Returns 'npm-global' | 'npm-local' | 'source-checkout' | 'unknown'.
|
|
171
|
+
* Synchronous + best-effort: any failure in the dist import or the
|
|
172
|
+
* underlying `npm root -g` call returns 'unknown', and the auto-
|
|
173
|
+
* update spawn refuses to fire on 'unknown' so we never run
|
|
174
|
+
* `npm install -g` when we can't confirm it would land where the
|
|
175
|
+
* user expects.
|
|
176
|
+
*/
|
|
177
|
+
function detectInstallChannelHook(pluginRoot) {
|
|
178
|
+
if (!_installChannelMod) return 'unknown';
|
|
179
|
+
try {
|
|
180
|
+
return _installChannelMod.getCurrentInstallChannel({ packageRoot: pluginRoot });
|
|
181
|
+
} catch {
|
|
182
|
+
return 'unknown';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Don't fire a fresh-check more often than this. Two parallel
|
|
187
|
+
// session-starts both spawning `memesh status` could otherwise race
|
|
188
|
+
// the cache: a later writer that hits a deprecation-only timeout
|
|
189
|
+
// would overwrite an earlier writer's successful deprecation flag,
|
|
190
|
+
// because each child reads `previous` from the cache *before* its
|
|
191
|
+
// own npm call. The TTL bounds concurrency to one refresh per
|
|
192
|
+
// window per machine, which is enough for the staleness window
|
|
193
|
+
// (24h) to stay accurate.
|
|
194
|
+
const FRESH_CHECK_THROTTLE_MS = 5 * 60 * 1000;
|
|
195
|
+
|
|
196
|
+
function spawnFreshUpdateCheck(installedVersion) {
|
|
197
|
+
try {
|
|
198
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
199
|
+
const cliPath = join(pluginRoot, 'dist/transports/cli/cli.js');
|
|
200
|
+
if (!existsSync(cliPath)) return false;
|
|
201
|
+
const fs = require('fs');
|
|
202
|
+
const dir = join(homedir(), '.memesh');
|
|
203
|
+
try { ensurePrivateDir(dir); } catch { /* best-effort */ }
|
|
204
|
+
// Codex round 37: scope the throttle marker to the installed
|
|
205
|
+
// version. The marker was machine-global, so a refresh started
|
|
206
|
+
// by a global 4.1.3 install would suppress refreshes for a
|
|
207
|
+
// sibling project-local 4.1.1 for the next 5 minutes — and the
|
|
208
|
+
// shared cache it wrote would carry version 4.1.3, so the
|
|
209
|
+
// 4.1.1 session would skip its banner because
|
|
210
|
+
// `cache.currentVersion !== currentVersion`. Per-version
|
|
211
|
+
// markers ensure each install gets its own refresh window.
|
|
212
|
+
// Sanitize version for filesystem (semver chars only, no path
|
|
213
|
+
// separators); fall back to 'unknown' if missing.
|
|
214
|
+
const versionTag = typeof installedVersion === 'string'
|
|
215
|
+
&& /^[0-9A-Za-z.+-]+$/.test(installedVersion)
|
|
216
|
+
? installedVersion
|
|
217
|
+
: 'unknown';
|
|
218
|
+
const markerPath = join(dir, `last-fresh-refresh.${versionTag}.lock`);
|
|
219
|
+
// Single-owner claim: O_EXCL atomic create. Codex round 27
|
|
220
|
+
// caught that the previous temp+rename+readback pattern was
|
|
221
|
+
// racy — both peers' renames are destructive, so each could
|
|
222
|
+
// read its own token back and both would spawn a refresh.
|
|
223
|
+
// O_EXCL is the standard POSIX/libuv primitive that lets at
|
|
224
|
+
// most one process succeed. Same pattern as
|
|
225
|
+
// tryAcquireAutoUpdateLock above.
|
|
226
|
+
const token = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
227
|
+
const claim = () => {
|
|
228
|
+
try {
|
|
229
|
+
const fd = fs.openSync(
|
|
230
|
+
markerPath,
|
|
231
|
+
fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL,
|
|
232
|
+
0o600,
|
|
233
|
+
);
|
|
234
|
+
try { fs.writeFileSync(fd, token); } finally { fs.closeSync(fd); }
|
|
235
|
+
return 'won';
|
|
236
|
+
} catch (err) {
|
|
237
|
+
if (err?.code === 'EEXIST') return 'exists';
|
|
238
|
+
return 'error';
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
let result = claim();
|
|
242
|
+
if (result === 'exists') {
|
|
243
|
+
// Marker already there. Honor the throttle window: if it's
|
|
244
|
+
// fresh, a peer owns this slot. If it's stale, take it over
|
|
245
|
+
// by removing the marker and retrying ONCE — best-effort,
|
|
246
|
+
// multiple processes may race the unlink but only one can
|
|
247
|
+
// win the subsequent O_EXCL.
|
|
248
|
+
let stat;
|
|
249
|
+
try { stat = fs.statSync(markerPath); } catch { return false; }
|
|
250
|
+
if (Date.now() - stat.mtimeMs < FRESH_CHECK_THROTTLE_MS) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
try { fs.unlinkSync(markerPath); } catch { /* peer already removed */ }
|
|
254
|
+
result = claim();
|
|
255
|
+
}
|
|
256
|
+
if (result !== 'won') return false;
|
|
257
|
+
const child = spawn(
|
|
258
|
+
process.execPath,
|
|
259
|
+
[cliPath, 'status'],
|
|
260
|
+
// windowsHide prevents a console-window flash on every session
|
|
261
|
+
// start on Windows; harmless on POSIX.
|
|
262
|
+
{
|
|
263
|
+
detached: true,
|
|
264
|
+
stdio: 'ignore',
|
|
265
|
+
env: { ...process.env, MEMESH_UPDATE_REFRESH: '1' },
|
|
266
|
+
windowsHide: true,
|
|
267
|
+
},
|
|
268
|
+
);
|
|
269
|
+
child.unref();
|
|
270
|
+
return true;
|
|
271
|
+
} catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Run the post-banner update tasks: spawn auto-update if policy + cache
|
|
278
|
+
* permit, and always refresh the cache for the next session.
|
|
279
|
+
*
|
|
280
|
+
* Known one-session delay (documented):
|
|
281
|
+
* The very first session after install (or after the cache file is
|
|
282
|
+
* deleted) emits the recall summary BEFORE this finally clause
|
|
283
|
+
* runs, so a freshly-installed deprecated version sees no
|
|
284
|
+
* deprecation banner on session 1. The detached refresh below
|
|
285
|
+
* populates the cache for session 2, where the banner and the
|
|
286
|
+
* security override fire normally. We deliberately don't do an
|
|
287
|
+
* inline synchronous npm fetch here because that would block every
|
|
288
|
+
* cold-cache session-start by ~3s for users whose installed version
|
|
289
|
+
* is healthy — a worse trade for the common case.
|
|
290
|
+
*
|
|
291
|
+
* Idempotent guard: callers should not invoke twice for the same
|
|
292
|
+
* session — duplicated `npm install -g` spawns would race. We use a
|
|
293
|
+
* one-shot flag rather than a no-op-on-second-call lock so a coding
|
|
294
|
+
* mistake produces visible breakage during testing instead of silent
|
|
295
|
+
* over-spawning.
|
|
296
|
+
*/
|
|
297
|
+
let __postBannerRan = false;
|
|
298
|
+
function runPostBannerUpdateTasks() {
|
|
299
|
+
if (__postBannerRan) return;
|
|
300
|
+
__postBannerRan = true;
|
|
301
|
+
try {
|
|
302
|
+
let installedVersion = null;
|
|
303
|
+
try {
|
|
304
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
305
|
+
const pkg = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf8'));
|
|
306
|
+
installedVersion = typeof pkg.version === 'string' ? pkg.version : null;
|
|
307
|
+
} catch { /* best-effort */ }
|
|
308
|
+
if (!installedVersion) return;
|
|
309
|
+
// Auto-update spawn moved to Stop hook (v4.1.4) to avoid TOCTOU race
|
|
310
|
+
// where npm install -g overwrites dist/ while peer hooks are still reading it.
|
|
311
|
+
spawnFreshUpdateCheck(installedVersion);
|
|
312
|
+
} catch {
|
|
313
|
+
// Best-effort — never crash the hook on a network or fs hiccup.
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Build a "base message + optional deprecation banner" combined
|
|
319
|
+
* single-line systemMessage payload. Keeps stdout a single JSON
|
|
320
|
+
* object on every empty/no-DB exit path so Claude Code's hook
|
|
321
|
+
* contract holds.
|
|
322
|
+
*/
|
|
323
|
+
function combineWithBanner(baseMessage) {
|
|
324
|
+
let lines = [];
|
|
325
|
+
try {
|
|
326
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
327
|
+
const pkg = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf8'));
|
|
328
|
+
const installedVersion = typeof pkg.version === 'string' ? pkg.version : null;
|
|
329
|
+
const cache = readUpdateCheckCache(installedVersion);
|
|
330
|
+
lines = installedVersion ? buildDeprecationBanner(installedVersion, cache) : [];
|
|
331
|
+
} catch {
|
|
332
|
+
// Best-effort — fall through to base message only.
|
|
333
|
+
}
|
|
334
|
+
if (lines.length === 0) return baseMessage;
|
|
335
|
+
return [...lines.filter((l) => l.length > 0), '', baseMessage].join('\n');
|
|
336
|
+
}
|
|
337
|
+
|
|
26
338
|
let input = '';
|
|
27
339
|
process.stdin.setEncoding('utf8');
|
|
28
340
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
29
341
|
process.stdin.on('end', async () => {
|
|
30
342
|
try {
|
|
343
|
+
try {
|
|
31
344
|
const data = JSON.parse(input);
|
|
32
345
|
const projectName = basename(data.cwd || process.cwd());
|
|
33
346
|
|
|
@@ -48,7 +361,10 @@ process.stdin.on('end', async () => {
|
|
|
48
361
|
}
|
|
49
362
|
|
|
50
363
|
if (!existsSync(dbPath)) {
|
|
51
|
-
|
|
364
|
+
// Combine deprecation banner (if any) into the same
|
|
365
|
+
// systemMessage so stdout stays a single JSON document. Outer
|
|
366
|
+
// finally runs runPostBannerUpdateTasks().
|
|
367
|
+
output(combineWithBanner('MeMesh: No database found. Memories will be created as you work.'));
|
|
52
368
|
return;
|
|
53
369
|
}
|
|
54
370
|
|
|
@@ -62,7 +378,7 @@ process.stdin.on('end', async () => {
|
|
|
62
378
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='entities'"
|
|
63
379
|
).get();
|
|
64
380
|
if (!tableCheck) {
|
|
65
|
-
output('MeMesh: Database exists but no memories stored yet.');
|
|
381
|
+
output(combineWithBanner('MeMesh: Database exists but no memories stored yet.'));
|
|
66
382
|
return;
|
|
67
383
|
}
|
|
68
384
|
|
|
@@ -157,8 +473,15 @@ process.stdin.on('end', async () => {
|
|
|
157
473
|
}
|
|
158
474
|
}
|
|
159
475
|
|
|
160
|
-
// No memories at all —
|
|
476
|
+
// No memories at all — surface only the deprecation banner if
|
|
477
|
+
// active (so a flagged install still warns the user) and skip
|
|
478
|
+
// the rest of the recall-summary work. The outer finally still
|
|
479
|
+
// runs runPostBannerUpdateTasks().
|
|
161
480
|
if (lines.length === 0) {
|
|
481
|
+
const bannerOnly = combineWithBanner('');
|
|
482
|
+
if (bannerOnly && bannerOnly.trim().length > 0) {
|
|
483
|
+
output(bannerOnly.trim());
|
|
484
|
+
}
|
|
162
485
|
return;
|
|
163
486
|
}
|
|
164
487
|
|
|
@@ -294,14 +617,28 @@ process.stdin.on('end', async () => {
|
|
|
294
617
|
// Non-critical — don't break session start
|
|
295
618
|
}
|
|
296
619
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
620
|
+
// Deprecation-aware banner. Reads the cache produced by the
|
|
621
|
+
// last `getUpdateCheck` (CLI or background refresh). When the
|
|
622
|
+
// installed version was flagged by maintainers (typically a
|
|
623
|
+
// security advisory), prepend a strong warning so the user sees
|
|
624
|
+
// it on every session start until they upgrade.
|
|
625
|
+
let installedVersion = null;
|
|
626
|
+
try {
|
|
627
|
+
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
628
|
+
const pkg = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf8'));
|
|
629
|
+
installedVersion = typeof pkg.version === 'string' ? pkg.version : null;
|
|
630
|
+
} catch {
|
|
631
|
+
// Best-effort — without the version we can't compare to cache.
|
|
632
|
+
}
|
|
633
|
+
const updateCache = readUpdateCheckCache(installedVersion);
|
|
634
|
+
const deprecationLines = installedVersion
|
|
635
|
+
? buildDeprecationBanner(installedVersion, updateCache)
|
|
636
|
+
: [];
|
|
637
|
+
const memorySummaryWithBanner = deprecationLines.length > 0
|
|
638
|
+
? [...deprecationLines, '', ...memorySummary.split('\n')].join('\n')
|
|
639
|
+
: memorySummary;
|
|
640
|
+
|
|
641
|
+
output(buildReferenceContext(memorySummaryWithBanner.split('\n')));
|
|
305
642
|
} finally {
|
|
306
643
|
db.close();
|
|
307
644
|
}
|
|
@@ -323,9 +660,21 @@ process.stdin.on('end', async () => {
|
|
|
323
660
|
} catch {
|
|
324
661
|
// Non-critical — noise compression failed, will retry next session
|
|
325
662
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
663
|
+
|
|
664
|
+
} catch (err) {
|
|
665
|
+
// Hooks must never crash Claude Code — but report honestly.
|
|
666
|
+
// Inner catch so the outer finally can still run the post-
|
|
667
|
+
// banner update tasks even when the recall flow blew up.
|
|
668
|
+
console.log(JSON.stringify({ systemMessage: `MeMesh: Session start failed (${err?.message || 'unknown error'}). Memories not loaded.` }));
|
|
669
|
+
}
|
|
670
|
+
} finally {
|
|
671
|
+
// ── Auto-update + cache refresh ──────────────────────────────
|
|
672
|
+
// Outer finally guarantees this runs on every exit path — no-DB
|
|
673
|
+
// short-circuit, empty-DB return, no-memories return, recall
|
|
674
|
+
// happy path, or even a thrown error caught above. The
|
|
675
|
+
// function is single-shot per process so the duplicated
|
|
676
|
+
// late-path call from older versions is now idempotent.
|
|
677
|
+
runPostBannerUpdateTasks();
|
|
329
678
|
}
|
|
330
679
|
});
|
|
331
680
|
|