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.
@@ -114,7 +114,7 @@ var SIGNIFICANCE_TYPES = [
114
114
  "release"
115
115
  ];
116
116
  var MAX_CONTENT_LENGTH = 5e4;
117
- var MAX_QUERY_LIMIT = 500;
117
+ var MAX_QUERY_LIMIT = Number("500");
118
118
  var DATE_MIN_SENTINEL = "1970-01-01";
119
119
  var DATE_MAX_SENTINEL = "2999-12-31";
120
120
  var DATE_FORMAT_REGEX = /^\d{4}-\d{2}-\d{2}$/;
@@ -140,7 +140,8 @@ var EntryOutputSchema = z.object({
140
140
  workflowRunId: z.number().nullable().optional(),
141
141
  workflowName: z.string().nullable().optional(),
142
142
  workflowStatus: z.string().nullable().optional(),
143
- source: z.enum(["personal", "team"]).optional()
143
+ source: z.enum(["personal", "team"]).optional(),
144
+ importanceScore: z.number().optional().describe("Importance score (0.0-1.0), present when sort_by=importance")
144
145
  }).extend(ErrorFieldsMixin.shape);
145
146
  var EntriesListOutputSchema = z.object({
146
147
  entries: z.array(EntryOutputSchema).optional(),
@@ -217,11 +218,13 @@ var GetEntryByIdSchemaMcp = z.object({
217
218
  });
218
219
  var GetRecentEntriesSchema = z.object({
219
220
  limit: z.number().min(1).max(MAX_QUERY_LIMIT).optional().default(5),
220
- is_personal: z.boolean().optional()
221
+ is_personal: z.boolean().optional(),
222
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
221
223
  });
222
224
  var GetRecentEntriesSchemaMcp = z.object({
223
225
  limit: relaxedNumber().optional().default(5),
224
- is_personal: z.boolean().optional()
226
+ is_personal: z.boolean().optional(),
227
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
225
228
  });
226
229
  var CreateEntryMinimalSchema = z.object({
227
230
  content: z.string().min(1).max(MAX_CONTENT_LENGTH)
@@ -389,8 +392,8 @@ function getCoreTools(context) {
389
392
  annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
390
393
  handler: (params) => {
391
394
  try {
392
- const { limit, is_personal } = GetRecentEntriesSchema.parse(params);
393
- const entries = db.getRecentEntries(limit, is_personal);
395
+ const { limit, is_personal, sort_by } = GetRecentEntriesSchema.parse(params);
396
+ const entries = db.getRecentEntries(limit, is_personal, sort_by);
394
397
  return { success: true, entries, count: entries.length };
395
398
  } catch (err) {
396
399
  return formatHandlerError(err);
@@ -459,10 +462,19 @@ var DEDUP_KEY_LENGTH = 200;
459
462
  function calcPerDbLimit(limit, hasTeamDb) {
460
463
  return hasTeamDb ? Math.min(limit * 2, MAX_QUERY_LIMIT) : limit;
461
464
  }
462
- function mergeAndDedup(personal, team, limit) {
465
+ function mergeAndDedup(personal, team, limit, sortBy = "timestamp") {
463
466
  const seen = /* @__PURE__ */ new Set();
464
467
  const merged = [];
465
- const all = [...personal, ...team].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
468
+ const all = [...personal, ...team].sort((a, b) => {
469
+ if (sortBy === "importance") {
470
+ const scoreA = Number(a["importanceScore"]) || 0;
471
+ const scoreB = Number(b["importanceScore"]) || 0;
472
+ if (scoreA !== scoreB) {
473
+ return scoreB - scoreA;
474
+ }
475
+ }
476
+ return b.timestamp.localeCompare(a.timestamp);
477
+ });
466
478
  for (const entry of all) {
467
479
  const key = entry.content.slice(0, DEDUP_KEY_LENGTH);
468
480
  if (!seen.has(key)) {
@@ -536,7 +548,7 @@ function ftsSearch(query, db, teamDb, options) {
536
548
  const perDbLimit = calcPerDbLimit(options.limit, !!teamDb);
537
549
  let personalEntries;
538
550
  if (!query && !hasFilters) {
539
- personalEntries = db.getRecentEntries(perDbLimit, options.isPersonal);
551
+ personalEntries = db.getRecentEntries(perDbLimit, options.isPersonal, options.sortBy);
540
552
  } else {
541
553
  personalEntries = db.searchEntries(query || "", {
542
554
  limit: perDbLimit,
@@ -549,13 +561,14 @@ function ftsSearch(query, db, teamDb, options) {
549
561
  tags: options.tags,
550
562
  entryType: options.entryType,
551
563
  startDate: options.startDate,
552
- endDate: options.endDate
564
+ endDate: options.endDate,
565
+ sortBy: options.sortBy
553
566
  });
554
567
  }
555
568
  if (teamDb && options.isPersonal !== true) {
556
569
  let teamEntries;
557
570
  if (!query && !hasFilters) {
558
- teamEntries = teamDb.getRecentEntries(perDbLimit);
571
+ teamEntries = teamDb.getRecentEntries(perDbLimit, void 0, options.sortBy);
559
572
  } else {
560
573
  teamEntries = teamDb.searchEntries(query || "", {
561
574
  limit: perDbLimit,
@@ -567,13 +580,15 @@ function ftsSearch(query, db, teamDb, options) {
567
580
  tags: options.tags,
568
581
  entryType: options.entryType,
569
582
  startDate: options.startDate,
570
- endDate: options.endDate
583
+ endDate: options.endDate,
584
+ sortBy: options.sortBy
571
585
  });
572
586
  }
573
587
  const merged = mergeAndDedup(
574
588
  personalEntries.map((e) => ({ ...e, source: "personal" })),
575
589
  teamEntries.map((e) => ({ ...e, source: "team" })),
576
- options.limit
590
+ options.limit,
591
+ options.sortBy
577
592
  );
578
593
  return { entries: merged, count: merged.length };
579
594
  }
@@ -651,7 +666,8 @@ var SearchEntriesSchema = z.object({
651
666
  tags: z.array(z.string()).optional(),
652
667
  entry_type: z.enum(ENTRY_TYPES).optional(),
653
668
  start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
654
- end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional()
669
+ end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
670
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
655
671
  });
656
672
  var SearchEntriesSchemaMcp = z.object({
657
673
  query: z.string().optional(),
@@ -668,7 +684,8 @@ var SearchEntriesSchemaMcp = z.object({
668
684
  tags: z.array(z.string()).optional(),
669
685
  entry_type: z.string().optional(),
670
686
  start_date: z.string().optional(),
671
- end_date: z.string().optional()
687
+ end_date: z.string().optional(),
688
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
672
689
  });
673
690
  var SearchByDateRangeSchema = z.object({
674
691
  start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE),
@@ -680,7 +697,8 @@ var SearchByDateRangeSchema = z.object({
680
697
  issue_number: z.number().optional(),
681
698
  pr_number: z.number().optional(),
682
699
  workflow_run_id: z.number().optional(),
683
- limit: z.number().max(MAX_QUERY_LIMIT).optional().default(500)
700
+ limit: z.number().max(MAX_QUERY_LIMIT).optional().default(500),
701
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
684
702
  });
685
703
  var SearchByDateRangeSchemaMcp = z.object({
686
704
  start_date: z.string(),
@@ -692,7 +710,8 @@ var SearchByDateRangeSchemaMcp = z.object({
692
710
  issue_number: relaxedNumber().optional(),
693
711
  pr_number: relaxedNumber().optional(),
694
712
  workflow_run_id: relaxedNumber().optional(),
695
- limit: relaxedNumber().optional().default(500)
713
+ limit: relaxedNumber().optional().default(500),
714
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
696
715
  });
697
716
  var SemanticSearchSchema = z.object({
698
717
  query: z.string().optional(),
@@ -786,7 +805,8 @@ function getSearchTools(context) {
786
805
  tags: input.tags,
787
806
  entryType: input.entry_type,
788
807
  startDate: input.start_date,
789
- endDate: input.end_date
808
+ endDate: input.end_date,
809
+ sortBy: input.sort_by
790
810
  };
791
811
  switch (effectiveMode) {
792
812
  case "semantic": {
@@ -805,9 +825,29 @@ function getSearchTools(context) {
805
825
  const entries = semanticResults.map((r) => {
806
826
  const entry = entriesMap.get(r.entryId);
807
827
  if (!entry) return null;
808
- if (!passMetadataFilters(entry, searchOptions, db)) return null;
828
+ if (!passMetadataFilters(
829
+ entry,
830
+ searchOptions,
831
+ db
832
+ ))
833
+ return null;
809
834
  return { ...entry, source: "personal" };
810
- }).filter((e) => e !== null);
835
+ }).filter((e) => e !== null).slice(0, input.limit);
836
+ if (input.sort_by === "importance") {
837
+ const scored = entries.map((e) => {
838
+ const { score } = db.calculateImportance(e.id);
839
+ return { ...e, importanceScore: Math.round(score * 100) / 100 };
840
+ });
841
+ scored.sort(
842
+ (a, b) => (b.importanceScore ?? 0) - (a.importanceScore ?? 0)
843
+ );
844
+ return {
845
+ success: true,
846
+ entries: scored,
847
+ count: scored.length,
848
+ searchMode: isAuto ? "semantic (auto)" : "semantic"
849
+ };
850
+ }
811
851
  return {
812
852
  success: true,
813
853
  entries,
@@ -826,6 +866,22 @@ function getSearchTools(context) {
826
866
  vectorManager,
827
867
  searchOptions
828
868
  );
869
+ if (input.sort_by === "importance") {
870
+ const scored = entries.map((e) => {
871
+ const entryId = e["id"];
872
+ const { score } = db.calculateImportance(entryId);
873
+ return { ...e, importanceScore: Math.round(score * 100) / 100 };
874
+ });
875
+ scored.sort(
876
+ (a, b) => (b.importanceScore ?? 0) - (a.importanceScore ?? 0)
877
+ );
878
+ return {
879
+ success: true,
880
+ entries: scored,
881
+ count: scored.length,
882
+ searchMode: isAuto ? "hybrid (auto)" : "hybrid"
883
+ };
884
+ }
829
885
  return {
830
886
  success: true,
831
887
  entries,
@@ -878,7 +934,8 @@ function getSearchTools(context) {
878
934
  issueNumber: input.issue_number,
879
935
  prNumber: input.pr_number,
880
936
  workflowRunId: input.workflow_run_id,
881
- limit: perDbLimit
937
+ limit: perDbLimit,
938
+ sortBy: input.sort_by
882
939
  });
883
940
  if (teamDb && input.is_personal !== true) {
884
941
  const teamEntries = teamDb.searchByDateRange(
@@ -891,13 +948,15 @@ function getSearchTools(context) {
891
948
  issueNumber: input.issue_number,
892
949
  prNumber: input.pr_number,
893
950
  workflowRunId: input.workflow_run_id,
894
- limit: perDbLimit
951
+ limit: perDbLimit,
952
+ sortBy: input.sort_by
895
953
  }
896
954
  );
897
955
  const merged = mergeAndDedup(
898
956
  personalEntries.map((e) => ({ ...e, source: "personal" })),
899
957
  teamEntries.map((e) => ({ ...e, source: "team" })),
900
- input.limit
958
+ input.limit,
959
+ input.sort_by
901
960
  );
902
961
  return { success: true, entries: merged, count: merged.length };
903
962
  }
@@ -975,7 +1034,8 @@ function getSearchTools(context) {
975
1034
  startDate: input.start_date,
976
1035
  endDate: input.end_date
977
1036
  };
978
- if (!passMetadataFilters(entry, filterOptions, db)) return null;
1037
+ if (!passMetadataFilters(entry, filterOptions, db))
1038
+ return null;
979
1039
  if (input.entry_id !== void 0 && entry.id === input.entry_id)
980
1040
  return null;
981
1041
  return {
@@ -2697,7 +2757,8 @@ var CloseGitHubIssueWithEntryOutputSchema = z.object({
2697
2757
  }).optional(),
2698
2758
  kanban: z.object({
2699
2759
  moved: z.boolean(),
2700
- projectNumber: z.number(),
2760
+ projectNumber: z.number().optional(),
2761
+ error: z.string().optional(),
2701
2762
  message: z.string().optional()
2702
2763
  }).optional(),
2703
2764
  message: z.string().optional(),
@@ -4720,20 +4781,24 @@ var TeamCreateEntrySchemaMcp = z.object({
4720
4781
  author: z.string().optional()
4721
4782
  });
4722
4783
  var TeamGetRecentSchema = z.object({
4723
- limit: z.number().min(1).max(500).optional().default(10)
4784
+ limit: z.number().min(1).max(500).optional().default(10),
4785
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4724
4786
  });
4725
4787
  var TeamGetRecentSchemaMcp = z.object({
4726
- limit: relaxedNumber().optional().default(10)
4788
+ limit: relaxedNumber().optional().default(10),
4789
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4727
4790
  });
4728
4791
  var TeamSearchSchema = z.object({
4729
4792
  query: z.string().optional(),
4730
4793
  tags: z.array(z.string()).optional(),
4731
- limit: z.number().max(500).optional().default(10)
4794
+ limit: z.number().max(500).optional().default(10),
4795
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4732
4796
  });
4733
4797
  var TeamSearchSchemaMcp = z.object({
4734
4798
  query: z.string().optional(),
4735
4799
  tags: z.array(z.string()).optional(),
4736
- limit: relaxedNumber().optional().default(10)
4800
+ limit: relaxedNumber().optional().default(10),
4801
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4737
4802
  });
4738
4803
  var TeamGetEntryByIdSchema = z.object({
4739
4804
  entry_id: z.number(),
@@ -4748,14 +4813,16 @@ var TeamSearchByDateRangeSchema = z.object({
4748
4813
  end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE),
4749
4814
  entry_type: z.enum(ENTRY_TYPES).optional(),
4750
4815
  tags: z.array(z.string()).optional(),
4751
- limit: z.number().max(500).optional().default(50)
4816
+ limit: z.number().max(500).optional().default(50),
4817
+ sort_by: z.enum(["timestamp", "importance"]).optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4752
4818
  });
4753
4819
  var TeamSearchByDateRangeSchemaMcp = z.object({
4754
4820
  start_date: z.string().optional().describe("Start date (YYYY-MM-DD)"),
4755
4821
  end_date: z.string().optional().describe("End date (YYYY-MM-DD)"),
4756
4822
  entry_type: z.string().optional(),
4757
4823
  tags: z.array(z.string()).optional(),
4758
- limit: relaxedNumber().optional().default(50)
4824
+ limit: relaxedNumber().optional().default(50),
4825
+ sort_by: z.string().optional().default("timestamp").describe("Sort results by timestamp (default) or importance score")
4759
4826
  });
4760
4827
  var TeamUpdateEntrySchema = z.object({
4761
4828
  entry_id: z.number(),
@@ -5042,6 +5109,40 @@ var TeamCrossProjectInsightsOutputSchema = z.object({
5042
5109
  success: z.boolean().optional(),
5043
5110
  error: z.string().optional()
5044
5111
  }).extend(ErrorFieldsMixin.shape);
5112
+ var TeamCollaborationMatrixSchema = z.object({
5113
+ period: z.enum(["week", "month", "quarter"]).optional().default("month").describe("Time granularity for the activity heatmap"),
5114
+ limit: z.number().max(100).optional().default(20).describe("Max authors to include")
5115
+ });
5116
+ var TeamCollaborationMatrixSchemaMcp = z.object({
5117
+ period: z.string().optional().default("month"),
5118
+ limit: relaxedNumber().optional().default(20)
5119
+ });
5120
+ var TeamCollaborationMatrixOutputSchema = z.object({
5121
+ success: z.boolean().optional(),
5122
+ totalAuthors: z.number().optional(),
5123
+ totalEntries: z.number().optional(),
5124
+ authorActivity: z.array(
5125
+ z.object({
5126
+ author: z.string(),
5127
+ period: z.string(),
5128
+ entryCount: z.number()
5129
+ })
5130
+ ).optional(),
5131
+ crossAuthorLinks: z.array(
5132
+ z.object({
5133
+ fromAuthor: z.string(),
5134
+ toAuthor: z.string(),
5135
+ linkCount: z.number()
5136
+ })
5137
+ ).optional(),
5138
+ impactFactor: z.array(
5139
+ z.object({
5140
+ author: z.string(),
5141
+ inboundLinks: z.number()
5142
+ })
5143
+ ).optional(),
5144
+ error: z.string().optional()
5145
+ }).extend(ErrorFieldsMixin.shape);
5045
5146
 
5046
5147
  // src/handlers/tools/team/core-tools.ts
5047
5148
  function getTeamCoreTools(context) {
@@ -5154,8 +5255,8 @@ function getTeamCoreTools(context) {
5154
5255
  if (!teamDb) {
5155
5256
  return { ...TEAM_DB_ERROR_RESPONSE };
5156
5257
  }
5157
- const { limit } = TeamGetRecentSchema.parse(params);
5158
- const entries = teamDb.getRecentEntries(limit);
5258
+ const { limit, sort_by } = TeamGetRecentSchema.parse(params);
5259
+ const entries = teamDb.getRecentEntries(limit, void 0, sort_by);
5159
5260
  const authorMap = batchFetchAuthors(
5160
5261
  teamDb,
5161
5262
  entries.map((e) => e.id)
@@ -5211,13 +5312,16 @@ function getTeamSearchTools(context) {
5211
5312
  if (!teamDb) {
5212
5313
  return { ...TEAM_DB_ERROR_RESPONSE };
5213
5314
  }
5214
- const { query, tags, limit } = TeamSearchSchema.parse(params);
5215
- const searchLimit = tags && tags.length > 0 ? Math.max(limit * 5, 50) : limit;
5315
+ const { query, tags, limit, sort_by } = TeamSearchSchema.parse(params);
5316
+ const searchLimit = tags && tags.length > 0 ? Math.min(Math.max(limit * 5, 50), 1e3) : limit;
5216
5317
  let entries;
5217
5318
  if (query) {
5218
- entries = teamDb.searchEntries(query, { limit: searchLimit });
5319
+ entries = teamDb.searchEntries(query, {
5320
+ limit: searchLimit,
5321
+ sortBy: sort_by
5322
+ });
5219
5323
  } else {
5220
- entries = teamDb.getRecentEntries(searchLimit);
5324
+ entries = teamDb.getRecentEntries(searchLimit, void 0, sort_by);
5221
5325
  }
5222
5326
  if (tags && tags.length > 0) {
5223
5327
  const entryIds = entries.map((e) => e.id);
@@ -5271,7 +5375,7 @@ function getTeamSearchTools(context) {
5271
5375
  if (!teamDb) {
5272
5376
  return { ...TEAM_DB_ERROR_RESPONSE };
5273
5377
  }
5274
- const { start_date, end_date, entry_type, tags, limit } = TeamSearchByDateRangeSchema.parse(params);
5378
+ const { start_date, end_date, entry_type, tags, limit, sort_by } = TeamSearchByDateRangeSchema.parse(params);
5275
5379
  if (start_date > end_date) {
5276
5380
  return {
5277
5381
  success: false,
@@ -5285,7 +5389,8 @@ function getTeamSearchTools(context) {
5285
5389
  const entries = teamDb.searchByDateRange(start_date, end_date, {
5286
5390
  entryType: entry_type,
5287
5391
  tags,
5288
- limit
5392
+ limit,
5393
+ sortBy: sort_by
5289
5394
  });
5290
5395
  const authorMap = batchFetchAuthors(
5291
5396
  teamDb,
@@ -5516,6 +5621,7 @@ function getTeamAnalyticsTools(context) {
5516
5621
  );
5517
5622
  if (!projectsResult[0] || projectsResult[0].values.length === 0) {
5518
5623
  return {
5624
+ success: true,
5519
5625
  project_count: 0,
5520
5626
  total_entries: 0,
5521
5627
  projects: [],
@@ -5592,6 +5698,7 @@ function getTeamAnalyticsTools(context) {
5592
5698
  )
5593
5699
  }));
5594
5700
  return {
5701
+ success: true,
5595
5702
  project_count: projects.length,
5596
5703
  total_entries: totalEntries,
5597
5704
  projects: projects.map((p) => ({
@@ -5606,6 +5713,97 @@ function getTeamAnalyticsTools(context) {
5606
5713
  return formatHandlerError(err);
5607
5714
  }
5608
5715
  }
5716
+ },
5717
+ {
5718
+ name: "team_get_collaboration_matrix",
5719
+ title: "Team Collaboration Matrix",
5720
+ description: "Analyze cross-author collaboration: activity heatmap per period, cross-linking patterns between authors, and impact factor (inbound links). Requires TEAM_DB_PATH.",
5721
+ group: "team",
5722
+ inputSchema: TeamCollaborationMatrixSchemaMcp,
5723
+ outputSchema: TeamCollaborationMatrixOutputSchema,
5724
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
5725
+ handler: (params) => {
5726
+ try {
5727
+ if (!teamDb) {
5728
+ return { ...TEAM_DB_ERROR_RESPONSE };
5729
+ }
5730
+ const { period, limit } = TeamCollaborationMatrixSchema.parse(params);
5731
+ const dateFormat = period === "week" ? "%Y-W%W" : period === "quarter" ? "%Y-Q" : "%Y-%m";
5732
+ const activityResult = teamDb.executeRawQuery(
5733
+ `SELECT
5734
+ COALESCE(author, 'unknown') AS author,
5735
+ strftime('${dateFormat}', timestamp) AS period,
5736
+ COUNT(*) AS entry_count
5737
+ FROM memory_journal
5738
+ WHERE deleted_at IS NULL
5739
+ GROUP BY author, period
5740
+ ORDER BY period DESC, entry_count DESC
5741
+ LIMIT ?`,
5742
+ [limit * 10]
5743
+ // Up to 10 periods per author
5744
+ );
5745
+ const authorActivity = activityResult[0]?.values.map((row) => ({
5746
+ author: row[0],
5747
+ period: row[1],
5748
+ entryCount: row[2]
5749
+ })) ?? [];
5750
+ const crossLinkResult = teamDb.executeRawQuery(
5751
+ `SELECT
5752
+ COALESCE(m1.author, 'unknown') AS from_author,
5753
+ COALESCE(m2.author, 'unknown') AS to_author,
5754
+ COUNT(*) AS link_count
5755
+ FROM relationships r
5756
+ JOIN memory_journal m1 ON r.from_entry_id = m1.id
5757
+ JOIN memory_journal m2 ON r.to_entry_id = m2.id
5758
+ WHERE m1.deleted_at IS NULL AND m2.deleted_at IS NULL
5759
+ AND COALESCE(m1.author, 'unknown') != COALESCE(m2.author, 'unknown')
5760
+ GROUP BY from_author, to_author
5761
+ ORDER BY link_count DESC
5762
+ LIMIT ?`,
5763
+ [limit]
5764
+ );
5765
+ const crossAuthorLinks = crossLinkResult[0]?.values.map((row) => ({
5766
+ fromAuthor: row[0],
5767
+ toAuthor: row[1],
5768
+ linkCount: row[2]
5769
+ })) ?? [];
5770
+ const impactResult = teamDb.executeRawQuery(
5771
+ `SELECT
5772
+ COALESCE(m2.author, 'unknown') AS author,
5773
+ COUNT(*) AS inbound_links
5774
+ FROM relationships r
5775
+ JOIN memory_journal m2 ON r.to_entry_id = m2.id
5776
+ WHERE m2.deleted_at IS NULL
5777
+ GROUP BY author
5778
+ ORDER BY inbound_links DESC
5779
+ LIMIT ?`,
5780
+ [limit]
5781
+ );
5782
+ const impactFactor = impactResult[0]?.values.map((row) => ({
5783
+ author: row[0],
5784
+ inboundLinks: row[1]
5785
+ })) ?? [];
5786
+ const totalsResult = teamDb.executeRawQuery(
5787
+ `SELECT
5788
+ COUNT(DISTINCT COALESCE(author, 'unknown')) AS total_authors,
5789
+ COUNT(*) AS total_entries
5790
+ FROM memory_journal
5791
+ WHERE deleted_at IS NULL`
5792
+ );
5793
+ const totalAuthors = totalsResult[0]?.values[0]?.[0] ?? 0;
5794
+ const totalEntries = totalsResult[0]?.values[0]?.[1] ?? 0;
5795
+ return {
5796
+ success: true,
5797
+ totalAuthors,
5798
+ totalEntries,
5799
+ authorActivity,
5800
+ crossAuthorLinks,
5801
+ impactFactor
5802
+ };
5803
+ } catch (err) {
5804
+ return formatHandlerError(err);
5805
+ }
5806
+ }
5609
5807
  }
5610
5808
  ];
5611
5809
  }
@@ -7774,7 +7972,8 @@ var TOOL_GROUPS = {
7774
7972
  "team_get_vector_index_stats",
7775
7973
  "team_rebuild_vector_index",
7776
7974
  "team_add_to_vector_index",
7777
- "team_get_cross_project_insights"
7975
+ "team_get_cross_project_insights",
7976
+ "team_get_collaboration_matrix"
7778
7977
  ],
7779
7978
  codemode: ["mj_execute_code"]
7780
7979
  };
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
- import { VERSION, createServer } from './chunk-CHWIPVQN.js';
2
- import { DEFAULT_AUDIT_LOG_MAX_SIZE_BYTES } from './chunk-ZJJD2F5T.js';
1
+ import { VERSION, createServer } from './chunk-5ZA77VUW.js';
2
+ import { DEFAULT_AUDIT_LOG_MAX_SIZE_BYTES } from './chunk-P5V2VY6N.js';
3
3
  import './chunk-OKOVZ5QE.js';
4
4
  import { logger } from './chunk-WXDEVIFL.js';
5
5
  import { Command } from 'commander';
@@ -35,6 +35,10 @@ program.name("memory-journal-mcp").description("Project context management for A
35
35
  "--rebuild-index-interval <minutes>",
36
36
  "Vector index rebuild interval in minutes, HTTP only (0 = disabled)",
37
37
  "0"
38
+ ).option(
39
+ "--digest-interval <minutes>",
40
+ "Analytics digest interval in minutes, HTTP only (0 = disabled; recommended: 1440 for daily)",
41
+ "0"
38
42
  ).option(
39
43
  "--sandbox-mode <mode>",
40
44
  'Code Mode sandbox: "worker" (production, default) or "vm" (lightweight)',
@@ -150,7 +154,8 @@ program.name("memory-journal-mcp").description("Project context management for A
150
154
  backupIntervalMinutes: parseInt(options.backupInterval, 10),
151
155
  keepBackups: parseInt(options.keepBackups, 10),
152
156
  vacuumIntervalMinutes: parseInt(options.vacuumInterval, 10),
153
- rebuildIndexIntervalMinutes: parseInt(options.rebuildIndexInterval, 10)
157
+ rebuildIndexIntervalMinutes: parseInt(options.rebuildIndexInterval, 10),
158
+ digestIntervalMinutes: parseInt(options.digestInterval, 10)
154
159
  },
155
160
  sandboxMode: options.sandboxMode,
156
161
  // OAuth 2.1
package/dist/index.d.ts CHANGED
@@ -383,7 +383,7 @@ interface IDatabaseAdapter {
383
383
  getEntryByIdIncludeDeleted(id: number): JournalEntry | null;
384
384
  getEntriesByIds(ids: number[]): Map<number, JournalEntry>;
385
385
  calculateImportance(entryId: number): ImportanceResult;
386
- getRecentEntries(limit?: number, isPersonal?: boolean): JournalEntry[];
386
+ getRecentEntries(limit?: number, isPersonal?: boolean, sortBy?: 'timestamp' | 'importance'): JournalEntry[];
387
387
  getEntriesPage(offset: number, limit: number): JournalEntry[];
388
388
  getActiveEntryCount(): number;
389
389
  updateEntry(id: number, updates: {
@@ -405,6 +405,7 @@ interface IDatabaseAdapter {
405
405
  entryType?: EntryType;
406
406
  startDate?: string;
407
407
  endDate?: string;
408
+ sortBy?: 'timestamp' | 'importance';
408
409
  }): JournalEntry[];
409
410
  searchByDateRange(startDate: string, endDate: string, options?: {
410
411
  entryType?: EntryType;
@@ -415,6 +416,7 @@ interface IDatabaseAdapter {
415
416
  prNumber?: number;
416
417
  workflowRunId?: number;
417
418
  limit?: number;
419
+ sortBy?: 'timestamp' | 'importance';
418
420
  }): JournalEntry[];
419
421
  getStatistics(groupBy?: 'day' | 'week' | 'month' | 'year', startDate?: string, endDate?: string, projectBreakdown?: boolean): Record<string, unknown>;
420
422
  getTagsForEntry(entryId: number): string[];
@@ -468,6 +470,17 @@ interface IDatabaseAdapter {
468
470
  getRawDb(): unknown;
469
471
  pragma(command: string): void;
470
472
  executeRawQuery(sql: string, params?: unknown[]): QueryResult[];
473
+ saveAnalyticsSnapshot(type: string, data: Record<string, unknown>): number;
474
+ getLatestAnalyticsSnapshot(type: string): {
475
+ id: number;
476
+ createdAt: string;
477
+ data: Record<string, unknown>;
478
+ } | null;
479
+ getAnalyticsSnapshots(type: string, limit?: number): {
480
+ id: number;
481
+ createdAt: string;
482
+ data: Record<string, unknown>;
483
+ }[];
471
484
  }
472
485
 
473
486
  /**
@@ -488,6 +501,8 @@ interface SchedulerOptions {
488
501
  vacuumIntervalMinutes: number;
489
502
  /** Vector index rebuild interval in minutes (0 = disabled) */
490
503
  rebuildIndexIntervalMinutes: number;
504
+ /** Analytics digest interval in minutes (0 = disabled; recommended: 1440 for daily) */
505
+ digestIntervalMinutes: number;
491
506
  }
492
507
 
493
508
  /**
@@ -510,7 +525,7 @@ type SandboxMode = 'vm' | 'worker';
510
525
  /**
511
526
  * Tool group definitions mapping group names to tool names
512
527
  *
513
- * All 67 tools are categorized here for filtering support.
528
+ * All 68 tools are categorized here for filtering support.
514
529
  */
515
530
  declare const TOOL_GROUPS: Record<ToolGroup, string[]>;
516
531
  /**
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { VERSION, createServer } from './chunk-CHWIPVQN.js';
2
- export { META_GROUPS, TOOL_GROUPS, calculateTokenSavings, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter } from './chunk-ZJJD2F5T.js';
1
+ export { VERSION, createServer } from './chunk-5ZA77VUW.js';
2
+ export { META_GROUPS, TOOL_GROUPS, calculateTokenSavings, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter } from './chunk-P5V2VY6N.js';
3
3
  import './chunk-OKOVZ5QE.js';
4
4
  export { logger } from './chunk-WXDEVIFL.js';
5
5
 
@@ -1,3 +1,3 @@
1
- export { callTool, getGlobalAuditLogger, getTools, initializeAuditLogger } from './chunk-ZJJD2F5T.js';
1
+ export { callTool, getGlobalAuditLogger, getTools, initializeAuditLogger } from './chunk-P5V2VY6N.js';
2
2
  import './chunk-OKOVZ5QE.js';
3
3
  import './chunk-WXDEVIFL.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-journal-mcp",
3
- "version": "7.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "Project context management for AI-assisted development - Persistent knowledge graphs and intelligent context recall across fragmented AI threads",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/skills/README.md CHANGED
@@ -42,18 +42,22 @@ The markdown body contains the full instructions the agent follows once the skil
42
42
  | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
43
43
  | `autonomous-dev` | Harness for autonomous software development — alignment gates, adversarial agents, Git workflows, and CI/CD pipelines |
44
44
  | `bun` | Master the Bun all-in-one toolkit — runtime, package manager, test runner, and bundler |
45
+ | `docker` | Production-grade Docker — multi-stage builds, security hardening, Compose v2, BuildKit, and CI/CD integration |
46
+ | `github-actions` | GitHub Actions CI/CD — SHA pinning, reusable workflows, caching, matrix strategies, and artifacts v4 |
45
47
  | `github-commander` | GitHub pipeline workflows for orchestrating issues, regressions, and deployments |
46
48
  | `gitlab` | Specialized assistant skill for managing repositories, code search, and CI/CD in GitLab |
47
49
  | `golang` | Master Go development with production-grade best practices from Google and Uber style guides |
48
- | `typescript` | Enterprise-grade TypeScript development with type-safe patterns, Zod validation, and modern tooling |
49
50
  | `mysql` | Enterprise MySQL production rules — query safety, connection pooling, strict schema configurations |
50
51
  | `playwright-standard` | Opinionated guidance for Playwright E2E/API tests, Page Object Models, and CI/CD resilience |
51
52
  | `postgres` | Advanced PostgreSQL patterns — indexing layouts, JSONB querying, transactional guardrails, and RLS |
53
+ | `python` | Modern Python engineering — uv, ruff, type hints, pytest, Pydantic v2, and src/ layout project structure |
52
54
  | `react-best-practices` | Vercel engineering guidelines for React/Next.js performance, hooks, and bundle optimization |
53
55
  | `rust` | Master Rust development using a layer-based "meta-cognition" framework for borrowing, lifetimes, and architecture |
54
56
  | `shadcn-ui` | Deep knowledge of shadcn/ui components, patterns, forms, and best practices |
55
57
  | `skill-builder` | Guide for creating, evaluating, and refining agent skills — progressive disclosure, triggers, and testing |
56
58
  | `sqlite` | Production configurations for concurrency (WAL), typing (STRICT), and data integrity |
59
+ | `tailwind-css` | Tailwind CSS v4 — CSS-first configuration, @theme directive, dark mode, responsive design, and v3 migration |
60
+ | `typescript` | Enterprise-grade TypeScript development with type-safe patterns, Zod validation, and modern tooling |
57
61
  | `vitest-standard` | Comprehensive unit testing expertise covering Vitest, TDD, mocking strategies, and test architecture |
58
62
 
59
63
  ## GitHub Commander Workflows