@pcircle/memesh 4.1.7 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.de.md +48 -10
- package/README.es.md +82 -17
- package/README.fr.md +49 -11
- package/README.ja.md +83 -17
- package/README.ko.md +86 -20
- package/README.md +12 -7
- package/README.pt.md +48 -10
- package/README.th.md +46 -8
- package/README.vi.md +48 -10
- package/README.zh-CN.md +83 -17
- package/README.zh-TW.md +110 -44
- package/dashboard/dist/index.html +9 -9
- package/dist/cli/view-live.js +2 -2
- package/dist/cli/view.d.ts.map +1 -1
- package/dist/cli/view.js +15 -12
- package/dist/cli/view.js.map +1 -1
- package/dist/core/analytics.d.ts +17 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +40 -0
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts +7 -2
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js +12 -4
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +19 -11
- package/dist/core/config.js.map +1 -1
- package/dist/core/consolidator.d.ts.map +1 -1
- package/dist/core/consolidator.js +13 -4
- package/dist/core/consolidator.js.map +1 -1
- package/dist/core/digest-validator.d.ts +18 -0
- package/dist/core/digest-validator.d.ts.map +1 -0
- package/dist/core/digest-validator.js +79 -0
- package/dist/core/digest-validator.js.map +1 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +28 -11
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +8 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +68 -14
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +2 -2
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/extractor.d.ts.map +1 -1
- package/dist/core/extractor.js +2 -1
- package/dist/core/extractor.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts +6 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +10 -2
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/install-hooks.d.ts.map +1 -1
- package/dist/core/install-hooks.js +1 -7
- package/dist/core/install-hooks.js.map +1 -1
- package/dist/core/install-id.d.ts.map +1 -1
- package/dist/core/install-id.js +2 -3
- package/dist/core/install-id.js.map +1 -1
- package/dist/core/kg-backfill.d.ts +39 -0
- package/dist/core/kg-backfill.d.ts.map +1 -0
- package/dist/core/kg-backfill.js +362 -0
- package/dist/core/kg-backfill.js.map +1 -0
- package/dist/core/llm-client.d.ts +13 -0
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js +63 -3
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-telemetry.d.ts +35 -0
- package/dist/core/llm-telemetry.d.ts.map +1 -0
- package/dist/core/llm-telemetry.js +96 -0
- package/dist/core/llm-telemetry.js.map +1 -0
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +3 -3
- package/dist/core/llm-validator.js.map +1 -1
- package/dist/core/operations.d.ts.map +1 -1
- package/dist/core/operations.js +4 -35
- package/dist/core/operations.js.map +1 -1
- package/dist/core/paths.d.ts +6 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +27 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt-safety.d.ts.map +1 -1
- package/dist/core/prompt-safety.js.map +1 -1
- package/dist/core/scoring.d.ts +5 -0
- package/dist/core/scoring.d.ts.map +1 -1
- package/dist/core/scoring.js +8 -0
- package/dist/core/scoring.js.map +1 -1
- package/dist/core/serializer.js +1 -1
- package/dist/core/serializer.js.map +1 -1
- package/dist/core/skill-usage-log.js +2 -2
- package/dist/core/skill-usage-log.js.map +1 -1
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/verifier.d.ts.map +1 -1
- package/dist/core/verifier.js +4 -4
- package/dist/core/verifier.js.map +1 -1
- package/dist/core/version-check.d.ts.map +1 -1
- package/dist/core/version-check.js +4 -3
- package/dist/core/version-check.js.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +71 -14
- package/dist/db.js.map +1 -1
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.d.ts.map +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-graph.js.map +1 -1
- package/dist/skills-manifest.json +16 -16
- package/dist/storage/fts-index.js +1 -1
- package/dist/storage/fts-index.js.map +1 -1
- package/dist/transports/cli/cli.js +128 -6
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +204 -25
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts +1 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +1 -1
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/package.json +2 -2
- package/scripts/hooks/_shared.js +177 -14
- package/scripts/hooks/post-commit.js +50 -8
- package/scripts/hooks/pre-bash-orchestration-nudge.js +8 -3
- package/scripts/hooks/pre-compact.js +13 -16
- package/scripts/hooks/pre-edit-recall.js +28 -13
- package/scripts/hooks/session-start.js +194 -184
- package/scripts/hooks/session-summary.js +376 -41
- package/dist/core/query-expander.d.ts +0 -4
- package/dist/core/query-expander.d.ts.map +0 -1
- package/dist/core/query-expander.js +0 -53
- package/dist/core/query-expander.js.map +0 -1
|
@@ -4,18 +4,20 @@ import { createRequire } from 'module';
|
|
|
4
4
|
import { spawn } from 'child_process';
|
|
5
5
|
import { createHash } from 'crypto';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
|
-
import { join
|
|
7
|
+
import { join } from 'path';
|
|
8
8
|
import { pathToFileURL } from 'url';
|
|
9
9
|
import { existsSync, readFileSync, unlinkSync, rmSync, appendFileSync, chmodSync } from 'fs';
|
|
10
10
|
import {
|
|
11
|
-
buildReferenceContext,
|
|
12
11
|
ensurePrivateDir,
|
|
13
|
-
|
|
12
|
+
getDbPath,
|
|
13
|
+
getMemeshDirFromDbPath,
|
|
14
|
+
getProjectName,
|
|
14
15
|
isAgenticOrchestrationEnabled,
|
|
15
16
|
isTrustedForAutoContext,
|
|
16
17
|
readUpdateCheckCache,
|
|
17
18
|
resolvePluginRoot,
|
|
18
19
|
resolveSessionLimit,
|
|
20
|
+
tryRequireBetterSqlite,
|
|
19
21
|
writePrivateJson,
|
|
20
22
|
} from './_shared.js';
|
|
21
23
|
|
|
@@ -38,8 +40,8 @@ try {
|
|
|
38
40
|
}
|
|
39
41
|
} catch { /* best-effort — fall through to 'unknown' channel */ }
|
|
40
42
|
|
|
41
|
-
const dbPath =
|
|
42
|
-
const memeshDir =
|
|
43
|
+
const dbPath = getDbPath();
|
|
44
|
+
const memeshDir = getMemeshDirFromDbPath();
|
|
43
45
|
const throttlePath = join(memeshDir, 'session-recalled-files.json');
|
|
44
46
|
const nudgeFlagsDir = join(memeshDir, 'agent-nudge-flags');
|
|
45
47
|
|
|
@@ -178,7 +180,13 @@ function detectInstallChannelHook(pluginRoot) {
|
|
|
178
180
|
if (!_installChannelMod) return 'unknown';
|
|
179
181
|
try {
|
|
180
182
|
return _installChannelMod.getCurrentInstallChannel({ packageRoot: pluginRoot });
|
|
181
|
-
} catch {
|
|
183
|
+
} catch (err) {
|
|
184
|
+
// Falling back to 'unknown' silences the deprecation banner's
|
|
185
|
+
// remediation hint (since it's gated on channel detection). When
|
|
186
|
+
// there's an active security-advisory deprecation, that's a
|
|
187
|
+
// user-visible regression — surface to stderr at least once per
|
|
188
|
+
// process so the reason is visible.
|
|
189
|
+
try { process.stderr.write(`[memesh session-start] install-channel detection: ${err?.message || err}\n`); } catch {}
|
|
182
190
|
return 'unknown';
|
|
183
191
|
}
|
|
184
192
|
}
|
|
@@ -342,7 +350,7 @@ process.stdin.on('end', async () => {
|
|
|
342
350
|
try {
|
|
343
351
|
try {
|
|
344
352
|
const data = JSON.parse(input);
|
|
345
|
-
const projectName =
|
|
353
|
+
const projectName = getProjectName(data.cwd);
|
|
346
354
|
|
|
347
355
|
// Clear per-session throttle files from previous session
|
|
348
356
|
try {
|
|
@@ -364,11 +372,16 @@ process.stdin.on('end', async () => {
|
|
|
364
372
|
// Combine deprecation banner (if any) into the same
|
|
365
373
|
// systemMessage so stdout stays a single JSON document. Outer
|
|
366
374
|
// finally runs runPostBannerUpdateTasks().
|
|
367
|
-
output(combineWithBanner('MeMesh
|
|
375
|
+
output(combineWithBanner('◉ MeMesh ready · no database yet, memories will be created as you work'));
|
|
368
376
|
return;
|
|
369
377
|
}
|
|
370
378
|
|
|
371
|
-
|
|
379
|
+
// Native module unavailable (typical for plugin-marketplace cache
|
|
380
|
+
// installs that ship without node_modules). Silently skip — the
|
|
381
|
+
// plugin's own MCP server runs via npx and a sibling registered
|
|
382
|
+
// copy of this hook (npm-global / dev path) supplies the summary.
|
|
383
|
+
const Database = tryRequireBetterSqlite();
|
|
384
|
+
if (!Database) return;
|
|
372
385
|
const db = new Database(dbPath, { readonly: true });
|
|
373
386
|
try {
|
|
374
387
|
db.pragma('journal_mode = WAL');
|
|
@@ -378,7 +391,7 @@ process.stdin.on('end', async () => {
|
|
|
378
391
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='entities'"
|
|
379
392
|
).get();
|
|
380
393
|
if (!tableCheck) {
|
|
381
|
-
output(combineWithBanner('MeMesh
|
|
394
|
+
output(combineWithBanner('◉ MeMesh ready · database initialised but no memories stored yet'));
|
|
382
395
|
return;
|
|
383
396
|
}
|
|
384
397
|
|
|
@@ -390,190 +403,164 @@ process.stdin.on('end', async () => {
|
|
|
390
403
|
const hasScoringCols = colNames.has('access_count') && colNames.has('last_accessed_at') && colNames.has('confidence');
|
|
391
404
|
|
|
392
405
|
const statusFilter = hasStatus ? "AND e.status = 'active'" : '';
|
|
393
|
-
|
|
406
|
+
// (Note: a previous `recentStatusFilter` constant lived here; the
|
|
407
|
+
// refactor that introduced `buildScoringQuery` aliased the table
|
|
408
|
+
// as `e` for both the project- and recent-pool queries, so the
|
|
409
|
+
// bare-column `WHERE status = 'active'` form was replaced by the
|
|
410
|
+
// qualified `WHERE e.status = 'active'` computed inline below.)
|
|
394
411
|
|
|
395
412
|
// Configurable limit: how many top-N entities to load per section.
|
|
396
413
|
// Env > config.sessionLimit > default 10.
|
|
397
414
|
const sessionLimit = resolveSessionLimit(process.env);
|
|
398
415
|
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
416
|
+
// Scoring math is aligned to src/core/scoring.ts exactly:
|
|
417
|
+
// - confidence weight 0.2833 (core 0.17 / 0.60 sub-total)
|
|
418
|
+
// - frequency weight 0.3000 (core 0.18 / 0.60)
|
|
419
|
+
// - recency weight 0.4167 (core 0.25 / 0.60)
|
|
420
|
+
// Sub-total excludes searchRelevance + impact, which session-start
|
|
421
|
+
// can't compute without an FTS query. The renormalised ratios are
|
|
422
|
+
// exported from core/scoring.ts as `SESSION_START_WEIGHT_RATIO`;
|
|
423
|
+
// a drift-guard test in tests/core/scoring.test.ts asserts the SQL
|
|
424
|
+
// here stays in sync.
|
|
425
|
+
//
|
|
426
|
+
// Functions:
|
|
427
|
+
// - frequency: log(c+1) / log(max(maxAccess,1) + 1) (matches frequencyScore)
|
|
428
|
+
// - recency: exp(-(now - lastAccessed_days) / 30) (matches recencyScore)
|
|
429
|
+
// SQLite >= 3.35 with -DSQLITE_ENABLE_MATH_FUNCTIONS provides exp/log;
|
|
430
|
+
// better-sqlite3 v8+ ships with this flag enabled by default. We probe
|
|
431
|
+
// once per process and fall back to the legacy linear/rational forms
|
|
432
|
+
// if a stripped-down build is detected, so ranking degrades gracefully
|
|
433
|
+
// rather than throwing.
|
|
434
|
+
// Test-only seam: force the legacy linear/rational fallback so the
|
|
435
|
+
// pre-math-functions code path is reachable in CI on builds where
|
|
436
|
+
// exp/log ARE available. Production callers never set this.
|
|
437
|
+
let hasSqliteMath = false;
|
|
438
|
+
if (process.env.MEMESH_TEST_FORCE_LEGACY_SCORING_SQL !== '1') {
|
|
439
|
+
try {
|
|
440
|
+
db.prepare('SELECT exp(1.0), log(2.0)').get();
|
|
441
|
+
hasSqliteMath = true;
|
|
442
|
+
} catch {
|
|
443
|
+
// Legacy SQLite build without math functions — keep linear fallback.
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// The legacy schema (createTestDb in tests, plus very old installs)
|
|
448
|
+
// doesn't have confidence/access_count/last_accessed_at, so the
|
|
449
|
+
// SELECT can't reference them. Build the column list to match
|
|
450
|
+
// what the schema actually supports.
|
|
451
|
+
const baseCols = 'e.id, e.name, e.type, e.created_at, e.metadata';
|
|
452
|
+
const scoringCols = hasScoringCols
|
|
453
|
+
? `, e.confidence, e.access_count, e.last_accessed_at`
|
|
454
|
+
: '';
|
|
455
|
+
|
|
456
|
+
const buildScoringQuery = (joinClause, whereClause) => {
|
|
457
|
+
const poolSelect = `SELECT DISTINCT ${baseCols}${scoringCols} FROM entities e ${joinClause}`;
|
|
458
|
+
if (!hasScoringCols) {
|
|
459
|
+
return `${poolSelect} ${whereClause} ORDER BY e.id DESC LIMIT ?`;
|
|
460
|
+
}
|
|
461
|
+
if (hasSqliteMath) {
|
|
462
|
+
return `WITH pool AS (
|
|
463
|
+
${poolSelect} ${whereClause}
|
|
464
|
+
),
|
|
465
|
+
pool_stats AS (
|
|
466
|
+
SELECT COALESCE(MAX(access_count), 0) AS max_access FROM pool
|
|
467
|
+
)
|
|
468
|
+
SELECT p.id, p.name, p.type, p.created_at, p.metadata
|
|
469
|
+
FROM pool p, pool_stats s
|
|
470
|
+
ORDER BY
|
|
471
|
+
COALESCE(p.confidence, 1.0) * 0.2833
|
|
472
|
+
+ (CASE WHEN s.max_access <= 0 THEN 0
|
|
473
|
+
ELSE log(COALESCE(p.access_count, 0) + 1) / log(max(s.max_access, 1) + 1) END) * 0.3000
|
|
474
|
+
+ (CASE WHEN p.last_accessed_at IS NULL THEN 0.5
|
|
475
|
+
ELSE exp(-(julianday('now') - julianday(p.last_accessed_at)) / 30.0) END) * 0.4167
|
|
476
|
+
DESC
|
|
477
|
+
LIMIT ?`;
|
|
478
|
+
}
|
|
479
|
+
// Legacy fallback (SQLite without math functions): linear cap + rational decay.
|
|
480
|
+
// Same direction as core ranking; absolute scores differ slightly.
|
|
481
|
+
return `${poolSelect} ${whereClause}
|
|
482
|
+
ORDER BY
|
|
483
|
+
COALESCE(e.confidence, 1.0) * 0.2833
|
|
403
484
|
+ CASE WHEN e.access_count IS NULL THEN 0
|
|
404
|
-
ELSE MIN(CAST(e.access_count AS REAL) / 50.0, 1.0) END * 0.
|
|
405
|
-
+ CASE WHEN e.last_accessed_at IS NULL THEN 0.
|
|
406
|
-
ELSE MIN(1.0, 1.0 / (1.0 + (julianday('now') - julianday(e.last_accessed_at)) / 30.0)) END * 0.
|
|
407
|
-
DESC
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
? `ORDER BY
|
|
412
|
-
CASE WHEN confidence IS NULL THEN 0.5 ELSE confidence END * 0.4
|
|
413
|
-
+ CASE WHEN access_count IS NULL THEN 0
|
|
414
|
-
ELSE MIN(CAST(access_count AS REAL) / 50.0, 1.0) END * 0.3
|
|
415
|
-
+ CASE WHEN last_accessed_at IS NULL THEN 0.3
|
|
416
|
-
ELSE MIN(1.0, 1.0 / (1.0 + (julianday('now') - julianday(last_accessed_at)) / 30.0)) END * 0.3
|
|
417
|
-
DESC`
|
|
418
|
-
: 'ORDER BY id DESC';
|
|
419
|
-
|
|
420
|
-
// Query project-specific top-N entities by relevance score
|
|
485
|
+
ELSE MIN(CAST(e.access_count AS REAL) / 50.0, 1.0) END * 0.3000
|
|
486
|
+
+ CASE WHEN e.last_accessed_at IS NULL THEN 0.5
|
|
487
|
+
ELSE MIN(1.0, 1.0 / (1.0 + (julianday('now') - julianday(e.last_accessed_at)) / 30.0)) END * 0.4167
|
|
488
|
+
DESC
|
|
489
|
+
LIMIT ?`;
|
|
490
|
+
};
|
|
491
|
+
|
|
421
492
|
const projectTag = `project:${projectName}`;
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
${statusFilter}
|
|
428
|
-
${scoringOrderBy}
|
|
429
|
-
LIMIT ?
|
|
430
|
-
`).all(projectTag, sessionLimit * 3)
|
|
493
|
+
const projectQuery = buildScoringQuery(
|
|
494
|
+
`JOIN tags t ON t.entity_id = e.id`,
|
|
495
|
+
`WHERE t.tag = ? ${statusFilter}`,
|
|
496
|
+
);
|
|
497
|
+
const projectEntities = db.prepare(projectQuery).all(projectTag, sessionLimit * 3)
|
|
431
498
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
432
499
|
.slice(0, sessionLimit);
|
|
433
500
|
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const recentEntities = db.prepare(`
|
|
441
|
-
SELECT id, name, type, created_at, metadata
|
|
442
|
-
FROM entities
|
|
443
|
-
${recentStatusFilter}
|
|
444
|
-
${recentScoringOrderBy}
|
|
445
|
-
LIMIT 15
|
|
446
|
-
`).all()
|
|
501
|
+
// recentStatusFilter is "WHERE status = 'active'" or "" — the bare-column
|
|
502
|
+
// form is fine when there's no JOIN, but we now alias the table as `e`,
|
|
503
|
+
// so rewrite to e.status for consistency.
|
|
504
|
+
const recentWhere = hasStatus ? "WHERE e.status = 'active'" : '';
|
|
505
|
+
const recentQuery = buildScoringQuery('', recentWhere);
|
|
506
|
+
const recentEntities = db.prepare(recentQuery).all(15)
|
|
447
507
|
.filter(entity => isTrustedForAutoContext(entity.metadata))
|
|
448
508
|
.slice(0, 5);
|
|
449
509
|
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
: `• ${entity.name} (${entity.type})`;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Build recall message
|
|
460
|
-
const lines = [];
|
|
461
|
-
if (projectEntities.length > 0) {
|
|
462
|
-
const label = hasScoringCols ? `top ${projectEntities.length} by relevance` : `${projectEntities.length}`;
|
|
463
|
-
lines.push(`Project "${projectName}" memories (${label}):`);
|
|
464
|
-
for (const e of projectEntities) {
|
|
465
|
-
lines.push(formatEntity(e));
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (recentEntities.length > 0) {
|
|
469
|
-
if (lines.length > 0) lines.push('');
|
|
470
|
-
lines.push('Recent memories:');
|
|
471
|
-
for (const e of recentEntities) {
|
|
472
|
-
lines.push(formatEntity(e));
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
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().
|
|
480
|
-
if (lines.length === 0) {
|
|
481
|
-
const bannerOnly = combineWithBanner('');
|
|
482
|
-
if (bannerOnly && bannerOnly.trim().length > 0) {
|
|
483
|
-
output(bannerOnly.trim());
|
|
484
|
-
}
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
let memorySummary = lines.join('\n');
|
|
489
|
-
|
|
490
|
-
// --- Proactive lesson warnings ---
|
|
510
|
+
// Lesson count (queried for summary, not listed individually).
|
|
511
|
+
// Status-column gate matches the project/recent queries above —
|
|
512
|
+
// legacy v2.11 schemas don't have e.status and would otherwise
|
|
513
|
+
// throw `no such column: status`, hiding lessons from session-start
|
|
514
|
+
// auto-context indefinitely.
|
|
515
|
+
let lessonCount = 0;
|
|
491
516
|
try {
|
|
492
|
-
const
|
|
493
|
-
SELECT DISTINCT e.id, e.
|
|
517
|
+
const lessonRows = db.prepare(`
|
|
518
|
+
SELECT DISTINCT e.id, e.metadata
|
|
494
519
|
FROM entities e
|
|
495
520
|
JOIN tags t ON t.entity_id = e.id
|
|
496
521
|
WHERE e.type = 'lesson_learned'
|
|
497
|
-
AND e.status = 'active'
|
|
522
|
+
${hasStatus ? "AND e.status = 'active'" : ''}
|
|
498
523
|
AND t.tag = ?
|
|
499
|
-
|
|
500
|
-
CASE WHEN e.access_count IS NULL THEN 0 ELSE e.access_count END DESC
|
|
501
|
-
LIMIT 15
|
|
524
|
+
LIMIT 50
|
|
502
525
|
`).all(projectTag).filter(entity => isTrustedForAutoContext(entity.metadata));
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const allObs = db.prepare(
|
|
509
|
-
'SELECT content FROM observations WHERE entity_id = ? ORDER BY id'
|
|
510
|
-
).all(lesson.id);
|
|
511
|
-
|
|
512
|
-
// Find the Prevention line
|
|
513
|
-
const prevention = allObs.find(o => o.content.startsWith('Prevention:'));
|
|
514
|
-
const display = prevention
|
|
515
|
-
? prevention.content.replace(/^Prevention:\s*/, '')
|
|
516
|
-
: (allObs[allObs.length - 1]?.content || lesson.name);
|
|
517
|
-
|
|
518
|
-
const conf = typeof lesson.confidence === 'number' ? lesson.confidence.toFixed(1) : '1.0';
|
|
519
|
-
memorySummary += `• ${display} (confidence: ${conf})\n`;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
} catch {
|
|
523
|
-
// Lesson query failed — don't break session start
|
|
526
|
+
lessonCount = lessonRows.length;
|
|
527
|
+
} catch (err) {
|
|
528
|
+
// Real query bug (typo, missing column on a schema older than v2.11)
|
|
529
|
+
// — surface to stderr so a maintainer sees it on next session.
|
|
530
|
+
try { process.stderr.write(`[memesh session-start] lesson query: ${err?.message || err}\n`); } catch {}
|
|
524
531
|
}
|
|
525
532
|
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
'Strategic work → stay foreground. Skill: agentic-orchestration.';
|
|
547
|
-
|
|
548
|
-
// Local-only telemetry — never networked. Hook writes the JSONL
|
|
549
|
-
// line directly to keep itself self-contained (no dynamic import
|
|
550
|
-
// of compiled TS). Only fires when the user has opted in above.
|
|
551
|
-
try {
|
|
552
|
-
const usagePath = join(homedir(), '.memesh', 'skill-usage.jsonl');
|
|
553
|
-
// Hash the cwd so distinct-project counting still works without
|
|
554
|
-
// persisting any path fragment. SHA-256 → first 16 hex chars =
|
|
555
|
-
// 64 bits, plenty to distinguish projects on one machine.
|
|
556
|
-
const cwd = String(data?.cwd || process.cwd());
|
|
557
|
-
const cwdHashed = createHash('sha256').update(cwd).digest('hex').slice(0, 16);
|
|
558
|
-
const line = JSON.stringify({
|
|
559
|
-
ts: new Date().toISOString(),
|
|
560
|
-
event: 'agentic_orchestration_banner_injected',
|
|
561
|
-
payload: { cwd_hashed: cwdHashed },
|
|
562
|
-
}) + '\n';
|
|
563
|
-
appendFileSync(usagePath, line);
|
|
564
|
-
// Tighten mode — telemetry includes timestamps + per-project
|
|
565
|
-
// hashed cwd which can profile user activity. Other local
|
|
566
|
-
// users on a shared system should not be able to read it.
|
|
567
|
-
try { chmodSync(usagePath, 0o600); } catch { /* non-POSIX */ }
|
|
568
|
-
} catch { /* swallow — telemetry must not break session-start */ }
|
|
569
|
-
} catch {
|
|
570
|
-
// Banner failed — non-critical, continue
|
|
533
|
+
// Build single-line summary with mid-dot separators. Earlier this
|
|
534
|
+
// was a multi-line tree (├─ / └─); switched to a one-liner so the
|
|
535
|
+
// SessionStart system message takes one row in the Claude Code
|
|
536
|
+
// log instead of four. Counts are count-only — no entity bullets.
|
|
537
|
+
const projectCount = projectEntities.length;
|
|
538
|
+
const recentCount = recentEntities.length;
|
|
539
|
+
const memoryFragments = [];
|
|
540
|
+
if (projectCount > 0) memoryFragments.push(`${projectCount} project`);
|
|
541
|
+
if (recentCount > 0) memoryFragments.push(`${recentCount} recent`);
|
|
542
|
+
|
|
543
|
+
let summary;
|
|
544
|
+
if (memoryFragments.length === 0 && lessonCount === 0) {
|
|
545
|
+
summary = `◉ MeMesh ready · no memories for "${projectName}" yet`;
|
|
546
|
+
} else {
|
|
547
|
+
const parts = ['◉ MeMesh'];
|
|
548
|
+
if (memoryFragments.length > 0) {
|
|
549
|
+
parts.push(`${memoryFragments.join(' + ')} memories`);
|
|
550
|
+
}
|
|
551
|
+
if (lessonCount > 0) {
|
|
552
|
+
parts.push(`${lessonCount} active lesson${lessonCount === 1 ? '' : 's'}`);
|
|
571
553
|
}
|
|
554
|
+
summary = parts.join(' · ');
|
|
572
555
|
}
|
|
573
556
|
|
|
574
557
|
// --- Record injected entity IDs for recall effectiveness tracking ---
|
|
558
|
+
// The hit/miss tracker excludes entity names found in `injectedContext`
|
|
559
|
+
// from the "user referenced this memory" signal. With the new
|
|
560
|
+
// count-only summary we no longer surface names, so set the field to
|
|
561
|
+
// a sentinel so substring matching is a no-op (any entity name is a
|
|
562
|
+
// genuine hit).
|
|
575
563
|
try {
|
|
576
|
-
// CRITICAL: Deduplicate by entity ID (entity may appear in both project and recent lists)
|
|
577
564
|
const seenIds = new Set();
|
|
578
565
|
const allInjected = [...projectEntities, ...recentEntities].filter(e => {
|
|
579
566
|
if (seenIds.has(e.id)) return false;
|
|
@@ -585,7 +572,6 @@ process.stdin.on('end', async () => {
|
|
|
585
572
|
const sessionsDir = join(memeshDir, 'sessions');
|
|
586
573
|
ensurePrivateDir(sessionsDir);
|
|
587
574
|
|
|
588
|
-
// FIX: Use session-scoped file with unique ID (pid + timestamp)
|
|
589
575
|
const sessionId = `${process.pid}-${Date.now()}`;
|
|
590
576
|
writePrivateJson(
|
|
591
577
|
join(sessionsDir, `${sessionId}.json`),
|
|
@@ -594,8 +580,7 @@ process.stdin.on('end', async () => {
|
|
|
594
580
|
project: projectName,
|
|
595
581
|
entityIds: allInjected.map(e => e.id),
|
|
596
582
|
entityNames: allInjected.map(e => e.name),
|
|
597
|
-
|
|
598
|
-
injectedContext: memorySummary,
|
|
583
|
+
injectedContext: summary,
|
|
599
584
|
}
|
|
600
585
|
);
|
|
601
586
|
|
|
@@ -613,15 +598,35 @@ process.stdin.on('end', async () => {
|
|
|
613
598
|
}
|
|
614
599
|
} catch {}
|
|
615
600
|
}
|
|
616
|
-
} catch {
|
|
617
|
-
// Non-critical —
|
|
601
|
+
} catch (err) {
|
|
602
|
+
// Non-critical — sessions-file write powers recall-effectiveness
|
|
603
|
+
// tracking (recall_hits / recall_misses on the dashboard). If it
|
|
604
|
+
// silently breaks, the impact-score factor in core/scoring.ts
|
|
605
|
+
// converges on 0.5 (neutral) for everything. Stderr trace so a
|
|
606
|
+
// permission/serialisation regression is visible.
|
|
607
|
+
try { process.stderr.write(`[memesh session-start] sessions-write: ${err?.message || err}\n`); } catch {}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// --- Agentic-orchestration mode (opt-in) ---
|
|
611
|
+
// Banner kept short. Telemetry write preserved for protocol validation.
|
|
612
|
+
if (isAgenticOrchestrationEnabled(process.env)) {
|
|
613
|
+
summary += '\n[AO opt-in: dispatch verifiable work as background agent · skill: agentic-orchestration]';
|
|
614
|
+
try {
|
|
615
|
+
const usagePath = join(homedir(), '.memesh', 'skill-usage.jsonl');
|
|
616
|
+
const cwd = String(data?.cwd || process.cwd());
|
|
617
|
+
const cwdHashed = createHash('sha256').update(cwd).digest('hex').slice(0, 16);
|
|
618
|
+
const line = JSON.stringify({
|
|
619
|
+
ts: new Date().toISOString(),
|
|
620
|
+
event: 'agentic_orchestration_banner_injected',
|
|
621
|
+
payload: { cwd_hashed: cwdHashed },
|
|
622
|
+
}) + '\n';
|
|
623
|
+
appendFileSync(usagePath, line);
|
|
624
|
+
try { chmodSync(usagePath, 0o600); } catch { /* non-POSIX */ }
|
|
625
|
+
} catch { /* swallow — telemetry must not break session-start */ }
|
|
618
626
|
}
|
|
619
627
|
|
|
620
|
-
// Deprecation
|
|
621
|
-
//
|
|
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.
|
|
628
|
+
// Deprecation banner (security advisory) — surfaced even with the
|
|
629
|
+
// short summary so flagged installs still warn on every session.
|
|
625
630
|
let installedVersion = null;
|
|
626
631
|
try {
|
|
627
632
|
const pluginRoot = resolvePluginRoot(import.meta.url);
|
|
@@ -634,11 +639,11 @@ process.stdin.on('end', async () => {
|
|
|
634
639
|
const deprecationLines = installedVersion
|
|
635
640
|
? buildDeprecationBanner(installedVersion, updateCache)
|
|
636
641
|
: [];
|
|
637
|
-
const
|
|
638
|
-
? [...deprecationLines, '',
|
|
639
|
-
:
|
|
642
|
+
const finalMessage = deprecationLines.length > 0
|
|
643
|
+
? [...deprecationLines.filter(l => l.length > 0), '', summary].join('\n')
|
|
644
|
+
: summary;
|
|
640
645
|
|
|
641
|
-
output(
|
|
646
|
+
output(finalMessage);
|
|
642
647
|
} finally {
|
|
643
648
|
db.close();
|
|
644
649
|
}
|
|
@@ -657,8 +662,13 @@ process.stdin.on('end', async () => {
|
|
|
657
662
|
} finally {
|
|
658
663
|
dbMod.closeDatabase();
|
|
659
664
|
}
|
|
660
|
-
} catch {
|
|
661
|
-
// Non-critical — noise compression failed, will retry next session
|
|
665
|
+
} catch (err) {
|
|
666
|
+
// Non-critical — noise compression failed, will retry next session.
|
|
667
|
+
// Trace because this catch previously hid an off-by-one regression
|
|
668
|
+
// in resolvePluginRoot (4.0.4-4.1.0) that silently disabled both
|
|
669
|
+
// noise compression AND LLM failure analysis for three minor
|
|
670
|
+
// releases. A one-line stderr would have surfaced it on day 1.
|
|
671
|
+
try { process.stderr.write(`[memesh session-start] noise-compression: ${err?.message || err}\n`); } catch {}
|
|
662
672
|
}
|
|
663
673
|
|
|
664
674
|
} catch (err) {
|