memory-journal-mcp 7.3.0 → 7.4.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.
@@ -1,4 +1,4 @@
1
- import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-ZJJD2F5T.js';
1
+ import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-P5V2VY6N.js';
2
2
  import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-WXDEVIFL.js';
3
3
  import { createRequire } from 'module';
4
4
  import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
@@ -99,6 +99,17 @@ CREATE INDEX IF NOT EXISTS idx_relationships_to ON relationships(to_entry_id);
99
99
  -- Composite covering index for getRecentEntries (WHERE deleted_at IS NULL ORDER BY timestamp DESC, id DESC)
100
100
  CREATE INDEX IF NOT EXISTS idx_memory_journal_recent ON memory_journal(deleted_at, timestamp DESC, id DESC);
101
101
 
102
+ -- Analytics snapshots for persisted digest data across server restarts
103
+ CREATE TABLE IF NOT EXISTS analytics_snapshots (
104
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
105
+ snapshot_type TEXT NOT NULL DEFAULT 'digest',
106
+ created_at TEXT DEFAULT CURRENT_TIMESTAMP,
107
+ data TEXT NOT NULL
108
+ );
109
+
110
+ CREATE INDEX IF NOT EXISTS idx_analytics_snapshots_type_created
111
+ ON analytics_snapshots(snapshot_type, created_at DESC);
112
+
102
113
  -- FTS5 full-text search index (content-sync: reads from source table, no duplicate storage)
103
114
  CREATE VIRTUAL TABLE IF NOT EXISTS fts_content USING fts5(
104
115
  content,
@@ -481,12 +492,16 @@ function rowsToEntries(tagsMgr, rows) {
481
492
  if (rows.length === 0) return [];
482
493
  const entries = rows.map((r) => {
483
494
  const p = r;
484
- return {
485
- ...p,
495
+ const { importanceScore, ...rest } = p;
496
+ const entry = {
497
+ ...rest,
486
498
  isPersonal: Boolean(p.isPersonal),
487
499
  // SQLite uses 0/1
488
- tags: []
500
+ tags: [],
501
+ // Attach importanceScore if the query computed it (importance-sorted results)
502
+ ...importanceScore !== void 0 ? { importanceScore: Math.round(importanceScore * 100) / 100 } : {}
489
503
  };
504
+ return entry;
490
505
  });
491
506
  const ids = entries.map((e) => e.id);
492
507
  const tagsMap = tagsMgr.batchGetTagsForEntries(ids);
@@ -500,6 +515,12 @@ function rowToObject(row) {
500
515
  if (typeof row !== "object") return void 0;
501
516
  return row;
502
517
  }
518
+ function queryRow(db, sql, ...params) {
519
+ return db.prepare(sql).get(...params);
520
+ }
521
+ function queryRows(db, sql, ...params) {
522
+ return db.prepare(sql).all(...params);
523
+ }
503
524
 
504
525
  // src/database/sqlite-adapter/entries/crud.ts
505
526
  function createEntry(context, input) {
@@ -671,9 +692,92 @@ function deleteEntry(context, id, permanent = false) {
671
692
  return result.changes > 0;
672
693
  }
673
694
 
695
+ // src/database/sqlite-adapter/entries/importance.ts
696
+ var IMPORTANCE_WEIGHTS = {
697
+ significance: 0.3,
698
+ relationships: 0.35,
699
+ causal: 0.2,
700
+ recency: 0.15
701
+ };
702
+ var MAX_RELATIONSHIP_SCORE_AT = 5;
703
+ var MAX_CAUSAL_SCORE_AT = 3;
704
+ var RECENCY_WINDOW_DAYS = 90;
705
+ function buildImportanceSqlExpression() {
706
+ const w = IMPORTANCE_WEIGHTS;
707
+ return `(
708
+ CASE WHEN e.significance_type IS NOT NULL THEN ${String(w.significance)} ELSE 0.0 END
709
+ + MIN(
710
+ COALESCE((SELECT COUNT(*) FROM relationships
711
+ WHERE from_entry_id = e.id OR to_entry_id = e.id), 0) * 1.0 / ${String(MAX_RELATIONSHIP_SCORE_AT)},
712
+ 1.0
713
+ ) * ${String(w.relationships)}
714
+ + MIN(
715
+ COALESCE((SELECT COUNT(*) FROM relationships
716
+ WHERE (from_entry_id = e.id OR to_entry_id = e.id)
717
+ AND relationship_type IN ('blocked_by', 'resolved', 'caused')), 0) * 1.0 / ${String(MAX_CAUSAL_SCORE_AT)},
718
+ 1.0
719
+ ) * ${String(w.causal)}
720
+ + MAX(0, 1.0 - (julianday('now') - julianday(e.timestamp)) / ${String(RECENCY_WINDOW_DAYS)}.0) * ${String(w.recency)}
721
+ )`;
722
+ }
723
+ function calculateImportance(context, entryId) {
724
+ const { db } = context;
725
+ const round2 = (n) => Math.round(n * 100) / 100;
726
+ const stmt = db.prepare(`SELECT
727
+ m.significance_type as significanceType,
728
+ m.timestamp,
729
+ (SELECT COUNT(*) FROM relationships
730
+ WHERE from_entry_id = ? OR to_entry_id = ?) AS rel_count,
731
+ (SELECT COUNT(*) FROM relationships
732
+ WHERE (from_entry_id = ? OR to_entry_id = ?)
733
+ AND relationship_type IN ('blocked_by', 'resolved', 'caused')) AS causal_count
734
+ FROM memory_journal m
735
+ WHERE m.id = ? AND m.deleted_at IS NULL`);
736
+ const row = rowToObject(stmt.get(entryId, entryId, entryId, entryId, entryId));
737
+ if (!row) {
738
+ return {
739
+ score: 0,
740
+ breakdown: { significance: 0, relationships: 0, causal: 0, recency: 0 }
741
+ };
742
+ }
743
+ const significanceType = row["significanceType"];
744
+ const timestamp = row["timestamp"];
745
+ const relCount = row["rel_count"] ?? 0;
746
+ const causalCount = row["causal_count"] ?? 0;
747
+ const significanceRaw = significanceType ? 1 : 0;
748
+ const relationshipsRaw = Math.min(relCount / MAX_RELATIONSHIP_SCORE_AT, 1);
749
+ const causalRaw = Math.min(causalCount / MAX_CAUSAL_SCORE_AT, 1);
750
+ const entryDate = new Date(timestamp);
751
+ const now = /* @__PURE__ */ new Date();
752
+ const daysSince = Math.floor((now.getTime() - entryDate.getTime()) / (1e3 * 60 * 60 * 24));
753
+ const recencyRaw = Math.max(0, 1 - daysSince / RECENCY_WINDOW_DAYS);
754
+ const w = IMPORTANCE_WEIGHTS;
755
+ const breakdown = {
756
+ significance: round2(significanceRaw * w.significance),
757
+ relationships: round2(relationshipsRaw * w.relationships),
758
+ causal: round2(causalRaw * w.causal),
759
+ recency: round2(recencyRaw * w.recency)
760
+ };
761
+ const score = round2(
762
+ significanceRaw * w.significance + relationshipsRaw * w.relationships + causalRaw * w.causal + recencyRaw * w.recency
763
+ );
764
+ return { score, breakdown };
765
+ }
766
+
674
767
  // src/database/sqlite-adapter/entries/search.ts
675
- function getRecentEntries(context, limit) {
768
+ function getRecentEntries(context, limit, sortBy = "timestamp") {
676
769
  const { db, tagsMgr } = context;
770
+ if (sortBy === "importance") {
771
+ const importanceExpr = buildImportanceSqlExpression();
772
+ const stmt2 = db.prepare(`
773
+ SELECT ${ALIASED_ENTRY_COLUMNS}, ${importanceExpr} AS importanceScore
774
+ FROM memory_journal e
775
+ WHERE e.deleted_at IS NULL
776
+ ORDER BY importanceScore DESC, e.timestamp DESC, e.id DESC LIMIT ?
777
+ `);
778
+ const rows2 = stmt2.all([limit]);
779
+ return rowsToEntries(tagsMgr, rows2);
780
+ }
677
781
  const stmt = db.prepare(`
678
782
  SELECT ${ENTRY_COLUMNS} FROM memory_journal
679
783
  WHERE deleted_at IS NULL
@@ -712,15 +816,17 @@ function searchEntries(context, queryStr, options) {
712
816
  }
713
817
  function buildSearchQuery(queryStr, options, useFts) {
714
818
  let query;
819
+ const useImportance = options?.sortBy === "importance";
820
+ const importanceCol = useImportance ? `, ${buildImportanceSqlExpression()} AS importanceScore` : "";
715
821
  if (useFts) {
716
822
  query = `
717
- SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS}
823
+ SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS}${importanceCol}
718
824
  FROM memory_journal e
719
825
  JOIN fts_content fts ON fts.rowid = e.id
720
826
  `;
721
827
  } else {
722
828
  query = `
723
- SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS}
829
+ SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS}${importanceCol}
724
830
  FROM memory_journal e
725
831
  `;
726
832
  }
@@ -789,7 +895,9 @@ function buildSearchQuery(queryStr, options, useFts) {
789
895
  if (conditions.length > 0) {
790
896
  query += ` WHERE ${conditions.join(" AND ")}`;
791
897
  }
792
- if (useFts) {
898
+ if (useImportance) {
899
+ query += ` ORDER BY importanceScore DESC, e.timestamp DESC, e.id DESC`;
900
+ } else if (useFts) {
793
901
  query += ` ORDER BY rank, e.timestamp DESC, e.id DESC`;
794
902
  } else {
795
903
  query += ` ORDER BY e.timestamp DESC, e.id DESC`;
@@ -810,8 +918,10 @@ function searchByDateRange(context, startDate, endDate, options) {
810
918
  if (!end.includes("T")) end += "T23:59:59.999Z";
811
919
  params.push(end);
812
920
  }
921
+ const useImportance = options?.sortBy === "importance";
922
+ const importanceCol = useImportance ? `, ${buildImportanceSqlExpression()} AS importanceScore` : "";
813
923
  let query = `
814
- SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS} FROM memory_journal e
924
+ SELECT DISTINCT ${ALIASED_ENTRY_COLUMNS}${importanceCol} FROM memory_journal e
815
925
  `;
816
926
  if (options?.tags && options.tags.length > 0) {
817
927
  query += `
@@ -846,7 +956,11 @@ function searchByDateRange(context, startDate, endDate, options) {
846
956
  conditions.push(`e.workflow_run_id = ?`);
847
957
  params.push(options.workflowRunId);
848
958
  }
849
- query += ` WHERE ${conditions.join(" AND ")} ORDER BY e.timestamp DESC, e.id DESC`;
959
+ if (useImportance) {
960
+ query += ` WHERE ${conditions.join(" AND ")} ORDER BY importanceScore DESC, e.timestamp DESC, e.id DESC`;
961
+ } else {
962
+ query += ` WHERE ${conditions.join(" AND ")} ORDER BY e.timestamp DESC, e.id DESC`;
963
+ }
850
964
  query += ` LIMIT ?`;
851
965
  params.push(options?.limit ?? 500);
852
966
  const stmt = db.prepare(query);
@@ -866,60 +980,6 @@ function sanitizeFtsQuery(query) {
866
980
  return query;
867
981
  }
868
982
 
869
- // src/database/sqlite-adapter/entries/importance.ts
870
- var IMPORTANCE_WEIGHTS = {
871
- significance: 0.3,
872
- relationships: 0.35,
873
- causal: 0.2,
874
- recency: 0.15
875
- };
876
- var MAX_RELATIONSHIP_SCORE_AT = 5;
877
- var MAX_CAUSAL_SCORE_AT = 3;
878
- var RECENCY_WINDOW_DAYS = 90;
879
- function calculateImportance(context, entryId) {
880
- const { db } = context;
881
- const round2 = (n) => Math.round(n * 100) / 100;
882
- const stmt = db.prepare(`SELECT
883
- m.significance_type as significanceType,
884
- m.timestamp,
885
- (SELECT COUNT(*) FROM relationships
886
- WHERE from_entry_id = ? OR to_entry_id = ?) AS rel_count,
887
- (SELECT COUNT(*) FROM relationships
888
- WHERE (from_entry_id = ? OR to_entry_id = ?)
889
- AND relationship_type IN ('blocked_by', 'resolved', 'caused')) AS causal_count
890
- FROM memory_journal m
891
- WHERE m.id = ? AND m.deleted_at IS NULL`);
892
- const row = rowToObject(stmt.get(entryId, entryId, entryId, entryId, entryId));
893
- if (!row) {
894
- return {
895
- score: 0,
896
- breakdown: { significance: 0, relationships: 0, causal: 0, recency: 0 }
897
- };
898
- }
899
- const significanceType = row["significanceType"];
900
- const timestamp = row["timestamp"];
901
- const relCount = row["rel_count"] ?? 0;
902
- const causalCount = row["causal_count"] ?? 0;
903
- const significanceRaw = significanceType ? 1 : 0;
904
- const relationshipsRaw = Math.min(relCount / MAX_RELATIONSHIP_SCORE_AT, 1);
905
- const causalRaw = Math.min(causalCount / MAX_CAUSAL_SCORE_AT, 1);
906
- const entryDate = new Date(timestamp);
907
- const now = /* @__PURE__ */ new Date();
908
- const daysSince = Math.floor((now.getTime() - entryDate.getTime()) / (1e3 * 60 * 60 * 24));
909
- const recencyRaw = Math.max(0, 1 - daysSince / RECENCY_WINDOW_DAYS);
910
- const w = IMPORTANCE_WEIGHTS;
911
- const breakdown = {
912
- significance: round2(significanceRaw * w.significance),
913
- relationships: round2(relationshipsRaw * w.relationships),
914
- causal: round2(causalRaw * w.causal),
915
- recency: round2(recencyRaw * w.recency)
916
- };
917
- const score = round2(
918
- significanceRaw * w.significance + relationshipsRaw * w.relationships + causalRaw * w.causal + recencyRaw * w.recency
919
- );
920
- return { score, breakdown };
921
- }
922
-
923
983
  // src/database/sqlite-adapter/entries/statistics.ts
924
984
  var MAX_PERIOD_ROWS = 52;
925
985
  function getStatistics(context, groupBy = "week", startDate, endDate, projectBreakdown) {
@@ -1068,11 +1128,11 @@ var EntriesManager = class {
1068
1128
  deleteEntry(id, permanent = false) {
1069
1129
  return deleteEntry(this.sharedContext, id, permanent);
1070
1130
  }
1071
- getRecentEntries(limit = 10, isPersonal) {
1131
+ getRecentEntries(limit = 10, isPersonal, sortBy) {
1072
1132
  if (isPersonal !== void 0) {
1073
- return searchEntries(this.sharedContext, "", { limit, isPersonal });
1133
+ return searchEntries(this.sharedContext, "", { limit, isPersonal, sortBy });
1074
1134
  }
1075
- return getRecentEntries(this.sharedContext, limit);
1135
+ return getRecentEntries(this.sharedContext, limit, sortBy);
1076
1136
  }
1077
1137
  getEntriesPage(offset, limit, order = "desc") {
1078
1138
  return getEntriesPage(this.sharedContext, offset, limit, order);
@@ -1242,6 +1302,143 @@ var BackupManager = class {
1242
1302
  return { restoredFrom: filename, previousEntryCount, newEntryCount };
1243
1303
  }
1244
1304
  };
1305
+
1306
+ // src/database/sqlite-adapter/entries/digest.ts
1307
+ var STALE_PROJECT_THRESHOLD_DAYS = 14;
1308
+ var PREVIEW_LENGTH = 80;
1309
+ var TOP_IMPORTANCE_COUNT = 3;
1310
+ function computeDigest(db) {
1311
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1312
+ const activityRow = queryRow(
1313
+ db,
1314
+ `SELECT
1315
+ COALESCE(SUM(CASE WHEN strftime('%Y-%m', timestamp) = strftime('%Y-%m', 'now') THEN 1 ELSE 0 END), 0) AS current_count,
1316
+ COALESCE(SUM(CASE WHEN strftime('%Y-%m', timestamp) = strftime('%Y-%m', 'now', '-1 month') THEN 1 ELSE 0 END), 0) AS previous_count
1317
+ FROM memory_journal
1318
+ WHERE deleted_at IS NULL`
1319
+ );
1320
+ const currentPeriodEntries = activityRow?.["current_count"] ?? 0;
1321
+ const previousPeriodEntries = activityRow?.["previous_count"] ?? 0;
1322
+ const activityGrowthPercent = previousPeriodEntries > 0 ? Math.round(
1323
+ (currentPeriodEntries - previousPeriodEntries) / previousPeriodEntries * 100
1324
+ ) : null;
1325
+ const sigRow = queryRow(
1326
+ db,
1327
+ `SELECT
1328
+ COALESCE(SUM(CASE
1329
+ WHEN significance_type IS NOT NULL AND strftime('%Y-%m', timestamp) = strftime('%Y-%m', 'now')
1330
+ THEN 1 ELSE 0 END), 0) AS current_significant,
1331
+ COUNT(DISTINCT strftime('%Y-%m', timestamp)) AS total_periods,
1332
+ COALESCE(SUM(CASE WHEN significance_type IS NOT NULL THEN 1 ELSE 0 END), 0) AS total_significant
1333
+ FROM memory_journal
1334
+ WHERE deleted_at IS NULL`
1335
+ );
1336
+ const currentPeriodSignificant = sigRow?.["current_significant"] ?? 0;
1337
+ const totalPeriods = sigRow?.["total_periods"] ?? 1;
1338
+ const totalSignificant = sigRow?.["total_significant"] ?? 0;
1339
+ const historicalAvgSignificant = totalPeriods > 0 ? Math.round(totalSignificant / totalPeriods * 10) / 10 : 0;
1340
+ const significanceMultiplier = historicalAvgSignificant > 0 ? Math.round(currentPeriodSignificant / historicalAvgSignificant * 10) / 10 : null;
1341
+ const staleRows = queryRows(
1342
+ db,
1343
+ `SELECT
1344
+ project_number,
1345
+ MAX(timestamp) AS last_entry_date,
1346
+ CAST(julianday('now') - julianday(MAX(timestamp)) AS INTEGER) AS days_silent
1347
+ FROM memory_journal
1348
+ WHERE deleted_at IS NULL AND project_number IS NOT NULL
1349
+ GROUP BY project_number
1350
+ HAVING days_silent > ?`,
1351
+ STALE_PROJECT_THRESHOLD_DAYS
1352
+ );
1353
+ const staleProjects = staleRows.map((r) => ({
1354
+ projectNumber: r["project_number"],
1355
+ lastEntryDate: r["last_entry_date"],
1356
+ daysSilent: r["days_silent"]
1357
+ }));
1358
+ const relDensityRow = queryRow(
1359
+ db,
1360
+ `SELECT
1361
+ COALESCE((SELECT COUNT(*) FROM relationships r
1362
+ JOIN memory_journal m ON r.from_entry_id = m.id
1363
+ WHERE strftime('%Y-%m', m.timestamp) = strftime('%Y-%m', 'now')
1364
+ ), 0) AS current_rels,
1365
+ COALESCE((SELECT COUNT(*) FROM relationships r
1366
+ JOIN memory_journal m ON r.from_entry_id = m.id
1367
+ WHERE strftime('%Y-%m', m.timestamp) = strftime('%Y-%m', 'now', '-1 month')
1368
+ ), 0) AS previous_rels`
1369
+ );
1370
+ const currentRels = relDensityRow?.["current_rels"] ?? 0;
1371
+ const previousRels = relDensityRow?.["previous_rels"] ?? 0;
1372
+ const currentRelDensity = currentPeriodEntries > 0 ? Math.round(currentRels / currentPeriodEntries * 100) / 100 : 0;
1373
+ const previousRelDensity = previousPeriodEntries > 0 ? Math.round(previousRels / previousPeriodEntries * 100) / 100 : 0;
1374
+ const importanceExpr = buildImportanceSqlExpression();
1375
+ const topRows = queryRows(
1376
+ db,
1377
+ `SELECT
1378
+ e.id,
1379
+ ${importanceExpr} AS importance_score,
1380
+ SUBSTR(e.content, 1, ${String(PREVIEW_LENGTH)}) AS preview
1381
+ FROM memory_journal e
1382
+ WHERE e.deleted_at IS NULL
1383
+ ORDER BY importance_score DESC
1384
+ LIMIT ${String(TOP_IMPORTANCE_COUNT)}`
1385
+ );
1386
+ const topImportanceEntries = topRows.map((r) => ({
1387
+ id: r["id"],
1388
+ score: Math.round(r["importance_score"] * 100) / 100,
1389
+ preview: r["preview"]
1390
+ }));
1391
+ return {
1392
+ computedAt: now,
1393
+ currentPeriodEntries,
1394
+ previousPeriodEntries,
1395
+ activityGrowthPercent,
1396
+ currentPeriodSignificant,
1397
+ historicalAvgSignificant,
1398
+ significanceMultiplier,
1399
+ staleProjects,
1400
+ currentRelDensity,
1401
+ previousRelDensity,
1402
+ topImportanceEntries
1403
+ };
1404
+ }
1405
+ function saveAnalyticsSnapshot(db, type, data) {
1406
+ const stmt = db.prepare(`INSERT INTO analytics_snapshots (snapshot_type, data) VALUES (?, ?)`);
1407
+ const result = stmt.run(type, JSON.stringify(data));
1408
+ return Number(result.lastInsertRowid);
1409
+ }
1410
+ function getLatestAnalyticsSnapshot(db, type) {
1411
+ const row = queryRow(
1412
+ db,
1413
+ `SELECT id, created_at, data FROM analytics_snapshots
1414
+ WHERE snapshot_type = ?
1415
+ ORDER BY created_at DESC
1416
+ LIMIT 1`,
1417
+ type
1418
+ );
1419
+ if (!row) return null;
1420
+ return {
1421
+ id: row["id"],
1422
+ createdAt: row["created_at"],
1423
+ data: JSON.parse(row["data"])
1424
+ };
1425
+ }
1426
+ function getAnalyticsSnapshots(db, type, limit = 10) {
1427
+ const rows = queryRows(
1428
+ db,
1429
+ `SELECT id, created_at, data FROM analytics_snapshots
1430
+ WHERE snapshot_type = ?
1431
+ ORDER BY created_at DESC
1432
+ LIMIT ?`,
1433
+ type,
1434
+ limit
1435
+ );
1436
+ return rows.map((r) => ({
1437
+ id: r["id"],
1438
+ createdAt: r["created_at"],
1439
+ data: JSON.parse(r["data"])
1440
+ }));
1441
+ }
1245
1442
  var DatabaseAdapter2 = class {
1246
1443
  connection;
1247
1444
  tagsMgr;
@@ -1282,8 +1479,8 @@ var DatabaseAdapter2 = class {
1282
1479
  calculateImportance(entryId) {
1283
1480
  return this.entriesMgr.calculateImportance(entryId);
1284
1481
  }
1285
- getRecentEntries(limit, isPersonal) {
1286
- return this.entriesMgr.getRecentEntries(limit ?? 10, isPersonal);
1482
+ getRecentEntries(limit, isPersonal, sortBy) {
1483
+ return this.entriesMgr.getRecentEntries(limit ?? 10, isPersonal, sortBy);
1287
1484
  }
1288
1485
  getEntriesPage(offset, limit) {
1289
1486
  return this.entriesMgr.getEntriesPage(offset, limit);
@@ -1392,6 +1589,15 @@ var DatabaseAdapter2 = class {
1392
1589
  executeRawQuery(sql, params) {
1393
1590
  return this.connection.exec(sql, params);
1394
1591
  }
1592
+ saveAnalyticsSnapshot(type, data) {
1593
+ return saveAnalyticsSnapshot(this.connection.getRawDb(), type, data);
1594
+ }
1595
+ getLatestAnalyticsSnapshot(type) {
1596
+ return getLatestAnalyticsSnapshot(this.connection.getRawDb(), type);
1597
+ }
1598
+ getAnalyticsSnapshots(type, limit) {
1599
+ return getAnalyticsSnapshots(this.connection.getRawDb(), type, limit);
1600
+ }
1395
1601
  };
1396
1602
 
1397
1603
  // src/database/adapter-factory.ts
@@ -2039,7 +2245,7 @@ async function fetchCopilotReviews(github, owner, repo) {
2039
2245
  return void 0;
2040
2246
  }
2041
2247
  }
2042
- var PREVIEW_LENGTH = 80;
2248
+ var PREVIEW_LENGTH2 = 80;
2043
2249
  function buildJournalContext(context, config, projectNumber) {
2044
2250
  const recentEntries = typeof projectNumber === "number" ? context.db.searchEntries("", { limit: config.entryCount, projectNumber }) : context.db.getRecentEntries(config.entryCount);
2045
2251
  const latestEntries = recentEntries.map((e) => {
@@ -2048,7 +2254,7 @@ function buildJournalContext(context, config, projectNumber) {
2048
2254
  id: e.id,
2049
2255
  timestamp: e.timestamp,
2050
2256
  type: e.entryType,
2051
- preview: content.slice(0, PREVIEW_LENGTH) + (content.length > PREVIEW_LENGTH ? "..." : "")
2257
+ preview: content.slice(0, PREVIEW_LENGTH2) + (content.length > PREVIEW_LENGTH2 ? "..." : "")
2052
2258
  };
2053
2259
  });
2054
2260
  const summaryEntries = typeof projectNumber === "number" ? context.db.searchEntries("", {
@@ -2077,7 +2283,7 @@ function buildJournalContext(context, config, projectNumber) {
2077
2283
  id: entry.id,
2078
2284
  timestamp: entry.timestamp,
2079
2285
  type: entry.entryType,
2080
- preview: c.slice(0, PREVIEW_LENGTH) + (c.length > PREVIEW_LENGTH ? "..." : "")
2286
+ preview: c.slice(0, PREVIEW_LENGTH2) + (c.length > PREVIEW_LENGTH2 ? "..." : "")
2081
2287
  };
2082
2288
  });
2083
2289
  latestSessionSummary = sessionSummaries[0];
@@ -2178,7 +2384,8 @@ function formatUserMessage(opts) {
2178
2384
  summaryPreviews,
2179
2385
  github,
2180
2386
  rulesFile,
2181
- skillsDir
2387
+ skillsDir,
2388
+ analyticsInsights
2182
2389
  } = opts;
2183
2390
  let ciDisplay = opts.ciStatus;
2184
2391
  if (github?.workflowSummary) {
@@ -2248,6 +2455,19 @@ function formatUserMessage(opts) {
2248
2455
  }
2249
2456
  const copilotRow = github?.copilotReviews ? `
2250
2457
  | **Copilot** | ${String(github.copilotReviews.reviewed)} reviewed \xB7 ${String(github.copilotReviews.approved)} approved${github.copilotReviews.changesRequested > 0 ? ` \xB7 ${String(github.copilotReviews.changesRequested)} changes requested` : ""}${github.copilotReviews.totalComments > 0 ? ` (${String(github.copilotReviews.totalComments)} comments)` : ""} |` : "";
2458
+ let analyticsRow = "";
2459
+ if (analyticsInsights) {
2460
+ const parts = [];
2461
+ parts.push(`\u{1F4C8} ${analyticsInsights.activityTrend}`);
2462
+ if (analyticsInsights.significanceSpike !== null)
2463
+ parts.push(`\u{1F525} ${analyticsInsights.significanceSpike}`);
2464
+ if (analyticsInsights.relationshipDensity !== void 0 && analyticsInsights.relationshipDensity >= 0)
2465
+ parts.push(`\u{1F517} Matrix Density: ${analyticsInsights.relationshipDensity}`);
2466
+ if (analyticsInsights.staleProjects.length > 0)
2467
+ parts.push(`\u{1F4A4} ${analyticsInsights.staleProjects.length} stale projects`);
2468
+ analyticsRow = `
2469
+ | **Analytics** | ${escapeTableCell(parts.join(" \xB7 "))} |`;
2470
+ }
2251
2471
  const summariesOutput = summaryPreviews && summaryPreviews.length > 0 ? summaryPreviews.map((s) => `
2252
2472
  | **Summary** | ${escapeTableCell(s)} |`).join("") : "";
2253
2473
  return `\u{1F4CB} **Session Context Loaded**
@@ -2258,11 +2478,56 @@ function formatUserMessage(opts) {
2258
2478
  | **CI** | ${escapeTableCell(ciDisplay)} |
2259
2479
  | **Journal** | ${totalEntries} entries |${opts.teamTotalEntries !== void 0 ? `
2260
2480
  | **Team DB** | ${opts.teamTotalEntries} entries |` : ""}
2261
- | **Latest** | ${escapeTableCell(latestPreview)} |${summariesOutput}${issuesRow}${prsRow}${milestoneRow}${insightsRow}${copilotRow}${rulesFile ? `
2481
+ | **Latest** | ${escapeTableCell(latestPreview)} |${summariesOutput}${issuesRow}${prsRow}${milestoneRow}${insightsRow}${copilotRow}${analyticsRow}${rulesFile ? `
2262
2482
  | **Rules** | ${escapeTableCell(rulesFile.name)} (${String(rulesFile.sizeKB)} KB, updated ${rulesFile.lastModified}) |` : ""}${skillsDir ? `
2263
2483
  | **Skills** | ${String(skillsDir.count)} skill${skillsDir.count !== 1 ? "s" : ""} available |` : ""}`;
2264
2484
  }
2265
2485
 
2486
+ // src/handlers/resources/core/briefing/insights-section.ts
2487
+ function buildInsightsSection(context) {
2488
+ const snapshot = resolveDigestSnapshot(context);
2489
+ if (!snapshot) return null;
2490
+ return formatDigest(snapshot);
2491
+ }
2492
+ function resolveDigestSnapshot(context) {
2493
+ const schedulerDigest = context.scheduler?.getLatestDigest?.();
2494
+ if (schedulerDigest) return schedulerDigest;
2495
+ const dbSnapshot = context.db?.getLatestAnalyticsSnapshot?.("digest");
2496
+ if (dbSnapshot) return dbSnapshot.data;
2497
+ return null;
2498
+ }
2499
+ function formatDigest(snapshot) {
2500
+ let activityTrend;
2501
+ if (snapshot.activityGrowthPercent !== null) {
2502
+ const sign = snapshot.activityGrowthPercent >= 0 ? "+" : "";
2503
+ activityTrend = `${sign}${String(snapshot.activityGrowthPercent)}% vs. last period (${String(snapshot.currentPeriodEntries)} entries)`;
2504
+ } else {
2505
+ activityTrend = `${String(snapshot.currentPeriodEntries)} entries this period (no previous data)`;
2506
+ }
2507
+ let significanceSpike = null;
2508
+ if (snapshot.currentPeriodSignificant > 0) {
2509
+ if (snapshot.significanceMultiplier !== null && snapshot.significanceMultiplier > 1.5) {
2510
+ significanceSpike = `${String(snapshot.currentPeriodSignificant)} significant entries (${String(snapshot.significanceMultiplier)}\xD7 avg)`;
2511
+ } else {
2512
+ significanceSpike = `${String(snapshot.currentPeriodSignificant)} significant entries this period`;
2513
+ }
2514
+ }
2515
+ return {
2516
+ activityTrend,
2517
+ significanceSpike,
2518
+ staleProjects: snapshot.staleProjects.map((p) => ({
2519
+ projectNumber: p.projectNumber,
2520
+ daysSilent: p.daysSilent
2521
+ })),
2522
+ topImportance: snapshot.topImportanceEntries.map((e) => ({
2523
+ id: e.id,
2524
+ score: e.score,
2525
+ preview: e.preview
2526
+ })),
2527
+ ...snapshot.currentRelDensity > 0 ? { relationshipDensity: snapshot.currentRelDensity } : {}
2528
+ };
2529
+ }
2530
+
2266
2531
  // src/handlers/resources/core/briefing/index.ts
2267
2532
  var briefingResource = {
2268
2533
  uri: "memory://briefing",
@@ -2309,6 +2574,7 @@ async function buildBriefingData(context, targetRepo) {
2309
2574
  const team = buildTeamContext(context, config, activeProjectNumber);
2310
2575
  const rulesFile = buildRulesFileInfo(config.rulesFilePath);
2311
2576
  const skillsDir = buildSkillsDirInfo(config.skillsDirPath);
2577
+ const insights = buildInsightsSection(context);
2312
2578
  const latestPreview = journal.latestEntries[0] ? `#${journal.latestEntries[0].id} (${journal.latestEntries[0].type}): ${journal.latestEntries[0].preview}` : "No entries yet";
2313
2579
  const summaryPreviews = journal.sessionSummaries ? journal.sessionSummaries.map((s) => `#${s.id} (${s.type}): ${s.preview}`) : null;
2314
2580
  const userMessage = formatUserMessage({
@@ -2321,7 +2587,8 @@ async function buildBriefingData(context, targetRepo) {
2321
2587
  github,
2322
2588
  teamTotalEntries: team?.teamInfo.totalEntries,
2323
2589
  rulesFile,
2324
- skillsDir
2590
+ skillsDir,
2591
+ analyticsInsights: insights ?? void 0
2325
2592
  });
2326
2593
  return {
2327
2594
  data: {
@@ -2337,6 +2604,7 @@ async function buildBriefingData(context, targetRepo) {
2337
2604
  ...team?.teamLatestEntries ? { teamLatestEntries: team.teamLatestEntries } : {},
2338
2605
  ...rulesFile ? { rulesFile } : {},
2339
2606
  ...skillsDir ? { skillsDir } : {},
2607
+ ...insights ? { insights } : {},
2340
2608
  ...config.projectRegistry ? { registeredWorkspaces: config.projectRegistry } : {},
2341
2609
  behaviors: {
2342
2610
  create: "implementations, decisions, bug-fixes, milestones",
@@ -2387,7 +2655,7 @@ var CORE_INSTRUCTIONS = `# memory-journal-mcp
2387
2655
  - Milestone progress (if any)
2388
2656
  - Template resources count
2389
2657
  - Registered Workspaces (if available - provides automatic repo-to-project routing)
2390
- - Optional metadata present (rulesFile, skillsDir, workflowSummary, copilotReviews, Team DB)
2658
+ - Optional metadata present (rulesFile, skillsDir, workflowSummary, copilotReviews, Team DB, insights)
2391
2659
 
2392
2660
  - **AntiGravity**: Tools are \`mcp_{name}_{tool}\` \u2192 server name = \`memory-journal-mcp\`
2393
2661
  - **Cursor**: Tools are \`user-{name}-{tool}\` \u2192 server name = \`user-memory-journal-mcp\`
@@ -2399,8 +2667,8 @@ var CORE_INSTRUCTIONS = `# memory-journal-mcp
2399
2667
 
2400
2668
  - **Personal vs Team**: **ALWAYS use the personal journal** (e.g., \`create_entry\`) by default. ONLY save to the team journal (e.g., \`team_create_entry\`) if the user explicitly requests it.
2401
2669
  - **Create entries for**: implementations, decisions, bug fixes, milestones, user requests to "remember"
2402
- - **Search before**: major decisions, referencing prior work, understanding project context
2403
- - **Analyze insights**: Use cross-project insights (\`get_cross_project_insights\`) before defining architectures or cross-cutting abstractions. Use repo insights (\`memory://github/insights\`) to gauge traction.
2670
+ - **Search before**: major decisions, referencing prior work, understanding project context. Use \`sort_by: "importance"\` on \`search_entries\`, \`get_recent_entries\`, or \`search_by_date_range\` to surface structurally significant entries (decisions, milestones, highly-connected nodes) over simply recent ones.
2671
+ - **Analyze insights**: Use cross-project insights (\`get_cross_project_insights\`) before defining architectures. Use \`team_get_collaboration_matrix\` to evaluate team health, cross-author activity patterns, and collaboration impact. Use repo insights (\`memory://github/insights\`) to gauge traction. View \`memory://insights/digest\` and \`memory://insights/team-collaboration\` for automated analytics snapshots.
2404
2672
  - **Link entries**: implementation\u2192spec, bugfix\u2192issue, followup\u2192prior work
2405
2673
 
2406
2674
  ### Rule & Skill Suggestions
@@ -2436,7 +2704,7 @@ When you notice the user consistently applies patterns, preferences, or workflow
2436
2704
 
2437
2705
  ### Native Agent Skills (NPM Distribution)
2438
2706
 
2439
- This server leverages the \`neverinfamous-agent-skills\` package. If the user's \`SKILLS_DIR_PATH\` environment variable targets these, you have native access to foundational frameworks (\`mastering-typescript\`, \`react-best-practices\`, \`playwright-standard\`, \`golang\`, \`rust\`, \`shadcn-ui\`) and the \`github-commander\` DevOps workflows (\`issue-triage\`, \`pr-review\`, etc.).
2707
+ This server leverages the \`neverinfamous-agent-skills\` package. If the user's \`SKILLS_DIR_PATH\` environment variable targets these, you have native access to foundational frameworks (\`mastering-typescript\`, \`react-best-practices\`, \`playwright-standard\`, \`golang\`, \`rust\`, \`python\`, \`docker\`, \`tailwind-css\`, \`shadcn-ui\`) and the \`github-commander\` DevOps workflows (\`issue-triage\`, \`pr-review\`, \`github-actions\`, etc.).
2440
2708
 
2441
2709
  - The user can distribute or update these skills across their repositories by running \`npx neverinfamous-agent-skills@latest\`.
2442
2710
  - If you need to create a new skill, reference the bundled \`skill-builder\` instructions!
@@ -2500,7 +2768,18 @@ var CODE_MODE_NAMESPACE_ROWS = [
2500
2768
  namespace: "`mj.relationships.*`",
2501
2769
  example: '`mj.relationships.linkEntries(1, 2, "implements")`'
2502
2770
  },
2503
- { group: "io", label: "IO", namespace: "`mj.io.*`", example: '`mj.io.exportEntries("json")`' },
2771
+ {
2772
+ group: "io",
2773
+ label: "IO",
2774
+ namespace: "`mj.io.*`",
2775
+ example: '`mj.io.importMarkdown("content")`'
2776
+ },
2777
+ {
2778
+ group: "io",
2779
+ label: "Export",
2780
+ namespace: "`mj.export.*`",
2781
+ example: '`mj.export.exportEntries("json")`'
2782
+ },
2504
2783
  {
2505
2784
  group: "admin",
2506
2785
  label: "Admin",
@@ -2553,7 +2832,8 @@ This executes JavaScript in a sandboxed environment with all tools available as
2553
2832
  | Search | \`mj.search.*\` | \`mj.search.searchEntries("performance")\` |
2554
2833
  | Analytics | \`mj.analytics.*\` | \`mj.analytics.getStatistics()\` |
2555
2834
  | Relationships | \`mj.relationships.*\` | \`mj.relationships.linkEntries(1, 2, "implements")\` |
2556
- | IO | \`mj.io.*\` | \`mj.io.exportEntries("json")\` |
2835
+ | IO | \`mj.io.*\` | \`mj.io.importMarkdown("content")\` |
2836
+ | Export | \`mj.export.*\` | \`mj.export.exportEntries("json")\` |
2557
2837
  | Admin | \`mj.admin.*\` | \`mj.admin.rebuildVectorIndex()\` |
2558
2838
  | GitHub | \`mj.github.*\` | \`mj.github.getGithubIssues({ state: "open" })\` |
2559
2839
  | Backup | \`mj.backup.*\` | \`mj.backup.backupJournal()\` |
@@ -2677,7 +2957,7 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
2677
2957
 
2678
2958
  - **Team cross-database search**: \`search_entries\` and \`search_by_date_range\` automatically merge team DB results when \`TEAM_DB_PATH\` is configured. Results include a \`source\` field ("personal" or "team").
2679
2959
  - **Team vector search**: Team has its own isolated vector index. Use \`team_rebuild_vector_index\` if the team index drifts. \`team_semantic_search\` works identically to personal \`semantic_search\`.
2680
- - **Team tools without \`TEAM_DB_PATH\`**: All 22 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
2960
+ - **Team tools without \`TEAM_DB_PATH\`**: All 23 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
2681
2961
  `;
2682
2962
  function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
2683
2963
  const groups = enabledGroups ?? getEnabledGroups(enabledTools);
@@ -2785,13 +3065,14 @@ ${entries.map((e) => `- [${String(e.id)}] ${e.content.slice(0, 100)}...`).join("
2785
3065
  const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
2786
3066
  const yesterday = new Date(Date.now() - MS_PER_DAY2).toISOString().split("T")[0] ?? "";
2787
3067
  const entries = db.searchByDateRange(yesterday, today);
3068
+ const digestSignal = buildDigestSignalForPrompt(db);
2788
3069
  return {
2789
3070
  messages: [
2790
3071
  {
2791
3072
  role: "user",
2792
3073
  content: {
2793
3074
  type: "text",
2794
- text: `Prepare a standup summary based on these recent entries:
3075
+ text: `${digestSignal}Prepare a standup summary based on these recent entries:
2795
3076
 
2796
3077
  ${entries.map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content}`).join("\n\n")}
2797
3078
 
@@ -2821,13 +3102,14 @@ Format as:
2821
3102
  const endDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
2822
3103
  const startDate = new Date(Date.now() - days * MS_PER_DAY2).toISOString().split("T")[0] ?? "";
2823
3104
  const entries = db.searchByDateRange(startDate, endDate);
3105
+ const digestSignal = buildDigestSignalForPrompt(db);
2824
3106
  return {
2825
3107
  messages: [
2826
3108
  {
2827
3109
  role: "user",
2828
3110
  content: {
2829
3111
  type: "text",
2830
- text: `Prepare a retrospective for the last ${String(days)} days based on these entries:
3112
+ text: `${digestSignal}Prepare a retrospective for the last ${String(days)} days based on these entries:
2831
3113
 
2832
3114
  ${entries.slice(0, 20).map(
2833
3115
  (e) => `[${e.timestamp}] ${e.entryType}: ${e.content.slice(0, 200)}`
@@ -3124,6 +3406,34 @@ ${entrySummary}
3124
3406
  }
3125
3407
  ];
3126
3408
  }
3409
+ function buildDigestSignalForPrompt(db) {
3410
+ const snapshot = db.getLatestAnalyticsSnapshot?.("digest");
3411
+ if (!snapshot) return "";
3412
+ const data = snapshot.data;
3413
+ const lines = ["[Analytics Context]"];
3414
+ const growth = data["activityGrowthPercent"];
3415
+ const currentEntries = data["currentPeriodEntries"];
3416
+ if (growth !== null && growth !== void 0) {
3417
+ const sign = growth >= 0 ? "+" : "";
3418
+ lines.push(
3419
+ `Activity: ${sign}${String(growth)}% vs. last period (${String(currentEntries)} entries)`
3420
+ );
3421
+ }
3422
+ const sigMultiplier = data["significanceMultiplier"];
3423
+ const sigCount = data["currentPeriodSignificant"];
3424
+ if (sigMultiplier !== null && sigMultiplier !== void 0 && sigMultiplier > 1.5) {
3425
+ lines.push(
3426
+ `Significance: ${String(sigCount)} significant entries (${String(sigMultiplier)}\xD7 historical avg)`
3427
+ );
3428
+ }
3429
+ const stale = data["staleProjects"];
3430
+ if (stale && stale.length > 0) {
3431
+ const staleStr = stale.map((p) => `P${String(p.projectNumber)} (${String(p.daysSilent)}d silent)`).join(", ");
3432
+ lines.push(`\u26A0 Stale: ${staleStr}`);
3433
+ }
3434
+ if (lines.length <= 1) return "";
3435
+ return lines.join("\\n") + "\\n\\n";
3436
+ }
3127
3437
 
3128
3438
  // src/handlers/prompts/github.ts
3129
3439
  function formatPromptEntries(entries, maxCount = 50) {
@@ -4979,7 +5289,7 @@ function getHelpResourceDefinitions() {
4979
5289
  var toolIndexModule = null;
4980
5290
  async function getAllToolDefinitionsAsync(context) {
4981
5291
  try {
4982
- toolIndexModule ??= await import('./tools-MNMGDTQI.js');
5292
+ toolIndexModule ??= await import('./tools-WZUENKJ6.js');
4983
5293
  if (toolIndexModule === null) return [];
4984
5294
  const tools = toolIndexModule.getTools(context.db, null);
4985
5295
  return tools.map((t) => ({
@@ -5056,6 +5366,163 @@ function inferGroupFromName(name) {
5056
5366
  return groupMap[name] ?? "core";
5057
5367
  }
5058
5368
 
5369
+ // src/handlers/resources/insights.ts
5370
+ var digestInsightsResource = {
5371
+ uri: "memory://insights/digest",
5372
+ name: "Analytics Digest",
5373
+ title: "Latest Analytics Digest Snapshot",
5374
+ description: "Full pre-computed analytics digest with activity trends, significance spikes, stale projects, relationship density, and top importance entries. Updated by the scheduled digest job.",
5375
+ mimeType: "application/json",
5376
+ icons: [ICON_ANALYTICS],
5377
+ annotations: {
5378
+ ...withPriority(0.5, ASSISTANT_FOCUSED)
5379
+ },
5380
+ handler: (_uri, context) => {
5381
+ const lastModified = (/* @__PURE__ */ new Date()).toISOString();
5382
+ const schedulerDigest = context.scheduler?.getLatestDigest?.();
5383
+ if (schedulerDigest) {
5384
+ return {
5385
+ data: { success: true, snapshot: schedulerDigest },
5386
+ annotations: { lastModified }
5387
+ };
5388
+ }
5389
+ const dbSnapshot = context.db?.getLatestAnalyticsSnapshot?.("digest");
5390
+ if (dbSnapshot) {
5391
+ return {
5392
+ data: {
5393
+ success: true,
5394
+ snapshot: dbSnapshot.data,
5395
+ computedAt: dbSnapshot.createdAt,
5396
+ source: "persisted"
5397
+ },
5398
+ annotations: { lastModified: dbSnapshot.createdAt }
5399
+ };
5400
+ }
5401
+ return {
5402
+ data: {
5403
+ success: true,
5404
+ snapshot: null,
5405
+ message: "No digest available \u2014 enable with --digest-interval <minutes> (HTTP transport only)"
5406
+ },
5407
+ annotations: { lastModified }
5408
+ };
5409
+ }
5410
+ };
5411
+ var teamCollaborationResource = {
5412
+ uri: "memory://insights/team-collaboration",
5413
+ name: "Team Collaboration Matrix",
5414
+ title: "Team Collaboration Insights",
5415
+ description: "Cross-author collaboration metrics: activity heatmap, cross-linking patterns, and impact factor per contributor. Requires TEAM_DB_PATH.",
5416
+ mimeType: "application/json",
5417
+ icons: [ICON_ANALYTICS],
5418
+ annotations: {
5419
+ ...withPriority(0.4, ASSISTANT_FOCUSED)
5420
+ },
5421
+ handler: (_uri, context) => {
5422
+ const lastModified = (/* @__PURE__ */ new Date()).toISOString();
5423
+ if (!context.teamDb) {
5424
+ return {
5425
+ data: {
5426
+ success: true,
5427
+ matrix: null,
5428
+ message: "Team database not configured \u2014 set TEAM_DB_PATH to enable."
5429
+ },
5430
+ annotations: { lastModified }
5431
+ };
5432
+ }
5433
+ try {
5434
+ const matrix = computeTeamCollaborationMatrix(context.teamDb);
5435
+ return {
5436
+ data: { success: true, ...matrix },
5437
+ annotations: { lastModified }
5438
+ };
5439
+ } catch (error) {
5440
+ return {
5441
+ data: {
5442
+ success: false,
5443
+ error: error instanceof Error ? error.message : String(error)
5444
+ },
5445
+ annotations: { lastModified }
5446
+ };
5447
+ }
5448
+ }
5449
+ };
5450
+ function computeTeamCollaborationMatrix(teamDb) {
5451
+ const activityRows = execQuery(
5452
+ teamDb,
5453
+ `SELECT
5454
+ COALESCE(author, 'unknown') AS author,
5455
+ strftime('%Y-%m', timestamp) AS period,
5456
+ COUNT(*) AS entry_count
5457
+ FROM memory_journal
5458
+ WHERE deleted_at IS NULL
5459
+ GROUP BY author, period
5460
+ ORDER BY period DESC, entry_count DESC
5461
+ LIMIT 100`
5462
+ );
5463
+ const authorActivity = activityRows.map((r) => ({
5464
+ author: r["author"],
5465
+ period: r["period"],
5466
+ entryCount: r["entry_count"]
5467
+ }));
5468
+ const crossLinkRows = execQuery(
5469
+ teamDb,
5470
+ `SELECT
5471
+ COALESCE(m1.author, 'unknown') AS from_author,
5472
+ COALESCE(m2.author, 'unknown') AS to_author,
5473
+ COUNT(*) AS link_count
5474
+ FROM relationships r
5475
+ JOIN memory_journal m1 ON r.from_entry_id = m1.id
5476
+ JOIN memory_journal m2 ON r.to_entry_id = m2.id
5477
+ WHERE m1.deleted_at IS NULL AND m2.deleted_at IS NULL
5478
+ AND COALESCE(m1.author, 'unknown') != COALESCE(m2.author, 'unknown')
5479
+ GROUP BY from_author, to_author
5480
+ ORDER BY link_count DESC
5481
+ LIMIT 50`
5482
+ );
5483
+ const crossAuthorLinks = crossLinkRows.map((r) => ({
5484
+ fromAuthor: r["from_author"],
5485
+ toAuthor: r["to_author"],
5486
+ linkCount: r["link_count"]
5487
+ }));
5488
+ const impactRows = execQuery(
5489
+ teamDb,
5490
+ `SELECT
5491
+ COALESCE(m2.author, 'unknown') AS author,
5492
+ COUNT(*) AS inbound_links
5493
+ FROM relationships r
5494
+ JOIN memory_journal m2 ON r.to_entry_id = m2.id
5495
+ WHERE m2.deleted_at IS NULL
5496
+ GROUP BY author
5497
+ ORDER BY inbound_links DESC
5498
+ LIMIT 20`
5499
+ );
5500
+ const impactFactor = impactRows.map((r) => ({
5501
+ author: r["author"],
5502
+ inboundLinks: r["inbound_links"]
5503
+ }));
5504
+ const totalsRow = execQuery(
5505
+ teamDb,
5506
+ `SELECT
5507
+ COUNT(DISTINCT COALESCE(author, 'unknown')) AS total_authors,
5508
+ COUNT(*) AS total_entries
5509
+ FROM memory_journal
5510
+ WHERE deleted_at IS NULL`
5511
+ );
5512
+ const totalAuthors = totalsRow[0]?.["total_authors"] ?? 0;
5513
+ const totalEntries = totalsRow[0]?.["total_entries"] ?? 0;
5514
+ return {
5515
+ authorActivity,
5516
+ crossAuthorLinks,
5517
+ impactFactor,
5518
+ totalAuthors,
5519
+ totalEntries
5520
+ };
5521
+ }
5522
+ function getInsightResourceDefinitions() {
5523
+ return [digestInsightsResource, teamCollaborationResource];
5524
+ }
5525
+
5059
5526
  // src/handlers/resources/index.ts
5060
5527
  function getResources() {
5061
5528
  const resources = getAllResourceDefinitions();
@@ -5129,6 +5596,7 @@ function getAllResourceDefinitions() {
5129
5596
  ...getTemplateResourceDefinitions(),
5130
5597
  ...getTeamResourceDefinitions(),
5131
5598
  ...getHelpResourceDefinitions(),
5599
+ ...getInsightResourceDefinitions(),
5132
5600
  // Audit resource — bound to the global audit logger (or null if unconfigured)
5133
5601
  getAuditResourceDef(getGlobalAuditLogger)
5134
5602
  ];
@@ -5182,6 +5650,12 @@ var Scheduler = class {
5182
5650
  );
5183
5651
  }
5184
5652
  }
5653
+ if (this.options.digestIntervalMinutes > 0) {
5654
+ this.scheduleJob("digest", this.options.digestIntervalMinutes, () => {
5655
+ this.runDigest();
5656
+ return Promise.resolve();
5657
+ });
5658
+ }
5185
5659
  if (this.timers.length > 0) {
5186
5660
  const summary = this.timers.map(
5187
5661
  (t) => `${t.name} (${String(t.intervalMinutes)}min)`
@@ -5323,6 +5797,38 @@ var Scheduler = class {
5323
5797
  context: { entriesIndexed: count }
5324
5798
  });
5325
5799
  }
5800
+ // ========================================================================
5801
+ // Private — Digest job
5802
+ // ========================================================================
5803
+ /**
5804
+ * Digest job: compute analytics snapshot and persist to database.
5805
+ */
5806
+ runDigest() {
5807
+ const rawDb = this.db.getRawDb();
5808
+ const snapshot = computeDigest(rawDb);
5809
+ this.db.saveAnalyticsSnapshot("digest", snapshot);
5810
+ logger.info("Scheduled analytics digest computed", {
5811
+ module: "Scheduler",
5812
+ operation: "digest",
5813
+ context: {
5814
+ currentPeriodEntries: snapshot.currentPeriodEntries,
5815
+ activityGrowthPercent: snapshot.activityGrowthPercent,
5816
+ topImportanceCount: snapshot.topImportanceEntries.length
5817
+ }
5818
+ });
5819
+ }
5820
+ // ========================================================================
5821
+ // Public — Digest accessor
5822
+ // ========================================================================
5823
+ /**
5824
+ * Get the latest digest snapshot from the database.
5825
+ * Returns null if no digest has been computed yet.
5826
+ */
5827
+ getLatestDigest() {
5828
+ const snapshot = this.db.getLatestAnalyticsSnapshot("digest");
5829
+ if (!snapshot) return null;
5830
+ return snapshot.data;
5831
+ }
5326
5832
  };
5327
5833
 
5328
5834
  // src/transports/http/types.ts
@@ -6809,7 +7315,7 @@ async function createServer(options) {
6809
7315
  }
6810
7316
  let scheduler = null;
6811
7317
  if (options.scheduler) {
6812
- const hasAnyJob = options.scheduler.backupIntervalMinutes > 0 || options.scheduler.vacuumIntervalMinutes > 0 || options.scheduler.rebuildIndexIntervalMinutes > 0;
7318
+ const hasAnyJob = options.scheduler.backupIntervalMinutes > 0 || options.scheduler.vacuumIntervalMinutes > 0 || options.scheduler.rebuildIndexIntervalMinutes > 0 || options.scheduler.digestIntervalMinutes > 0;
6813
7319
  if (hasAnyJob && transport === "stdio") {
6814
7320
  logger.warning(
6815
7321
  "Scheduler options ignored for stdio transport (session is ephemeral). Use HTTP/SSE transport for automated scheduling.",