memory-journal-mcp 6.2.1 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,11 @@
1
- import { setDefaultSandboxMode, logger, getTools, callTool, ConfigurationError, sendProgress, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, MemoryJournalMcpError, validateDateFormatPattern, DEFAULT_BRIEFING_CONFIG } from './chunk-BI4ZNSKA.js';
1
+ import { withSessionInit, withPriority, ASSISTANT_FOCUSED, TOOL_GROUPS, HIGH_PRIORITY, LOW_PRIORITY, MEDIUM_PRIORITY, setDefaultSandboxMode, initializeAuditLogger, parseToolFilter, getFilterSummary, getToolFilterFromEnv, getTools, getEnabledGroups, callTool, getGlobalAuditLogger, sendProgress, SUPPORTED_SCOPES, getRequiredScope, hasScope, getAuditResourceDef, execQuery, transformEntryRow, resolveGitHubRepo, isResourceError, milestoneCompletionPct, parseScopes, BASE_SCOPES, getAllToolNames, globalMetrics, DEFAULT_BRIEFING_CONFIG } from './chunk-2BJHLTYP.js';
2
+ import { logger, GitHubIntegration, ConfigurationError, ResourceNotFoundError, ConnectionError, QueryError, assertNoPathTraversal, ValidationError, MemoryJournalMcpError, validateDateFormatPattern } from './chunk-ARLH46WS.js';
2
3
  import { createRequire } from 'module';
3
4
  import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
4
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
6
  import DatabaseAdapter from 'better-sqlite3';
6
7
  import * as fs2 from 'fs';
7
8
  import * as path4 from 'path';
8
- import { Octokit } from '@octokit/rest';
9
- import { graphql } from '@octokit/graphql';
10
- import * as simpleGitImport from 'simple-git';
11
9
  import { fileURLToPath } from 'url';
12
10
  import express from 'express';
13
11
  import { timingSafeEqual, randomUUID } from 'crypto';
@@ -19,216 +17,6 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
19
17
  import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
20
18
  import { z } from 'zod';
21
19
 
22
- // src/filtering/tool-filter.ts
23
- var TOOL_GROUPS = {
24
- core: [
25
- "create_entry",
26
- "get_entry_by_id",
27
- "get_recent_entries",
28
- "create_entry_minimal",
29
- "test_simple",
30
- "list_tags"
31
- ],
32
- search: ["search_entries", "search_by_date_range", "semantic_search", "get_vector_index_stats"],
33
- analytics: ["get_statistics", "get_cross_project_insights"],
34
- relationships: ["link_entries", "visualize_relationships"],
35
- export: ["export_entries"],
36
- admin: [
37
- "update_entry",
38
- "delete_entry",
39
- "rebuild_vector_index",
40
- "add_to_vector_index",
41
- "merge_tags"
42
- ],
43
- github: [
44
- "get_github_issues",
45
- "get_github_prs",
46
- "get_github_issue",
47
- "get_github_pr",
48
- "get_github_context",
49
- "get_kanban_board",
50
- "move_kanban_item",
51
- "create_github_issue_with_entry",
52
- "close_github_issue_with_entry",
53
- "get_github_milestones",
54
- "get_github_milestone",
55
- "create_github_milestone",
56
- "update_github_milestone",
57
- "delete_github_milestone",
58
- "get_repo_insights",
59
- "get_copilot_reviews"
60
- ],
61
- backup: ["backup_journal", "list_backups", "restore_backup", "cleanup_backups"],
62
- team: [
63
- "team_create_entry",
64
- "team_get_entry_by_id",
65
- "team_get_recent",
66
- "team_list_tags",
67
- "team_search",
68
- "team_search_by_date_range",
69
- "team_update_entry",
70
- "team_delete_entry",
71
- "team_merge_tags",
72
- "team_get_statistics",
73
- "team_link_entries",
74
- "team_visualize_relationships",
75
- "team_export_entries",
76
- "team_backup",
77
- "team_list_backups",
78
- "team_semantic_search",
79
- "team_get_vector_index_stats",
80
- "team_rebuild_vector_index",
81
- "team_add_to_vector_index",
82
- "team_get_cross_project_insights"
83
- ],
84
- codemode: ["mj_execute_code"]
85
- };
86
- var META_GROUPS = {
87
- starter: ["core", "search", "codemode"],
88
- essential: ["core", "codemode"],
89
- full: [
90
- "core",
91
- "search",
92
- "analytics",
93
- "relationships",
94
- "export",
95
- "admin",
96
- "github",
97
- "backup",
98
- "team",
99
- "codemode"
100
- ],
101
- readonly: ["core", "search", "analytics", "relationships", "export"]
102
- };
103
- function getAllToolNames() {
104
- const allTools = [];
105
- for (const tools of Object.values(TOOL_GROUPS)) {
106
- allTools.push(...tools);
107
- }
108
- return allTools;
109
- }
110
- function getToolGroup(toolName) {
111
- for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
112
- if (tools.includes(toolName)) {
113
- return group;
114
- }
115
- }
116
- return void 0;
117
- }
118
- function getEnabledGroups(enabledTools) {
119
- const groups = /* @__PURE__ */ new Set();
120
- for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
121
- if (tools.some((t) => enabledTools.has(t))) {
122
- groups.add(group);
123
- }
124
- }
125
- return groups;
126
- }
127
- function isGroup(name) {
128
- return name in TOOL_GROUPS;
129
- }
130
- function isMetaGroup(name) {
131
- return name in META_GROUPS;
132
- }
133
- function parseToolFilter(filterString) {
134
- const rules = [];
135
- const parts = filterString.split(",").map((p) => p.trim()).filter(Boolean);
136
- let enabledTools = /* @__PURE__ */ new Set();
137
- let isWhitelistMode = false;
138
- for (let i = 0; i < parts.length; i++) {
139
- const part = parts[i];
140
- if (!part) continue;
141
- const isAdd = part.startsWith("+");
142
- const isRemove = part.startsWith("-");
143
- const name = isAdd || isRemove ? part.slice(1) : part;
144
- if (i === 0 && !isAdd && !isRemove) {
145
- isWhitelistMode = true;
146
- if (isMetaGroup(name)) {
147
- for (const group of META_GROUPS[name]) {
148
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
149
- }
150
- } else if (isGroup(name)) {
151
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
152
- } else {
153
- enabledTools.add(name);
154
- }
155
- rules.push({
156
- type: "include",
157
- target: name,
158
- isGroup: isGroup(name) || isMetaGroup(name)
159
- });
160
- } else if (isRemove) {
161
- if (isGroup(name)) {
162
- for (const tool of TOOL_GROUPS[name]) {
163
- enabledTools.delete(tool);
164
- }
165
- } else {
166
- enabledTools.delete(name);
167
- }
168
- rules.push({
169
- type: "exclude",
170
- target: name,
171
- isGroup: isGroup(name)
172
- });
173
- } else {
174
- if (isMetaGroup(name)) {
175
- for (const group of META_GROUPS[name]) {
176
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[group]]);
177
- }
178
- } else if (isGroup(name)) {
179
- enabledTools = /* @__PURE__ */ new Set([...enabledTools, ...TOOL_GROUPS[name]]);
180
- } else {
181
- enabledTools.add(name);
182
- }
183
- rules.push({
184
- type: "include",
185
- target: name,
186
- isGroup: isGroup(name) || isMetaGroup(name)
187
- });
188
- }
189
- }
190
- if (!isWhitelistMode && rules.length > 0 && rules[0]?.type === "exclude") {
191
- enabledTools = new Set(getAllToolNames());
192
- for (const rule of rules) {
193
- if (rule.type === "exclude") {
194
- if (isGroup(rule.target)) {
195
- for (const tool of TOOL_GROUPS[rule.target]) {
196
- enabledTools.delete(tool);
197
- }
198
- } else {
199
- enabledTools.delete(rule.target);
200
- }
201
- }
202
- }
203
- }
204
- return {
205
- raw: filterString,
206
- rules,
207
- enabledTools
208
- };
209
- }
210
- function isToolEnabled(toolName, filterConfig) {
211
- return filterConfig.enabledTools.has(toolName);
212
- }
213
- function filterTools(tools, filterConfig) {
214
- return tools.filter((tool) => isToolEnabled(tool.name, filterConfig));
215
- }
216
- function getToolFilterFromEnv() {
217
- const filterString = process.env["MEMORY_JOURNAL_MCP_TOOL_FILTER"];
218
- if (!filterString) return null;
219
- return parseToolFilter(filterString);
220
- }
221
- function calculateTokenSavings(totalTools, enabledTools, avgTokensPerTool = 150) {
222
- const savedTokens = (totalTools - enabledTools) * avgTokensPerTool;
223
- const reduction = (totalTools - enabledTools) / totalTools * 100;
224
- return { reduction, savedTokens };
225
- }
226
- function getFilterSummary(filterConfig) {
227
- const total = getAllToolNames().length;
228
- const enabled = filterConfig.enabledTools.size;
229
- const { reduction } = calculateTokenSavings(total, enabled);
230
- return `${enabled}/${total} tools enabled (${reduction.toFixed(0)}% reduction)`;
231
- }
232
20
  var require2 = createRequire(import.meta.url);
233
21
  var pkg = require2("../package.json");
234
22
  var VERSION = pkg.version;
@@ -423,7 +211,11 @@ var NativeConnectionManager = class {
423
211
  }
424
212
  }
425
213
  db.prepare("UPDATE tags SET usage_count = 0 WHERE usage_count IS NULL").run();
426
- const ftsCount = db.prepare("SELECT COUNT(*) as c FROM fts_content").get().c;
214
+ let ftsCount = 0;
215
+ try {
216
+ ftsCount = db.prepare("SELECT COUNT(*) as c FROM fts_content_docsize").get().c;
217
+ } catch {
218
+ }
427
219
  const entryCount = db.prepare("SELECT COUNT(*) as c FROM memory_journal").get().c;
428
220
  if (ftsCount === 0 && entryCount > 0) {
429
221
  db.exec("INSERT INTO fts_content(fts_content) VALUES ('rebuild')");
@@ -932,6 +724,12 @@ function buildSearchQuery(queryStr, options, useFts) {
932
724
  FROM memory_journal e
933
725
  `;
934
726
  }
727
+ if (options?.tags && options.tags.length > 0) {
728
+ query += `
729
+ JOIN entry_tags et ON e.id = et.entry_id
730
+ JOIN tags t ON et.tag_id = t.id
731
+ `;
732
+ }
935
733
  const params = [];
936
734
  const conditions = ["e.deleted_at IS NULL"];
937
735
  if (queryStr.length > 0) {
@@ -967,6 +765,27 @@ function buildSearchQuery(queryStr, options, useFts) {
967
765
  conditions.push(`e.workflow_run_id = ?`);
968
766
  params.push(options.workflowRunId);
969
767
  }
768
+ if (options?.tags && options.tags.length > 0) {
769
+ const placeholders = options.tags.map(() => "?").join(",");
770
+ conditions.push(`t.name IN (${placeholders})`);
771
+ params.push(...options.tags);
772
+ }
773
+ if (options?.entryType !== void 0) {
774
+ conditions.push(`e.entry_type = ?`);
775
+ params.push(options.entryType);
776
+ }
777
+ if (options?.startDate) {
778
+ let start = options.startDate;
779
+ if (!start.includes("T")) start += "T00:00:00.000Z";
780
+ conditions.push(`e.timestamp >= ?`);
781
+ params.push(start);
782
+ }
783
+ if (options?.endDate) {
784
+ let end = options.endDate;
785
+ if (!end.includes("T")) end += "T23:59:59.999Z";
786
+ conditions.push(`e.timestamp <= ?`);
787
+ params.push(end);
788
+ }
970
789
  if (conditions.length > 0) {
971
790
  query += ` WHERE ${conditions.join(" AND ")}`;
972
791
  }
@@ -1121,7 +940,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
1121
940
  const countRow = db.prepare(
1122
941
  `SELECT COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL${dateFilter}`
1123
942
  ).get(...dateParams);
1124
- totalEntries = countRow?.count ?? 0;
943
+ totalEntries = countRow.count;
1125
944
  const typeRows = db.prepare(
1126
945
  `SELECT entry_type, COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL${dateFilter} GROUP BY entry_type`
1127
946
  ).all(...dateParams);
@@ -1130,7 +949,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
1130
949
  }
1131
950
  } else {
1132
951
  const countRow = db.prepare("SELECT COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL").get();
1133
- totalEntries = countRow?.count ?? 0;
952
+ totalEntries = countRow.count;
1134
953
  const typeRows = db.prepare(
1135
954
  `SELECT entry_type, COUNT(*) as count FROM memory_journal WHERE deleted_at IS NULL GROUP BY entry_type`
1136
955
  ).all();
@@ -1167,7 +986,7 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
1167
986
  GROUP BY relationship_type
1168
987
  `
1169
988
  ).all();
1170
- const totalRelationships = relCountRow?.count ?? 0;
989
+ const totalRelationships = relCountRow.count;
1171
990
  const avgPerEntry = totalEntries > 0 ? totalRelationships / totalEntries : 0;
1172
991
  const currentPeriod = entriesByPeriod[0]?.period ?? "";
1173
992
  const previousPeriod = entriesByPeriod[1]?.period ?? "";
@@ -1196,8 +1015,8 @@ function getStatistics(context, groupBy = "week", startDate, endDate, projectBre
1196
1015
  };
1197
1016
  if (startDate || endDate) {
1198
1017
  result["dateRange"] = {
1199
- startDate: startDate ?? "",
1200
- endDate: endDate ?? ""
1018
+ startDate: startDate || "",
1019
+ endDate: endDate || ""
1201
1020
  };
1202
1021
  }
1203
1022
  if (projectBreakdown) {
@@ -1317,6 +1136,7 @@ var BackupManager = class {
1317
1136
  constructor(ctx) {
1318
1137
  this.ctx = ctx;
1319
1138
  }
1139
+ ctx;
1320
1140
  async exportToFile(backupName) {
1321
1141
  const backupsDir = this.ctx.getBackupsDir();
1322
1142
  if (backupName) {
@@ -1409,9 +1229,7 @@ var BackupManager = class {
1409
1229
  await this.exportToFile(`pre_restore_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
1410
1230
  this.ctx.closeDbBeforeRestore();
1411
1231
  await fs2.promises.copyFile(backupPath, this.ctx.getDbPath());
1412
- const DatabaseAdapter3 = await import('better-sqlite3').then((m) => m.default);
1413
- const newDb = new DatabaseAdapter3(this.ctx.getDbPath());
1414
- this.ctx.setDbAndInitialized(newDb);
1232
+ await this.ctx.initialize();
1415
1233
  const newCountResult = this.ctx.exec(
1416
1234
  "SELECT COUNT(*) FROM memory_journal WHERE deleted_at IS NULL"
1417
1235
  );
@@ -1600,9 +1418,16 @@ var VectorSearchManager = class {
1600
1418
  this.dbAdapter = dbAdapter;
1601
1419
  this.modelName = modelName;
1602
1420
  }
1421
+ dbAdapter;
1603
1422
  // Use a more flexible type since FeatureExtractionPipeline doesn't fully implement Pipeline
1604
1423
  embedder = null;
1605
- db = null;
1424
+ get db() {
1425
+ try {
1426
+ return this.dbAdapter.getRawDb();
1427
+ } catch {
1428
+ return null;
1429
+ }
1430
+ }
1606
1431
  modelName;
1607
1432
  initialized = false;
1608
1433
  initializing = false;
@@ -1627,7 +1452,6 @@ var VectorSearchManager = class {
1627
1452
  // Quantized int8 for faster inference and smaller model size
1628
1453
  });
1629
1454
  logger.info("Embedding model loaded", { module: "VectorSearch" });
1630
- this.db = this.dbAdapter.getRawDb();
1631
1455
  this.initialized = true;
1632
1456
  this.initializing = false;
1633
1457
  logger.info("Vector search initialized successfully", { module: "VectorSearch" });
@@ -1730,6 +1554,60 @@ var VectorSearchManager = class {
1730
1554
  return [];
1731
1555
  }
1732
1556
  }
1557
+ /**
1558
+ * Find entries related to a given entry by its existing embedding.
1559
+ * Uses the stored embedding directly, skipping the re-embedding step.
1560
+ *
1561
+ * @param entryId - Entry ID whose embedding is used as the search vector
1562
+ * @param limit - Max number of results
1563
+ * @param similarityThreshold - Minimum similarity score
1564
+ */
1565
+ async searchByEntryId(entryId, limit = 10, similarityThreshold = 0.3) {
1566
+ if (!this.initialized) {
1567
+ try {
1568
+ await this.initialize();
1569
+ } catch {
1570
+ return [];
1571
+ }
1572
+ }
1573
+ if (!this.db) {
1574
+ return [];
1575
+ }
1576
+ try {
1577
+ const row = this.db.prepare("SELECT embedding FROM vec_embeddings WHERE entry_id = ?").get(BigInt(entryId));
1578
+ if (!row) {
1579
+ logger.debug("No embedding found for entry", {
1580
+ module: "VectorSearch",
1581
+ entityId: entryId
1582
+ });
1583
+ return [];
1584
+ }
1585
+ const queryVec = new Float32Array(
1586
+ row.embedding.buffer,
1587
+ row.embedding.byteOffset,
1588
+ EMBEDDING_DIMENSIONS
1589
+ );
1590
+ const results = this.db.prepare(
1591
+ `SELECT entry_id, distance
1592
+ FROM vec_embeddings
1593
+ WHERE embedding MATCH ?
1594
+ ORDER BY distance
1595
+ LIMIT ?`
1596
+ ).all(queryVec, (limit + 1) * 2);
1597
+ const filteredResults = results.filter((r) => r.entry_id !== entryId).map((r) => ({
1598
+ entryId: r.entry_id,
1599
+ score: 1 / (1 + r.distance)
1600
+ })).filter((r) => r.score >= similarityThreshold).slice(0, limit);
1601
+ return filteredResults;
1602
+ } catch (error) {
1603
+ logger.error("searchByEntryId failed", {
1604
+ module: "VectorSearch",
1605
+ entityId: entryId,
1606
+ error: error instanceof Error ? error.message : String(error)
1607
+ });
1608
+ return [];
1609
+ }
1610
+ }
1733
1611
  /**
1734
1612
  * Remove an entry from the vector index
1735
1613
  */
@@ -1822,1378 +1700,54 @@ var VectorSearchManager = class {
1822
1700
  } else {
1823
1701
  failed++;
1824
1702
  if (embError !== null) firstError ??= embError;
1825
- }
1826
- }
1827
- if (indexed % 10 === 0 || indexed === totalEntries) {
1828
- await sendProgress(
1829
- progress,
1830
- indexed,
1831
- totalEntries,
1832
- `Indexed ${String(indexed)} of ${String(totalEntries)} entries`
1833
- );
1834
- }
1835
- }
1836
- }
1837
- await sendProgress(progress, indexed, totalEntries, "Vector index rebuild complete");
1838
- if (failed > 0) {
1839
- logger.warning(
1840
- `Vector index rebuild: ${String(indexed)} indexed, ${String(failed)} failed`,
1841
- {
1842
- module: "VectorSearch"
1843
- }
1844
- );
1845
- } else {
1846
- logger.info(`Rebuilt vector index with ${String(indexed)} entries`, {
1847
- module: "VectorSearch"
1848
- });
1849
- }
1850
- return { indexed, failed, firstError };
1851
- }
1852
- /**
1853
- * Get index statistics
1854
- */
1855
- getStats() {
1856
- if (!this.db) {
1857
- return { itemCount: 0, modelName: this.modelName, dimensions: EMBEDDING_DIMENSIONS };
1858
- }
1859
- try {
1860
- const result = this.db.prepare("SELECT COUNT(*) as count FROM vec_embeddings").get();
1861
- return {
1862
- itemCount: result?.count ?? 0,
1863
- modelName: this.modelName,
1864
- dimensions: EMBEDDING_DIMENSIONS
1865
- };
1866
- } catch (error) {
1867
- logger.debug("Failed to get vector index stats", {
1868
- module: "VectorSearch",
1869
- error: error instanceof Error ? error.message : String(error)
1870
- });
1871
- return { itemCount: 0, modelName: this.modelName, dimensions: EMBEDDING_DIMENSIONS };
1872
- }
1873
- }
1874
- };
1875
- var CACHE_TTL_MS = 5 * 60 * 1e3;
1876
- var TRAFFIC_CACHE_TTL_MS = 10 * 60 * 1e3;
1877
- var simpleGit2 = simpleGitImport.simpleGit;
1878
- var GitHubClient = class {
1879
- octokit = null;
1880
- graphqlWithAuth = null;
1881
- git;
1882
- token;
1883
- cachedRepoInfo = null;
1884
- apiCache = /* @__PURE__ */ new Map();
1885
- constructor(workingDir = ".") {
1886
- this.token = process.env["GITHUB_TOKEN"];
1887
- const envRepoPath = process.env["GITHUB_REPO_PATH"];
1888
- const effectiveDir = envRepoPath || workingDir;
1889
- const resolvedDir = effectiveDir === "." ? process.cwd() : effectiveDir;
1890
- logger.info("GitHub integration using directory", {
1891
- module: "GitHub",
1892
- workingDir,
1893
- envRepoPath: envRepoPath ?? "not set",
1894
- effectiveDir,
1895
- resolvedDir,
1896
- cwd: process.cwd()
1897
- });
1898
- this.git = simpleGit2(effectiveDir);
1899
- if (this.token) {
1900
- this.octokit = new Octokit({ auth: this.token });
1901
- this.graphqlWithAuth = graphql.defaults({
1902
- headers: { authorization: `token ${this.token}` }
1903
- });
1904
- logger.info("GitHub integration initialized with token", { module: "GitHub" });
1905
- } else {
1906
- logger.info("GitHub integration initialized without token (limited functionality)", {
1907
- module: "GitHub"
1908
- });
1909
- }
1910
- }
1911
- isApiAvailable() {
1912
- return this.octokit !== null;
1913
- }
1914
- getCached(key) {
1915
- const entry = this.apiCache.get(key);
1916
- if (entry && Date.now() - entry.timestamp < CACHE_TTL_MS) {
1917
- this.apiCache.delete(key);
1918
- this.apiCache.set(key, entry);
1919
- return entry.data;
1920
- }
1921
- if (entry) {
1922
- this.apiCache.delete(key);
1923
- }
1924
- return void 0;
1925
- }
1926
- getCachedWithTtl(key, ttlMs) {
1927
- const entry = this.apiCache.get(key);
1928
- if (entry && Date.now() - entry.timestamp < ttlMs) {
1929
- this.apiCache.delete(key);
1930
- this.apiCache.set(key, entry);
1931
- return entry.data;
1932
- }
1933
- if (entry) {
1934
- this.apiCache.delete(key);
1935
- }
1936
- return void 0;
1937
- }
1938
- setCache(key, data) {
1939
- this.apiCache.delete(key);
1940
- this.apiCache.set(key, { data, timestamp: Date.now() });
1941
- if (this.apiCache.size > 100) {
1942
- const oldestKey = this.apiCache.keys().next().value;
1943
- if (oldestKey !== void 0) {
1944
- this.apiCache.delete(oldestKey);
1945
- }
1946
- }
1947
- }
1948
- invalidateCache(prefix) {
1949
- for (const key of this.apiCache.keys()) {
1950
- if (key.startsWith(prefix)) {
1951
- this.apiCache.delete(key);
1952
- }
1953
- }
1954
- }
1955
- clearCache() {
1956
- this.apiCache.clear();
1957
- }
1958
- };
1959
-
1960
- // src/github/github-integration/issues.ts
1961
- var IssuesManager = class {
1962
- constructor(client) {
1963
- this.client = client;
1964
- }
1965
- async getIssues(owner, repo, state = "open", limit = 20) {
1966
- if (!this.client.octokit) {
1967
- return [];
1968
- }
1969
- const cacheKey = `issues:${owner}:${repo}:${state}:${String(limit)}`;
1970
- const cached = this.client.getCached(cacheKey);
1971
- if (cached) return cached;
1972
- try {
1973
- const response = await this.client.octokit.issues.listForRepo({
1974
- owner,
1975
- repo,
1976
- state,
1977
- per_page: Math.min(limit * 2, 100),
1978
- sort: "updated",
1979
- direction: "desc"
1980
- });
1981
- const result = response.data.filter((issue) => !issue.pull_request).slice(0, limit).map((issue) => ({
1982
- number: issue.number,
1983
- title: issue.title,
1984
- url: issue.html_url,
1985
- state: issue.state === "open" ? "OPEN" : "CLOSED",
1986
- milestone: issue.milestone ? {
1987
- number: issue.milestone.number,
1988
- title: issue.milestone.title
1989
- } : null
1990
- }));
1991
- this.client.setCache(cacheKey, result);
1992
- return result;
1993
- } catch (error) {
1994
- logger.error("Failed to get issues", {
1995
- module: "GitHub",
1996
- error: error instanceof Error ? error.message : String(error)
1997
- });
1998
- return [];
1999
- }
2000
- }
2001
- async getIssue(owner, repo, issueNumber) {
2002
- if (!this.client.octokit) {
2003
- return null;
2004
- }
2005
- const cacheKey = `issue:${owner}:${repo}:${String(issueNumber)}`;
2006
- const cached = this.client.getCached(cacheKey);
2007
- if (cached !== void 0) return cached;
2008
- try {
2009
- const response = await this.client.octokit.issues.get({
2010
- owner,
2011
- repo,
2012
- issue_number: issueNumber
2013
- });
2014
- const issue = response.data;
2015
- if (issue.pull_request) {
2016
- return null;
2017
- }
2018
- const details = {
2019
- number: issue.number,
2020
- title: issue.title,
2021
- url: issue.html_url,
2022
- state: issue.state === "open" ? "OPEN" : "CLOSED",
2023
- nodeId: issue.node_id,
2024
- body: issue.body ?? null,
2025
- labels: issue.labels.map((l) => typeof l === "string" ? l : l.name ?? ""),
2026
- assignees: issue.assignees?.map((a) => a.login) ?? [],
2027
- createdAt: issue.created_at,
2028
- updatedAt: issue.updated_at,
2029
- closedAt: issue.closed_at,
2030
- commentsCount: issue.comments,
2031
- milestone: issue.milestone ? { number: issue.milestone.number, title: issue.milestone.title } : null
2032
- };
2033
- this.client.setCache(cacheKey, details);
2034
- return details;
2035
- } catch (error) {
2036
- logger.error("Failed to get issue details", {
2037
- module: "GitHub",
2038
- entityId: issueNumber,
2039
- error: error instanceof Error ? error.message : String(error)
2040
- });
2041
- return null;
2042
- }
2043
- }
2044
- async createIssue(owner, repo, title, body, labels, assignees, milestone) {
2045
- if (!this.client.octokit) {
2046
- logger.error("Cannot create issue: GitHub API not available", { module: "GitHub" });
2047
- return null;
2048
- }
2049
- try {
2050
- const response = await this.client.octokit.issues.create({
2051
- owner,
2052
- repo,
2053
- title,
2054
- body,
2055
- labels,
2056
- assignees,
2057
- milestone
2058
- });
2059
- logger.info("Created GitHub issue", {
2060
- module: "GitHub",
2061
- entityId: response.data.number,
2062
- context: { title, owner, repo }
2063
- });
2064
- return {
2065
- number: response.data.number,
2066
- url: response.data.html_url,
2067
- title: response.data.title,
2068
- nodeId: response.data.node_id
2069
- };
2070
- } catch (error) {
2071
- logger.error("Failed to create issue", {
2072
- module: "GitHub",
2073
- error: error instanceof Error ? error.message : String(error),
2074
- context: { title, owner, repo }
2075
- });
2076
- return null;
2077
- } finally {
2078
- this.client.invalidateCache(`issues:${owner}:${repo}`);
2079
- this.client.invalidateCache("context:");
2080
- }
2081
- }
2082
- async closeIssue(owner, repo, issueNumber, comment) {
2083
- if (!this.client.octokit) {
2084
- logger.error("Cannot close issue: GitHub API not available", { module: "GitHub" });
2085
- return null;
2086
- }
2087
- try {
2088
- if (comment) {
2089
- await this.client.octokit.issues.createComment({
2090
- owner,
2091
- repo,
2092
- issue_number: issueNumber,
2093
- body: comment
2094
- });
2095
- }
2096
- const response = await this.client.octokit.issues.update({
2097
- owner,
2098
- repo,
2099
- issue_number: issueNumber,
2100
- state: "closed"
2101
- });
2102
- logger.info("Closed GitHub issue", {
2103
- module: "GitHub",
2104
- entityId: issueNumber,
2105
- context: { owner, repo, hadComment: !!comment }
2106
- });
2107
- return {
2108
- success: true,
2109
- url: response.data.html_url
2110
- };
2111
- } catch (error) {
2112
- logger.error("Failed to close issue", {
2113
- module: "GitHub",
2114
- entityId: issueNumber,
2115
- error: error instanceof Error ? error.message : String(error)
2116
- });
2117
- return null;
2118
- } finally {
2119
- this.client.invalidateCache(`issues:${owner}:${repo}`);
2120
- this.client.invalidateCache(`issue:${owner}:${repo}:${String(issueNumber)}`);
2121
- this.client.invalidateCache("context:");
2122
- }
2123
- }
2124
- };
2125
-
2126
- // src/github/github-integration/pull-requests.ts
2127
- var PullRequestsManager = class _PullRequestsManager {
2128
- constructor(client) {
2129
- this.client = client;
2130
- }
2131
- /** Known Copilot bot login patterns */
2132
- static COPILOT_BOT_PATTERNS = [
2133
- "copilot-pull-request-reviewer[bot]",
2134
- "github-copilot[bot]",
2135
- "copilot[bot]"
2136
- ];
2137
- async getPullRequests(owner, repo, state = "open", limit = 20) {
2138
- if (!this.client.octokit) {
2139
- return [];
2140
- }
2141
- const cacheKey = `prs:${owner}:${repo}:${state}:${String(limit)}`;
2142
- const cached = this.client.getCached(cacheKey);
2143
- if (cached) return cached;
2144
- try {
2145
- const response = await this.client.octokit.pulls.list({
2146
- owner,
2147
- repo,
2148
- state,
2149
- per_page: limit,
2150
- sort: "updated",
2151
- direction: "desc"
2152
- });
2153
- const result = response.data.map((pr) => ({
2154
- number: pr.number,
2155
- title: pr.title,
2156
- url: pr.html_url,
2157
- state: pr.merged_at ? "MERGED" : pr.state === "open" ? "OPEN" : "CLOSED"
2158
- }));
2159
- this.client.setCache(cacheKey, result);
2160
- return result;
2161
- } catch (error) {
2162
- logger.error("Failed to get pull requests", {
2163
- module: "GitHub",
2164
- error: error instanceof Error ? error.message : String(error)
2165
- });
2166
- return [];
2167
- }
2168
- }
2169
- async getPullRequest(owner, repo, prNumber) {
2170
- if (!this.client.octokit) {
2171
- return null;
2172
- }
2173
- const cacheKey = `pr:${owner}:${repo}:${String(prNumber)}`;
2174
- const cached = this.client.getCached(cacheKey);
2175
- if (cached !== void 0) return cached;
2176
- try {
2177
- const response = await this.client.octokit.pulls.get({
2178
- owner,
2179
- repo,
2180
- pull_number: prNumber
2181
- });
2182
- const pr = response.data;
2183
- const details = {
2184
- number: pr.number,
2185
- title: pr.title,
2186
- url: pr.html_url,
2187
- state: pr.merged_at ? "MERGED" : pr.state === "open" ? "OPEN" : "CLOSED",
2188
- body: pr.body,
2189
- draft: pr.draft ?? false,
2190
- headBranch: pr.head.ref,
2191
- baseBranch: pr.base.ref,
2192
- author: pr.user?.login ?? "unknown",
2193
- createdAt: pr.created_at,
2194
- updatedAt: pr.updated_at,
2195
- mergedAt: pr.merged_at,
2196
- closedAt: pr.closed_at,
2197
- additions: pr.additions,
2198
- deletions: pr.deletions,
2199
- changedFiles: pr.changed_files
2200
- };
2201
- this.client.setCache(cacheKey, details);
2202
- return details;
2203
- } catch (error) {
2204
- logger.error("Failed to get PR details", {
2205
- module: "GitHub",
2206
- entityId: prNumber,
2207
- error: error instanceof Error ? error.message : String(error)
2208
- });
2209
- return null;
2210
- }
2211
- }
2212
- static isCopilotAuthor(login) {
2213
- const lower = login.toLowerCase();
2214
- return _PullRequestsManager.COPILOT_BOT_PATTERNS.some(
2215
- (p) => lower === p || lower.includes("copilot")
2216
- );
2217
- }
2218
- async getReviews(owner, repo, prNumber) {
2219
- if (!this.client.octokit) return [];
2220
- const cacheKey = `reviews:${owner}:${repo}:${String(prNumber)}`;
2221
- const cached = this.client.getCached(cacheKey);
2222
- if (cached) return cached;
2223
- try {
2224
- const response = await this.client.octokit.rest.pulls.listReviews({
2225
- owner,
2226
- repo,
2227
- pull_number: prNumber,
2228
- per_page: 100
2229
- });
2230
- const reviews = response.data.map((r) => ({
2231
- id: r.id,
2232
- author: r.user?.login ?? "unknown",
2233
- state: r.state,
2234
- body: r.body ?? null,
2235
- submittedAt: r.submitted_at ?? r.commit_id ?? (/* @__PURE__ */ new Date()).toISOString(),
2236
- isCopilot: _PullRequestsManager.isCopilotAuthor(r.user?.login ?? "")
2237
- }));
2238
- this.client.setCache(cacheKey, reviews);
2239
- return reviews;
2240
- } catch (error) {
2241
- logger.error("Failed to get PR reviews", {
2242
- module: "GitHub",
2243
- entityId: prNumber,
2244
- error: error instanceof Error ? error.message : String(error)
2245
- });
2246
- return [];
2247
- }
2248
- }
2249
- async getReviewComments(owner, repo, prNumber) {
2250
- if (!this.client.octokit) return [];
2251
- const cacheKey = `review-comments:${owner}:${repo}:${String(prNumber)}`;
2252
- const cached = this.client.getCached(cacheKey);
2253
- if (cached) return cached;
2254
- try {
2255
- const response = await this.client.octokit.rest.pulls.listReviewComments({
2256
- owner,
2257
- repo,
2258
- pull_number: prNumber,
2259
- per_page: 100
2260
- });
2261
- const comments = response.data.map((c) => ({
2262
- id: c.id,
2263
- author: c.user?.login ?? "unknown",
2264
- body: c.body,
2265
- path: c.path,
2266
- line: c.line ?? c.original_line ?? null,
2267
- side: c.side ?? "RIGHT",
2268
- createdAt: c.created_at,
2269
- isCopilot: _PullRequestsManager.isCopilotAuthor(c.user?.login ?? "")
2270
- }));
2271
- this.client.setCache(cacheKey, comments);
2272
- return comments;
2273
- } catch (error) {
2274
- logger.error("Failed to get review comments", {
2275
- module: "GitHub",
2276
- entityId: prNumber,
2277
- error: error instanceof Error ? error.message : String(error)
2278
- });
2279
- return [];
2280
- }
2281
- }
2282
- async getCopilotReviewSummary(owner, repo, prNumber) {
2283
- const [reviews, comments] = await Promise.all([
2284
- this.getReviews(owner, repo, prNumber),
2285
- this.getReviewComments(owner, repo, prNumber)
2286
- ]);
2287
- const copilotReviews = reviews.filter((r) => r.isCopilot);
2288
- const copilotComments = comments.filter((c) => c.isCopilot);
2289
- let state = "none";
2290
- if (copilotReviews.length > 0) {
2291
- const latest = copilotReviews[copilotReviews.length - 1];
2292
- if (latest !== void 0) {
2293
- if (latest.state === "APPROVED") state = "approved";
2294
- else if (latest.state === "CHANGES_REQUESTED") state = "changes_requested";
2295
- else if (latest.state === "COMMENTED") state = "commented";
2296
- }
2297
- }
2298
- return {
2299
- prNumber,
2300
- state,
2301
- commentCount: copilotComments.length,
2302
- comments: copilotComments
2303
- };
2304
- }
2305
- };
2306
-
2307
- // src/github/github-integration/projects.ts
2308
- var ProjectsManager = class {
2309
- constructor(client) {
2310
- this.client = client;
2311
- }
2312
- async getProjectKanban(owner, projectNumber, repo) {
2313
- if (!this.client.graphqlWithAuth) {
2314
- logger.debug("GraphQL not available - no token", { module: "GitHub" });
2315
- return null;
2316
- }
2317
- const projectFragment = `
2318
- fragment ProjectData on ProjectV2 {
2319
- id
2320
- title
2321
- fields(first: 20) {
2322
- nodes {
2323
- ... on ProjectV2SingleSelectField {
2324
- id
2325
- name
2326
- options {
2327
- id
2328
- name
2329
- color
2330
- }
2331
- }
2332
- }
2333
- }
2334
- items(first: 100) {
2335
- nodes {
2336
- id
2337
- type
2338
- createdAt
2339
- updatedAt
2340
- fieldValues(first: 10) {
2341
- nodes {
2342
- ... on ProjectV2ItemFieldSingleSelectValue {
2343
- name
2344
- field {
2345
- ... on ProjectV2SingleSelectField {
2346
- name
2347
- }
2348
- }
2349
- }
2350
- }
2351
- }
2352
- content {
2353
- ... on Issue {
2354
- number
2355
- title
2356
- url
2357
- labels(first: 5) {
2358
- nodes { name }
2359
- }
2360
- assignees(first: 5) {
2361
- nodes { login }
2362
- }
2363
- }
2364
- ... on PullRequest {
2365
- number
2366
- title
2367
- url
2368
- labels(first: 5) {
2369
- nodes { name }
2370
- }
2371
- assignees(first: 5) {
2372
- nodes { login }
2373
- }
2374
- }
2375
- ... on DraftIssue {
2376
- title
2377
- }
2378
- }
2379
- }
2380
- }
2381
- }
2382
- `;
2383
- const userQuery = `
2384
- ${projectFragment}
2385
- query($owner: String!, $number: Int!) {
2386
- user(login: $owner) {
2387
- projectV2(number: $number) {
2388
- ...ProjectData
2389
- }
2390
- }
2391
- }
2392
- `;
2393
- const repoQuery = `
2394
- ${projectFragment}
2395
- query($owner: String!, $repo: String!, $number: Int!) {
2396
- repository(owner: $owner, name: $repo) {
2397
- projectV2(number: $number) {
2398
- ...ProjectData
2399
- }
2400
- }
2401
- }
2402
- `;
2403
- const orgQuery = `
2404
- ${projectFragment}
2405
- query($owner: String!, $number: Int!) {
2406
- organization(login: $owner) {
2407
- projectV2(number: $number) {
2408
- ...ProjectData
2409
- }
2410
- }
2411
- }
2412
- `;
2413
- let project = null;
2414
- let source = "";
2415
- try {
2416
- const response = await this.client.graphqlWithAuth(userQuery, {
2417
- owner,
2418
- number: projectNumber
2419
- });
2420
- if (response.user?.projectV2) {
2421
- project = response.user.projectV2;
2422
- source = "user";
2423
- }
2424
- } catch {
2425
- logger.debug("User project not found, trying repository...", { module: "GitHub" });
2426
- }
2427
- if (!project && repo) {
2428
- try {
2429
- const response = await this.client.graphqlWithAuth(repoQuery, {
2430
- owner,
2431
- repo,
2432
- number: projectNumber
2433
- });
2434
- if (response.repository?.projectV2) {
2435
- project = response.repository.projectV2;
2436
- source = "repository";
2437
- }
2438
- } catch {
2439
- logger.debug("Repository project not found, trying organization...", {
2440
- module: "GitHub"
2441
- });
2442
- }
2443
- }
2444
- if (!project) {
2445
- try {
2446
- const response = await this.client.graphqlWithAuth(orgQuery, {
2447
- owner,
2448
- number: projectNumber
2449
- });
2450
- if (response.organization?.projectV2) {
2451
- project = response.organization.projectV2;
2452
- source = "organization";
2453
- }
2454
- } catch {
2455
- logger.debug("Organization project not found", { module: "GitHub" });
2456
- }
2457
- }
2458
- if (!project) {
2459
- logger.warning("Project not found", { module: "GitHub", entityId: projectNumber });
2460
- return null;
2461
- }
2462
- const statusField = project.fields.nodes.find(
2463
- (f) => f.name === "Status" && f.options !== void 0 && f.options.length > 0
2464
- );
2465
- if (!statusField?.id || !statusField.options) {
2466
- logger.warning("Status field not found in project", {
2467
- module: "GitHub",
2468
- entityId: projectNumber
2469
- });
2470
- return null;
2471
- }
2472
- const statusOptions = statusField.options.map((opt) => ({
2473
- id: opt.id,
2474
- name: opt.name,
2475
- color: opt.color
2476
- }));
2477
- const columnMap = /* @__PURE__ */ new Map();
2478
- for (const opt of statusOptions) {
2479
- columnMap.set(opt.name, []);
2480
- }
2481
- columnMap.set("No Status", []);
2482
- for (const item of project.items.nodes) {
2483
- const statusValue = item.fieldValues.nodes.find((fv) => fv.field?.name === "Status");
2484
- const status = statusValue?.name ?? "No Status";
2485
- const content = item.content;
2486
- const projectItem = {
2487
- id: item.id,
2488
- title: content?.title ?? "Draft Issue",
2489
- url: content?.url ?? "",
2490
- type: item.type,
2491
- status,
2492
- number: content?.number,
2493
- labels: content?.labels?.nodes.map((l) => l.name) ?? [],
2494
- assignees: content?.assignees?.nodes.map((a) => a.login) ?? [],
2495
- createdAt: item.createdAt,
2496
- updatedAt: item.updatedAt
2497
- };
2498
- const column = columnMap.get(status);
2499
- if (column) {
2500
- column.push(projectItem);
2501
- } else {
2502
- columnMap.get("No Status")?.push(projectItem);
2503
- }
2504
- }
2505
- const columns = [];
2506
- for (const opt of statusOptions) {
2507
- const items = columnMap.get(opt.name) ?? [];
2508
- columns.push({
2509
- status: opt.name,
2510
- statusOptionId: opt.id,
2511
- items
2512
- });
2513
- }
2514
- const noStatusItems = columnMap.get("No Status") ?? [];
2515
- if (noStatusItems.length > 0) {
2516
- columns.push({
2517
- status: "No Status",
2518
- statusOptionId: "",
2519
- items: noStatusItems
2520
- });
2521
- }
2522
- const totalItems = project.items.nodes.length;
2523
- logger.info("Fetched Kanban board", {
2524
- module: "GitHub",
2525
- entityId: projectNumber,
2526
- context: { columns: columns.length, items: totalItems, source }
2527
- });
2528
- return {
2529
- projectId: project.id,
2530
- projectNumber,
2531
- projectTitle: project.title,
2532
- statusFieldId: statusField.id,
2533
- statusOptions,
2534
- columns,
2535
- totalItems
2536
- };
2537
- }
2538
- async moveProjectItem(projectId, itemId, statusFieldId, statusOptionId) {
2539
- if (!this.client.graphqlWithAuth) {
2540
- return { success: false, error: "GraphQL not available - no token" };
2541
- }
2542
- try {
2543
- const mutation = `
2544
- mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
2545
- updateProjectV2ItemFieldValue(
2546
- input: {
2547
- projectId: $projectId
2548
- itemId: $itemId
2549
- fieldId: $fieldId
2550
- value: { singleSelectOptionId: $optionId }
2551
- }
2552
- ) {
2553
- projectV2Item {
2554
- id
2555
- }
2556
- }
2557
- }
2558
- `;
2559
- await this.client.graphqlWithAuth(mutation, {
2560
- projectId,
2561
- itemId,
2562
- fieldId: statusFieldId,
2563
- optionId: statusOptionId
2564
- });
2565
- logger.info("Moved project item", {
2566
- module: "GitHub",
2567
- entityId: itemId,
2568
- context: { targetStatus: statusOptionId }
2569
- });
2570
- return { success: true };
2571
- } catch (error) {
2572
- const errorMessage = error instanceof Error ? error.message : String(error);
2573
- logger.error("Failed to move project item", {
2574
- module: "GitHub",
2575
- entityId: itemId,
2576
- error: errorMessage
2577
- });
2578
- return { success: false, error: errorMessage };
2579
- } finally {
2580
- this.client.invalidateCache("kanban:");
2581
- }
2582
- }
2583
- async addProjectItem(projectId, contentId) {
2584
- if (!this.client.graphqlWithAuth) {
2585
- return { success: false, error: "GraphQL not available - no token" };
2586
- }
2587
- try {
2588
- const mutation = `
2589
- mutation($projectId: ID!, $contentId: ID!) {
2590
- addProjectV2ItemById(input: { projectId: $projectId, contentId: $contentId }) {
2591
- item {
2592
- id
2593
- }
2594
- }
2595
- }
2596
- `;
2597
- const response = await this.client.graphqlWithAuth(mutation, {
2598
- projectId,
2599
- contentId
2600
- });
2601
- const itemId = response.addProjectV2ItemById?.item?.id;
2602
- logger.info("Added item to project", {
2603
- module: "GitHub",
2604
- context: { projectId, contentId, itemId }
2605
- });
2606
- return { success: true, itemId };
2607
- } catch (error) {
2608
- const errorMessage = error instanceof Error ? error.message : String(error);
2609
- logger.error("Failed to add item to project", {
2610
- module: "GitHub",
2611
- context: { projectId, contentId },
2612
- error: errorMessage
2613
- });
2614
- return { success: false, error: errorMessage };
2615
- } finally {
2616
- this.client.invalidateCache("kanban:");
2617
- }
2618
- }
2619
- };
2620
-
2621
- // src/github/github-integration/milestones.ts
2622
- var MilestonesManager = class {
2623
- constructor(client) {
2624
- this.client = client;
2625
- }
2626
- async getMilestones(owner, repo, state = "open", limit = 20) {
2627
- if (!this.client.octokit) {
2628
- return [];
2629
- }
2630
- const cacheKey = `milestones:${owner}:${repo}:${state}:${String(limit)}`;
2631
- const cached = this.client.getCached(cacheKey);
2632
- if (cached) return cached;
2633
- try {
2634
- const response = await this.client.octokit.issues.listMilestones({
2635
- owner,
2636
- repo,
2637
- state,
2638
- per_page: limit,
2639
- sort: "due_on",
2640
- direction: "asc"
2641
- });
2642
- const result = response.data.map((ms) => ({
2643
- number: ms.number,
2644
- title: ms.title,
2645
- description: ms.description ?? null,
2646
- state: ms.state === "open" ? "open" : "closed",
2647
- url: ms.html_url,
2648
- dueOn: ms.due_on ?? null,
2649
- openIssues: ms.open_issues,
2650
- closedIssues: ms.closed_issues,
2651
- createdAt: ms.created_at,
2652
- updatedAt: ms.updated_at,
2653
- creator: ms.creator?.login ?? null
2654
- }));
2655
- this.client.setCache(cacheKey, result);
2656
- return result;
2657
- } catch (error) {
2658
- logger.error("Failed to get milestones", {
2659
- module: "GitHub",
2660
- error: error instanceof Error ? error.message : String(error)
2661
- });
2662
- return [];
2663
- }
2664
- }
2665
- async getMilestone(owner, repo, milestoneNumber) {
2666
- if (!this.client.octokit) {
2667
- return null;
2668
- }
2669
- const cacheKey = `milestone:${owner}:${repo}:${String(milestoneNumber)}`;
2670
- const cached = this.client.getCached(cacheKey);
2671
- if (cached !== void 0) return cached;
2672
- try {
2673
- const response = await this.client.octokit.issues.getMilestone({
2674
- owner,
2675
- repo,
2676
- milestone_number: milestoneNumber
2677
- });
2678
- const ms = response.data;
2679
- const milestone = {
2680
- number: ms.number,
2681
- title: ms.title,
2682
- description: ms.description ?? null,
2683
- state: ms.state === "open" ? "open" : "closed",
2684
- url: ms.html_url,
2685
- dueOn: ms.due_on ?? null,
2686
- openIssues: ms.open_issues,
2687
- closedIssues: ms.closed_issues,
2688
- createdAt: ms.created_at,
2689
- updatedAt: ms.updated_at,
2690
- creator: ms.creator?.login ?? null
2691
- };
2692
- this.client.setCache(cacheKey, milestone);
2693
- return milestone;
2694
- } catch (error) {
2695
- logger.error("Failed to get milestone", {
2696
- module: "GitHub",
2697
- entityId: milestoneNumber,
2698
- error: error instanceof Error ? error.message : String(error)
2699
- });
2700
- return null;
2701
- }
2702
- }
2703
- async createMilestone(owner, repo, title, description, dueOn) {
2704
- if (!this.client.octokit) {
2705
- logger.error("Cannot create milestone: GitHub API not available", {
2706
- module: "GitHub"
2707
- });
2708
- return null;
2709
- }
2710
- try {
2711
- const response = await this.client.octokit.issues.createMilestone({
2712
- owner,
2713
- repo,
2714
- title,
2715
- description,
2716
- due_on: dueOn
2717
- });
2718
- const ms = response.data;
2719
- logger.info("Created GitHub milestone", {
2720
- module: "GitHub",
2721
- entityId: ms.number,
2722
- context: { title, owner, repo }
2723
- });
2724
- return {
2725
- number: ms.number,
2726
- title: ms.title,
2727
- description: ms.description ?? null,
2728
- state: ms.state === "open" ? "open" : "closed",
2729
- url: ms.html_url,
2730
- dueOn: ms.due_on ?? null,
2731
- openIssues: ms.open_issues,
2732
- closedIssues: ms.closed_issues,
2733
- createdAt: ms.created_at,
2734
- updatedAt: ms.updated_at,
2735
- creator: ms.creator?.login ?? null
2736
- };
2737
- } catch (error) {
2738
- logger.error("Failed to create milestone", {
2739
- module: "GitHub",
2740
- error: error instanceof Error ? error.message : String(error),
2741
- context: { title, owner, repo }
2742
- });
2743
- return null;
2744
- } finally {
2745
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
2746
- this.client.invalidateCache("context:");
2747
- }
2748
- }
2749
- async updateMilestone(owner, repo, milestoneNumber, updates) {
2750
- if (!this.client.octokit) {
2751
- logger.error("Cannot update milestone: GitHub API not available", {
2752
- module: "GitHub"
2753
- });
2754
- return null;
2755
- }
2756
- try {
2757
- const response = await this.client.octokit.issues.updateMilestone({
2758
- owner,
2759
- repo,
2760
- milestone_number: milestoneNumber,
2761
- title: updates.title,
2762
- description: updates.description,
2763
- due_on: updates.dueOn === null ? void 0 : updates.dueOn,
2764
- state: updates.state
2765
- });
2766
- const ms = response.data;
2767
- logger.info("Updated GitHub milestone", {
2768
- module: "GitHub",
2769
- entityId: milestoneNumber,
2770
- context: { owner, repo, updates: Object.keys(updates) }
2771
- });
2772
- return {
2773
- number: ms.number,
2774
- title: ms.title,
2775
- description: ms.description ?? null,
2776
- state: ms.state === "open" ? "open" : "closed",
2777
- url: ms.html_url,
2778
- dueOn: ms.due_on ?? null,
2779
- openIssues: ms.open_issues,
2780
- closedIssues: ms.closed_issues,
2781
- createdAt: ms.created_at,
2782
- updatedAt: ms.updated_at,
2783
- creator: ms.creator?.login ?? null
2784
- };
2785
- } catch (error) {
2786
- logger.error("Failed to update milestone", {
2787
- module: "GitHub",
2788
- entityId: milestoneNumber,
2789
- error: error instanceof Error ? error.message : String(error)
2790
- });
2791
- return null;
2792
- } finally {
2793
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
2794
- this.client.invalidateCache(`milestone:${owner}:${repo}:${String(milestoneNumber)}`);
2795
- this.client.invalidateCache("context:");
2796
- }
2797
- }
2798
- async deleteMilestone(owner, repo, milestoneNumber) {
2799
- if (!this.client.octokit) {
2800
- return { success: false, error: "GitHub API not available" };
2801
- }
2802
- try {
2803
- await this.client.octokit.issues.deleteMilestone({
2804
- owner,
2805
- repo,
2806
- milestone_number: milestoneNumber
2807
- });
2808
- logger.info("Deleted GitHub milestone", {
2809
- module: "GitHub",
2810
- entityId: milestoneNumber,
2811
- context: { owner, repo }
2812
- });
2813
- return { success: true };
2814
- } catch (error) {
2815
- const errorMessage = error instanceof Error ? error.message : String(error);
2816
- logger.error("Failed to delete milestone", {
2817
- module: "GitHub",
2818
- entityId: milestoneNumber,
2819
- error: errorMessage
2820
- });
2821
- return { success: false, error: errorMessage };
2822
- } finally {
2823
- this.client.invalidateCache(`milestones:${owner}:${repo}`);
2824
- this.client.invalidateCache(`milestone:${owner}:${repo}:${String(milestoneNumber)}`);
2825
- this.client.invalidateCache("context:");
2826
- }
2827
- }
2828
- };
2829
-
2830
- // src/github/github-integration/insights.ts
2831
- var InsightsManager = class {
2832
- constructor(client) {
2833
- this.client = client;
2834
- }
2835
- async getRepoStats(owner, repo) {
2836
- if (!this.client.octokit) {
2837
- return null;
2838
- }
2839
- const cacheKey = `repostats:${owner}:${repo}`;
2840
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
2841
- if (cached) return cached;
2842
- try {
2843
- const response = await this.client.octokit.repos.get({ owner, repo });
2844
- const data = response.data;
2845
- const result = {
2846
- stars: data.stargazers_count,
2847
- forks: data.forks_count,
2848
- watchers: data.subscribers_count,
2849
- openIssues: data.open_issues_count,
2850
- size: data.size,
2851
- defaultBranch: data.default_branch
2852
- };
2853
- this.client.setCache(cacheKey, result);
2854
- return result;
2855
- } catch (error) {
2856
- logger.error("Failed to get repo stats", {
2857
- module: "GitHub",
2858
- error: error instanceof Error ? error.message : String(error),
2859
- context: { owner, repo }
2860
- });
2861
- return null;
2862
- }
2863
- }
2864
- async getTrafficData(owner, repo) {
2865
- if (!this.client.octokit) {
2866
- return null;
2867
- }
2868
- const cacheKey = `traffic:${owner}:${repo}`;
2869
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
2870
- if (cached) return cached;
2871
- try {
2872
- const [clonesRes, viewsRes] = await Promise.all([
2873
- this.client.octokit.rest.repos.getClones({ owner, repo }),
2874
- this.client.octokit.rest.repos.getViews({ owner, repo })
2875
- ]);
2876
- const clonesDays = clonesRes.data.clones?.length ?? 0;
2877
- const viewsDays = viewsRes.data.views?.length ?? 0;
2878
- const result = {
2879
- clones: {
2880
- total: clonesRes.data.count,
2881
- unique: clonesRes.data.uniques,
2882
- dailyAvg: clonesDays > 0 ? Math.round(clonesRes.data.count / clonesDays) : 0
2883
- },
2884
- views: {
2885
- total: viewsRes.data.count,
2886
- unique: viewsRes.data.uniques,
2887
- dailyAvg: viewsDays > 0 ? Math.round(viewsRes.data.count / viewsDays) : 0
2888
- },
2889
- period: "14 days"
2890
- };
2891
- this.client.setCache(cacheKey, result);
2892
- return result;
2893
- } catch (error) {
2894
- logger.error("Failed to get traffic data", {
2895
- module: "GitHub",
2896
- error: error instanceof Error ? error.message : String(error),
2897
- context: { owner, repo }
2898
- });
2899
- return null;
2900
- }
2901
- }
2902
- async getTopReferrers(owner, repo, limit = 5) {
2903
- if (!this.client.octokit) {
2904
- return [];
2905
- }
2906
- const cacheKey = `referrers:${owner}:${repo}`;
2907
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
2908
- if (cached) return cached.slice(0, limit);
2909
- try {
2910
- const response = await this.client.octokit.rest.repos.getTopReferrers({ owner, repo });
2911
- const result = response.data.map((r) => ({
2912
- referrer: r.referrer,
2913
- count: r.count,
2914
- uniques: r.uniques
2915
- }));
2916
- this.client.setCache(cacheKey, result);
2917
- return result.slice(0, limit);
2918
- } catch (error) {
2919
- logger.error("Failed to get top referrers", {
2920
- module: "GitHub",
2921
- error: error instanceof Error ? error.message : String(error),
2922
- context: { owner, repo }
2923
- });
2924
- return [];
2925
- }
2926
- }
2927
- async getPopularPaths(owner, repo, limit = 5) {
2928
- if (!this.client.octokit) {
2929
- return [];
2930
- }
2931
- const cacheKey = `paths:${owner}:${repo}`;
2932
- const cached = this.client.getCachedWithTtl(cacheKey, TRAFFIC_CACHE_TTL_MS);
2933
- if (cached) return cached.slice(0, limit);
2934
- try {
2935
- const response = await this.client.octokit.rest.repos.getTopPaths({ owner, repo });
2936
- const result = response.data.map((p) => ({
2937
- path: p.path,
2938
- title: p.title,
2939
- count: p.count,
2940
- uniques: p.uniques
2941
- }));
2942
- this.client.setCache(cacheKey, result);
2943
- return result.slice(0, limit);
2944
- } catch (error) {
2945
- logger.error("Failed to get popular paths", {
2946
- module: "GitHub",
2947
- error: error instanceof Error ? error.message : String(error),
2948
- context: { owner, repo }
2949
- });
2950
- return [];
2951
- }
2952
- }
2953
- };
2954
-
2955
- // src/github/github-integration/repository.ts
2956
- var RepositoryManager = class {
2957
- constructor(client) {
2958
- this.client = client;
2959
- }
2960
- async getRepoInfo() {
2961
- try {
2962
- const branchResult = await this.client.git.branch();
2963
- const branch = branchResult.current || null;
2964
- const remotes = await this.client.git.getRemotes(true);
2965
- const origin = remotes.find((r) => r.name === "origin");
2966
- const remoteUrl = origin?.refs?.fetch || null;
2967
- const { owner, repo } = this.parseRemoteUrl(remoteUrl);
2968
- const repoInfo = { owner, repo, branch, remoteUrl };
2969
- this.client.cachedRepoInfo = repoInfo;
2970
- return repoInfo;
2971
- } catch (error) {
2972
- logger.debug("Failed to get repo info (may not be a git repo)", {
2973
- module: "GitHub",
2974
- error: error instanceof Error ? error.message : String(error)
2975
- });
2976
- return { owner: null, repo: null, branch: null, remoteUrl: null };
2977
- }
2978
- }
2979
- getCachedRepoInfo() {
2980
- return this.client.cachedRepoInfo;
2981
- }
2982
- parseRemoteUrl(remoteUrl) {
2983
- if (!remoteUrl) return { owner: null, repo: null };
2984
- if (remoteUrl.startsWith("git@github.com:")) {
2985
- const pathPart = remoteUrl.replace("git@github.com:", "").replace(".git", "");
2986
- const parts = pathPart.split("/");
2987
- if (parts.length >= 2) {
2988
- return { owner: parts[0] ?? null, repo: parts[1] ?? null };
1703
+ }
1704
+ }
1705
+ if (indexed % 10 === 0 || indexed === totalEntries) {
1706
+ await sendProgress(
1707
+ progress,
1708
+ indexed,
1709
+ totalEntries,
1710
+ `Indexed ${String(indexed)} of ${String(totalEntries)} entries`
1711
+ );
1712
+ }
2989
1713
  }
2990
1714
  }
2991
- try {
2992
- const url = new URL(remoteUrl);
2993
- if (url.hostname === "github.com") {
2994
- const path5 = url.pathname.replace(".git", "").replace(/^\//, "");
2995
- const parts = path5.split("/");
2996
- if (parts.length >= 2) {
2997
- return { owner: parts[0] ?? null, repo: parts[1] ?? null };
1715
+ await sendProgress(progress, indexed, totalEntries, "Vector index rebuild complete");
1716
+ if (failed > 0) {
1717
+ logger.warning(
1718
+ `Vector index rebuild: ${String(indexed)} indexed, ${String(failed)} failed`,
1719
+ {
1720
+ module: "VectorSearch"
2998
1721
  }
2999
- }
3000
- } catch {
1722
+ );
1723
+ } else {
1724
+ logger.info(`Rebuilt vector index with ${String(indexed)} entries`, {
1725
+ module: "VectorSearch"
1726
+ });
3001
1727
  }
3002
- return { owner: null, repo: null };
1728
+ return { indexed, failed, firstError };
3003
1729
  }
3004
- async getWorkflowRuns(owner, repo, limit = 10) {
3005
- if (!this.client.octokit) {
3006
- logger.debug("GitHub API not available - no token", { module: "GitHub" });
3007
- return [];
1730
+ /**
1731
+ * Get index statistics
1732
+ */
1733
+ getStats() {
1734
+ if (!this.db) {
1735
+ return { itemCount: 0, modelName: this.modelName, dimensions: EMBEDDING_DIMENSIONS };
3008
1736
  }
3009
- const cacheKey = `workflows:${owner}:${repo}:${String(limit)}`;
3010
- const cached = this.client.getCached(cacheKey);
3011
- if (cached) return cached;
3012
1737
  try {
3013
- const response = await this.client.octokit.rest.actions.listWorkflowRunsForRepo({
3014
- owner,
3015
- repo,
3016
- per_page: limit
3017
- });
3018
- const result = response.data.workflow_runs.map((run) => ({
3019
- id: run.id,
3020
- name: run.name ?? "Unknown Workflow",
3021
- status: run.status,
3022
- conclusion: run.conclusion,
3023
- url: run.html_url,
3024
- headBranch: run.head_branch ?? "",
3025
- headSha: run.head_sha,
3026
- createdAt: run.created_at,
3027
- updatedAt: run.updated_at
3028
- }));
3029
- this.client.setCache(cacheKey, result);
3030
- return result;
1738
+ const result = this.db.prepare("SELECT COUNT(*) as count FROM vec_embeddings").get();
1739
+ return {
1740
+ itemCount: result?.count ?? 0,
1741
+ modelName: this.modelName,
1742
+ dimensions: EMBEDDING_DIMENSIONS
1743
+ };
3031
1744
  } catch (error) {
3032
- logger.error("Failed to get workflow runs", {
3033
- module: "GitHub",
1745
+ logger.debug("Failed to get vector index stats", {
1746
+ module: "VectorSearch",
3034
1747
  error: error instanceof Error ? error.message : String(error)
3035
1748
  });
3036
- return [];
3037
- }
3038
- }
3039
- };
3040
-
3041
- // src/github/github-integration/index.ts
3042
- var GitHubIntegration = class {
3043
- client;
3044
- issuesManager;
3045
- pullRequestsManager;
3046
- projectsManager;
3047
- milestonesManager;
3048
- insightsManager;
3049
- repositoryManager;
3050
- constructor(workingDir = ".") {
3051
- this.client = new GitHubClient(workingDir);
3052
- this.issuesManager = new IssuesManager(this.client);
3053
- this.pullRequestsManager = new PullRequestsManager(this.client);
3054
- this.projectsManager = new ProjectsManager(this.client);
3055
- this.milestonesManager = new MilestonesManager(this.client);
3056
- this.insightsManager = new InsightsManager(this.client);
3057
- this.repositoryManager = new RepositoryManager(this.client);
3058
- }
3059
- isApiAvailable() {
3060
- return this.client.isApiAvailable();
3061
- }
3062
- clearCache() {
3063
- this.client.clearCache();
3064
- }
3065
- async getRepoInfo() {
3066
- return this.repositoryManager.getRepoInfo();
3067
- }
3068
- getCachedRepoInfo() {
3069
- return this.repositoryManager.getCachedRepoInfo();
3070
- }
3071
- async getIssues(owner, repo, state = "open", limit = 20) {
3072
- return this.issuesManager.getIssues(owner, repo, state, limit);
3073
- }
3074
- async getIssue(owner, repo, issueNumber) {
3075
- return this.issuesManager.getIssue(owner, repo, issueNumber);
3076
- }
3077
- async createIssue(owner, repo, title, body, labels, assignees, milestone) {
3078
- return this.issuesManager.createIssue(
3079
- owner,
3080
- repo,
3081
- title,
3082
- body,
3083
- labels,
3084
- assignees,
3085
- milestone
3086
- );
3087
- }
3088
- async closeIssue(owner, repo, issueNumber, comment) {
3089
- return this.issuesManager.closeIssue(owner, repo, issueNumber, comment);
3090
- }
3091
- async getPullRequests(owner, repo, state = "open", limit = 20) {
3092
- return this.pullRequestsManager.getPullRequests(owner, repo, state, limit);
3093
- }
3094
- async getPullRequest(owner, repo, prNumber) {
3095
- return this.pullRequestsManager.getPullRequest(owner, repo, prNumber);
3096
- }
3097
- async getReviews(owner, repo, prNumber) {
3098
- return this.pullRequestsManager.getReviews(owner, repo, prNumber);
3099
- }
3100
- async getReviewComments(owner, repo, prNumber) {
3101
- return this.pullRequestsManager.getReviewComments(owner, repo, prNumber);
3102
- }
3103
- async getCopilotReviewSummary(owner, repo, prNumber) {
3104
- return this.pullRequestsManager.getCopilotReviewSummary(owner, repo, prNumber);
3105
- }
3106
- async getWorkflowRuns(owner, repo, limit = 10) {
3107
- return this.repositoryManager.getWorkflowRuns(owner, repo, limit);
3108
- }
3109
- async getRepoContext() {
3110
- const cached = this.client.getCached("context:repo");
3111
- if (cached) return cached;
3112
- const repoInfo = await this.repositoryManager.getRepoInfo();
3113
- const context = {
3114
- repoName: repoInfo.repo,
3115
- branch: repoInfo.branch,
3116
- commit: null,
3117
- remoteUrl: repoInfo.remoteUrl,
3118
- projects: [],
3119
- issues: [],
3120
- pullRequests: [],
3121
- workflowRuns: [],
3122
- milestones: []
3123
- };
3124
- try {
3125
- const log = await this.client.git.log({ maxCount: 1 });
3126
- context.commit = log.latest?.hash ?? null;
3127
- } catch {
3128
- }
3129
- if (repoInfo.owner && repoInfo.repo) {
3130
- context.issues = await this.issuesManager.getIssues(
3131
- repoInfo.owner,
3132
- repoInfo.repo,
3133
- "open",
3134
- 10
3135
- );
3136
- context.pullRequests = await this.pullRequestsManager.getPullRequests(
3137
- repoInfo.owner,
3138
- repoInfo.repo,
3139
- "open",
3140
- 10
3141
- );
3142
- context.workflowRuns = await this.repositoryManager.getWorkflowRuns(
3143
- repoInfo.owner,
3144
- repoInfo.repo,
3145
- 10
3146
- );
3147
- context.milestones = await this.milestonesManager.getMilestones(
3148
- repoInfo.owner,
3149
- repoInfo.repo,
3150
- "open",
3151
- 10
3152
- );
1749
+ return { itemCount: 0, modelName: this.modelName, dimensions: EMBEDDING_DIMENSIONS };
3153
1750
  }
3154
- this.client.setCache("context:repo", context);
3155
- return context;
3156
- }
3157
- async getProjectKanban(owner, projectNumber, repo) {
3158
- return this.projectsManager.getProjectKanban(owner, projectNumber, repo);
3159
- }
3160
- async moveProjectItem(projectId, itemId, statusFieldId, statusOptionId) {
3161
- return this.projectsManager.moveProjectItem(
3162
- projectId,
3163
- itemId,
3164
- statusFieldId,
3165
- statusOptionId
3166
- );
3167
- }
3168
- async addProjectItem(projectId, contentId) {
3169
- return this.projectsManager.addProjectItem(projectId, contentId);
3170
- }
3171
- async getMilestones(owner, repo, state = "open", limit = 20) {
3172
- return this.milestonesManager.getMilestones(owner, repo, state, limit);
3173
- }
3174
- async getMilestone(owner, repo, milestoneNumber) {
3175
- return this.milestonesManager.getMilestone(owner, repo, milestoneNumber);
3176
- }
3177
- async createMilestone(owner, repo, title, description, dueOn) {
3178
- return this.milestonesManager.createMilestone(owner, repo, title, description, dueOn);
3179
- }
3180
- async updateMilestone(owner, repo, milestoneNumber, updates) {
3181
- return this.milestonesManager.updateMilestone(owner, repo, milestoneNumber, updates);
3182
- }
3183
- async deleteMilestone(owner, repo, milestoneNumber) {
3184
- return this.milestonesManager.deleteMilestone(owner, repo, milestoneNumber);
3185
- }
3186
- async getRepoStats(owner, repo) {
3187
- return this.insightsManager.getRepoStats(owner, repo);
3188
- }
3189
- async getTrafficData(owner, repo) {
3190
- return this.insightsManager.getTrafficData(owner, repo);
3191
- }
3192
- async getTopReferrers(owner, repo, limit = 5) {
3193
- return this.insightsManager.getTopReferrers(owner, repo, limit);
3194
- }
3195
- async getPopularPaths(owner, repo, limit = 5) {
3196
- return this.insightsManager.getPopularPaths(owner, repo, limit);
3197
1751
  }
3198
1752
  };
3199
1753
 
@@ -3264,35 +1818,11 @@ var ICON_PROMPT = {
3264
1818
  sizes: ["24x24"]
3265
1819
  };
3266
1820
 
3267
- // src/utils/resource-annotations.ts
3268
- var HIGH_PRIORITY = {
3269
- priority: 0.9,
3270
- audience: ["user", "assistant"]
3271
- };
3272
- var MEDIUM_PRIORITY = {
3273
- priority: 0.6,
3274
- audience: ["user", "assistant"]
3275
- };
3276
- var LOW_PRIORITY = {
3277
- priority: 0.4,
3278
- audience: ["user", "assistant"]
3279
- };
3280
- var ASSISTANT_FOCUSED = {
3281
- priority: 0.5,
3282
- audience: ["assistant"]
3283
- };
3284
- function withPriority(priority, base = MEDIUM_PRIORITY) {
3285
- return { ...base, priority };
3286
- }
3287
- function withSessionInit(base = HIGH_PRIORITY) {
3288
- return { ...base, sessionInit: true };
3289
- }
3290
-
3291
1821
  // src/handlers/resources/core/briefing/github-section.ts
3292
1822
  async function buildGitHubSection(github, config) {
3293
1823
  if (!github) return null;
3294
1824
  try {
3295
- const resolved = await resolveGitHubRepo(github);
1825
+ const resolved = await resolveGitHubRepo(github, config);
3296
1826
  if (isResourceError(resolved)) return null;
3297
1827
  const { owner, repo } = resolved;
3298
1828
  const [ciStatus, issuesAndPrs, milestones, insights, copilotReviews] = await Promise.all([
@@ -3338,7 +1868,10 @@ async function fetchCiStatus(github, owner, repo, config) {
3338
1868
  const runLimit = Math.max(1, config.workflowCount, config.workflowStatusBreakdown ? 10 : 1);
3339
1869
  const runs = await github.getWorkflowRuns(owner, repo, runLimit);
3340
1870
  if (runs.length === 0) return { status: "unknown" };
3341
- const latestRun = runs[0];
1871
+ const primaryRun = runs.find(
1872
+ (r) => r.status !== "completed" || ["success", "failure", "cancelled"].includes(r.conclusion ?? "")
1873
+ ) ?? runs[0];
1874
+ const latestRun = primaryRun;
3342
1875
  let status;
3343
1876
  if (!latestRun) {
3344
1877
  status = "unknown";
@@ -3699,92 +2232,129 @@ var briefingResource = {
3699
2232
  autoRead: true
3700
2233
  },
3701
2234
  handler: async (_uri, context) => {
3702
- const config = context.briefingConfig ?? DEFAULT_BRIEFING_CONFIG;
3703
- const journal = buildJournalContext(context, config);
3704
- const github = await buildGitHubSection(context.github, config);
3705
- const team = buildTeamContext(context, config);
3706
- const rulesFile = buildRulesFileInfo(config.rulesFilePath);
3707
- const skillsDir = buildSkillsDirInfo(config.skillsDirPath);
3708
- const latestPreview = journal.latestEntries[0] ? `#${journal.latestEntries[0].id} (${journal.latestEntries[0].type}): ${journal.latestEntries[0].preview}` : "No entries yet";
3709
- const userMessage = formatUserMessage({
3710
- repoName: github?.repo ?? "local",
3711
- branchName: github?.branch ?? "unknown",
3712
- ciStatus: github?.ci ?? "unknown",
3713
- totalEntries: journal.totalEntries,
3714
- latestPreview,
3715
- github,
3716
- teamTotalEntries: team?.teamInfo.totalEntries,
3717
- rulesFile,
3718
- skillsDir
3719
- });
3720
- return {
3721
- data: {
3722
- version: VERSION,
3723
- serverTime: (/* @__PURE__ */ new Date()).toISOString(),
3724
- journal: {
3725
- totalEntries: journal.totalEntries,
3726
- latestEntries: journal.latestEntries
3727
- },
3728
- github,
3729
- teamContext: team?.teamInfo,
3730
- ...team?.teamLatestEntries ? { teamLatestEntries: team.teamLatestEntries } : {},
3731
- ...rulesFile ? { rulesFile } : {},
3732
- ...skillsDir ? { skillsDir } : {},
3733
- behaviors: {
3734
- create: "implementations, decisions, bug-fixes, milestones",
3735
- search: "before decisions, referencing prior work",
3736
- link: "implementation\u2192spec, bugfix\u2192issue"
3737
- },
3738
- templateResources: [
3739
- "memory://projects/{number}/timeline",
3740
- "memory://issues/{issue_number}/entries",
3741
- "memory://prs/{pr_number}/entries",
3742
- "memory://prs/{pr_number}/timeline",
3743
- "memory://kanban/{project_number}",
3744
- "memory://kanban/{project_number}/diagram",
3745
- "memory://milestones/{number}"
3746
- ],
3747
- more: {
3748
- fullHealth: "memory://health",
3749
- allRecent: "memory://recent",
3750
- githubStatus: "memory://github/status",
3751
- repoInsights: "memory://github/insights",
3752
- contextBundle: "get-context-bundle prompt"
3753
- },
3754
- userMessage,
3755
- clientNote: "For full tool reference and field notes, read memory://instructions \u2014 only if your client did NOT auto-inject server instructions at session start (most modern clients including AntiGravity do this automatically)."
3756
- },
3757
- annotations: { lastModified: journal.lastModified }
3758
- };
2235
+ return buildBriefingData(context);
2236
+ }
2237
+ };
2238
+ var dynamicBriefingResource = {
2239
+ uri: "memory://briefing/{+repo}",
2240
+ name: "Dynamic Briefing",
2241
+ title: "Project-Specific Session Context",
2242
+ description: "Project-specific briefing context for AI agents. Same as memory://briefing but targets a specific repository name from the registered workspaces.",
2243
+ mimeType: "application/json",
2244
+ icons: [ICON_BRIEFING],
2245
+ annotations: {
2246
+ ...withPriority(0.8, ASSISTANT_FOCUSED)
2247
+ },
2248
+ handler: async (uri, context) => {
2249
+ const match = /memory:\/\/briefing\/(.+)/.exec(uri);
2250
+ const repoName = match?.[1] ? decodeURIComponent(match[1]) : void 0;
2251
+ return buildBriefingData(context, repoName);
3759
2252
  }
3760
2253
  };
2254
+ async function buildBriefingData(context, targetRepo) {
2255
+ const config = context.briefingConfig ?? DEFAULT_BRIEFING_CONFIG;
2256
+ let activeGithub = context.github;
2257
+ if (targetRepo && config.projectRegistry?.[targetRepo]) {
2258
+ const repoPath = config.projectRegistry[targetRepo].path;
2259
+ activeGithub = new GitHubIntegration(repoPath);
2260
+ }
2261
+ const journal = buildJournalContext(context, config);
2262
+ const github = await buildGitHubSection(activeGithub, config);
2263
+ const team = buildTeamContext(context, config);
2264
+ const rulesFile = buildRulesFileInfo(config.rulesFilePath);
2265
+ const skillsDir = buildSkillsDirInfo(config.skillsDirPath);
2266
+ const latestPreview = journal.latestEntries[0] ? `#${journal.latestEntries[0].id} (${journal.latestEntries[0].type}): ${journal.latestEntries[0].preview}` : "No entries yet";
2267
+ const userMessage = formatUserMessage({
2268
+ repoName: github?.repo ?? "local",
2269
+ branchName: github?.branch ?? "unknown",
2270
+ ciStatus: github?.ci ?? "unknown",
2271
+ totalEntries: journal.totalEntries,
2272
+ latestPreview,
2273
+ github,
2274
+ teamTotalEntries: team?.teamInfo.totalEntries,
2275
+ rulesFile,
2276
+ skillsDir
2277
+ });
2278
+ return {
2279
+ data: {
2280
+ version: VERSION,
2281
+ serverTime: (/* @__PURE__ */ new Date()).toISOString(),
2282
+ journal: {
2283
+ totalEntries: journal.totalEntries,
2284
+ latestEntries: journal.latestEntries
2285
+ },
2286
+ github,
2287
+ teamContext: team?.teamInfo,
2288
+ ...team?.teamLatestEntries ? { teamLatestEntries: team.teamLatestEntries } : {},
2289
+ ...rulesFile ? { rulesFile } : {},
2290
+ ...skillsDir ? { skillsDir } : {},
2291
+ ...config.projectRegistry ? { registeredWorkspaces: config.projectRegistry } : {},
2292
+ behaviors: {
2293
+ create: "implementations, decisions, bug-fixes, milestones",
2294
+ search: "before decisions, referencing prior work",
2295
+ link: "implementation\u2192spec, bugfix\u2192issue"
2296
+ },
2297
+ templateResources: [
2298
+ "memory://github/status/{repo}",
2299
+ "memory://github/insights/{repo}",
2300
+ "memory://github/milestones/{repo}",
2301
+ "memory://milestones/{repo}/{number}",
2302
+ "memory://projects/{number}/timeline",
2303
+ "memory://issues/{issue_number}/entries",
2304
+ "memory://prs/{pr_number}/entries",
2305
+ "memory://prs/{pr_number}/timeline",
2306
+ "memory://kanban/{project_number}",
2307
+ "memory://kanban/{project_number}/diagram",
2308
+ "memory://milestones/{number}"
2309
+ ],
2310
+ more: {
2311
+ fullHealth: "memory://health",
2312
+ allRecent: "memory://recent",
2313
+ githubStatus: "memory://github/status",
2314
+ repoInsights: "memory://github/insights",
2315
+ contextBundle: "get-context-bundle prompt"
2316
+ },
2317
+ userMessage,
2318
+ clientNote: "For full tool reference and field notes, read memory://instructions \u2014 only if your client did NOT auto-inject server instructions at session start (most modern clients including AntiGravity do this automatically).\\n" + (config.projectRegistry ? "\\nMulti-project registry detected. To retrieve CI status, branch, and issues for a specific project, use the get_github_context tool or dynamic resources (e.g. memory://github/status/{repo}) with the repository name." : "")
2319
+ },
2320
+ annotations: { lastModified: journal.lastModified }
2321
+ };
2322
+ }
3761
2323
 
3762
2324
  // src/constants/server-instructions.ts
3763
2325
  var CORE_INSTRUCTIONS = `# memory-journal-mcp
3764
2326
 
3765
- ## Session Start
2327
+ ## ESSENTIAL SESSION START!**
3766
2328
 
3767
- **REQUIRED**: Before processing any user request, read \`memory://briefing\` and **present the \`userMessage\` to the user as a formatted bullet list of key facts:**
2329
+ 1. You **MUST** read the \`memory://briefing/{repo_name}\` at the start of each chat!
2330
+ 2. Use the standard MCP \`read_resource\` tool for this (do NOT use Code Mode/execute_code).
2331
+ 3. Infer the \`repo_name\` from the user's prompt or your active workspace context.
2332
+ 4. Once read, present the \`userMessage\` from the briefing to the user as a formatted bullet list containing the key facts for all available fields including:
3768
2333
 
2334
+ - Project Name:
3769
2335
  - Entry counts (journal + team)
3770
- - GitHub: repo, branch, CI status, open issues/PRs
2336
+ - Latest Entry (journal + team):
2337
+ - GitHub: repo, branch, CI status, open issues/PRs, insights
3771
2338
  - Milestone progress (if any)
3772
2339
  - Template resources count
2340
+ - Registered Workspaces (if available - provides automatic repo-to-project routing)
3773
2341
  - Optional metadata present (rulesFile, skillsDir, workflowSummary, copilotReviews, Team DB)
3774
2342
 
3775
- **Server name for resource calls**: Derive from tool prefixes \u2014 strip the tool name suffix to get the server name.
3776
-
3777
2343
  - **AntiGravity**: Tools are \`mcp_{name}_{tool}\` \u2192 server name = \`memory-journal-mcp\`
3778
2344
  - **Cursor**: Tools are \`user-{name}-{tool}\` \u2192 server name = \`user-memory-journal-mcp\`
3779
2345
  - **Other clients**: Use configured name exactly. Use tool-prefix discovery if unsure.
3780
2346
 
3781
2347
  ## Behaviors
3782
2348
 
2349
+ ### memory-journal-mcp Behaviors
2350
+
2351
+ - **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.
3783
2352
  - **Create entries for**: implementations, decisions, bug fixes, milestones, user requests to "remember"
3784
2353
  - **Search before**: major decisions, referencing prior work, understanding project context
2354
+ - **Analyze insights**: Use cross-project insights (\`get_cross_project_insights\`) before defining architectures or cross-cutting abstractions. Use repo insights (\`memory://github/insights\`) to gauge traction.
3785
2355
  - **Link entries**: implementation\u2192spec, bugfix\u2192issue, followup\u2192prior work
3786
2356
 
3787
- ## Rule & Skill Suggestions
2357
+ ### Rule & Skill Suggestions
3788
2358
 
3789
2359
  When you notice the user consistently applies patterns, preferences, or workflows that could be codified:
3790
2360
 
@@ -3850,18 +2420,65 @@ function buildQuickAccess(groups) {
3850
2420
  return table;
3851
2421
  }
3852
2422
  var CODE_MODE_NAMESPACE_ROWS = [
3853
- { group: "core", label: "Core", namespace: "`mj.core.*`", example: '`mj.core.createEntry("Implemented feature X")`' },
3854
- { group: "search", label: "Search", namespace: "`mj.search.*`", example: '`mj.search.searchEntries("performance")`' },
3855
- { group: "analytics", label: "Analytics", namespace: "`mj.analytics.*`", example: "`mj.analytics.getStatistics()`" },
3856
- { group: "relationships", label: "Relationships", namespace: "`mj.relationships.*`", example: '`mj.relationships.linkEntries(1, 2, "implements")`' },
3857
- { group: "export", label: "Export", namespace: "`mj.export.*`", example: '`mj.export.exportEntries("json")`' },
3858
- { group: "admin", label: "Admin", namespace: "`mj.admin.*`", example: "`mj.admin.rebuildVectorIndex()`" },
3859
- { group: "github", label: "GitHub", namespace: "`mj.github.*`", example: '`mj.github.getGithubIssues({ state: "open" })`' },
3860
- { group: "backup", label: "Backup", namespace: "`mj.backup.*`", example: "`mj.backup.backupJournal()`" },
3861
- { group: "team", label: "Team", namespace: "`mj.team.*`", example: '`mj.team.teamCreateEntry("Team update")`' }
2423
+ {
2424
+ group: "core",
2425
+ label: "Core",
2426
+ namespace: "`mj.core.*`",
2427
+ example: '`mj.core.createEntry("Implemented feature X")`'
2428
+ },
2429
+ {
2430
+ group: "search",
2431
+ label: "Search",
2432
+ namespace: "`mj.search.*`",
2433
+ example: '`mj.search.searchEntries("performance")`'
2434
+ },
2435
+ {
2436
+ group: "analytics",
2437
+ label: "Analytics",
2438
+ namespace: "`mj.analytics.*`",
2439
+ example: "`mj.analytics.getStatistics()`"
2440
+ },
2441
+ {
2442
+ group: "relationships",
2443
+ label: "Relationships",
2444
+ namespace: "`mj.relationships.*`",
2445
+ example: '`mj.relationships.linkEntries(1, 2, "implements")`'
2446
+ },
2447
+ {
2448
+ group: "export",
2449
+ label: "Export",
2450
+ namespace: "`mj.export.*`",
2451
+ example: '`mj.export.exportEntries("json")`'
2452
+ },
2453
+ {
2454
+ group: "admin",
2455
+ label: "Admin",
2456
+ namespace: "`mj.admin.*`",
2457
+ example: "`mj.admin.rebuildVectorIndex()`"
2458
+ },
2459
+ {
2460
+ group: "github",
2461
+ label: "GitHub",
2462
+ namespace: "`mj.github.*`",
2463
+ example: '`mj.github.getGithubIssues({ state: "open" })`'
2464
+ },
2465
+ {
2466
+ group: "backup",
2467
+ label: "Backup",
2468
+ namespace: "`mj.backup.*`",
2469
+ example: "`mj.backup.backupJournal()`"
2470
+ },
2471
+ {
2472
+ group: "team",
2473
+ label: "Team",
2474
+ namespace: "`mj.team.*`",
2475
+ example: '`mj.team.teamCreateEntry("Team update")`'
2476
+ }
3862
2477
  ];
3863
2478
  function buildCodeModeInstructions(groups) {
3864
- const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map((r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`).join("\n");
2479
+ const rows = CODE_MODE_NAMESPACE_ROWS.filter((r) => groups.has(r.group)).map(
2480
+ (r) => `| ${r.label.padEnd(13)} | ${r.namespace.padEnd(20)} | ${r.example.padEnd(50)} |`
2481
+ ).join("\n");
3865
2482
  const fullSection = CODE_MODE_FULL_TEXT;
3866
2483
  const tableStart = fullSection.indexOf("| Group");
3867
2484
  const tableEnd = fullSection.indexOf("\n\n**Features**");
@@ -3895,6 +2512,8 @@ This executes JavaScript in a sandboxed environment with all tools available as
3895
2512
  **Readonly mode**: \`readonly: true\` restricts to read-only tools only. Calling a mutation method (e.g., \`mj.core.create(...)\`) in readonly mode throws an error that halts execution \u2014 the sandbox returns \`{ success: false, error: "Operation '...' is not found in group" }\`. If a group has no methods at all (fully stripped), the error says \`"no methods (read-only mode?)"\`.
3896
2513
  **Returns**: Last expression value. Errors return \`{ success: false, error: "..." }\`.
3897
2514
 
2515
+ **GitHub Context Injection**: You can pass \`repo: 'my-repo'\` directly to \`mj_execute_code\` (e.g., \`mj_execute_code({ code, repo: 'memory-journal-mcp' })\`) to instantly bind that repository and its default Kanban board to all GitHub and Kanban tools running inside the sandbox, avoiding the need to pass \`owner\`/\`repo\` manually to individual methods inside.
2516
+
3898
2517
  **Important \u2014 all \`mj.*\` methods return Promises. Always \`await\` them:**
3899
2518
 
3900
2519
  \`\`\`js
@@ -3923,9 +2542,12 @@ var GITHUB_INSTRUCTIONS = `
3923
2542
  - Include \`issue_number\`/\`pr_number\` in \`create_entry\` to auto-link
3924
2543
  - After closing issue/merging PR \u2192 create summary entry with learnings
3925
2544
  - CI failures \u2192 \`actions-failure-digest\` prompt or \`memory://actions/recent\`
3926
- - Kanban: \`get_kanban_board(project_number)\` \u2192 \`move_kanban_item\` \u2192 document completion
2545
+ - Kanban: \`get_kanban_board\` \u2192 \`move_kanban_item\` \u2192 document completion (project_number auto-resolves if repo is registered)
3927
2546
  - Milestones: \`get_github_milestones\` \u2192 track project progress, \`memory://github/milestones\`
3928
- - GitHub tools auto-detect owner/repo from git context; specify explicitly if null
2547
+ - **Multi-Project Routing**: If \`memory://briefing\` shows "Registered Workspaces":
2548
+ - **Tools**: Pass a \`repo\` parameter to ALL GitHub tools (including \`get_github_context\`) to explicitly target a specific project.
2549
+ - **Resources**: You MUST use the dynamic \`{repo}\` variants for resources (e.g., \`memory://github/status/{repo}\`, \`memory://github/insights/{repo}\`) rather than the base URI (\`memory://github/status\`), which will fail with a detection error.
2550
+ - **Dynamic Briefings**: You can explicitly request the briefing for a specific project by reading \`memory://briefing/{repo}\` instead of the global \`memory://briefing\` resource.
3929
2551
  `;
3930
2552
  var HELP_POINTERS = `
3931
2553
  ## Help Resources
@@ -3976,10 +2598,13 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
3976
2598
  ## Semantic Search
3977
2599
 
3978
2600
  - **Indexing**: Entries are auto-indexed on creation (fire-and-forget). If index count drifts from DB count, use \`rebuild_vector_index\` or enable \`AUTO_REBUILD_INDEX=true\` for automatic reconciliation on server startup.
2601
+ - **Related by ID**: Provide \`entry_id\` instead of a query string to find entries semantically related to an existing entry (reuses the existing embedding to avoid inference costs).
2602
+ - **Metadata Filters**: Semantic search supports explicit filtering by \`tags\`, \`entry_type\`, \`start_date\`, and \`end_date\`.
3979
2603
  - **Thresholds**: Default similarity threshold is 0.25. For broader matches, try 0.15-0.2. Higher values (0.4+) return only very close semantic matches. A quality floor of 0.5 is always enforced: if all results score below 0.5, a hint is included indicating results may be noise. The \`hint_on_empty\` flag (default true) only controls advisory hints for empty indexes and zero-match queries \u2014 the quality gate hint is always shown.
3980
2604
 
3981
2605
  ## Search
3982
2606
 
2607
+ - **Hybrid Ranking**: \`search_entries\` defaults to \`mode: 'auto'\`. Conversational prompts automatically utilize Reciprocal Rank Fusion (true Hybrid) bridging keyword and vector algorithms.
3983
2608
  - **\`search_entries\` FTS5 query syntax**: Uses FTS5 full-text search with Porter stemmer. Phrase queries: \`"error handling"\`. Prefix: \`auth*\`. Boolean: \`deploy OR release\`, \`error NOT warning\`. Word-boundary matching ("log" matches "log" but not "catalog"). Results ranked by BM25 relevance. Falls back to LIKE substring matching for queries with unbalanced quotes or special characters.
3984
2609
 
3985
2610
  ## Relationships & Analytics
@@ -4001,7 +2626,7 @@ var GOTCHAS_CONTENT = `# memory-journal-mcp \u2014 Field Notes & Gotchas
4001
2626
 
4002
2627
  - **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").
4003
2628
  - **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\`.
4004
- - **Team tools without \`TEAM_DB_PATH\`**: All ${TOOL_GROUPS.team.length} team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
2629
+ - **Team tools without \`TEAM_DB_PATH\`**: All 20 team tools return \`{ success: false, error: "Team collaboration is not configured..." }\` \u2014 no crash, no partial results.
4005
2630
  `;
4006
2631
  function generateInstructions(enabledTools, prompts, latestEntry, level = "standard", enabledGroups) {
4007
2632
  const groups = enabledGroups ?? getEnabledGroups(enabledTools);
@@ -4403,6 +3028,42 @@ ${entrySummary}
4403
3028
  ]
4404
3029
  };
4405
3030
  }
3031
+ },
3032
+ {
3033
+ name: "team-session-summary",
3034
+ description: "Create a session summary entry for the team capturing what was accomplished, pending items, and context for the next team session",
3035
+ icons: [ICON_PROMPT],
3036
+ arguments: [],
3037
+ handler: (_args, _db, teamDb) => {
3038
+ if (!teamDb) {
3039
+ throw new ConfigurationError("Team database not configured");
3040
+ }
3041
+ const recent = teamDb.getRecentEntries(5);
3042
+ const entrySummary = recent.length > 0 ? recent.map(
3043
+ (e) => `- #${String(e.id)} (${e.entryType}) ${e.content.slice(0, 80)}${e.content.length > 80 ? "..." : ""}`
3044
+ ).join("\n") : "- No entries yet";
3045
+ return {
3046
+ messages: [
3047
+ {
3048
+ role: "user",
3049
+ content: {
3050
+ type: "text",
3051
+ text: `Create a team session summary journal entry based on this context:
3052
+
3053
+ **Recent Team Entries:**
3054
+ ${entrySummary}
3055
+
3056
+ **Instructions:**
3057
+ 1. Summarize what the team accomplished in this session (key changes, decisions, files modified)
3058
+ 2. Note what's unfinished or blocked for the team (pending items, open questions)
3059
+ 3. Include context for the next team session (relevant entry IDs, branch names, PR numbers)
3060
+ 4. Use \`entry_type: "retrospective"\` and tag with \`session-summary\`
3061
+ 5. YOU MUST USE \`team_create_entry\` OR \`mj.team.create\` TO SAVE THIS ENTRY.`
3062
+ }
3063
+ }
3064
+ ]
3065
+ };
3066
+ }
4406
3067
  }
4407
3068
  ];
4408
3069
  }
@@ -4644,13 +3305,13 @@ function getPrompts() {
4644
3305
  icons: p.icons
4645
3306
  }));
4646
3307
  }
4647
- function getPrompt(name, args, db) {
3308
+ function getPrompt(name, args, db, teamDb) {
4648
3309
  const prompts = getAllPromptDefinitions();
4649
3310
  const prompt = prompts.find((p) => p.name === name);
4650
3311
  if (!prompt) {
4651
3312
  throw new ResourceNotFoundError("Prompt", name);
4652
3313
  }
4653
- return prompt.handler(args, db);
3314
+ return prompt.handler(args, db, teamDb);
4654
3315
  }
4655
3316
  function getAllPromptDefinitions() {
4656
3317
  return [...getWorkflowPromptDefinitions(), ...getGitHubPromptDefinitions()];
@@ -4725,6 +3386,19 @@ var healthResource = {
4725
3386
  filterString: context.filterConfig?.raw ?? null
4726
3387
  };
4727
3388
  const lastModified = (/* @__PURE__ */ new Date()).toISOString();
3389
+ const metricsSummary = (() => {
3390
+ try {
3391
+ const s = globalMetrics.getSummary();
3392
+ return {
3393
+ totalCalls: s.totalCalls,
3394
+ totalErrors: s.totalErrors,
3395
+ totalOutputTokens: s.totalOutputTokens,
3396
+ upSince: s.upSince
3397
+ };
3398
+ } catch {
3399
+ return null;
3400
+ }
3401
+ })();
4728
3402
  return {
4729
3403
  data: {
4730
3404
  ...dbHealth,
@@ -4735,6 +3409,7 @@ var healthResource = {
4735
3409
  ...context.teamDb.getHealthStatus()
4736
3410
  } : { configured: false },
4737
3411
  scheduler: context.scheduler ? context.scheduler.getStatus() : { active: false, jobs: [] },
3412
+ metrics: metricsSummary,
4738
3413
  timestamp: lastModified
4739
3414
  },
4740
3415
  annotations: { lastModified }
@@ -5031,10 +3706,148 @@ var skillsResource = {
5031
3706
  }
5032
3707
  };
5033
3708
 
3709
+ // src/handlers/resources/core/metrics-resource.ts
3710
+ function nowIso() {
3711
+ return (/* @__PURE__ */ new Date()).toISOString();
3712
+ }
3713
+ var metricsSummaryResource = {
3714
+ uri: "memory://metrics/summary",
3715
+ name: "Metrics Summary",
3716
+ title: "Tool Call Metrics Summary",
3717
+ description: "Aggregate metrics across all tool calls since server start. Includes total calls, errors, duration, and token estimates.",
3718
+ mimeType: "text/plain",
3719
+ annotations: {
3720
+ ...HIGH_PRIORITY,
3721
+ audience: ["assistant"]
3722
+ },
3723
+ handler: (_uri, _ctx) => {
3724
+ const lastModified = nowIso();
3725
+ const s = globalMetrics.getSummary();
3726
+ const errorRate = s.totalCalls > 0 ? (s.totalErrors / s.totalCalls * 100).toFixed(1) : "0.0";
3727
+ const avgDuration = s.totalCalls > 0 ? Math.round(s.totalDurationMs / s.totalCalls) : 0;
3728
+ const text = `metrics_summary:
3729
+ up_since: ${s.upSince}
3730
+ as_of: ${lastModified}
3731
+ total_calls: ${s.totalCalls}
3732
+ total_errors: ${s.totalErrors}
3733
+ error_rate_pct: ${errorRate}
3734
+ total_duration_ms: ${s.totalDurationMs}
3735
+ avg_duration_ms: ${avgDuration}
3736
+ total_input_tokens: ${s.totalInputTokens}
3737
+ total_output_tokens: ${s.totalOutputTokens}
3738
+ tools_called: ${Object.keys(s.toolBreakdown).length}
3739
+ `;
3740
+ return { data: text, annotations: { lastModified } };
3741
+ }
3742
+ };
3743
+ var metricsTokensResource = {
3744
+ uri: "memory://metrics/tokens",
3745
+ name: "Metrics Tokens",
3746
+ title: "Token Usage Breakdown by Tool",
3747
+ description: "Per-tool token usage breakdown sorted by total output tokens. Use this to identify which tools are consuming the most context window.",
3748
+ mimeType: "text/plain",
3749
+ annotations: {
3750
+ ...MEDIUM_PRIORITY,
3751
+ audience: ["assistant"]
3752
+ },
3753
+ handler: (_uri, _ctx) => {
3754
+ const lastModified = nowIso();
3755
+ const breakdown = globalMetrics.getTokenBreakdown();
3756
+ if (breakdown.length === 0) {
3757
+ return {
3758
+ data: `token_breakdown:
3759
+ note: No tool calls recorded yet.
3760
+ as_of: ${lastModified}
3761
+ `,
3762
+ annotations: { lastModified }
3763
+ };
3764
+ }
3765
+ const rows = breakdown.map(
3766
+ (t) => ` - tool: ${t.toolName}
3767
+ calls: ${t.callCount}
3768
+ input_tokens: ${t.inputTokens}
3769
+ output_tokens: ${t.outputTokens}
3770
+ avg_output_tokens: ${t.avgOutputTokens}`
3771
+ ).join("\n");
3772
+ const text = `token_breakdown:
3773
+ as_of: ${lastModified}
3774
+ ${rows}
3775
+ `;
3776
+ return { data: text, annotations: { lastModified } };
3777
+ }
3778
+ };
3779
+ var metricsSystemResource = {
3780
+ uri: "memory://metrics/system",
3781
+ name: "Metrics System",
3782
+ title: "System Metrics",
3783
+ description: "Process-level system metrics: memory usage, uptime, Node.js version, and platform.",
3784
+ mimeType: "text/plain",
3785
+ annotations: {
3786
+ ...MEDIUM_PRIORITY,
3787
+ audience: ["assistant"]
3788
+ },
3789
+ handler: (_uri, _ctx) => {
3790
+ const lastModified = nowIso();
3791
+ const sys = globalMetrics.getSystemMetrics();
3792
+ const text = `system_metrics:
3793
+ up_since: ${sys.upSince}
3794
+ uptime_seconds: ${sys.uptimeSeconds}
3795
+ process_memory_mb: ${sys.processMemoryMb}
3796
+ node_version: ${sys.nodeVersion}
3797
+ platform: ${sys.platform}
3798
+ as_of: ${lastModified}
3799
+ `;
3800
+ return { data: text, annotations: { lastModified } };
3801
+ }
3802
+ };
3803
+ var metricsUsersResource = {
3804
+ uri: "memory://metrics/users",
3805
+ name: "Metrics Users",
3806
+ title: "Per-User Call Counts",
3807
+ description: "Per-user tool call counts. Populated when user identifiers are provided via OAuth or request metadata. Returns empty breakdown when no user tracking configured.",
3808
+ mimeType: "text/plain",
3809
+ annotations: {
3810
+ ...LOW_PRIORITY,
3811
+ audience: ["assistant"]
3812
+ },
3813
+ handler: (_uri, _ctx) => {
3814
+ const lastModified = nowIso();
3815
+ const userBreakdown = globalMetrics.getUserBreakdown();
3816
+ const users = Object.entries(userBreakdown);
3817
+ if (users.length === 0) {
3818
+ return {
3819
+ data: `user_metrics:
3820
+ note: No user tracking data available.
3821
+ hint: User tracking activates when OAuth user identifiers are present.
3822
+ as_of: ${lastModified}
3823
+ `,
3824
+ annotations: { lastModified }
3825
+ };
3826
+ }
3827
+ const sorted = users.sort(([, a], [, b]) => b - a);
3828
+ const rows = sorted.map(([user, count]) => ` - user: ${user}
3829
+ calls: ${count}`).join("\n");
3830
+ const text = `user_metrics:
3831
+ as_of: ${lastModified}
3832
+ ${rows}
3833
+ `;
3834
+ return { data: text, annotations: { lastModified } };
3835
+ }
3836
+ };
3837
+ function getMetricsResourceDefinitions() {
3838
+ return [
3839
+ metricsSummaryResource,
3840
+ metricsTokensResource,
3841
+ metricsSystemResource,
3842
+ metricsUsersResource
3843
+ ];
3844
+ }
3845
+
5034
3846
  // src/handlers/resources/core/index.ts
5035
3847
  function getCoreResourceDefinitions() {
5036
3848
  return [
5037
3849
  briefingResource,
3850
+ dynamicBriefingResource,
5038
3851
  instructionsResource,
5039
3852
  recentResource,
5040
3853
  significantResource,
@@ -5043,7 +3856,8 @@ function getCoreResourceDefinitions() {
5043
3856
  rulesResource,
5044
3857
  workflowsResource,
5045
3858
  skillsResource,
5046
- healthResource
3859
+ healthResource,
3860
+ ...getMetricsResourceDefinitions()
5047
3861
  ];
5048
3862
  }
5049
3863
 
@@ -5124,11 +3938,11 @@ function getGraphResourceDefinitions() {
5124
3938
  annotations: MEDIUM_PRIORITY,
5125
3939
  handler: async (_uri, context) => {
5126
3940
  if (!context.github) {
5127
- return 'graph LR\n NoGitHub["GitHub integration not available \u2014 set GITHUB_TOKEN and GITHUB_REPO_PATH"]';
3941
+ return 'graph LR\n NoGitHub["GitHub integration not available \u2014 set GITHUB_TOKEN"]';
5128
3942
  }
5129
3943
  const repoInfo = await context.github.getRepoInfo();
5130
3944
  if (!repoInfo.owner || !repoInfo.repo) {
5131
- return 'graph LR\n NoRepo["Repository not detected \u2014 set GITHUB_REPO_PATH in your config"]';
3945
+ return 'graph LR\n NoRepo["Repository not detected \u2014 run in valid git repo or configure PROJECT_REGISTRY"]';
5132
3946
  }
5133
3947
  const workflowRuns = await context.github.getWorkflowRuns(
5134
3948
  repoInfo.owner,
@@ -5224,7 +4038,7 @@ var RESOURCE_WORKFLOW_LIMIT = 5;
5224
4038
  var RESOURCE_STATUS_MILESTONE_LIMIT = 5;
5225
4039
  var RESOURCE_MILESTONE_LIMIT = 20;
5226
4040
  function getGitHubResourceDefinitions() {
5227
- return [
4041
+ const definitions = [
5228
4042
  {
5229
4043
  uri: "memory://github/status",
5230
4044
  name: "GitHub Status",
@@ -5233,8 +4047,14 @@ function getGitHubResourceDefinitions() {
5233
4047
  mimeType: "application/json",
5234
4048
  icons: [ICON_GITHUB],
5235
4049
  annotations: withPriority(0.7, ASSISTANT_FOCUSED),
5236
- handler: async (_uri, context) => {
5237
- const resolved = await resolveGitHubRepo(context.github);
4050
+ handler: async (uri, context) => {
4051
+ const match = /memory:\/\/github\/status\/?(.*)?/.exec(uri);
4052
+ const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
4053
+ const resolved = await resolveGitHubRepo(
4054
+ context.github,
4055
+ context.briefingConfig,
4056
+ targetRepo
4057
+ );
5238
4058
  if (isResourceError(resolved)) return resolved;
5239
4059
  const { owner, repo, branch, lastModified, github } = resolved;
5240
4060
  const defaultProjectNumber = context.briefingConfig?.defaultProjectNumber;
@@ -5368,8 +4188,14 @@ function getGitHubResourceDefinitions() {
5368
4188
  mimeType: "application/json",
5369
4189
  icons: [ICON_ANALYTICS],
5370
4190
  annotations: { ...LOW_PRIORITY, audience: ["assistant"] },
5371
- handler: async (_uri, context) => {
5372
- const resolved = await resolveGitHubRepo(context.github);
4191
+ handler: async (uri, context) => {
4192
+ const match = /memory:\/\/github\/insights\/?(.*)?/.exec(uri);
4193
+ const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
4194
+ const resolved = await resolveGitHubRepo(
4195
+ context.github,
4196
+ context.briefingConfig,
4197
+ targetRepo
4198
+ );
5373
4199
  if (isResourceError(resolved)) return resolved;
5374
4200
  const { owner, repo, lastModified, github } = resolved;
5375
4201
  const stats = await github.getRepoStats(owner, repo);
@@ -5406,8 +4232,14 @@ function getGitHubResourceDefinitions() {
5406
4232
  mimeType: "application/json",
5407
4233
  icons: [ICON_MILESTONE],
5408
4234
  annotations: { ...MEDIUM_PRIORITY, audience: ["assistant"] },
5409
- handler: async (_uri, context) => {
5410
- const resolved = await resolveGitHubRepo(context.github);
4235
+ handler: async (uri, context) => {
4236
+ const match = /memory:\/\/github\/milestones\/?(.*)?/.exec(uri);
4237
+ const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
4238
+ const resolved = await resolveGitHubRepo(
4239
+ context.github,
4240
+ context.briefingConfig,
4241
+ targetRepo
4242
+ );
5411
4243
  if (isResourceError(resolved)) return resolved;
5412
4244
  const { owner, repo, lastModified, github } = resolved;
5413
4245
  const milestones = await github.getMilestones(
@@ -5444,15 +4276,20 @@ function getGitHubResourceDefinitions() {
5444
4276
  annotations: ASSISTANT_FOCUSED,
5445
4277
  handler: async (uri, context) => {
5446
4278
  const lastModified = (/* @__PURE__ */ new Date()).toISOString();
5447
- const match = /memory:\/\/milestones\/(\d+)/.exec(uri);
5448
- const milestoneNumber = match?.[1] ? parseInt(match[1], 10) : null;
4279
+ const match = /memory:\/\/milestones\/(?:(.*)\/)?(\d+)$/.exec(uri);
4280
+ const targetRepo = match?.[1] ? decodeURIComponent(match[1]) : void 0;
4281
+ const milestoneNumber = match?.[2] ? parseInt(match[2], 10) : null;
5449
4282
  if (milestoneNumber === null) {
5450
4283
  return {
5451
4284
  data: { error: "Invalid milestone number" },
5452
4285
  annotations: { lastModified }
5453
4286
  };
5454
4287
  }
5455
- const resolved = await resolveGitHubRepo(context.github);
4288
+ const resolved = await resolveGitHubRepo(
4289
+ context.github,
4290
+ context.briefingConfig,
4291
+ targetRepo
4292
+ );
5456
4293
  if (isResourceError(resolved)) return resolved;
5457
4294
  const { owner, repo, github } = resolved;
5458
4295
  const milestone = await github.getMilestone(owner, repo, milestoneNumber);
@@ -5477,6 +4314,22 @@ function getGitHubResourceDefinitions() {
5477
4314
  }
5478
4315
  }
5479
4316
  ];
4317
+ const dynamicDefinitions = definitions.map((def) => {
4318
+ const dynamicName = def.name + " (Dynamic)";
4319
+ let dynamicUri;
4320
+ if (def.uri === "memory://milestones/{number}") {
4321
+ dynamicUri = "memory://milestones/{+repo}/{number}";
4322
+ } else {
4323
+ dynamicUri = def.uri + "/{+repo}";
4324
+ }
4325
+ return {
4326
+ ...def,
4327
+ uri: dynamicUri,
4328
+ name: dynamicName,
4329
+ description: def.description + " (Supports explicit multi-project repository targeting via {repo})"
4330
+ };
4331
+ });
4332
+ return [...definitions, ...dynamicDefinitions];
5480
4333
  }
5481
4334
 
5482
4335
  // src/handlers/resources/templates.ts
@@ -5662,7 +4515,7 @@ function getTemplateResourceDefinitions() {
5662
4515
  if (!context.github) {
5663
4516
  return {
5664
4517
  error: "GitHub integration not available",
5665
- hint: "Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables."
4518
+ hint: "Set GITHUB_TOKEN environment variable."
5666
4519
  };
5667
4520
  }
5668
4521
  const repoInfo = await context.github.getRepoInfo();
@@ -5671,7 +4524,7 @@ function getTemplateResourceDefinitions() {
5671
4524
  if (!owner) {
5672
4525
  return {
5673
4526
  error: "Could not detect repository owner",
5674
- hint: "Set GITHUB_REPO_PATH to your git repository."
4527
+ hint: "Run the MCP server from a valid git repository or configure PROJECT_REGISTRY."
5675
4528
  };
5676
4529
  }
5677
4530
  const board = await context.github.getProjectKanban(owner, projectNumber, repo);
@@ -6041,7 +4894,7 @@ function getHelpResourceDefinitions() {
6041
4894
  var toolIndexModule = null;
6042
4895
  async function getAllToolDefinitionsAsync(context) {
6043
4896
  try {
6044
- toolIndexModule ??= await import('./tools-WPRY5MJ6.js');
4897
+ toolIndexModule ??= await import('./tools-FFFGXIKN.js');
6045
4898
  if (toolIndexModule === null) return [];
6046
4899
  const tools = toolIndexModule.getTools(context.db, null);
6047
4900
  return tools.map((t) => ({
@@ -6162,7 +5015,13 @@ async function readResource(uri, db, vectorManager, filterConfig, github, schedu
6162
5015
  }
6163
5016
  for (const resource of resources) {
6164
5017
  if (resource.uri.includes("{")) {
6165
- const pattern = resource.uri.replace(/\{[^}]+\}/g, "([^/]+)");
5018
+ const pattern = resource.uri.replace(
5019
+ /\{([^}]+)\}/g,
5020
+ (_match, paramName) => {
5021
+ const cleanParam = paramName.startsWith("+") ? paramName.slice(1) : paramName;
5022
+ return cleanParam === "repo" ? "(.+)" : "([^/]+)";
5023
+ }
5024
+ );
6166
5025
  const regex = new RegExp(`^${pattern}$`);
6167
5026
  if (regex.test(baseUri)) {
6168
5027
  const result = await Promise.resolve(resource.handler(uri, context));
@@ -6182,7 +5041,9 @@ function getAllResourceDefinitions() {
6182
5041
  ...getGitHubResourceDefinitions(),
6183
5042
  ...getTemplateResourceDefinitions(),
6184
5043
  ...getTeamResourceDefinitions(),
6185
- ...getHelpResourceDefinitions()
5044
+ ...getHelpResourceDefinitions(),
5045
+ // Audit resource — bound to the global audit logger (or null if unconfigured)
5046
+ getAuditResourceDef(getGlobalAuditLogger)
6186
5047
  ];
6187
5048
  }
6188
5049
 
@@ -6594,81 +5455,6 @@ var JwksFetchError = class extends OAuthError {
6594
5455
  function isOAuthError(error) {
6595
5456
  return error instanceof OAuthError;
6596
5457
  }
6597
-
6598
- // src/auth/scopes.ts
6599
- var SCOPES = {
6600
- /** Read-only access */
6601
- READ: "read",
6602
- /** Read and write access */
6603
- WRITE: "write",
6604
- /** Administrative access */
6605
- ADMIN: "admin",
6606
- /** Unrestricted access to all operations */
6607
- FULL: "full"
6608
- };
6609
- var BASE_SCOPES = ["read", "write", "admin", "full"];
6610
- var SUPPORTED_SCOPES = ["read", "write", "admin", "full"];
6611
- var TOOL_GROUP_SCOPES = {
6612
- core: SCOPES.READ,
6613
- search: SCOPES.READ,
6614
- analytics: SCOPES.READ,
6615
- relationships: SCOPES.READ,
6616
- export: SCOPES.READ,
6617
- admin: SCOPES.ADMIN,
6618
- github: SCOPES.WRITE,
6619
- backup: SCOPES.ADMIN,
6620
- team: SCOPES.WRITE,
6621
- codemode: SCOPES.ADMIN
6622
- };
6623
- var groupsForScope = (maxScope) => {
6624
- const hierarchy = {
6625
- read: 0,
6626
- write: 1,
6627
- admin: 2,
6628
- full: 3
6629
- };
6630
- const maxLevel = hierarchy[maxScope];
6631
- return Object.entries(TOOL_GROUP_SCOPES).filter(([, scope]) => hierarchy[scope] <= maxLevel).map(([group]) => group);
6632
- };
6633
- groupsForScope(SCOPES.READ);
6634
- groupsForScope(SCOPES.WRITE);
6635
- groupsForScope(SCOPES.ADMIN);
6636
- function parseScopes(scopeString) {
6637
- return scopeString.split(/\s+/).map((s) => s.trim()).filter((s) => s.length > 0);
6638
- }
6639
- function hasScope(grantedScopes, requiredScope) {
6640
- if (grantedScopes.includes(SCOPES.FULL)) {
6641
- return true;
6642
- }
6643
- if (grantedScopes.includes(requiredScope)) {
6644
- return true;
6645
- }
6646
- if (requiredScope === SCOPES.READ || requiredScope === SCOPES.WRITE) {
6647
- if (grantedScopes.includes(SCOPES.ADMIN)) {
6648
- return true;
6649
- }
6650
- }
6651
- if (requiredScope === SCOPES.READ) {
6652
- if (grantedScopes.includes(SCOPES.WRITE)) {
6653
- return true;
6654
- }
6655
- }
6656
- return false;
6657
- }
6658
-
6659
- // src/auth/scope-map.ts
6660
- var toolScopeMap = /* @__PURE__ */ new Map();
6661
- for (const [group, tools] of Object.entries(TOOL_GROUPS)) {
6662
- const scope = TOOL_GROUP_SCOPES[group];
6663
- if (scope) {
6664
- for (const toolName of tools) {
6665
- toolScopeMap.set(toolName, scope);
6666
- }
6667
- }
6668
- }
6669
- function getRequiredScope(toolName) {
6670
- return toolScopeMap.get(toolName) ?? SCOPES.READ;
6671
- }
6672
5458
  new AsyncLocalStorage();
6673
5459
 
6674
5460
  // src/auth/oauth-resource-server.ts
@@ -7645,7 +6431,7 @@ function registerResources(server, resources, handleResourceRead) {
7645
6431
  }
7646
6432
  }
7647
6433
  }
7648
- function registerPrompts(server, prompts, db) {
6434
+ function registerPrompts(server, prompts, db, teamDb) {
7649
6435
  for (const promptDef of prompts) {
7650
6436
  let argsSchema;
7651
6437
  if (promptDef.arguments && promptDef.arguments.length > 0) {
@@ -7663,7 +6449,7 @@ function registerPrompts(server, prompts, db) {
7663
6449
  },
7664
6450
  (providedArgs) => {
7665
6451
  const args = providedArgs;
7666
- const promptResult = getPrompt(promptDef.name, args, db);
6452
+ const promptResult = getPrompt(promptDef.name, args, db, teamDb);
7667
6453
  const result = {
7668
6454
  messages: promptResult.messages.map((m) => ({
7669
6455
  role: m.role,
@@ -7689,6 +6475,15 @@ async function createServer(options) {
7689
6475
  const db = await DatabaseAdapterFactory.create(dbPath);
7690
6476
  await db.initialize();
7691
6477
  logger.info("Database initialized", { module: "McpServer", dbPath });
6478
+ if (options.auditConfig?.enabled) {
6479
+ initializeAuditLogger(options.auditConfig);
6480
+ logger.info("Audit logging enabled", {
6481
+ module: "McpServer",
6482
+ path: options.auditConfig.logPath,
6483
+ redact: options.auditConfig.redact,
6484
+ auditReads: options.auditConfig.auditReads
6485
+ });
6486
+ }
7692
6487
  let teamDb;
7693
6488
  if (teamDbPath) {
7694
6489
  teamDb = await DatabaseAdapterFactory.create(teamDbPath);
@@ -7712,7 +6507,28 @@ async function createServer(options) {
7712
6507
  entriesIndexed: count
7713
6508
  });
7714
6509
  }
7715
- const github = new GitHubIntegration();
6510
+ let githubPath = ".";
6511
+ if (options.projectRegistry && Object.keys(options.projectRegistry).length > 0) {
6512
+ if (options.defaultProjectNumber !== void 0) {
6513
+ const defaultEntry = Object.values(options.projectRegistry).find(
6514
+ (r) => r.project_number === options.defaultProjectNumber
6515
+ );
6516
+ if (defaultEntry?.path) {
6517
+ githubPath = defaultEntry.path;
6518
+ }
6519
+ }
6520
+ if (githubPath === ".") {
6521
+ const firstEntry = Object.values(options.projectRegistry)[0];
6522
+ if (firstEntry?.path) {
6523
+ githubPath = firstEntry.path;
6524
+ }
6525
+ }
6526
+ }
6527
+ const github = new GitHubIntegration(githubPath);
6528
+ try {
6529
+ await github.getRepoInfo();
6530
+ } catch {
6531
+ }
7716
6532
  logger.info("GitHub integration initialized", {
7717
6533
  module: "McpServer",
7718
6534
  hasToken: github.isApiAvailable()
@@ -7742,12 +6558,16 @@ async function createServer(options) {
7742
6558
  entryType: recentEntries[0].entryType,
7743
6559
  content: recentEntries[0].content
7744
6560
  } : void 0;
6561
+ const customToolHandlerConfig = {
6562
+ defaultProjectNumber,
6563
+ projectRegistry: options.projectRegistry
6564
+ };
7745
6565
  const allTools = getTools(
7746
6566
  db,
7747
6567
  null,
7748
6568
  vectorManager,
7749
6569
  github,
7750
- { defaultProjectNumber },
6570
+ customToolHandlerConfig,
7751
6571
  teamDb,
7752
6572
  teamVectorManager
7753
6573
  );
@@ -7781,7 +6601,7 @@ async function createServer(options) {
7781
6601
  filterConfig,
7782
6602
  vectorManager,
7783
6603
  github,
7784
- { defaultProjectNumber },
6604
+ customToolHandlerConfig,
7785
6605
  teamDb,
7786
6606
  teamVectorManager
7787
6607
  ) : allTools;
@@ -7806,7 +6626,16 @@ async function createServer(options) {
7806
6626
  }
7807
6627
  }
7808
6628
  if (tool.outputSchema !== void 0) {
7809
- toolOptions["outputSchema"] = tool.outputSchema;
6629
+ const outSchema = tool.outputSchema;
6630
+ if (typeof outSchema === "object" && outSchema !== null && "passthrough" in outSchema && typeof outSchema.passthrough === "function") {
6631
+ try {
6632
+ toolOptions["outputSchema"] = outSchema.passthrough();
6633
+ } catch {
6634
+ toolOptions["outputSchema"] = outSchema;
6635
+ }
6636
+ } else {
6637
+ toolOptions["outputSchema"] = outSchema;
6638
+ }
7810
6639
  }
7811
6640
  if (tool.annotations !== void 0) {
7812
6641
  toolOptions["annotations"] = tool.annotations;
@@ -7829,7 +6658,7 @@ async function createServer(options) {
7829
6658
  db,
7830
6659
  vectorManager,
7831
6660
  github,
7832
- { defaultProjectNumber },
6661
+ customToolHandlerConfig,
7833
6662
  progressContext,
7834
6663
  teamDb,
7835
6664
  teamVectorManager
@@ -7893,7 +6722,8 @@ async function createServer(options) {
7893
6722
  ...options.briefingConfig ?? DEFAULT_BRIEFING_CONFIG,
7894
6723
  // Ensure defaultProjectNumber is available to resource handlers
7895
6724
  // (may come via briefingConfig from CLI, or directly from server options)
7896
- defaultProjectNumber: options.briefingConfig?.defaultProjectNumber ?? defaultProjectNumber
6725
+ defaultProjectNumber: options.briefingConfig?.defaultProjectNumber ?? defaultProjectNumber,
6726
+ projectRegistry: options.projectRegistry
7897
6727
  };
7898
6728
  const result = await readResource(
7899
6729
  uri.href,
@@ -7922,13 +6752,17 @@ async function createServer(options) {
7922
6752
  resources,
7923
6753
  handleResourceRead
7924
6754
  );
7925
- registerPrompts(server, prompts, db);
6755
+ registerPrompts(server, prompts, db, teamDb);
7926
6756
  if (transport === "stdio") {
7927
6757
  const stdioTransport = new StdioServerTransport();
7928
6758
  await server.connect(stdioTransport);
7929
6759
  logger.info("MCP server started on stdio", { module: "McpServer" });
7930
6760
  process.on("SIGINT", () => {
7931
6761
  logger.info("Shutting down...", { module: "McpServer" });
6762
+ const auditLogger = getGlobalAuditLogger();
6763
+ if (auditLogger) {
6764
+ void auditLogger.close();
6765
+ }
7932
6766
  db.close();
7933
6767
  teamDb?.close();
7934
6768
  process.exit(0);
@@ -7956,6 +6790,10 @@ async function createServer(options) {
7956
6790
  process.on("SIGINT", () => {
7957
6791
  void (async () => {
7958
6792
  await httpTransport.stop(scheduler);
6793
+ const auditLogger = getGlobalAuditLogger();
6794
+ if (auditLogger) {
6795
+ await auditLogger.close();
6796
+ }
7959
6797
  db.close();
7960
6798
  teamDb?.close();
7961
6799
  process.exit(0);
@@ -7964,4 +6802,4 @@ async function createServer(options) {
7964
6802
  }
7965
6803
  }
7966
6804
 
7967
- export { META_GROUPS, TOOL_GROUPS, VERSION, calculateTokenSavings, createServer, filterTools, getAllToolNames, getFilterSummary, getToolFilterFromEnv, getToolGroup, isToolEnabled, parseToolFilter };
6805
+ export { VERSION, createServer };