memory-journal-mcp 7.2.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.
- package/README.md +69 -64
- package/dist/{chunk-GR4T3SRW.js → chunk-5ZA77VUW.js} +688 -121
- package/dist/{chunk-ORV7ZZOE.js → chunk-P5V2VY6N.js} +365 -74
- package/dist/{chunk-IWKLHSPU.js → chunk-WXDEVIFL.js} +6 -6
- package/dist/cli.js +9 -4
- package/dist/github-integration-YODGZH3K.js +1 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +3 -3
- package/dist/{tools-CXR2FEB2.js → tools-WZUENKJ6.js} +2 -2
- package/package.json +4 -4
- package/skills/README.md +5 -1
- package/skills/docker/SKILL.md +262 -0
- package/skills/github-actions/SKILL.md +315 -0
- package/skills/package.json +5 -1
- package/skills/python/SKILL.md +257 -0
- package/skills/tailwind-css/SKILL.md +268 -0
- package/dist/github-integration-2TFMXHIJ.js +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
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-
|
|
2
|
-
import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-
|
|
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
|
+
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';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.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
|
-
|
|
485
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
@@ -1540,7 +1746,7 @@ var VectorSearchManager = class {
|
|
|
1540
1746
|
WHERE embedding MATCH ?
|
|
1541
1747
|
ORDER BY distance
|
|
1542
1748
|
LIMIT ?`
|
|
1543
|
-
).all(queryVec, limit
|
|
1749
|
+
).all(queryVec, limit);
|
|
1544
1750
|
const filteredResults = results.map((r) => ({
|
|
1545
1751
|
entryId: r.entry_id,
|
|
1546
1752
|
score: 1 / (1 + r.distance)
|
|
@@ -1593,7 +1799,7 @@ var VectorSearchManager = class {
|
|
|
1593
1799
|
WHERE embedding MATCH ?
|
|
1594
1800
|
ORDER BY distance
|
|
1595
1801
|
LIMIT ?`
|
|
1596
|
-
).all(queryVec,
|
|
1802
|
+
).all(queryVec, limit + 1);
|
|
1597
1803
|
const filteredResults = results.filter((r) => r.entry_id !== entryId).map((r) => ({
|
|
1598
1804
|
entryId: r.entry_id,
|
|
1599
1805
|
score: 1 / (1 + r.distance)
|
|
@@ -2039,7 +2245,7 @@ async function fetchCopilotReviews(github, owner, repo) {
|
|
|
2039
2245
|
return void 0;
|
|
2040
2246
|
}
|
|
2041
2247
|
}
|
|
2042
|
-
var
|
|
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,
|
|
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,
|
|
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
|
|
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!
|
|
@@ -2476,18 +2744,71 @@ function buildQuickAccess(groups) {
|
|
|
2476
2744
|
return table;
|
|
2477
2745
|
}
|
|
2478
2746
|
var CODE_MODE_NAMESPACE_ROWS = [
|
|
2479
|
-
{
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
{
|
|
2486
|
-
|
|
2487
|
-
|
|
2747
|
+
{
|
|
2748
|
+
group: "core",
|
|
2749
|
+
label: "Core",
|
|
2750
|
+
namespace: "`mj.core.*`",
|
|
2751
|
+
example: '`mj.core.createEntry("Implemented feature X")`'
|
|
2752
|
+
},
|
|
2753
|
+
{
|
|
2754
|
+
group: "search",
|
|
2755
|
+
label: "Search",
|
|
2756
|
+
namespace: "`mj.search.*`",
|
|
2757
|
+
example: '`mj.search.searchEntries("performance")`'
|
|
2758
|
+
},
|
|
2759
|
+
{
|
|
2760
|
+
group: "analytics",
|
|
2761
|
+
label: "Analytics",
|
|
2762
|
+
namespace: "`mj.analytics.*`",
|
|
2763
|
+
example: "`mj.analytics.getStatistics()`"
|
|
2764
|
+
},
|
|
2765
|
+
{
|
|
2766
|
+
group: "relationships",
|
|
2767
|
+
label: "Relationships",
|
|
2768
|
+
namespace: "`mj.relationships.*`",
|
|
2769
|
+
example: '`mj.relationships.linkEntries(1, 2, "implements")`'
|
|
2770
|
+
},
|
|
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
|
+
},
|
|
2783
|
+
{
|
|
2784
|
+
group: "admin",
|
|
2785
|
+
label: "Admin",
|
|
2786
|
+
namespace: "`mj.admin.*`",
|
|
2787
|
+
example: "`mj.admin.rebuildVectorIndex()`"
|
|
2788
|
+
},
|
|
2789
|
+
{
|
|
2790
|
+
group: "github",
|
|
2791
|
+
label: "GitHub",
|
|
2792
|
+
namespace: "`mj.github.*`",
|
|
2793
|
+
example: '`mj.github.getGithubIssues({ state: "open" })`'
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
group: "backup",
|
|
2797
|
+
label: "Backup",
|
|
2798
|
+
namespace: "`mj.backup.*`",
|
|
2799
|
+
example: "`mj.backup.backupJournal()`"
|
|
2800
|
+
},
|
|
2801
|
+
{
|
|
2802
|
+
group: "team",
|
|
2803
|
+
label: "Team",
|
|
2804
|
+
namespace: "`mj.team.*`",
|
|
2805
|
+
example: '`mj.team.teamCreateEntry("Team update")`'
|
|
2806
|
+
}
|
|
2488
2807
|
];
|
|
2489
2808
|
function buildCodeModeInstructions(groups) {
|
|
2490
|
-
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
2809
|
+
const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
|
|
2810
|
+
(r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`
|
|
2811
|
+
).join("\n");
|
|
2491
2812
|
const fullSection = CODE_MODE_FULL_TEXT;
|
|
2492
2813
|
const tableStart = fullSection.indexOf("| Group");
|
|
2493
2814
|
const tableEnd = fullSection.indexOf("\n\n**Features**");
|
|
@@ -2511,7 +2832,8 @@ This executes JavaScript in a sandboxed environment with all tools available as
|
|
|
2511
2832
|
| Search | \`mj.search.*\` | \`mj.search.searchEntries("performance")\` |
|
|
2512
2833
|
| Analytics | \`mj.analytics.*\` | \`mj.analytics.getStatistics()\` |
|
|
2513
2834
|
| Relationships | \`mj.relationships.*\` | \`mj.relationships.linkEntries(1, 2, "implements")\` |
|
|
2514
|
-
| IO | \`mj.io.*\` | \`mj.io.
|
|
2835
|
+
| IO | \`mj.io.*\` | \`mj.io.importMarkdown("content")\` |
|
|
2836
|
+
| Export | \`mj.export.*\` | \`mj.export.exportEntries("json")\` |
|
|
2515
2837
|
| Admin | \`mj.admin.*\` | \`mj.admin.rebuildVectorIndex()\` |
|
|
2516
2838
|
| GitHub | \`mj.github.*\` | \`mj.github.getGithubIssues({ state: "open" })\` |
|
|
2517
2839
|
| Backup | \`mj.backup.*\` | \`mj.backup.backupJournal()\` |
|
|
@@ -2635,7 +2957,7 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
|
|
|
2635
2957
|
|
|
2636
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").
|
|
2637
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\`.
|
|
2638
|
-
- **Team tools without \`TEAM_DB_PATH\`**: All
|
|
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.
|
|
2639
2961
|
`;
|
|
2640
2962
|
function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
|
|
2641
2963
|
const groups = enabledGroups ?? getEnabledGroups(enabledTools);
|
|
@@ -2743,13 +3065,14 @@ ${entries.map((e) => `- [${String(e.id)}] ${e.content.slice(0, 100)}...`).join("
|
|
|
2743
3065
|
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
2744
3066
|
const yesterday = new Date(Date.now() - MS_PER_DAY2).toISOString().split("T")[0] ?? "";
|
|
2745
3067
|
const entries = db.searchByDateRange(yesterday, today);
|
|
3068
|
+
const digestSignal = buildDigestSignalForPrompt(db);
|
|
2746
3069
|
return {
|
|
2747
3070
|
messages: [
|
|
2748
3071
|
{
|
|
2749
3072
|
role: "user",
|
|
2750
3073
|
content: {
|
|
2751
3074
|
type: "text",
|
|
2752
|
-
text:
|
|
3075
|
+
text: `${digestSignal}Prepare a standup summary based on these recent entries:
|
|
2753
3076
|
|
|
2754
3077
|
${entries.map((e) => `[${e.timestamp}] ${e.entryType}: ${e.content}`).join("\n\n")}
|
|
2755
3078
|
|
|
@@ -2779,13 +3102,14 @@ Format as:
|
|
|
2779
3102
|
const endDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0] ?? "";
|
|
2780
3103
|
const startDate = new Date(Date.now() - days * MS_PER_DAY2).toISOString().split("T")[0] ?? "";
|
|
2781
3104
|
const entries = db.searchByDateRange(startDate, endDate);
|
|
3105
|
+
const digestSignal = buildDigestSignalForPrompt(db);
|
|
2782
3106
|
return {
|
|
2783
3107
|
messages: [
|
|
2784
3108
|
{
|
|
2785
3109
|
role: "user",
|
|
2786
3110
|
content: {
|
|
2787
3111
|
type: "text",
|
|
2788
|
-
text:
|
|
3112
|
+
text: `${digestSignal}Prepare a retrospective for the last ${String(days)} days based on these entries:
|
|
2789
3113
|
|
|
2790
3114
|
${entries.slice(0, 20).map(
|
|
2791
3115
|
(e) => `[${e.timestamp}] ${e.entryType}: ${e.content.slice(0, 200)}`
|
|
@@ -3082,6 +3406,34 @@ ${entrySummary}
|
|
|
3082
3406
|
}
|
|
3083
3407
|
];
|
|
3084
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
|
+
}
|
|
3085
3437
|
|
|
3086
3438
|
// src/handlers/prompts/github.ts
|
|
3087
3439
|
function formatPromptEntries(entries, maxCount = 50) {
|
|
@@ -4173,21 +4525,25 @@ function getGitHubResourceDefinitions() {
|
|
|
4173
4525
|
error: kanbanResult.reason
|
|
4174
4526
|
});
|
|
4175
4527
|
}
|
|
4176
|
-
let milestoneSummary =
|
|
4177
|
-
if (milestoneResult.status === "fulfilled"
|
|
4178
|
-
milestoneSummary =
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4528
|
+
let milestoneSummary = { openCount: 0, items: [] };
|
|
4529
|
+
if (milestoneResult.status === "fulfilled") {
|
|
4530
|
+
milestoneSummary = {
|
|
4531
|
+
openCount: milestoneResult.value.length,
|
|
4532
|
+
items: milestoneResult.value.map((ms) => {
|
|
4533
|
+
const pct = milestoneCompletionPct(ms.openIssues, ms.closedIssues);
|
|
4534
|
+
return {
|
|
4535
|
+
number: ms.number,
|
|
4536
|
+
title: ms.title,
|
|
4537
|
+
state: ms.state,
|
|
4538
|
+
openIssues: ms.openIssues,
|
|
4539
|
+
closedIssues: ms.closedIssues,
|
|
4540
|
+
completionPercentage: pct,
|
|
4541
|
+
dueOn: ms.dueOn
|
|
4542
|
+
};
|
|
4543
|
+
})
|
|
4544
|
+
};
|
|
4190
4545
|
} else if (milestoneResult.status === "rejected") {
|
|
4546
|
+
milestoneSummary = null;
|
|
4191
4547
|
logger.debug("Failed to fetch milestones", {
|
|
4192
4548
|
module: "RESOURCE",
|
|
4193
4549
|
operation: "github-status",
|
|
@@ -4933,7 +5289,7 @@ function getHelpResourceDefinitions() {
|
|
|
4933
5289
|
var toolIndexModule = null;
|
|
4934
5290
|
async function getAllToolDefinitionsAsync(context) {
|
|
4935
5291
|
try {
|
|
4936
|
-
toolIndexModule ??= await import('./tools-
|
|
5292
|
+
toolIndexModule ??= await import('./tools-WZUENKJ6.js');
|
|
4937
5293
|
if (toolIndexModule === null) return [];
|
|
4938
5294
|
const tools = toolIndexModule.getTools(context.db, null);
|
|
4939
5295
|
return tools.map((t) => ({
|
|
@@ -5010,6 +5366,163 @@ function inferGroupFromName(name) {
|
|
|
5010
5366
|
return groupMap[name] ?? "core";
|
|
5011
5367
|
}
|
|
5012
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
|
+
|
|
5013
5526
|
// src/handlers/resources/index.ts
|
|
5014
5527
|
function getResources() {
|
|
5015
5528
|
const resources = getAllResourceDefinitions();
|
|
@@ -5083,6 +5596,7 @@ function getAllResourceDefinitions() {
|
|
|
5083
5596
|
...getTemplateResourceDefinitions(),
|
|
5084
5597
|
...getTeamResourceDefinitions(),
|
|
5085
5598
|
...getHelpResourceDefinitions(),
|
|
5599
|
+
...getInsightResourceDefinitions(),
|
|
5086
5600
|
// Audit resource — bound to the global audit logger (or null if unconfigured)
|
|
5087
5601
|
getAuditResourceDef(getGlobalAuditLogger)
|
|
5088
5602
|
];
|
|
@@ -5136,6 +5650,12 @@ var Scheduler = class {
|
|
|
5136
5650
|
);
|
|
5137
5651
|
}
|
|
5138
5652
|
}
|
|
5653
|
+
if (this.options.digestIntervalMinutes > 0) {
|
|
5654
|
+
this.scheduleJob("digest", this.options.digestIntervalMinutes, () => {
|
|
5655
|
+
this.runDigest();
|
|
5656
|
+
return Promise.resolve();
|
|
5657
|
+
});
|
|
5658
|
+
}
|
|
5139
5659
|
if (this.timers.length > 0) {
|
|
5140
5660
|
const summary = this.timers.map(
|
|
5141
5661
|
(t) => `${t.name} (${String(t.intervalMinutes)}min)`
|
|
@@ -5277,6 +5797,38 @@ var Scheduler = class {
|
|
|
5277
5797
|
context: { entriesIndexed: count }
|
|
5278
5798
|
});
|
|
5279
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
|
+
}
|
|
5280
5832
|
};
|
|
5281
5833
|
|
|
5282
5834
|
// src/transports/http/types.ts
|
|
@@ -6489,15 +7041,30 @@ function registerPrompts(server, prompts, db, teamDb) {
|
|
|
6489
7041
|
...promptDef.icons ? { icons: promptDef.icons } : {}
|
|
6490
7042
|
},
|
|
6491
7043
|
(providedArgs) => {
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
7044
|
+
try {
|
|
7045
|
+
const args = providedArgs;
|
|
7046
|
+
const promptResult = getPrompt(promptDef.name, args, db, teamDb);
|
|
7047
|
+
const result = {
|
|
7048
|
+
messages: promptResult.messages.map((m) => ({
|
|
7049
|
+
role: m.role,
|
|
7050
|
+
content: m.content
|
|
7051
|
+
}))
|
|
7052
|
+
};
|
|
7053
|
+
return Promise.resolve(result);
|
|
7054
|
+
} catch (err) {
|
|
7055
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7056
|
+
return Promise.resolve({
|
|
7057
|
+
messages: [
|
|
7058
|
+
{
|
|
7059
|
+
role: "user",
|
|
7060
|
+
content: {
|
|
7061
|
+
type: "text",
|
|
7062
|
+
text: `[Prompt handler error] ${message}`
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
]
|
|
7066
|
+
});
|
|
7067
|
+
}
|
|
6501
7068
|
}
|
|
6502
7069
|
);
|
|
6503
7070
|
}
|
|
@@ -6748,7 +7315,7 @@ async function createServer(options) {
|
|
|
6748
7315
|
}
|
|
6749
7316
|
let scheduler = null;
|
|
6750
7317
|
if (options.scheduler) {
|
|
6751
|
-
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;
|
|
6752
7319
|
if (hasAnyJob && transport === "stdio") {
|
|
6753
7320
|
logger.warning(
|
|
6754
7321
|
"Scheduler options ignored for stdio transport (session is ephemeral). Use HTTP/SSE transport for automated scheduling.",
|