engram-mcp-server 1.2.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.
Files changed (44) hide show
  1. package/README.md +645 -0
  2. package/dist/constants.d.ts +21 -0
  3. package/dist/constants.js +81 -0
  4. package/dist/database.d.ts +30 -0
  5. package/dist/database.js +134 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.js +67 -0
  8. package/dist/migrations.d.ts +4 -0
  9. package/dist/migrations.js +342 -0
  10. package/dist/scripts/install-hooks.d.ts +3 -0
  11. package/dist/scripts/install-hooks.js +89 -0
  12. package/dist/tools/intelligence.d.ts +3 -0
  13. package/dist/tools/intelligence.js +427 -0
  14. package/dist/tools/maintenance.d.ts +3 -0
  15. package/dist/tools/maintenance.js +646 -0
  16. package/dist/tools/memory.d.ts +3 -0
  17. package/dist/tools/memory.js +446 -0
  18. package/dist/tools/scheduler.d.ts +3 -0
  19. package/dist/tools/scheduler.js +363 -0
  20. package/dist/tools/sessions.d.ts +3 -0
  21. package/dist/tools/sessions.js +355 -0
  22. package/dist/tools/tasks.d.ts +3 -0
  23. package/dist/tools/tasks.js +206 -0
  24. package/dist/types.d.ts +170 -0
  25. package/dist/types.js +5 -0
  26. package/dist/utils.d.ts +58 -0
  27. package/dist/utils.js +190 -0
  28. package/docs/scheduled-events.md +150 -0
  29. package/package.json +43 -0
  30. package/scripts/install-mcp.js +175 -0
  31. package/src/constants.ts +86 -0
  32. package/src/database.ts +162 -0
  33. package/src/index.ts +79 -0
  34. package/src/migrations.ts +367 -0
  35. package/src/scripts/install-hooks.ts +96 -0
  36. package/src/tools/intelligence.ts +469 -0
  37. package/src/tools/maintenance.ts +783 -0
  38. package/src/tools/memory.ts +543 -0
  39. package/src/tools/scheduler.ts +413 -0
  40. package/src/tools/sessions.ts +430 -0
  41. package/src/tools/tasks.ts +215 -0
  42. package/src/types.ts +267 -0
  43. package/src/utils.ts +216 -0
  44. package/tsconfig.json +19 -0
package/src/types.ts ADDED
@@ -0,0 +1,267 @@
1
+ // ============================================================================
2
+ // Engram MCP Server — Type Definitions
3
+ // ============================================================================
4
+
5
+ // ─── Database Row Types ─────────────────────────────────────────────────────
6
+
7
+ export interface SessionRow {
8
+ id: number;
9
+ started_at: string;
10
+ ended_at: string | null;
11
+ summary: string | null;
12
+ agent_name: string;
13
+ project_root: string;
14
+ tags: string | null; // JSON array
15
+ parent_session_id: number | null;
16
+ }
17
+
18
+ export interface ChangeRow {
19
+ id: number;
20
+ session_id: number | null;
21
+ timestamp: string;
22
+ file_path: string;
23
+ change_type: ChangeType;
24
+ description: string;
25
+ diff_summary: string | null;
26
+ impact_scope: ImpactScope;
27
+ }
28
+
29
+ export interface DecisionRow {
30
+ id: number;
31
+ session_id: number | null;
32
+ timestamp: string;
33
+ decision: string;
34
+ rationale: string | null;
35
+ affected_files: string | null; // JSON array
36
+ tags: string | null; // JSON array
37
+ status: DecisionStatus;
38
+ superseded_by: number | null;
39
+ }
40
+
41
+ export interface FileNoteRow {
42
+ file_path: string;
43
+ purpose: string | null;
44
+ dependencies: string | null; // JSON array
45
+ dependents: string | null; // JSON array
46
+ layer: ArchLayer | null;
47
+ last_reviewed: string | null;
48
+ last_modified_session: number | null;
49
+ notes: string | null;
50
+ complexity: Complexity | null;
51
+ }
52
+
53
+ export interface ConventionRow {
54
+ id: number;
55
+ session_id: number | null;
56
+ timestamp: string;
57
+ category: ConventionCategory;
58
+ rule: string;
59
+ examples: string | null; // JSON array
60
+ enforced: boolean;
61
+ }
62
+
63
+ export interface TaskRow {
64
+ id: number;
65
+ session_id: number | null;
66
+ created_at: string;
67
+ updated_at: string;
68
+ title: string;
69
+ description: string | null;
70
+ status: TaskStatus;
71
+ priority: TaskPriority;
72
+ assigned_files: string | null; // JSON array
73
+ tags: string | null; // JSON array
74
+ completed_at: string | null;
75
+ blocked_by: string | null; // JSON array of task IDs
76
+ }
77
+
78
+ export interface SnapshotRow {
79
+ key: string;
80
+ value: string;
81
+ updated_at: string;
82
+ ttl_minutes: number | null;
83
+ }
84
+
85
+ export interface MilestoneRow {
86
+ id: number;
87
+ session_id: number | null;
88
+ timestamp: string;
89
+ title: string;
90
+ description: string | null;
91
+ version: string | null;
92
+ tags: string | null; // JSON array
93
+ }
94
+
95
+ export interface ScheduledEventRow {
96
+ id: number;
97
+ session_id: number | null;
98
+ created_at: string;
99
+ title: string;
100
+ description: string | null;
101
+ trigger_type: EventTriggerType;
102
+ trigger_value: string | null;
103
+ status: EventStatus;
104
+ triggered_at: string | null;
105
+ acknowledged_at: string | null;
106
+ requires_approval: number; // 0 or 1 (SQLite boolean)
107
+ action_summary: string | null;
108
+ action_data: string | null; // JSON
109
+ priority: TaskPriority;
110
+ tags: string | null; // JSON array
111
+ recurrence: EventRecurrence | null;
112
+ }
113
+
114
+ // ─── Enum Types ─────────────────────────────────────────────────────────────
115
+
116
+ export type ChangeType =
117
+ | "created"
118
+ | "modified"
119
+ | "deleted"
120
+ | "refactored"
121
+ | "renamed"
122
+ | "moved"
123
+ | "config_changed";
124
+
125
+ export type ImpactScope =
126
+ | "local" // Single file change
127
+ | "module" // Affects a module/package
128
+ | "cross_module"// Affects multiple modules
129
+ | "global"; // Architecture-level change
130
+
131
+ export type DecisionStatus =
132
+ | "active"
133
+ | "superseded"
134
+ | "deprecated"
135
+ | "experimental";
136
+
137
+ export type ArchLayer =
138
+ | "ui"
139
+ | "viewmodel"
140
+ | "domain"
141
+ | "data"
142
+ | "network"
143
+ | "database"
144
+ | "di"
145
+ | "util"
146
+ | "test"
147
+ | "config"
148
+ | "build"
149
+ | "other";
150
+
151
+ export type Complexity =
152
+ | "trivial"
153
+ | "simple"
154
+ | "moderate"
155
+ | "complex"
156
+ | "critical";
157
+
158
+ export type ConventionCategory =
159
+ | "naming"
160
+ | "architecture"
161
+ | "styling"
162
+ | "testing"
163
+ | "git"
164
+ | "documentation"
165
+ | "error_handling"
166
+ | "performance"
167
+ | "security"
168
+ | "other";
169
+
170
+ export type TaskStatus =
171
+ | "backlog"
172
+ | "in_progress"
173
+ | "blocked"
174
+ | "review"
175
+ | "done"
176
+ | "cancelled";
177
+
178
+ export type TaskPriority =
179
+ | "critical"
180
+ | "high"
181
+ | "medium"
182
+ | "low";
183
+
184
+ export type EventTriggerType =
185
+ | "next_session"
186
+ | "datetime"
187
+ | "task_complete"
188
+ | "manual";
189
+
190
+ export type EventStatus =
191
+ | "pending"
192
+ | "triggered"
193
+ | "acknowledged"
194
+ | "executed"
195
+ | "cancelled"
196
+ | "snoozed";
197
+
198
+ export type EventRecurrence =
199
+ | "once"
200
+ | "every_session"
201
+ | "daily"
202
+ | "weekly";
203
+
204
+ // ─── Response Types ─────────────────────────────────────────────────────────
205
+
206
+ export interface SessionContext {
207
+ session_id: number;
208
+ previous_session: {
209
+ id: number;
210
+ summary: string | null;
211
+ ended_at: string | null;
212
+ agent: string;
213
+ } | null;
214
+ changes_since_last: {
215
+ recorded: ChangeRow[];
216
+ git_log: string;
217
+ };
218
+ active_decisions: DecisionRow[];
219
+ active_conventions: ConventionRow[];
220
+ open_tasks: TaskRow[];
221
+ project_snapshot_age_minutes: number | null;
222
+ message: string;
223
+ }
224
+
225
+ export interface ProjectSnapshot {
226
+ project_root: string;
227
+ file_tree: string[];
228
+ total_files: number;
229
+ file_notes: FileNoteRow[];
230
+ recent_decisions: DecisionRow[];
231
+ active_conventions: ConventionRow[];
232
+ layer_distribution: Record<string, number>;
233
+ generated_at: string;
234
+ }
235
+
236
+ export interface MemoryStats {
237
+ total_sessions: number;
238
+ total_changes: number;
239
+ total_decisions: number;
240
+ total_file_notes: number;
241
+ total_conventions: number;
242
+ total_tasks: number;
243
+ total_milestones: number;
244
+ oldest_session: string | null;
245
+ newest_session: string | null;
246
+ most_changed_files: Array<{ file_path: string; change_count: number }>;
247
+ database_size_kb: number;
248
+ }
249
+
250
+ export interface CompactionResult {
251
+ sessions_compacted: number;
252
+ changes_summarized: number;
253
+ storage_freed_kb: number;
254
+ }
255
+
256
+ export interface ConfigRow {
257
+ key: string;
258
+ value: string;
259
+ updated_at: string;
260
+ }
261
+
262
+ export interface BackupInfo {
263
+ path: string;
264
+ size_kb: number;
265
+ created_at: string;
266
+ database_version: number;
267
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,216 @@
1
+ // ============================================================================
2
+ // Engram MCP Server — Utilities
3
+ // ============================================================================
4
+
5
+ import { execSync } from "child_process";
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import {
9
+ EXCLUDED_DIRS,
10
+ LAYER_PATTERNS,
11
+ MAX_FILE_TREE_DEPTH,
12
+ MAX_FILE_TREE_ENTRIES,
13
+ PROJECT_MARKERS,
14
+ } from "./constants.js";
15
+ import type { ArchLayer } from "./types.js";
16
+
17
+ /**
18
+ * Auto-detect the project root by walking up from cwd looking for marker files.
19
+ */
20
+ export function findProjectRoot(startDir?: string): string {
21
+ // 1. Explicit env var
22
+ if (process.env.ENGRAM_PROJECT_ROOT) return process.env.ENGRAM_PROJECT_ROOT;
23
+ if (process.env.PROJECT_ROOT) return process.env.PROJECT_ROOT;
24
+
25
+ // 2. Walk up directory tree
26
+ let dir = startDir || process.cwd();
27
+ while (dir !== path.dirname(dir)) {
28
+ for (const marker of PROJECT_MARKERS) {
29
+ if (fs.existsSync(path.join(dir, marker))) {
30
+ return dir;
31
+ }
32
+ }
33
+ dir = path.dirname(dir);
34
+ }
35
+
36
+ // 3. Fallback to cwd
37
+ return process.cwd();
38
+ }
39
+
40
+ /**
41
+ * Scan the project file tree, respecting exclusions and depth limits.
42
+ */
43
+ export function scanFileTree(
44
+ rootDir: string,
45
+ maxDepth: number = MAX_FILE_TREE_DEPTH,
46
+ maxEntries: number = MAX_FILE_TREE_ENTRIES
47
+ ): string[] {
48
+ const files: string[] = [];
49
+
50
+ // Merge .engramignore entries with the default exclusion set
51
+ const exclusions = new Set(EXCLUDED_DIRS);
52
+ try {
53
+ const ignorePath = path.join(rootDir, ".engramignore");
54
+ if (fs.existsSync(ignorePath)) {
55
+ const lines = fs.readFileSync(ignorePath, "utf-8").split("\n");
56
+ for (const line of lines) {
57
+ const trimmed = line.trim();
58
+ if (trimmed && !trimmed.startsWith("#")) {
59
+ exclusions.add(trimmed.replace(/\/$/, "")); // Strip trailing slash
60
+ }
61
+ }
62
+ }
63
+ } catch { /* .engramignore is optional */ }
64
+
65
+ function walk(dir: string, depth: number): void {
66
+ if (depth > maxDepth || files.length >= maxEntries) return;
67
+
68
+ let entries: fs.Dirent[];
69
+ try {
70
+ entries = fs.readdirSync(dir, { withFileTypes: true });
71
+ } catch {
72
+ return;
73
+ }
74
+
75
+ // Sort for deterministic output
76
+ entries.sort((a, b) => a.name.localeCompare(b.name));
77
+
78
+ for (const entry of entries) {
79
+ if (files.length >= maxEntries) break;
80
+ if (entry.name.startsWith(".") && entry.isDirectory()) continue;
81
+
82
+ if (entry.isDirectory()) {
83
+ if (exclusions.has(entry.name)) continue;
84
+ const subPath = path.relative(rootDir, path.join(dir, entry.name));
85
+ files.push(subPath + "/");
86
+ walk(path.join(dir, entry.name), depth + 1);
87
+ } else {
88
+ const relPath = path.relative(rootDir, path.join(dir, entry.name));
89
+ files.push(relPath);
90
+ }
91
+ }
92
+ }
93
+
94
+ walk(rootDir, 0);
95
+ return files;
96
+ }
97
+
98
+ /**
99
+ * Detect the architectural layer of a file based on its path.
100
+ */
101
+ export function detectLayer(filePath: string): ArchLayer {
102
+ const normalized = "/" + filePath.replace(/\\/g, "/");
103
+ for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
104
+ for (const pattern of patterns) {
105
+ if (pattern.test(normalized)) {
106
+ return layer as ArchLayer;
107
+ }
108
+ }
109
+ }
110
+ return "other";
111
+ }
112
+
113
+ /**
114
+ * Run a git command in the project root. Returns empty string on failure.
115
+ */
116
+ export function gitCommand(projectRoot: string, command: string): string {
117
+ try {
118
+ return execSync(`cd "${projectRoot}" && git ${command}`, {
119
+ encoding: "utf-8",
120
+ timeout: 10000,
121
+ stdio: ["pipe", "pipe", "pipe"],
122
+ }).trim();
123
+ } catch {
124
+ return "";
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Check if the project is a git repository.
130
+ */
131
+ export function isGitRepo(projectRoot: string): boolean {
132
+ return fs.existsSync(path.join(projectRoot, ".git"));
133
+ }
134
+
135
+ /**
136
+ * Get git log since a given timestamp.
137
+ */
138
+ export function getGitLogSince(projectRoot: string, since: string, limit: number = 50): string {
139
+ return gitCommand(
140
+ projectRoot,
141
+ `log --name-status --since="${since}" --pretty=format:"[%h] %s (%ar)" -${limit}`
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Get git diff stat (files changed, insertions, deletions).
147
+ */
148
+ export function getGitDiffStat(projectRoot: string, since: string): string {
149
+ return gitCommand(projectRoot, `diff --stat HEAD@{${since}} 2>/dev/null`);
150
+ }
151
+
152
+ /**
153
+ * Get files changed in git since a timestamp.
154
+ */
155
+ export function getGitFilesChanged(projectRoot: string, since: string): string[] {
156
+ const result = gitCommand(
157
+ projectRoot,
158
+ `log --name-only --since="${since}" --pretty=format:"" 2>/dev/null`
159
+ );
160
+ if (!result) return [];
161
+ return [...new Set(result.split("\n").filter(Boolean))];
162
+ }
163
+
164
+ /**
165
+ * Get the current git branch name.
166
+ */
167
+ export function getGitBranch(projectRoot: string): string {
168
+ return gitCommand(projectRoot, "rev-parse --abbrev-ref HEAD");
169
+ }
170
+
171
+ /**
172
+ * Get the latest git commit hash (short).
173
+ */
174
+ export function getGitHead(projectRoot: string): string {
175
+ return gitCommand(projectRoot, "rev-parse --short HEAD");
176
+ }
177
+
178
+ /**
179
+ * Safely parse a JSON string, returning a default value on failure.
180
+ */
181
+ export function safeJsonParse<T>(json: string | null | undefined, fallback: T): T {
182
+ if (!json) return fallback;
183
+ try {
184
+ return JSON.parse(json) as T;
185
+ } catch {
186
+ return fallback;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Format a response as either JSON or Markdown.
192
+ */
193
+ export function formatResponse(data: unknown, format: "json" | "markdown" = "json"): string {
194
+ if (format === "json") {
195
+ return JSON.stringify(data, null, 2);
196
+ }
197
+ // For markdown, we just pretty-print JSON in a code block
198
+ return "```json\n" + JSON.stringify(data, null, 2) + "\n```";
199
+ }
200
+
201
+ /**
202
+ * Calculate minutes between now and a given ISO timestamp.
203
+ */
204
+ export function minutesSince(isoTimestamp: string): number {
205
+ const then = new Date(isoTimestamp).getTime();
206
+ const diff = Date.now() - then;
207
+ return Math.floor(diff / 60000);
208
+ }
209
+
210
+ /**
211
+ * Truncate a string to a max length with ellipsis.
212
+ */
213
+ export function truncate(str: string, maxLength: number): string {
214
+ if (str.length <= maxLength) return str;
215
+ return str.slice(0, maxLength - 3) + "...";
216
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }