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.
- package/README.md +157 -95
- package/dist/{chunk-BI4ZNSKA.js → chunk-2BJHLTYP.js} +1406 -446
- package/dist/chunk-ARLH46WS.js +1659 -0
- package/dist/{chunk-N6EBIDN7.js → chunk-SBNQ7MXZ.js} +631 -1793
- package/dist/cli.js +40 -3
- package/dist/github-integration-PDRLXKGM.js +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +3 -2
- package/dist/tools-FFFGXIKN.js +3 -0
- package/package.json +15 -14
- package/dist/tools-WPRY5MJ6.js +0 -2
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { setDefaultSandboxMode,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
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
|
-
}
|
|
1722
|
+
);
|
|
1723
|
+
} else {
|
|
1724
|
+
logger.info(`Rebuilt vector index with ${String(indexed)} entries`, {
|
|
1725
|
+
module: "VectorSearch"
|
|
1726
|
+
});
|
|
3001
1727
|
}
|
|
3002
|
-
return {
|
|
1728
|
+
return { indexed, failed, firstError };
|
|
3003
1729
|
}
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
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
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
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.
|
|
3033
|
-
module: "
|
|
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
|
|
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
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
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
|
-
##
|
|
2327
|
+
## ESSENTIAL SESSION START!**
|
|
3766
2328
|
|
|
3767
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
{
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
{
|
|
3860
|
-
|
|
3861
|
-
|
|
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(
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
5237
|
-
const
|
|
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 (
|
|
5372
|
-
const
|
|
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 (
|
|
5410
|
-
const
|
|
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+)
|
|
5448
|
-
const
|
|
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(
|
|
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
|
|
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: "
|
|
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-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
6805
|
+
export { VERSION, createServer };
|