@pcircle/memesh 4.5.0 → 4.6.0
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 +5 -3
- package/.claude-plugin/plugin.json +6 -4
- package/.mcp.json +1 -1
- package/AGENTS.md +95 -0
- package/README.de.md +130 -39
- package/README.md +188 -43
- package/README.zh-TW.md +133 -41
- package/dashboard/dist/index.html +9 -9
- package/dist/cli/view-live.js +3 -3
- 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/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js +4 -9
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/briefing.d.ts +8 -0
- package/dist/core/briefing.d.ts.map +1 -0
- package/dist/core/briefing.js +91 -0
- package/dist/core/briefing.js.map +1 -0
- package/dist/core/capture-flag.d.ts +5 -0
- package/dist/core/capture-flag.d.ts.map +1 -0
- package/dist/core/capture-flag.js +10 -0
- package/dist/core/capture-flag.js.map +1 -0
- 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/conflict-candidates.d.ts +20 -0
- package/dist/core/conflict-candidates.d.ts.map +1 -0
- package/dist/core/conflict-candidates.js +79 -0
- package/dist/core/conflict-candidates.js.map +1 -0
- package/dist/core/conflict-judge.d.ts +47 -0
- package/dist/core/conflict-judge.d.ts.map +1 -0
- package/dist/core/conflict-judge.js +189 -0
- package/dist/core/conflict-judge.js.map +1 -0
- 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/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +3 -5
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +3 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +207 -85
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +20 -9
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +416 -58
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +7 -5
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +38 -9
- 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.map +1 -1
- package/dist/core/failure-analyzer.js +7 -12
- package/dist/core/failure-analyzer.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-channel.d.ts +1 -1
- package/dist/core/install-channel.d.ts.map +1 -1
- package/dist/core/install-channel.js +16 -5
- package/dist/core/install-channel.js.map +1 -1
- package/dist/core/install-hooks.d.ts +6 -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/json-utils.d.ts +1 -0
- package/dist/core/json-utils.d.ts.map +1 -1
- package/dist/core/json-utils.js +19 -10
- package/dist/core/json-utils.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +3 -4
- package/dist/core/kg-backfill.d.ts.map +1 -1
- package/dist/core/kg-backfill.js +1 -4
- 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 +15 -22
- 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 +8 -4
- package/dist/core/memory-tool.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +41 -17
- 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/prompt-safety.d.ts +1 -0
- package/dist/core/prompt-safety.d.ts.map +1 -1
- package/dist/core/prompt-safety.js +7 -0
- package/dist/core/prompt-safety.js.map +1 -1
- package/dist/core/schema-export.d.ts.map +1 -1
- package/dist/core/schema-export.js +27 -30
- 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/setup.d.ts +29 -0
- package/dist/core/setup.d.ts.map +1 -0
- package/dist/core/setup.js +127 -0
- package/dist/core/setup.js.map +1 -0
- 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/task-state-store.d.ts +17 -0
- package/dist/core/task-state-store.d.ts.map +1 -0
- package/dist/core/task-state-store.js +45 -0
- package/dist/core/task-state-store.js.map +1 -0
- package/dist/core/task-state.d.ts +19 -0
- package/dist/core/task-state.d.ts.map +1 -0
- package/dist/core/task-state.js +91 -0
- package/dist/core/task-state.js.map +1 -0
- package/dist/core/time-utils.d.ts +2 -0
- package/dist/core/time-utils.d.ts.map +1 -0
- package/dist/core/time-utils.js +14 -0
- package/dist/core/time-utils.js.map +1 -0
- package/dist/core/title.d.ts +5 -0
- package/dist/core/title.d.ts.map +1 -0
- package/dist/core/title.js +14 -0
- package/dist/core/title.js.map +1 -0
- 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 +2 -3
- package/dist/core/transcript-source.js.map +1 -1
- package/dist/core/types.d.ts +21 -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/core/work-topology.d.ts +33 -0
- package/dist/core/work-topology.d.ts.map +1 -0
- package/dist/core/work-topology.js +183 -0
- package/dist/core/work-topology.js.map +1 -0
- package/dist/db.d.ts +5 -10
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +194 -196
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +4 -2
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +68 -49
- 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 +61 -36
- package/dist/storage/conflicts.d.ts +3 -3
- package/dist/storage/conflicts.d.ts.map +1 -1
- package/dist/storage/conflicts.js +2 -7
- package/dist/storage/conflicts.js.map +1 -1
- package/dist/storage/fts-index.d.ts +6 -4
- package/dist/storage/fts-index.d.ts.map +1 -1
- package/dist/storage/fts-index.js +16 -4
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/storage/schema.d.ts +20 -0
- package/dist/storage/schema.d.ts.map +1 -0
- package/dist/storage/schema.js +274 -0
- package/dist/storage/schema.js.map +1 -0
- 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 +1 -4
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +494 -76
- 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 +243 -323
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +48 -94
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +77 -56
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/dist/transports/schemas.d.ts +31 -40
- package/dist/transports/schemas.d.ts.map +1 -1
- package/dist/transports/schemas.js +31 -37
- package/dist/transports/schemas.js.map +1 -1
- package/hooks/hooks.json +0 -10
- package/llms-install.md +138 -0
- package/package.json +19 -18
- package/scripts/hooks/_generated/capture-flag.js +17 -0
- package/scripts/hooks/_generated/core-paths.js +67 -1
- package/scripts/hooks/_generated/fts-index.js +16 -4
- package/scripts/hooks/_generated/schema.js +281 -0
- package/scripts/hooks/_generated/sqlite.js +71 -0
- package/scripts/hooks/_generated/task-state.js +98 -0
- package/scripts/hooks/_generated/time-utils.js +21 -0
- package/scripts/hooks/_generated/title.js +21 -0
- package/scripts/hooks/_generated/work-topology.js +190 -0
- package/scripts/hooks/_shared.js +269 -534
- package/scripts/hooks/post-commit.js +55 -10
- package/scripts/hooks/pre-compact.js +22 -7
- package/scripts/hooks/pre-edit-recall.js +9 -11
- package/scripts/hooks/session-start.js +230 -106
- package/scripts/hooks/session-summary.js +176 -73
- package/scripts/hooks/user-prompt-intent.js +3 -2
- package/skills/memesh/SKILL.md +97 -77
- package/README.es.md +0 -470
- package/README.fr.md +0 -462
- package/README.ja.md +0 -470
- package/README.ko.md +0 -470
- package/README.pt.md +0 -462
- package/README.th.md +0 -463
- package/README.vi.md +0 -462
- package/README.zh-CN.md +0 -469
- package/dist/cli/view.d.ts +0 -3
- package/dist/cli/view.d.ts.map +0 -1
- package/dist/cli/view.js +0 -523
- package/dist/cli/view.js.map +0 -1
- 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
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
|
-
import { homedir } from 'os';
|
|
6
5
|
import { join } from 'path';
|
|
7
6
|
import { pathToFileURL } from 'url';
|
|
8
|
-
import { existsSync, readFileSync, unlinkSync,
|
|
7
|
+
import { existsSync, readFileSync, unlinkSync, mkdirSync, accessSync, constants as fsConstants } from 'fs';
|
|
9
8
|
import {
|
|
10
9
|
buildReferenceContext,
|
|
11
10
|
ensurePrivateDir,
|
|
@@ -13,14 +12,25 @@ import {
|
|
|
13
12
|
getMemeshDirFromDbPath,
|
|
14
13
|
getProjectName,
|
|
15
14
|
importFromPluginRoot,
|
|
16
|
-
|
|
15
|
+
assembleTopologyBlock,
|
|
16
|
+
DEFAULT_TOPOLOGY_BUDGET,
|
|
17
|
+
SNIPPET_FETCH_CHARS,
|
|
18
|
+
TOPOLOGY_CANDIDATE_CAP,
|
|
17
19
|
isTrustedForAutoContext,
|
|
20
|
+
parseEntityMetadata,
|
|
21
|
+
// Aliased: this file already has a local `const memeshDir` (a resolved
|
|
22
|
+
// db-path-derived directory string) — the helper here is the MEMESH_DIR/
|
|
23
|
+
// home resolver the update-check cache itself uses.
|
|
24
|
+
memeshDir as memeshHomeDir,
|
|
25
|
+
parseTaskState,
|
|
18
26
|
readUpdateCheckCache,
|
|
19
27
|
resolvePluginRoot,
|
|
20
28
|
resolveSessionLimit,
|
|
21
|
-
|
|
29
|
+
taskStateLines,
|
|
30
|
+
taskStateName,
|
|
22
31
|
writePrivateJson,
|
|
23
32
|
} from './_shared.js';
|
|
33
|
+
import { MemeshDatabase } from './_generated/sqlite.js';
|
|
24
34
|
|
|
25
35
|
const require = createRequire(import.meta.url);
|
|
26
36
|
|
|
@@ -44,7 +54,6 @@ try {
|
|
|
44
54
|
const dbPath = getDbPath();
|
|
45
55
|
const memeshDir = getMemeshDirFromDbPath();
|
|
46
56
|
const throttlePath = join(memeshDir, 'session-recalled-files.json');
|
|
47
|
-
const nudgeFlagsDir = join(memeshDir, 'agent-nudge-flags');
|
|
48
57
|
|
|
49
58
|
/**
|
|
50
59
|
* Build the strong deprecation warning lines to prepend to the
|
|
@@ -194,7 +203,7 @@ function isStrictlyOlder(a, b) {
|
|
|
194
203
|
return pa.tail < pb.tail;
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
function buildUpdateAvailableBanner(currentVersion, cache,
|
|
206
|
+
function buildUpdateAvailableBanner(currentVersion, cache, getChannel) {
|
|
198
207
|
if (!cache || cache.currentVersion !== currentVersion) return [];
|
|
199
208
|
// Deprecation banner takes precedence — when set, it owns the
|
|
200
209
|
// session-start real estate. Skip the soft banner so the user sees
|
|
@@ -217,7 +226,10 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
217
226
|
// this user a banner today?"
|
|
218
227
|
try {
|
|
219
228
|
const fs = require('fs');
|
|
220
|
-
|
|
229
|
+
// memeshHomeDir(), not join(homedir(), '.memesh'): the throttle marker
|
|
230
|
+
// must sit next to the update-check cache it throttles when MEMESH_DIR
|
|
231
|
+
// is set (readUpdateCheckCache resolves its path with this same helper).
|
|
232
|
+
const dir = memeshHomeDir();
|
|
221
233
|
try { ensurePrivateDir(dir); } catch { /* best-effort */ }
|
|
222
234
|
const versionTag = /^[0-9A-Za-z.+-]+$/.test(currentVersion) ? currentVersion : 'unknown';
|
|
223
235
|
const markerPath = join(dir, `last-update-banner.${versionTag}.lock`);
|
|
@@ -236,6 +248,13 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
236
248
|
} catch { /* best-effort */ }
|
|
237
249
|
} catch { /* best-effort */ }
|
|
238
250
|
|
|
251
|
+
// Channel detection spawns `npm root -g` (50-200ms) — resolve it only
|
|
252
|
+
// here, after every early return above has had its chance to suppress
|
|
253
|
+
// the banner. The guards fire on ~every session; the banner at most
|
|
254
|
+
// once per 24h.
|
|
255
|
+
let channel = 'unknown';
|
|
256
|
+
try { channel = getChannel(); } catch { /* best-effort */ }
|
|
257
|
+
|
|
239
258
|
const lines = [
|
|
240
259
|
'',
|
|
241
260
|
`ℹ️ MeMesh update available: ${cache.latestVersion} (you're on ${currentVersion}).`,
|
|
@@ -243,7 +262,7 @@ function buildUpdateAvailableBanner(currentVersion, cache, channel) {
|
|
|
243
262
|
if (channel === 'npm-global') {
|
|
244
263
|
lines.push(` Run: memesh update`);
|
|
245
264
|
} else if (channel === 'plugin-marketplace') {
|
|
246
|
-
lines.push(` Run:
|
|
265
|
+
lines.push(` Run: memesh upgrade-plugin (no CLI? npx @pcircle/memesh upgrade-plugin — or reinstall from /plugin UI)`);
|
|
247
266
|
} else if (channel === 'source-checkout') {
|
|
248
267
|
lines.push(` Source checkout: \`git pull && npm install && npm run build\`.`);
|
|
249
268
|
} else if (channel === 'npm-local') {
|
|
@@ -311,7 +330,9 @@ function spawnFreshUpdateCheck(installedVersion) {
|
|
|
311
330
|
const cliPath = join(pluginRoot, 'dist/transports/cli/cli.js');
|
|
312
331
|
if (!existsSync(cliPath)) return false;
|
|
313
332
|
const fs = require('fs');
|
|
314
|
-
|
|
333
|
+
// memeshHomeDir(), not join(homedir(), '.memesh') — same reasoning as
|
|
334
|
+
// the banner marker above: marker and cache must share a directory.
|
|
335
|
+
const dir = memeshHomeDir();
|
|
315
336
|
try { ensurePrivateDir(dir); } catch { /* best-effort */ }
|
|
316
337
|
// Codex round 37: scope the throttle marker to the installed
|
|
317
338
|
// version. The marker was machine-global, so a refresh started
|
|
@@ -430,6 +451,48 @@ function runPostBannerUpdateTasks() {
|
|
|
430
451
|
}
|
|
431
452
|
}
|
|
432
453
|
|
|
454
|
+
/**
|
|
455
|
+
* Can the capture hooks actually write? Returns the offending path, or null.
|
|
456
|
+
*
|
|
457
|
+
* Probes the two things a capture hook needs and nothing else: the memesh
|
|
458
|
+
* directory has to exist and be writable, and where the database file already
|
|
459
|
+
* exists, that file has to be writable too. The second half matters on its
|
|
460
|
+
* own — a writable directory holding a read-only database is a state the old
|
|
461
|
+
* mkdir-only probe called healthy, and it is exactly what a botched `sudo`
|
|
462
|
+
* leaves behind.
|
|
463
|
+
*
|
|
464
|
+
* `accessSync(W_OK)` rather than opening a handle: this is the SessionStart
|
|
465
|
+
* hot path, and a read-write open would run the whole migration chain here
|
|
466
|
+
* just to answer a permissions question. It is one syscall and it fails in
|
|
467
|
+
* the same cases EACCES would.
|
|
468
|
+
*
|
|
469
|
+
* Deliberately not detected: a full disk. No cheap probe finds it, and
|
|
470
|
+
* claiming otherwise would be worse than the honest gap.
|
|
471
|
+
*/
|
|
472
|
+
function captureTargetUnwritable() {
|
|
473
|
+
try {
|
|
474
|
+
mkdirSync(memeshDir, { recursive: true });
|
|
475
|
+
accessSync(memeshDir, fsConstants.W_OK);
|
|
476
|
+
} catch {
|
|
477
|
+
return memeshDir;
|
|
478
|
+
}
|
|
479
|
+
// The WAL/SHM sidecars are probed too: an interrupted `sudo` run leaves a
|
|
480
|
+
// user-owned database next to root-owned `-wal`/`-shm` files, and SQLite
|
|
481
|
+
// then fails every write with EACCES while the db file itself probes
|
|
482
|
+
// writable — the most common botched-sudo residue, and exactly the state
|
|
483
|
+
// the db-file probe alone called healthy.
|
|
484
|
+
for (const suffix of ['', '-wal', '-shm']) {
|
|
485
|
+
const p = `${dbPath}${suffix}`;
|
|
486
|
+
if (!existsSync(p)) continue;
|
|
487
|
+
try {
|
|
488
|
+
accessSync(p, fsConstants.W_OK);
|
|
489
|
+
} catch {
|
|
490
|
+
return p;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
433
496
|
/**
|
|
434
497
|
* Build a "base message + optional deprecation banner" combined
|
|
435
498
|
* single-line systemMessage payload. Keeps stdout a single JSON
|
|
@@ -451,8 +514,8 @@ function combineWithBanner(baseMessage) {
|
|
|
451
514
|
if (deprecation.length > 0) {
|
|
452
515
|
lines = deprecation;
|
|
453
516
|
} else {
|
|
454
|
-
|
|
455
|
-
|
|
517
|
+
lines = buildUpdateAvailableBanner(
|
|
518
|
+
installedVersion, cache, () => detectInstallChannelHook(pluginRoot));
|
|
456
519
|
}
|
|
457
520
|
}
|
|
458
521
|
} catch {
|
|
@@ -466,6 +529,14 @@ let input = '';
|
|
|
466
529
|
process.stdin.setEncoding('utf8');
|
|
467
530
|
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
468
531
|
process.stdin.on('end', async () => {
|
|
532
|
+
// Hoisted above both try blocks: the recall-failure catch below must be
|
|
533
|
+
// able to lead with this warning too, or a capture-dead session that ALSO
|
|
534
|
+
// hits a recall error silently drops the more important half of the story.
|
|
535
|
+
let captureWarning = null;
|
|
536
|
+
const withCaptureWarning = (msg) => {
|
|
537
|
+
if (!captureWarning) return msg;
|
|
538
|
+
return `${captureWarning}\n${msg.replace(/^◉ MeMesh ready · /, '◉ MeMesh · ')}`;
|
|
539
|
+
};
|
|
469
540
|
try {
|
|
470
541
|
try {
|
|
471
542
|
const data = JSON.parse(input);
|
|
@@ -479,38 +550,61 @@ process.stdin.on('end', async () => {
|
|
|
479
550
|
} catch {
|
|
480
551
|
// Non-critical
|
|
481
552
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
553
|
+
|
|
554
|
+
// Every banner below this line is a PROMISE that memories will be saved,
|
|
555
|
+
// so check that it can be kept before making any of them.
|
|
556
|
+
//
|
|
557
|
+
// This probe used to live INSIDE the `!existsSync(dbPath)` branch below,
|
|
558
|
+
// which meant it only ever ran before the database existed — while the
|
|
559
|
+
// failure it detects has nothing to do with first runs. A `~/.memesh` that
|
|
560
|
+
// became unwritable later (permissions changed, a read-only mount, a
|
|
561
|
+
// directory that changed owner) produced the cheerful green count banner
|
|
562
|
+
// on every session, forever, while every capture hook failed with EACCES.
|
|
563
|
+
// Fixing the first-run case and leaving the steady-state case is how a
|
|
564
|
+
// detector ends up covering the one day the bug is least likely to happen.
|
|
565
|
+
// The warning does NOT return: this hook's other job is recall, which
|
|
566
|
+
// opens the database read-only and works fine on an unwritable target.
|
|
567
|
+
// Returning here turned "capture is off" into "your memory is gone" —
|
|
568
|
+
// every existing memory silently withheld exactly when the user needs
|
|
569
|
+
// the context to notice something is wrong. Warn, then keep reading.
|
|
570
|
+
const unwritable = captureTargetUnwritable();
|
|
571
|
+
// "ready" is a promise about capture — when the warning is present,
|
|
572
|
+
// withCaptureWarning (hoisted above) demotes it instead of contradicting
|
|
573
|
+
// it one line later.
|
|
574
|
+
captureWarning = unwritable
|
|
575
|
+
? `◉ MeMesh cannot write to ${unwritable} — memories will NOT be saved this session (recall still works). Run 'memesh doctor'.`
|
|
576
|
+
: null;
|
|
489
577
|
|
|
490
578
|
if (!existsSync(dbPath)) {
|
|
491
579
|
// Combine deprecation banner (if any) into the same
|
|
492
580
|
// systemMessage so stdout stays a single JSON document. Outer
|
|
493
581
|
// finally runs runPostBannerUpdateTasks().
|
|
494
|
-
|
|
582
|
+
// With no database there is nothing to recall either — the warning IS
|
|
583
|
+
// the whole truth, and "memories will be created as you work" would
|
|
584
|
+
// contradict it one line later.
|
|
585
|
+
output(combineWithBanner(captureWarning ?? '◉ MeMesh ready · no database yet, memories will be created as you work'));
|
|
495
586
|
return;
|
|
496
587
|
}
|
|
497
588
|
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
589
|
+
// `readOnly`, not `readonly`: node:sqlite ignores the lowercase spelling
|
|
590
|
+
// and hands back a WRITABLE handle. This hook only reads.
|
|
591
|
+
//
|
|
592
|
+
// No `journal_mode = WAL` here. Setting it is a write, so a read-only
|
|
593
|
+
// connection refuses it — and it was never doing anything: the mode is a
|
|
594
|
+
// property of the database file that the writing side already set, and a
|
|
595
|
+
// reader opens a WAL database perfectly well without asking for it.
|
|
596
|
+
const db = new MemeshDatabase(dbPath, { readOnly: true });
|
|
597
|
+
// Whether the noise-compression epilogue below should run at all —
|
|
598
|
+
// pre-read from this readonly handle before it closes. Defaults to
|
|
599
|
+
// true so any early exit still lets the epilogue's own throttle decide.
|
|
600
|
+
let noiseCompressDue = true;
|
|
505
601
|
try {
|
|
506
|
-
db.pragma('journal_mode = WAL');
|
|
507
|
-
|
|
508
602
|
// Check if tables exist (db may exist but be empty)
|
|
509
603
|
const tableCheck = db.prepare(
|
|
510
604
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='entities'"
|
|
511
605
|
).get();
|
|
512
606
|
if (!tableCheck) {
|
|
513
|
-
output(combineWithBanner('◉ MeMesh ready · database initialised but no memories stored yet'));
|
|
607
|
+
output(combineWithBanner(captureWarning ?? '◉ MeMesh ready · database initialised but no memories stored yet'));
|
|
514
608
|
return;
|
|
515
609
|
}
|
|
516
610
|
|
|
@@ -545,11 +639,10 @@ process.stdin.on('end', async () => {
|
|
|
545
639
|
// Functions:
|
|
546
640
|
// - frequency: log(c+1) / log(max(maxAccess,1) + 1) (matches frequencyScore)
|
|
547
641
|
// - 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.
|
|
642
|
+
// SQLite >= 3.35 with -DSQLITE_ENABLE_MATH_FUNCTIONS provides exp/log,
|
|
643
|
+
// which Node's bundled SQLite has. We probe once per process and fall
|
|
644
|
+
// back to the legacy linear/rational forms if a stripped-down build is
|
|
645
|
+
// detected, so ranking degrades gracefully rather than throwing.
|
|
553
646
|
// Test-only seam: force the legacy linear/rational fallback so the
|
|
554
647
|
// pre-math-functions code path is reachable in CI on builds where
|
|
555
648
|
// exp/log ARE available. Production callers never set this.
|
|
@@ -567,7 +660,12 @@ process.stdin.on('end', async () => {
|
|
|
567
660
|
// doesn't have confidence/access_count/last_accessed_at, so the
|
|
568
661
|
// SELECT can't reference them. Build the column list to match
|
|
569
662
|
// what the schema actually supports.
|
|
570
|
-
|
|
663
|
+
// `title` is an ALTER-added column (UX-1) and gets the same
|
|
664
|
+
// legacy-schema guard as the scoring columns: a database that predates
|
|
665
|
+
// it must still produce an injection, falling back to the observation
|
|
666
|
+
// snippet for its display text.
|
|
667
|
+
const hasTitle = colNames.has('title');
|
|
668
|
+
const baseCols = `e.id, e.name, e.type,${hasTitle ? ' e.title,' : ''} e.created_at, e.metadata`;
|
|
571
669
|
const scoringCols = hasScoringCols
|
|
572
670
|
? `, e.confidence, e.access_count, e.last_accessed_at`
|
|
573
671
|
: '';
|
|
@@ -584,7 +682,7 @@ process.stdin.on('end', async () => {
|
|
|
584
682
|
pool_stats AS (
|
|
585
683
|
SELECT COALESCE(MAX(access_count), 0) AS max_access FROM pool
|
|
586
684
|
)
|
|
587
|
-
SELECT p.id, p.name, p.type, p.created_at, p.metadata
|
|
685
|
+
SELECT p.id, p.name, p.type,${hasTitle ? ' p.title,' : ''} p.created_at, p.metadata
|
|
588
686
|
FROM pool p, pool_stats s
|
|
589
687
|
ORDER BY
|
|
590
688
|
COALESCE(p.confidence, 1.0) * 0.2833
|
|
@@ -613,7 +711,19 @@ process.stdin.on('end', async () => {
|
|
|
613
711
|
`JOIN tags t ON t.entity_id = e.id`,
|
|
614
712
|
`WHERE t.tag = ? ${statusFilter}`,
|
|
615
713
|
);
|
|
616
|
-
|
|
714
|
+
// Over-fetch WIDE, then filter. The window used to be `sessionLimit * 3`
|
|
715
|
+
// and the trust filter ran after it — so a class of entity that ranks
|
|
716
|
+
// high can consume the entire window and leave nothing. That was not
|
|
717
|
+
// hypothetical: measured on a real graph, all 30 top-ranked rows were
|
|
718
|
+
// filtered out and the "project memory" section rendered empty while 92
|
|
719
|
+
// eligible entities sat below the cut. The filter is a JS predicate with
|
|
720
|
+
// one owner (`isTrustedForAutoContext`); rather than restate it as SQL
|
|
721
|
+
// and own it twice, the window is made wide enough that the filtered
|
|
722
|
+
// class cannot fill it. CANDIDATE_CAP bounds the work for a large graph.
|
|
723
|
+
// Shared with the briefing surface via the leaf, so the two sides'
|
|
724
|
+
// candidate windows cannot drift apart.
|
|
725
|
+
const CANDIDATE_CAP = TOPOLOGY_CANDIDATE_CAP;
|
|
726
|
+
const projectEntities = db.prepare(projectQuery).all(projectTag, CANDIDATE_CAP)
|
|
617
727
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
618
728
|
.slice(0, sessionLimit);
|
|
619
729
|
|
|
@@ -622,7 +732,7 @@ process.stdin.on('end', async () => {
|
|
|
622
732
|
// so rewrite to e.status for consistency.
|
|
623
733
|
const recentWhere = hasStatus ? "WHERE e.status = 'active'" : '';
|
|
624
734
|
const recentQuery = buildScoringQuery('', recentWhere);
|
|
625
|
-
const recentEntities = db.prepare(recentQuery).all(
|
|
735
|
+
const recentEntities = db.prepare(recentQuery).all(CANDIDATE_CAP)
|
|
626
736
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
627
737
|
.slice(0, 5);
|
|
628
738
|
|
|
@@ -635,7 +745,7 @@ process.stdin.on('end', async () => {
|
|
|
635
745
|
let lessonEntities = [];
|
|
636
746
|
try {
|
|
637
747
|
const lessonRows = db.prepare(`
|
|
638
|
-
SELECT DISTINCT e.id, e.name, e.type, e.metadata
|
|
748
|
+
SELECT DISTINCT e.id, e.name, e.type,${hasTitle ? ' e.title,' : ''} e.metadata
|
|
639
749
|
FROM entities e
|
|
640
750
|
JOIN tags t ON t.entity_id = e.id
|
|
641
751
|
WHERE e.type = 'lesson_learned'
|
|
@@ -688,8 +798,9 @@ process.stdin.on('end', async () => {
|
|
|
688
798
|
// stay far under that on purpose — session start should prime the
|
|
689
799
|
// model, not consume its working context. Snippets are truncated per
|
|
690
800
|
// observation and the whole block is hard-capped.
|
|
691
|
-
|
|
692
|
-
|
|
801
|
+
// The budget itself comes from the leaf (DEFAULT_TOPOLOGY_BUDGET) at
|
|
802
|
+
// the assembleTopologyBlock call — "the same block" depends on the two
|
|
803
|
+
// surfaces agreeing, so neither side restates the numbers.
|
|
693
804
|
|
|
694
805
|
const memoryLines = [];
|
|
695
806
|
try {
|
|
@@ -722,33 +833,59 @@ process.stdin.on('end', async () => {
|
|
|
722
833
|
// later ones are refinements.
|
|
723
834
|
if (snippets.has(row.entity_id)) continue;
|
|
724
835
|
const text = String(row.content ?? '').replace(/\s+/g, ' ').trim();
|
|
725
|
-
|
|
836
|
+
// A few line-widths, not the exact line cap: the final cut is
|
|
837
|
+
// clip()'s, on a word boundary — a hard slice at the line cap
|
|
838
|
+
// would hand it a string with nothing left to trim and ship
|
|
839
|
+
// mid-word fragments again.
|
|
840
|
+
if (text) snippets.set(row.entity_id, text.slice(0, SNIPPET_FETCH_CHARS));
|
|
726
841
|
}
|
|
727
842
|
}
|
|
728
843
|
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
746
|
-
memoryLines.push('');
|
|
747
|
-
};
|
|
844
|
+
// "Where we left off" leads the block. It is the one memory a new
|
|
845
|
+
// session needs before any other: everything below is context for
|
|
846
|
+
// work, this IS the work. It is also the only line here a human
|
|
847
|
+
// (or an agent acting for one) stated on purpose — the rest is
|
|
848
|
+
// ranked, and ranking cannot know what you meant to do next.
|
|
849
|
+
//
|
|
850
|
+
// Read from metadata, not from the observation trail: observations
|
|
851
|
+
// are the CHANGE history, and picking "the current goal" out of them
|
|
852
|
+
// means guessing which line is newest. Metadata holds one answer.
|
|
853
|
+
const taskRow = db
|
|
854
|
+
.prepare('SELECT metadata FROM entities WHERE name = ?')
|
|
855
|
+
.get(taskStateName(projectName));
|
|
856
|
+
const stateLines = taskStateLines(
|
|
857
|
+
parseTaskState(parseEntityMetadata(taskRow?.metadata)),
|
|
858
|
+
projectName,
|
|
859
|
+
);
|
|
748
860
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
861
|
+
// The pools overlap by construction (a lesson tagged to this project
|
|
862
|
+
// is in lessonEntities AND projectEntities); the shared assembler
|
|
863
|
+
// dedupes across them in claim order, so a project-scoped row is
|
|
864
|
+
// never marked foreign by the cross-project recent pool, and the
|
|
865
|
+
// topology grouping decides where each one belongs. This mapping —
|
|
866
|
+
// raw row → TopologyEntity — is the only part this hook owns; the
|
|
867
|
+
// assembly order, the spacer discipline, the budget and the
|
|
868
|
+
// task-state exclusion live in the leaf, shared with `briefing`.
|
|
869
|
+
const toEntity = (e) => {
|
|
870
|
+
const meta = parseEntityMetadata(e.metadata);
|
|
871
|
+
return {
|
|
872
|
+
name: e.name,
|
|
873
|
+
type: e.type || 'memory',
|
|
874
|
+
title: e.title ?? null,
|
|
875
|
+
snippet: snippets.get(e.id) ?? null,
|
|
876
|
+
signalScore: meta && typeof meta.signal_score === 'number' ? meta.signal_score : null,
|
|
877
|
+
};
|
|
878
|
+
};
|
|
879
|
+
memoryLines.push(...assembleTopologyBlock(
|
|
880
|
+
stateLines,
|
|
881
|
+
[
|
|
882
|
+
{ entities: topLessons.map(toEntity), foreign: false },
|
|
883
|
+
{ entities: projectEntities.map(toEntity), foreign: false },
|
|
884
|
+
{ entities: recentEntities.map(toEntity), foreign: true },
|
|
885
|
+
],
|
|
886
|
+
projectName,
|
|
887
|
+
DEFAULT_TOPOLOGY_BUDGET,
|
|
888
|
+
));
|
|
752
889
|
} catch (err) {
|
|
753
890
|
// Snippet enrichment is best-effort. A failure here must not stop
|
|
754
891
|
// the banner or the session — but trace it, because a silent break
|
|
@@ -764,22 +901,10 @@ process.stdin.on('end', async () => {
|
|
|
764
901
|
// attacker-influenced in the general case (anything the agent has
|
|
765
902
|
// ever been told can end up in an observation), so it must be
|
|
766
903
|
// delimited the same way on every injection path — not hand-rolled
|
|
767
|
-
// per hook.
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
|
|
771
|
-
// its delimiter.
|
|
772
|
-
const budgeted = [];
|
|
773
|
-
let used = 0;
|
|
774
|
-
for (const line of memoryLines) {
|
|
775
|
-
if (used + line.length + 1 > MAX_CONTEXT_CHARS) {
|
|
776
|
-
budgeted.push('… (truncated)');
|
|
777
|
-
break;
|
|
778
|
-
}
|
|
779
|
-
budgeted.push(line);
|
|
780
|
-
used += line.length + 1;
|
|
781
|
-
}
|
|
782
|
-
memoryContext = buildReferenceContext(budgeted);
|
|
904
|
+
// per hook. The lines arrive already budgeted — assembleTopologyBlock
|
|
905
|
+
// charges the task-state block and the sections against ONE ceiling
|
|
906
|
+
// and returns whole lines only, so the closing fence cannot be cut.
|
|
907
|
+
memoryContext = buildReferenceContext(memoryLines);
|
|
783
908
|
}
|
|
784
909
|
|
|
785
910
|
// --- Record injected entity IDs for recall effectiveness tracking ---
|
|
@@ -842,24 +967,6 @@ process.stdin.on('end', async () => {
|
|
|
842
967
|
try { process.stderr.write(`[memesh session-start] sessions-write: ${err?.message || err}\n`); } catch {}
|
|
843
968
|
}
|
|
844
969
|
|
|
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
970
|
// Deprecation banner (security advisory) — surfaced even with the
|
|
864
971
|
// short summary so flagged installs still warn on every session.
|
|
865
972
|
let installedVersion = null;
|
|
@@ -877,19 +984,34 @@ process.stdin.on('end', async () => {
|
|
|
877
984
|
if (deprecation.length > 0) {
|
|
878
985
|
bannerLines = deprecation;
|
|
879
986
|
} else {
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
883
|
-
channel = detectInstallChannelHook(pluginRoot);
|
|
884
|
-
} catch { /* best-effort */ }
|
|
885
|
-
bannerLines = buildUpdateAvailableBanner(installedVersion, updateCache, channel);
|
|
987
|
+
bannerLines = buildUpdateAvailableBanner(installedVersion, updateCache,
|
|
988
|
+
() => detectInstallChannelHook(resolvePluginRoot(import.meta.url)));
|
|
886
989
|
}
|
|
887
990
|
}
|
|
888
991
|
const finalMessage = bannerLines.length > 0
|
|
889
992
|
? [...bannerLines.filter(l => l.length > 0), '', summary].join('\n')
|
|
890
993
|
: summary;
|
|
891
994
|
|
|
892
|
-
output(finalMessage, memoryContext);
|
|
995
|
+
output(withCaptureWarning(finalMessage), memoryContext);
|
|
996
|
+
|
|
997
|
+
// Pre-read the noise-compression throttle on the handle we already
|
|
998
|
+
// hold. compressWeeklyNoise() re-checks under its own connection, but
|
|
999
|
+
// ~364/365 sessions are inside the 24h window — and the full path
|
|
1000
|
+
// costs two dist module-graph imports plus a write-capable
|
|
1001
|
+
// migration-chain open (WAL writer lock) that must stay off the
|
|
1002
|
+
// SessionStart hot path. Missing table / any error ⇒ due (the full
|
|
1003
|
+
// path owns schema creation).
|
|
1004
|
+
noiseCompressDue = (() => {
|
|
1005
|
+
try {
|
|
1006
|
+
const row = db.prepare(
|
|
1007
|
+
"SELECT value FROM memesh_metadata WHERE key = 'last_noise_compress_at'"
|
|
1008
|
+
).get();
|
|
1009
|
+
if (!row) return true;
|
|
1010
|
+
return Date.now() - new Date(row.value).getTime() >= 24 * 60 * 60 * 1000;
|
|
1011
|
+
} catch {
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
})();
|
|
893
1015
|
} finally {
|
|
894
1016
|
db.close();
|
|
895
1017
|
}
|
|
@@ -897,6 +1019,7 @@ process.stdin.on('end', async () => {
|
|
|
897
1019
|
// Opens a separate read-write connection via the core module.
|
|
898
1020
|
// Throttled to once per 24h inside compressWeeklyNoise().
|
|
899
1021
|
try {
|
|
1022
|
+
if (noiseCompressDue) {
|
|
900
1023
|
// F5: derive pluginRoot strictly from this file's location.
|
|
901
1024
|
// See `resolvePluginRoot` for the full reasoning.
|
|
902
1025
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
@@ -908,6 +1031,7 @@ process.stdin.on('end', async () => {
|
|
|
908
1031
|
} finally {
|
|
909
1032
|
dbMod.closeDatabase();
|
|
910
1033
|
}
|
|
1034
|
+
}
|
|
911
1035
|
} catch (err) {
|
|
912
1036
|
// Non-critical — noise compression failed, will retry next session.
|
|
913
1037
|
// Trace because this catch previously hid an off-by-one regression
|
|
@@ -921,7 +1045,7 @@ process.stdin.on('end', async () => {
|
|
|
921
1045
|
// Hooks must never crash Claude Code — but report honestly.
|
|
922
1046
|
// Inner catch so the outer finally can still run the post-
|
|
923
1047
|
// 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.` }));
|
|
1048
|
+
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
1049
|
}
|
|
926
1050
|
} finally {
|
|
927
1051
|
// ── Auto-update + cache refresh ──────────────────────────────
|