memory-journal-mcp 4.4.2 → 5.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.
Files changed (291) hide show
  1. package/.github/workflows/codeql.yml +1 -6
  2. package/.github/workflows/docker-publish.yml +15 -49
  3. package/.github/workflows/lint-and-test.yml +1 -1
  4. package/.github/workflows/secrets-scanning.yml +4 -3
  5. package/.github/workflows/security-update.yml +3 -3
  6. package/CHANGELOG.md +213 -0
  7. package/CONTRIBUTING.md +132 -97
  8. package/DOCKER_README.md +184 -235
  9. package/Dockerfile +27 -24
  10. package/README.md +218 -190
  11. package/SECURITY.md +27 -35
  12. package/dist/cli.js +16 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/constants/ServerInstructions.d.ts +5 -1
  15. package/dist/constants/ServerInstructions.d.ts.map +1 -1
  16. package/dist/constants/ServerInstructions.js +133 -73
  17. package/dist/constants/ServerInstructions.js.map +1 -1
  18. package/dist/constants/icons.d.ts +2 -2
  19. package/dist/constants/icons.d.ts.map +1 -1
  20. package/dist/constants/icons.js +7 -6
  21. package/dist/constants/icons.js.map +1 -1
  22. package/dist/database/SqliteAdapter.d.ts +37 -24
  23. package/dist/database/SqliteAdapter.d.ts.map +1 -1
  24. package/dist/database/SqliteAdapter.js +319 -157
  25. package/dist/database/SqliteAdapter.js.map +1 -1
  26. package/dist/database/schema.d.ts +45 -0
  27. package/dist/database/schema.d.ts.map +1 -0
  28. package/dist/database/schema.js +92 -0
  29. package/dist/database/schema.js.map +1 -0
  30. package/dist/filtering/ToolFilter.d.ts +1 -1
  31. package/dist/filtering/ToolFilter.d.ts.map +1 -1
  32. package/dist/filtering/ToolFilter.js +13 -2
  33. package/dist/filtering/ToolFilter.js.map +1 -1
  34. package/dist/github/GitHubIntegration.d.ts.map +1 -1
  35. package/dist/github/GitHubIntegration.js +1 -3
  36. package/dist/github/GitHubIntegration.js.map +1 -1
  37. package/dist/handlers/prompts/github.d.ts +12 -0
  38. package/dist/handlers/prompts/github.d.ts.map +1 -0
  39. package/dist/handlers/prompts/github.js +178 -0
  40. package/dist/handlers/prompts/github.js.map +1 -0
  41. package/dist/handlers/prompts/index.d.ts +23 -2
  42. package/dist/handlers/prompts/index.d.ts.map +1 -1
  43. package/dist/handlers/prompts/index.js +7 -432
  44. package/dist/handlers/prompts/index.js.map +1 -1
  45. package/dist/handlers/prompts/workflow.d.ts +12 -0
  46. package/dist/handlers/prompts/workflow.d.ts.map +1 -0
  47. package/dist/handlers/prompts/workflow.js +277 -0
  48. package/dist/handlers/prompts/workflow.js.map +1 -0
  49. package/dist/handlers/resources/core.d.ts +11 -0
  50. package/dist/handlers/resources/core.d.ts.map +1 -0
  51. package/dist/handlers/resources/core.js +433 -0
  52. package/dist/handlers/resources/core.js.map +1 -0
  53. package/dist/handlers/resources/github.d.ts +11 -0
  54. package/dist/handlers/resources/github.d.ts.map +1 -0
  55. package/dist/handlers/resources/github.js +314 -0
  56. package/dist/handlers/resources/github.js.map +1 -0
  57. package/dist/handlers/resources/graph.d.ts +11 -0
  58. package/dist/handlers/resources/graph.d.ts.map +1 -0
  59. package/dist/handlers/resources/graph.js +204 -0
  60. package/dist/handlers/resources/graph.js.map +1 -0
  61. package/dist/handlers/resources/index.d.ts +5 -20
  62. package/dist/handlers/resources/index.d.ts.map +1 -1
  63. package/dist/handlers/resources/index.js +16 -1278
  64. package/dist/handlers/resources/index.js.map +1 -1
  65. package/dist/handlers/resources/shared.d.ts +60 -0
  66. package/dist/handlers/resources/shared.d.ts.map +1 -0
  67. package/dist/handlers/resources/shared.js +49 -0
  68. package/dist/handlers/resources/shared.js.map +1 -0
  69. package/dist/handlers/resources/team.d.ts +13 -0
  70. package/dist/handlers/resources/team.d.ts.map +1 -0
  71. package/dist/handlers/resources/team.js +119 -0
  72. package/dist/handlers/resources/team.js.map +1 -0
  73. package/dist/handlers/resources/templates.d.ts +13 -0
  74. package/dist/handlers/resources/templates.d.ts.map +1 -0
  75. package/dist/handlers/resources/templates.js +310 -0
  76. package/dist/handlers/resources/templates.js.map +1 -0
  77. package/dist/handlers/tools/admin.d.ts +8 -0
  78. package/dist/handlers/tools/admin.d.ts.map +1 -0
  79. package/dist/handlers/tools/admin.js +270 -0
  80. package/dist/handlers/tools/admin.js.map +1 -0
  81. package/dist/handlers/tools/analytics.d.ts +8 -0
  82. package/dist/handlers/tools/analytics.d.ts.map +1 -0
  83. package/dist/handlers/tools/analytics.js +256 -0
  84. package/dist/handlers/tools/analytics.js.map +1 -0
  85. package/dist/handlers/tools/backup.d.ts +8 -0
  86. package/dist/handlers/tools/backup.d.ts.map +1 -0
  87. package/dist/handlers/tools/backup.js +224 -0
  88. package/dist/handlers/tools/backup.js.map +1 -0
  89. package/dist/handlers/tools/core.d.ts +9 -0
  90. package/dist/handlers/tools/core.d.ts.map +1 -0
  91. package/dist/handlers/tools/core.js +326 -0
  92. package/dist/handlers/tools/core.js.map +1 -0
  93. package/dist/handlers/tools/export.d.ts +8 -0
  94. package/dist/handlers/tools/export.d.ts.map +1 -0
  95. package/dist/handlers/tools/export.js +89 -0
  96. package/dist/handlers/tools/export.js.map +1 -0
  97. package/dist/handlers/tools/github/helpers.d.ts +34 -0
  98. package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
  99. package/dist/handlers/tools/github/helpers.js +52 -0
  100. package/dist/handlers/tools/github/helpers.js.map +1 -0
  101. package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
  102. package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
  103. package/dist/handlers/tools/github/insights-tools.js +104 -0
  104. package/dist/handlers/tools/github/insights-tools.js.map +1 -0
  105. package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
  106. package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
  107. package/dist/handlers/tools/github/issue-tools.js +359 -0
  108. package/dist/handlers/tools/github/issue-tools.js.map +1 -0
  109. package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
  110. package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
  111. package/dist/handlers/tools/github/kanban-tools.js +108 -0
  112. package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
  113. package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
  114. package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
  115. package/dist/handlers/tools/github/milestone-tools.js +302 -0
  116. package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
  117. package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
  118. package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
  119. package/dist/handlers/tools/github/mutation-tools.js +15 -0
  120. package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
  121. package/dist/handlers/tools/github/read-tools.d.ts +8 -0
  122. package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
  123. package/dist/handlers/tools/github/read-tools.js +260 -0
  124. package/dist/handlers/tools/github/read-tools.js.map +1 -0
  125. package/dist/handlers/tools/github/schemas.d.ts +467 -0
  126. package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
  127. package/dist/handlers/tools/github/schemas.js +335 -0
  128. package/dist/handlers/tools/github/schemas.js.map +1 -0
  129. package/dist/handlers/tools/github.d.ts +14 -0
  130. package/dist/handlers/tools/github.d.ts.map +1 -0
  131. package/dist/handlers/tools/github.js +28 -0
  132. package/dist/handlers/tools/github.js.map +1 -0
  133. package/dist/handlers/tools/index.d.ts +15 -20
  134. package/dist/handlers/tools/index.d.ts.map +1 -1
  135. package/dist/handlers/tools/index.js +117 -2909
  136. package/dist/handlers/tools/index.js.map +1 -1
  137. package/dist/handlers/tools/relationships.d.ts +8 -0
  138. package/dist/handlers/tools/relationships.d.ts.map +1 -0
  139. package/dist/handlers/tools/relationships.js +308 -0
  140. package/dist/handlers/tools/relationships.js.map +1 -0
  141. package/dist/handlers/tools/schemas.d.ts +108 -0
  142. package/dist/handlers/tools/schemas.d.ts.map +1 -0
  143. package/dist/handlers/tools/schemas.js +122 -0
  144. package/dist/handlers/tools/schemas.js.map +1 -0
  145. package/dist/handlers/tools/search.d.ts +8 -0
  146. package/dist/handlers/tools/search.d.ts.map +1 -0
  147. package/dist/handlers/tools/search.js +282 -0
  148. package/dist/handlers/tools/search.js.map +1 -0
  149. package/dist/handlers/tools/team.d.ts +11 -0
  150. package/dist/handlers/tools/team.d.ts.map +1 -0
  151. package/dist/handlers/tools/team.js +239 -0
  152. package/dist/handlers/tools/team.js.map +1 -0
  153. package/dist/server/McpServer.d.ts +4 -0
  154. package/dist/server/McpServer.d.ts.map +1 -1
  155. package/dist/server/McpServer.js +48 -297
  156. package/dist/server/McpServer.js.map +1 -1
  157. package/dist/server/Scheduler.d.ts +91 -0
  158. package/dist/server/Scheduler.d.ts.map +1 -0
  159. package/dist/server/Scheduler.js +201 -0
  160. package/dist/server/Scheduler.js.map +1 -0
  161. package/dist/transports/http.d.ts +66 -0
  162. package/dist/transports/http.d.ts.map +1 -0
  163. package/dist/transports/http.js +519 -0
  164. package/dist/transports/http.js.map +1 -0
  165. package/dist/types/entities.d.ts +101 -0
  166. package/dist/types/entities.d.ts.map +1 -0
  167. package/dist/types/entities.js +5 -0
  168. package/dist/types/entities.js.map +1 -0
  169. package/dist/types/filtering.d.ts +34 -0
  170. package/dist/types/filtering.d.ts.map +1 -0
  171. package/dist/types/filtering.js +5 -0
  172. package/dist/types/filtering.js.map +1 -0
  173. package/dist/types/github.d.ts +166 -0
  174. package/dist/types/github.d.ts.map +1 -0
  175. package/dist/types/github.js +5 -0
  176. package/dist/types/github.js.map +1 -0
  177. package/dist/types/index.d.ts +35 -292
  178. package/dist/types/index.d.ts.map +1 -1
  179. package/dist/types/index.js +2 -2
  180. package/dist/types/index.js.map +1 -1
  181. package/dist/utils/error-helpers.d.ts +37 -0
  182. package/dist/utils/error-helpers.d.ts.map +1 -0
  183. package/dist/utils/error-helpers.js +47 -0
  184. package/dist/utils/error-helpers.js.map +1 -0
  185. package/dist/utils/logger.d.ts.map +1 -1
  186. package/dist/utils/logger.js +6 -3
  187. package/dist/utils/logger.js.map +1 -1
  188. package/dist/utils/security-utils.d.ts +0 -21
  189. package/dist/utils/security-utils.d.ts.map +1 -1
  190. package/dist/utils/security-utils.js +0 -47
  191. package/dist/utils/security-utils.js.map +1 -1
  192. package/dist/vector/VectorSearchManager.d.ts.map +1 -1
  193. package/dist/vector/VectorSearchManager.js +9 -32
  194. package/dist/vector/VectorSearchManager.js.map +1 -1
  195. package/docker-compose.yml +11 -2
  196. package/hooks/README.md +107 -0
  197. package/hooks/cursor/hooks.json +10 -0
  198. package/hooks/cursor/memory-journal.mdc +22 -0
  199. package/hooks/cursor/session-end.sh +19 -0
  200. package/hooks/kilo-code/session-end-mode.json +11 -0
  201. package/hooks/kiro/session-end.md +13 -0
  202. package/mcp-config-example.json +1 -0
  203. package/package.json +11 -9
  204. package/playwright.config.ts +29 -0
  205. package/releases/v4.5.0.md +116 -0
  206. package/releases/v5.0.0.md +105 -0
  207. package/scripts/generate-server-instructions.ts +176 -0
  208. package/scripts/server-instructions-function-body.ts +77 -0
  209. package/server.json +3 -3
  210. package/src/cli.ts +45 -1
  211. package/src/constants/ServerInstructions.ts +133 -73
  212. package/src/constants/icons.ts +8 -7
  213. package/src/constants/server-instructions.md +268 -0
  214. package/src/database/SqliteAdapter.ts +358 -192
  215. package/src/database/schema.ts +125 -0
  216. package/src/filtering/ToolFilter.ts +13 -2
  217. package/src/github/GitHubIntegration.ts +1 -3
  218. package/src/handlers/prompts/github.ts +209 -0
  219. package/src/handlers/prompts/index.ts +10 -499
  220. package/src/handlers/prompts/workflow.ts +314 -0
  221. package/src/handlers/resources/core.ts +528 -0
  222. package/src/handlers/resources/github.ts +358 -0
  223. package/src/handlers/resources/graph.ts +254 -0
  224. package/src/handlers/resources/index.ts +23 -1570
  225. package/src/handlers/resources/shared.ts +103 -0
  226. package/src/handlers/resources/team.ts +133 -0
  227. package/src/handlers/resources/templates.ts +374 -0
  228. package/src/handlers/tools/admin.ts +285 -0
  229. package/src/handlers/tools/analytics.ts +301 -0
  230. package/src/handlers/tools/backup.ts +242 -0
  231. package/src/handlers/tools/core.ts +350 -0
  232. package/src/handlers/tools/export.ts +115 -0
  233. package/src/handlers/tools/github/helpers.ts +86 -0
  234. package/src/handlers/tools/github/insights-tools.ts +119 -0
  235. package/src/handlers/tools/github/issue-tools.ts +439 -0
  236. package/src/handlers/tools/github/kanban-tools.ts +134 -0
  237. package/src/handlers/tools/github/milestone-tools.ts +392 -0
  238. package/src/handlers/tools/github/mutation-tools.ts +17 -0
  239. package/src/handlers/tools/github/read-tools.ts +328 -0
  240. package/src/handlers/tools/github/schemas.ts +369 -0
  241. package/src/handlers/tools/github.ts +36 -0
  242. package/src/handlers/tools/index.ts +144 -3325
  243. package/src/handlers/tools/relationships.ts +358 -0
  244. package/src/handlers/tools/schemas.ts +132 -0
  245. package/src/handlers/tools/search.ts +343 -0
  246. package/src/handlers/tools/team.ts +273 -0
  247. package/src/server/McpServer.ts +63 -358
  248. package/src/server/Scheduler.ts +278 -0
  249. package/src/transports/http.ts +635 -0
  250. package/src/types/entities.ts +145 -0
  251. package/src/types/filtering.ts +54 -0
  252. package/src/types/github.ts +180 -0
  253. package/src/types/index.ts +67 -375
  254. package/src/utils/error-helpers.ts +52 -0
  255. package/src/utils/logger.ts +6 -3
  256. package/src/utils/security-utils.ts +0 -52
  257. package/src/vector/VectorSearchManager.ts +9 -33
  258. package/tests/constants/icons.test.ts +1 -2
  259. package/tests/constants/server-instructions.test.ts +30 -4
  260. package/tests/database/sqlite-adapter.test.ts +91 -7
  261. package/tests/e2e/auth.spec.ts +154 -0
  262. package/tests/e2e/health.spec.ts +63 -0
  263. package/tests/e2e/protocols.spec.ts +134 -0
  264. package/tests/e2e/resources.spec.ts +103 -0
  265. package/tests/e2e/scheduler.spec.ts +79 -0
  266. package/tests/e2e/security.spec.ts +91 -0
  267. package/tests/e2e/sessions.spec.ts +95 -0
  268. package/tests/e2e/stateless.spec.ts +121 -0
  269. package/tests/e2e/tools.spec.ts +111 -0
  270. package/tests/filtering/tool-filter.test.ts +46 -0
  271. package/tests/handlers/error-path-coverage.test.ts +324 -0
  272. package/tests/handlers/github-resource-handlers.test.ts +453 -0
  273. package/tests/handlers/github-tool-handlers.test.ts +899 -0
  274. package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
  275. package/tests/handlers/prompt-handlers.test.ts +40 -0
  276. package/tests/handlers/resource-handler-coverage.test.ts +181 -0
  277. package/tests/handlers/resource-handlers.test.ts +33 -9
  278. package/tests/handlers/search-tool-handlers.test.ts +272 -0
  279. package/tests/handlers/targeted-gap-closure.test.ts +387 -0
  280. package/tests/handlers/team-resource-handlers.test.ts +156 -0
  281. package/tests/handlers/team-tool-handlers.test.ts +301 -0
  282. package/tests/handlers/tool-handler-coverage.test.ts +469 -0
  283. package/tests/handlers/tool-handlers.test.ts +2 -2
  284. package/tests/security/sql-injection.test.ts +3 -54
  285. package/tests/server/mcp-server.test.ts +503 -8
  286. package/tests/server/scheduler.test.ts +400 -0
  287. package/tests/transports/http-transport.test.ts +620 -0
  288. package/tests/vector/vector-search-manager.test.ts +60 -0
  289. package/vitest.config.ts +4 -1
  290. package/.memory-journal-team.db +0 -0
  291. package/.vscode/settings.json +0 -84
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * Memory Journal MCP Server - Resource Handlers
3
3
  *
4
+ * Barrel file composing resource definitions from sub-modules.
4
5
  * Exports all MCP resources with annotations following MCP 2025-11-25 spec.
5
6
  */
6
- import { getAllToolNames } from '../../filtering/ToolFilter.js';
7
- import { generateInstructions } from '../../constants/ServerInstructions.js';
8
- import { getPrompts } from '../prompts/index.js';
9
- import { ICON_BRIEFING, ICON_CLOCK, ICON_GRAPH, ICON_HEALTH, ICON_GITHUB, ICON_MILESTONE, ICON_STAR, ICON_TAG, ICON_TEAM, ICON_ISSUE, ICON_PR, ICON_ANALYTICS, } from '../../constants/icons.js';
10
- import pkg from '../../../package.json' with { type: 'json' };
7
+ // Import sub-module definitions
8
+ import { getCoreResourceDefinitions } from './core.js';
9
+ import { getGraphResourceDefinitions } from './graph.js';
10
+ import { getGitHubResourceDefinitions } from './github.js';
11
+ import { getTemplateResourceDefinitions } from './templates.js';
12
+ import { getTeamResourceDefinitions } from './team.js';
11
13
  /**
12
14
  * Get all resource definitions for MCP list
13
15
  */
@@ -19,7 +21,7 @@ export function getResources() {
19
21
  description: r.description,
20
22
  mimeType: r.mimeType,
21
23
  annotations: r.annotations,
22
- icons: r.icons, // MCP 2025-11-25 icons
24
+ icons: r.icons,
23
25
  }));
24
26
  }
25
27
  /**
@@ -61,9 +63,9 @@ function getBaseUri(uri) {
61
63
  /**
62
64
  * Read a resource by URI - returns data and optional annotations
63
65
  */
64
- export async function readResource(uri, db, vectorManager, filterConfig, github) {
66
+ export async function readResource(uri, db, vectorManager, filterConfig, github, scheduler, teamDb) {
65
67
  const resources = getAllResourceDefinitions();
66
- const context = { db, vectorManager, filterConfig, github };
68
+ const context = { db, teamDb, vectorManager, filterConfig, github, scheduler };
67
69
  // Strip query parameters for matching, but pass full URI to handler
68
70
  const baseUri = getBaseUri(uri);
69
71
  // Check for exact match first (using base URI without query params)
@@ -93,1279 +95,15 @@ export async function readResource(uri, db, vectorManager, filterConfig, github)
93
95
  throw new Error(`Unknown resource: ${uri}`);
94
96
  }
95
97
  /**
96
- * Execute a raw SQL query on the database
97
- */
98
- function execQuery(db, sql, params = []) {
99
- const rawDb = db.getRawDb();
100
- const result = rawDb.exec(sql, params);
101
- if (result.length === 0)
102
- return [];
103
- const columns = result[0]?.columns ?? [];
104
- return (result[0]?.values ?? []).map((values) => {
105
- const obj = {};
106
- columns.forEach((col, i) => {
107
- obj[col] = values[i];
108
- });
109
- return obj;
110
- });
111
- }
112
- /**
113
- * Get total tool count for health status
114
- */
115
- function getTotalToolCount() {
116
- return getAllToolNames().length;
117
- }
118
- /**
119
- * Transform snake_case SQL row to camelCase entry object
120
- * Ensures consistency with SqliteAdapter.getRecentEntries() output
121
- */
122
- function transformEntryRow(row) {
123
- return {
124
- id: row['id'],
125
- entryType: row['entry_type'],
126
- content: row['content'],
127
- timestamp: row['timestamp'],
128
- isPersonal: row['is_personal'] === 1 || row['is_personal'] === true,
129
- significanceType: row['significance_type'] ?? null,
130
- autoContext: row['auto_context'] ?? null,
131
- deletedAt: row['deleted_at'] ?? null,
132
- projectNumber: row['project_number'] ?? null,
133
- projectOwner: row['project_owner'] ?? null,
134
- issueNumber: row['issue_number'] ?? null,
135
- issueUrl: row['issue_url'] ?? null,
136
- prNumber: row['pr_number'] ?? null,
137
- prUrl: row['pr_url'] ?? null,
138
- prStatus: row['pr_status'] ?? null,
139
- workflowRunId: row['workflow_run_id'] ?? null,
140
- workflowName: row['workflow_name'] ?? null,
141
- workflowStatus: row['workflow_status'] ?? null,
142
- };
143
- }
144
- /**
145
- * Get all resource definitions
98
+ * Get all resource definitions by composing sub-module definitions
146
99
  */
147
100
  function getAllResourceDefinitions() {
148
101
  return [
149
- // Session initialization resource - highest priority, auto-subscribe hint for session start
150
- {
151
- uri: 'memory://briefing',
152
- name: 'Initial Briefing',
153
- title: 'Session Initialization Context',
154
- description: 'AUTO-READ AT SESSION START: Project context for AI agents (~300 tokens). Contains userMessage to show user.',
155
- mimeType: 'application/json',
156
- icons: [ICON_BRIEFING],
157
- annotations: {
158
- audience: ['assistant'],
159
- priority: 1.0, // Highest priority - should be read first
160
- // Custom hints for clients that support auto-subscribe behavior
161
- autoRead: true, // Hint: automatically fetch this resource at session start
162
- sessionInit: true, // Hint: this resource is specifically for session initialization
163
- },
164
- handler: async (_uri, context) => {
165
- // Get latest 3 entries (compact)
166
- const recentEntries = context.db.getRecentEntries(3);
167
- const latestEntries = recentEntries.map((e) => ({
168
- id: e.id,
169
- timestamp: e.timestamp,
170
- type: e.entryType,
171
- preview: e.content.slice(0, 80) + (e.content.length > 80 ? '...' : ''),
172
- }));
173
- // Get compact GitHub status if available
174
- let github = null;
175
- if (context.github) {
176
- try {
177
- const repoInfo = await context.github.getRepoInfo();
178
- const owner = repoInfo.owner;
179
- const repo = repoInfo.repo;
180
- if (owner && repo) {
181
- // Get CI status (based on latest run only)
182
- let ciStatus = 'unknown';
183
- try {
184
- const runs = await context.github.getWorkflowRuns(owner, repo, 1);
185
- if (runs.length > 0) {
186
- const latestRun = runs[0];
187
- if (!latestRun) {
188
- ciStatus = 'unknown';
189
- }
190
- else if (latestRun.status !== 'completed') {
191
- ciStatus = 'pending';
192
- }
193
- else {
194
- // Map workflow conclusion to CI status
195
- switch (latestRun.conclusion) {
196
- case 'success':
197
- ciStatus = 'passing';
198
- break;
199
- case 'failure':
200
- ciStatus = 'failing';
201
- break;
202
- case 'cancelled':
203
- ciStatus = 'cancelled';
204
- break;
205
- default:
206
- ciStatus = 'unknown';
207
- }
208
- }
209
- }
210
- }
211
- catch {
212
- // CI status unavailable
213
- }
214
- // Get issue/PR counts
215
- let openIssues = 0;
216
- let openPRs = 0;
217
- try {
218
- const issues = await context.github.getIssues(owner, repo, 'open', 1);
219
- openIssues = issues.length > 0 ? issues.length : 0;
220
- const prs = await context.github.getPullRequests(owner, repo, 'open', 1);
221
- openPRs = prs.length > 0 ? prs.length : 0;
222
- }
223
- catch {
224
- // Counts unavailable
225
- }
226
- // Get milestone summary for briefing
227
- let milestones = [];
228
- try {
229
- const msList = await context.github.getMilestones(owner, repo, 'open', 3);
230
- milestones = msList.map((m) => {
231
- const total = m.closedIssues + m.openIssues;
232
- const pct = total > 0 ? Math.round((m.closedIssues / total) * 100) : 0;
233
- return {
234
- title: m.title,
235
- progress: `${String(pct)}%`,
236
- dueOn: m.dueOn,
237
- };
238
- });
239
- }
240
- catch {
241
- // Milestones unavailable
242
- }
243
- // Get repo insights (stars, forks, traffic)
244
- let insights = undefined;
245
- try {
246
- const repoStats = await context.github.getRepoStats(owner, repo);
247
- if (repoStats) {
248
- insights = {
249
- stars: repoStats.stars ?? null,
250
- forks: repoStats.forks ?? null,
251
- };
252
- // Traffic requires push access - may fail
253
- try {
254
- const trafficData = await context.github.getTrafficData(owner, repo);
255
- if (trafficData) {
256
- insights.clones14d = trafficData.clones.total;
257
- insights.views14d = trafficData.views.total;
258
- }
259
- }
260
- catch {
261
- // Traffic data unavailable (requires push access)
262
- }
263
- }
264
- }
265
- catch {
266
- // Repo stats unavailable
267
- }
268
- github = {
269
- repo: `${owner}/${repo}`,
270
- branch: repoInfo.branch ?? null,
271
- ci: ciStatus,
272
- openIssues,
273
- openPRs,
274
- milestones,
275
- insights,
276
- };
277
- }
278
- }
279
- catch {
280
- // GitHub unavailable
281
- }
282
- }
283
- // Get entry count for context
284
- const stats = context.db.getStatistics('week');
285
- const totalEntries = stats.totalEntries ?? 0;
286
- // Determine lastModified from most recent entry or current time
287
- const lastModified = recentEntries[0]?.timestamp ?? new Date().toISOString();
288
- // Build acknowledgment message for user
289
- const repoName = github?.repo ?? 'local';
290
- const branchName = github?.branch ?? 'unknown';
291
- const ciStatus = github?.ci ?? 'unknown';
292
- const latestPreview = latestEntries[0]
293
- ? `#${latestEntries[0].id} (${latestEntries[0].type}): ${latestEntries[0].preview}`
294
- : 'No entries yet';
295
- const milestoneRow = github?.milestones && github.milestones.length > 0
296
- ? `\n| **Milestones** | ${github.milestones.map((m) => `${m.title} (${m.progress}${m.dueOn ? `, due ${m.dueOn.split('T')[0] ?? ''}` : ''})`).join(', ')} |`
297
- : '';
298
- // Build insights row for userMessage
299
- let insightsRow = '';
300
- if (github?.insights) {
301
- const parts = [];
302
- if (github.insights.stars !== null)
303
- parts.push(`⭐ ${String(github.insights.stars)} stars`);
304
- if (github.insights.forks !== null)
305
- parts.push(`🍴 ${String(github.insights.forks)} forks`);
306
- if (github.insights.clones14d !== undefined)
307
- parts.push(`📦 ${String(github.insights.clones14d)} clones`);
308
- if (github.insights.views14d !== undefined)
309
- parts.push(`👁️ ${String(github.insights.views14d)} views`);
310
- if (parts.length > 0) {
311
- const trafficNote = github.insights.clones14d !== undefined ? ' (14d)' : '';
312
- insightsRow = `\n| **Insights** | ${parts.join(' · ')}${trafficNote} |`;
313
- }
314
- }
315
- return {
316
- data: {
317
- version: pkg.version,
318
- serverTime: new Date().toISOString(),
319
- journal: {
320
- totalEntries,
321
- latestEntries,
322
- },
323
- github,
324
- behaviors: {
325
- create: 'implementations, decisions, bug-fixes, milestones',
326
- search: 'before decisions, referencing prior work',
327
- link: 'implementation→spec, bugfix→issue',
328
- },
329
- templateResources: [
330
- 'memory://projects/{number}/timeline',
331
- 'memory://issues/{issue_number}/entries',
332
- 'memory://prs/{pr_number}/entries',
333
- 'memory://prs/{pr_number}/timeline',
334
- 'memory://kanban/{project_number}',
335
- 'memory://kanban/{project_number}/diagram',
336
- 'memory://milestones/{number}',
337
- ],
338
- more: {
339
- fullHealth: 'memory://health',
340
- allRecent: 'memory://recent',
341
- githubStatus: 'memory://github/status',
342
- repoInsights: 'memory://github/insights',
343
- contextBundle: 'get-context-bundle prompt',
344
- },
345
- // IMPORTANT: Agent should relay this message to the user
346
- userMessage: `📋 **Session Context Loaded**
347
- | Context | Value |
348
- |---------|-------|
349
- | **Project** | ${repoName} |
350
- | **Branch** | ${branchName} |
351
- | **CI Status** | ${ciStatus} |
352
- | **Journal** | ${totalEntries} entries |
353
- | **Latest** | ${latestPreview} |${milestoneRow}${insightsRow}
354
-
355
- I have project memory access and will create entries for significant work.`,
356
- // Note for clients that don't auto-inject ServerInstructions
357
- clientNote: 'For complete tool reference and field notes, read memory://instructions.',
358
- },
359
- annotations: { lastModified },
360
- };
361
- },
362
- },
363
- // Server instructions resource - for clients that don't auto-inject ServerInstructions
364
- {
365
- uri: 'memory://instructions',
366
- name: 'Server Instructions',
367
- title: 'Full Server Behavioral Guidance',
368
- description: 'Full server instructions for AI agents.',
369
- mimeType: 'text/markdown',
370
- icons: [ICON_BRIEFING],
371
- annotations: {
372
- audience: ['assistant'],
373
- priority: 0.95, // High priority, but below briefing
374
- },
375
- handler: (_uri, context) => {
376
- // Note: Query parameters (e.g., ?level=essential) are not supported
377
- // because the MCP SDK performs exact URI matching before calling handlers.
378
- const level = 'full';
379
- // Get enabled tools from filter config, or fall back to all tool names
380
- const allToolNames = new Set(getAllToolNames());
381
- const enabledTools = context.filterConfig?.enabledTools ?? allToolNames;
382
- // Get prompts for instruction generation
383
- const prompts = getPrompts().map((p) => {
384
- const prompt = p;
385
- return { name: prompt.name, description: prompt.description };
386
- });
387
- // Get resources for instruction generation (simplified)
388
- const resources = getResources().map((r) => {
389
- const res = r;
390
- return { uri: res.uri, name: res.name, description: res.description };
391
- });
392
- // Generate instructions at requested level
393
- const instructions = generateInstructions(enabledTools, resources, prompts, undefined, // No latest entry needed for instructions
394
- level);
395
- return {
396
- data: instructions,
397
- };
398
- },
399
- },
400
- {
401
- uri: 'memory://recent',
402
- name: 'Recent Entries',
403
- title: 'Recent Journal Entries',
404
- description: '10 most recent journal entries',
405
- mimeType: 'application/json',
406
- icons: [ICON_CLOCK],
407
- annotations: {
408
- audience: ['assistant'],
409
- priority: 0.8,
410
- },
411
- handler: (_uri, context) => {
412
- const entries = context.db.getRecentEntries(10);
413
- const lastModified = entries[0]?.timestamp ?? new Date().toISOString();
414
- return {
415
- data: { entries, count: entries.length },
416
- annotations: { lastModified },
417
- };
418
- },
419
- },
420
- {
421
- uri: 'memory://significant',
422
- name: 'Significant Entries',
423
- title: 'Significant Milestones',
424
- description: 'Significant milestones and breakthroughs',
425
- mimeType: 'application/json',
426
- icons: [ICON_STAR],
427
- annotations: {
428
- audience: ['assistant'],
429
- priority: 0.7,
430
- },
431
- handler: (_uri, context) => {
432
- // Fetch ALL significant entries so importance sort runs on the full set
433
- // (not just the 20 most recent). We then slice after sorting.
434
- const rows = execQuery(context.db, `
435
- SELECT * FROM memory_journal
436
- WHERE significance_type IS NOT NULL
437
- AND deleted_at IS NULL
438
- `);
439
- // Transform entries and calculate importance scores
440
- const entriesWithImportance = rows.map((row) => {
441
- const entry = transformEntryRow(row);
442
- const { score: importance } = context.db.calculateImportance(entry['id']);
443
- return { ...entry, importance };
444
- });
445
- // Sort by importance (highest first), then by timestamp (newest first) for ties
446
- entriesWithImportance.sort((a, b) => {
447
- if (b.importance !== a.importance) {
448
- return b.importance - a.importance;
449
- }
450
- // Secondary sort: newest first for equal importance
451
- const aTime = new Date(a['timestamp']).getTime();
452
- const bTime = new Date(b['timestamp']).getTime();
453
- return bTime - aTime;
454
- });
455
- // Slice to top 20 AFTER sorting (not before) to ensure correctness
456
- const top20 = entriesWithImportance.slice(0, 20);
457
- return { entries: top20, count: top20.length };
458
- },
459
- },
460
- {
461
- uri: 'memory://graph/recent',
462
- name: 'Recent Relationship Graph',
463
- title: 'Live Mermaid Diagram',
464
- description: 'Live Mermaid diagram of recent relationships',
465
- mimeType: 'text/plain',
466
- icons: [ICON_GRAPH],
467
- annotations: {
468
- audience: ['user', 'assistant'],
469
- priority: 0.5,
470
- },
471
- handler: (_uri, context) => {
472
- // Get recent relationships from database
473
- const relationships = execQuery(context.db, `
474
- SELECT
475
- r.id, r.from_entry_id, r.to_entry_id, r.relationship_type, r.description,
476
- e1.content as from_content,
477
- e2.content as to_content
478
- FROM relationships r
479
- JOIN memory_journal e1 ON r.from_entry_id = e1.id
480
- JOIN memory_journal e2 ON r.to_entry_id = e2.id
481
- WHERE e1.deleted_at IS NULL AND e2.deleted_at IS NULL
482
- ORDER BY r.created_at DESC
483
- LIMIT 20
484
- `);
485
- if (relationships.length === 0) {
486
- return {
487
- format: 'mermaid',
488
- diagram: 'graph TD\n NoData[No relationships found]',
489
- message: 'No entry relationships exist yet. Use link_entries tool to create relationships.',
490
- };
491
- }
492
- // Build Mermaid graph
493
- const lines = ['graph TD'];
494
- const seenNodes = new Set();
495
- // Relationship type to arrow style mapping (harmonized with visualize_relationships tool)
496
- const arrowStyles = {
497
- // Standard relationship types
498
- references: '-->',
499
- evolves_from: '-->',
500
- depends_on: '-->',
501
- // Emphasis relationships
502
- implements: '==>',
503
- resolved: '==>',
504
- // Subtle/clarifying relationships
505
- clarifies: '-.->',
506
- caused: '-.->',
507
- // Bidirectional
508
- related_to: '<-->',
509
- response_to: '<-->',
510
- // Blockers (crossed arrow)
511
- blocked_by: '--x',
512
- };
513
- for (const rel of relationships) {
514
- // Add node definitions if not seen
515
- if (!seenNodes.has(rel.from_entry_id)) {
516
- const label = rel.from_content
517
- .slice(0, 30)
518
- .replace(/[\]"'`[]/g, ' ')
519
- .trim();
520
- lines.push(` E${String(rel.from_entry_id)}["#${String(rel.from_entry_id)}: ${label}..."]`);
521
- seenNodes.add(rel.from_entry_id);
522
- }
523
- if (!seenNodes.has(rel.to_entry_id)) {
524
- const label = rel.to_content
525
- .slice(0, 30)
526
- .replace(/[\]"'`[]/g, ' ')
527
- .trim();
528
- lines.push(` E${String(rel.to_entry_id)}["#${String(rel.to_entry_id)}: ${label}..."]`);
529
- seenNodes.add(rel.to_entry_id);
530
- }
531
- // Add edge with relationship label
532
- const arrow = arrowStyles[rel.relationship_type] ?? '-->';
533
- lines.push(` E${String(rel.from_entry_id)} ${arrow}|${rel.relationship_type}| E${String(rel.to_entry_id)}`);
534
- }
535
- return {
536
- format: 'mermaid',
537
- diagram: lines.join('\n'),
538
- relationshipCount: relationships.length,
539
- nodeCount: seenNodes.size,
540
- };
541
- },
542
- },
543
- {
544
- uri: 'memory://team/recent',
545
- name: 'Team Entries',
546
- title: 'Recent Team-Shared Entries',
547
- description: 'Recent team-shared entries',
548
- mimeType: 'application/json',
549
- icons: [ICON_TEAM],
550
- annotations: {
551
- audience: ['assistant'],
552
- priority: 0.6,
553
- },
554
- handler: (_uri, context) => {
555
- const entries = context.db.getRecentEntries(10, false);
556
- return { entries, count: entries.length };
557
- },
558
- },
559
- {
560
- uri: 'memory://projects/{number}/timeline',
561
- name: 'Project Timeline',
562
- title: 'Project Activity Timeline',
563
- description: 'Project activity timeline',
564
- mimeType: 'application/json',
565
- annotations: {
566
- audience: ['assistant'],
567
- priority: 0.6,
568
- },
569
- handler: (uri, context) => {
570
- const match = /memory:\/\/projects\/(\d+)\/timeline/.exec(uri);
571
- const projectNumber = match?.[1] ? parseInt(match[1], 10) : null;
572
- if (projectNumber === null) {
573
- return { error: 'Invalid project number' };
574
- }
575
- const rows = execQuery(context.db, `
576
- SELECT * FROM memory_journal
577
- WHERE project_number = ?
578
- AND deleted_at IS NULL
579
- ORDER BY timestamp DESC
580
- LIMIT 50
581
- `, [projectNumber]);
582
- const entries = rows.map(transformEntryRow);
583
- return { projectNumber, entries, count: entries.length };
584
- },
585
- },
586
- {
587
- uri: 'memory://issues/{issue_number}/entries',
588
- name: 'Issue Entries',
589
- title: 'Entries Linked to Issue',
590
- description: 'All entries linked to a specific issue',
591
- mimeType: 'application/json',
592
- icons: [ICON_ISSUE],
593
- annotations: {
594
- audience: ['assistant'],
595
- priority: 0.6,
596
- },
597
- handler: (uri, context) => {
598
- const match = /memory:\/\/issues\/(\d+)\/entries/.exec(uri);
599
- const issueNumber = match?.[1] ? parseInt(match[1], 10) : null;
600
- if (issueNumber === null) {
601
- return { error: 'Invalid issue number' };
602
- }
603
- const rows = execQuery(context.db, `
604
- SELECT * FROM memory_journal
605
- WHERE issue_number = ?
606
- AND deleted_at IS NULL
607
- ORDER BY timestamp DESC
608
- `, [issueNumber]);
609
- const entries = rows.map(transformEntryRow);
610
- return { issueNumber, entries, count: entries.length };
611
- },
612
- },
613
- {
614
- uri: 'memory://prs/{pr_number}/entries',
615
- name: 'PR Entries',
616
- title: 'Entries Linked to PR',
617
- description: 'All entries linked to a specific pull request',
618
- mimeType: 'application/json',
619
- icons: [ICON_PR],
620
- annotations: {
621
- audience: ['assistant'],
622
- priority: 0.6,
623
- },
624
- handler: (uri, context) => {
625
- const match = /memory:\/\/prs\/(\d+)\/entries/.exec(uri);
626
- const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
627
- if (prNumber === null) {
628
- return { error: 'Invalid PR number' };
629
- }
630
- const rows = execQuery(context.db, `
631
- SELECT * FROM memory_journal
632
- WHERE pr_number = ?
633
- AND deleted_at IS NULL
634
- ORDER BY timestamp DESC
635
- `, [prNumber]);
636
- const entries = rows.map(transformEntryRow);
637
- return {
638
- prNumber,
639
- entries,
640
- count: entries.length,
641
- ...(entries.length === 0
642
- ? {
643
- hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
644
- }
645
- : {}),
646
- };
647
- },
648
- },
649
- {
650
- uri: 'memory://prs/{pr_number}/timeline',
651
- name: 'PR Timeline',
652
- title: 'Combined PR and Journal Timeline',
653
- description: 'Combined PR + journal timeline with live PR metadata',
654
- mimeType: 'application/json',
655
- icons: [ICON_PR],
656
- annotations: {
657
- audience: ['assistant'],
658
- priority: 0.5,
659
- },
660
- handler: async (uri, context) => {
661
- const match = /memory:\/\/prs\/(\d+)\/timeline/.exec(uri);
662
- const prNumber = match?.[1] ? parseInt(match[1], 10) : null;
663
- if (prNumber === null) {
664
- return { error: 'Invalid PR number' };
665
- }
666
- // Fetch live PR metadata from GitHub if available
667
- let prMetadata = null;
668
- if (context.github) {
669
- try {
670
- const repoInfo = await context.github.getRepoInfo();
671
- if (repoInfo.owner && repoInfo.repo) {
672
- const pr = await context.github.getPullRequest(repoInfo.owner, repoInfo.repo, prNumber);
673
- if (pr) {
674
- prMetadata = {
675
- title: pr.title,
676
- state: pr.state,
677
- draft: pr.draft,
678
- mergedAt: pr.mergedAt,
679
- closedAt: pr.closedAt,
680
- author: pr.author,
681
- headBranch: pr.headBranch,
682
- baseBranch: pr.baseBranch,
683
- };
684
- }
685
- }
686
- }
687
- catch {
688
- // GitHub not available, proceed without metadata
689
- }
690
- }
691
- const rows = execQuery(context.db, `
692
- SELECT * FROM memory_journal
693
- WHERE pr_number = ?
694
- AND deleted_at IS NULL
695
- ORDER BY timestamp DESC
696
- `, [prNumber]);
697
- const entries = rows.map(transformEntryRow);
698
- // Build timeline note based on PR state
699
- let timelineNote;
700
- if (prMetadata) {
701
- const stateDesc = prMetadata.state.toLowerCase();
702
- const mergedNote = prMetadata.mergedAt ? ' (merged)' : '';
703
- const draftNote = prMetadata.draft ? ' [DRAFT]' : '';
704
- timelineNote = `PR #${String(prNumber)} is ${stateDesc}${mergedNote}${draftNote}`;
705
- }
706
- else {
707
- timelineNote =
708
- 'GitHub integration unavailable for live PR status. Entry timestamps show journal activity.';
709
- }
710
- return {
711
- prNumber,
712
- prMetadata,
713
- entries,
714
- count: entries.length,
715
- timelineNote,
716
- ...(entries.length === 0
717
- ? {
718
- hint: 'No journal entries linked to this PR. Use create_entry with pr_number to link entries.',
719
- }
720
- : {}),
721
- };
722
- },
723
- },
724
- {
725
- uri: 'memory://graph/actions',
726
- name: 'Actions Graph',
727
- title: 'CI/CD Narrative Graph',
728
- description: 'CI/CD narrative graph: commits → runs → failures → entries → fixes → deployments',
729
- mimeType: 'text/plain',
730
- icons: [ICON_GITHUB],
731
- annotations: {
732
- audience: ['user', 'assistant'],
733
- priority: 0.5,
734
- },
735
- handler: async (_uri, context) => {
736
- // Check if GitHub integration is available
737
- if (!context.github) {
738
- return {
739
- format: 'mermaid',
740
- diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
741
- message: 'GitHub integration not configured. Set GITHUB_TOKEN and GITHUB_REPO_PATH.',
742
- };
743
- }
744
- // Get repository info and workflow runs
745
- const repoInfo = await context.github.getRepoInfo();
746
- if (!repoInfo.owner || !repoInfo.repo) {
747
- return {
748
- format: 'mermaid',
749
- diagram: 'graph LR\n NoRepo[Repository not detected]',
750
- message: 'Could not detect repository. Set GITHUB_REPO_PATH in your config.',
751
- };
752
- }
753
- const workflowRuns = await context.github.getWorkflowRuns(repoInfo.owner, repoInfo.repo, 10);
754
- if (workflowRuns.length === 0) {
755
- return {
756
- format: 'mermaid',
757
- diagram: 'graph LR\n NoRuns[No workflow runs found]',
758
- message: 'No GitHub Actions workflow runs found for this repository.',
759
- };
760
- }
761
- // Build Mermaid graph showing workflow runs
762
- const lines = ['graph LR'];
763
- // Status to styling map
764
- const statusStyles = {
765
- success: ':::success',
766
- failure: ':::failure',
767
- cancelled: ':::cancelled',
768
- skipped: ':::skipped',
769
- };
770
- // Add style definitions
771
- lines.push(' classDef success fill:#28a745,color:#fff');
772
- lines.push(' classDef failure fill:#dc3545,color:#fff');
773
- lines.push(' classDef cancelled fill:#6c757d,color:#fff');
774
- lines.push(' classDef skipped fill:#ffc107,color:#000');
775
- for (const run of workflowRuns) {
776
- const shortSha = run.headSha.slice(0, 7);
777
- const nodeId = `R${String(run.id)}`;
778
- const commitId = `C${shortSha}`;
779
- const style = statusStyles[run.conclusion ?? 'skipped'] ?? '';
780
- const statusIcon = run.conclusion === 'success'
781
- ? '✓'
782
- : run.conclusion === 'failure'
783
- ? '✗'
784
- : '○';
785
- // Add commit and run nodes
786
- lines.push(` ${commitId}["${shortSha}"]`);
787
- lines.push(` ${nodeId}["${statusIcon} ${run.name}"]${style}`);
788
- lines.push(` ${commitId} --> ${nodeId}`);
789
- }
790
- return {
791
- format: 'mermaid',
792
- diagram: lines.join('\n'),
793
- workflowRunCount: workflowRuns.length,
794
- repository: `${repoInfo.owner}/${repoInfo.repo}`,
795
- };
796
- },
797
- },
798
- {
799
- uri: 'memory://actions/recent',
800
- name: 'Recent Actions',
801
- title: 'Recent Workflow Runs',
802
- description: 'Recent workflow runs with CI status',
803
- mimeType: 'application/json',
804
- icons: [ICON_GITHUB],
805
- annotations: {
806
- audience: ['assistant'],
807
- priority: 0.5,
808
- },
809
- handler: async (_uri, context) => {
810
- // If GitHub integration is available, synthesize entries from recent workflow runs
811
- if (context.github) {
812
- try {
813
- const repoInfo = await context.github.getRepoInfo();
814
- if (repoInfo.owner && repoInfo.repo) {
815
- const runs = await context.github.getWorkflowRuns(repoInfo.owner, repoInfo.repo, 10);
816
- // Return virtual entries synthesized from workflow runs
817
- const entries = runs.map((run) => ({
818
- id: -1 * run.id, // Virtual ID (negative to distinguish from DB)
819
- entryType: 'tool_output',
820
- content: `Workflow: ${run.name}\nStatus: ${run.status}\nConclusion: ${run.conclusion || 'pending'}\nBranch: ${run.headBranch}\nURL: ${run.url}`,
821
- timestamp: run.createdAt,
822
- isPersonal: false,
823
- significanceType: null,
824
- workflowRunId: run.id,
825
- workflowName: run.name,
826
- workflowStatus: run.conclusion || run.status,
827
- }));
828
- return { entries, count: entries.length, source: 'github_api' };
829
- }
830
- }
831
- catch {
832
- // Fallback to DB if GitHub fails
833
- }
834
- }
835
- const rows = execQuery(context.db, `
836
- SELECT * FROM memory_journal
837
- WHERE workflow_run_id IS NOT NULL
838
- AND deleted_at IS NULL
839
- ORDER BY timestamp DESC
840
- LIMIT 10
841
- `);
842
- const entries = rows.map(transformEntryRow);
843
- return { entries, count: entries.length, source: 'database' };
844
- },
845
- },
846
- {
847
- uri: 'memory://tags',
848
- name: 'All Tags',
849
- title: 'Tag List',
850
- description: 'All available tags with usage counts',
851
- mimeType: 'application/json',
852
- icons: [ICON_TAG],
853
- annotations: {
854
- audience: ['assistant'],
855
- priority: 0.4,
856
- },
857
- handler: (_uri, context) => {
858
- const tags = context.db.listTags();
859
- // Map usageCount to count for consistency with list_tags tool
860
- const mappedTags = tags.map((t) => ({
861
- id: t.id,
862
- name: t.name,
863
- count: t.usageCount,
864
- }));
865
- return { tags: mappedTags, count: mappedTags.length };
866
- },
867
- },
868
- {
869
- uri: 'memory://statistics',
870
- name: 'Statistics',
871
- title: 'Journal Statistics',
872
- description: 'Overall journal statistics',
873
- mimeType: 'application/json',
874
- icons: [ICON_ANALYTICS],
875
- annotations: {
876
- audience: ['assistant'],
877
- priority: 0.4,
878
- },
879
- handler: (_uri, context) => {
880
- return context.db.getStatistics('week');
881
- },
882
- },
883
- {
884
- uri: 'memory://health',
885
- name: 'Server Health',
886
- title: 'Server Health & Diagnostics',
887
- description: 'Server health status including database, backups, vector index (real-time stats), and tool filter status',
888
- mimeType: 'application/json',
889
- icons: [ICON_HEALTH],
890
- annotations: {
891
- audience: ['assistant'],
892
- priority: 0.9,
893
- },
894
- handler: async (_uri, context) => {
895
- const dbHealth = context.db.getHealthStatus();
896
- // Get vector index status if available
897
- let vectorIndex = null;
898
- if (context.vectorManager) {
899
- try {
900
- const stats = await context.vectorManager.getStats();
901
- vectorIndex = {
902
- available: true,
903
- itemCount: stats.itemCount,
904
- modelName: stats.modelName,
905
- };
906
- }
907
- catch {
908
- vectorIndex = { available: false, itemCount: 0, modelName: null };
909
- }
910
- }
911
- // Get tool filter status
912
- const totalTools = getTotalToolCount();
913
- const toolFilter = {
914
- active: context.filterConfig !== null && context.filterConfig !== undefined,
915
- enabledCount: context.filterConfig?.enabledTools.size ?? totalTools,
916
- totalCount: totalTools,
917
- filterString: context.filterConfig?.raw ?? null,
918
- };
919
- const lastModified = new Date().toISOString();
920
- return {
921
- data: {
922
- ...dbHealth,
923
- vectorIndex,
924
- toolFilter,
925
- timestamp: lastModified,
926
- },
927
- annotations: { lastModified },
928
- };
929
- },
930
- },
931
- // GitHub status resource - compact overview with progressive disclosure
932
- {
933
- uri: 'memory://github/status',
934
- name: 'GitHub Status',
935
- title: 'GitHub Repository Status',
936
- description: 'Compact GitHub status: repository, branch, CI, issues, PRs, Kanban summary',
937
- mimeType: 'application/json',
938
- icons: [ICON_GITHUB],
939
- annotations: {
940
- audience: ['assistant'],
941
- priority: 0.7,
942
- },
943
- handler: async (_uri, context) => {
944
- const lastModified = new Date().toISOString();
945
- if (!context.github) {
946
- return {
947
- data: {
948
- error: 'GitHub integration not available',
949
- hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
950
- },
951
- annotations: { lastModified },
952
- };
953
- }
954
- const repoInfo = await context.github.getRepoInfo();
955
- const owner = repoInfo.owner;
956
- const repo = repoInfo.repo;
957
- if (!owner || !repo) {
958
- return {
959
- data: {
960
- error: 'Could not detect repository',
961
- hint: 'Set GITHUB_REPO_PATH to your git repository.',
962
- branch: repoInfo.branch,
963
- },
964
- annotations: { lastModified },
965
- };
966
- }
967
- // Get current commit
968
- let commit = null;
969
- try {
970
- const repoContext = await context.github.getRepoContext();
971
- commit = repoContext.commit;
972
- }
973
- catch {
974
- // Ignore
975
- }
976
- // Get open issues (limited for token efficiency)
977
- const issues = await context.github.getIssues(owner, repo, 'open', 5);
978
- const openIssues = issues.map((i) => ({
979
- number: i.number,
980
- title: i.title.slice(0, 50),
981
- }));
982
- // Get open PRs (limited for token efficiency)
983
- const prs = await context.github.getPullRequests(owner, repo, 'open', 5);
984
- const openPrs = prs.map((pr) => ({
985
- number: pr.number,
986
- title: pr.title.slice(0, 50),
987
- state: pr.state,
988
- }));
989
- // Get CI status from workflow runs
990
- // Get CI status from latest workflow run (matches briefing logic)
991
- const workflowRuns = await context.github.getWorkflowRuns(owner, repo, 5);
992
- let ciStatus = 'unknown';
993
- let latestRun = null;
994
- if (workflowRuns.length > 0) {
995
- // Find the latest completed run for accurate CI status
996
- const latestCompleted = workflowRuns.find((r) => r.status === 'completed');
997
- const latest = workflowRuns[0];
998
- latestRun = {
999
- name: latest?.name ?? 'Unknown',
1000
- conclusion: latest?.conclusion ?? null,
1001
- headSha: latest?.headSha?.slice(0, 7) ?? '',
1002
- };
1003
- // CI status based on latest completed run (consistent with briefing)
1004
- if (latestCompleted) {
1005
- // Map workflow conclusion to CI status
1006
- switch (latestCompleted.conclusion) {
1007
- case 'success':
1008
- ciStatus = 'passing';
1009
- break;
1010
- case 'failure':
1011
- ciStatus = 'failing';
1012
- break;
1013
- case 'cancelled':
1014
- ciStatus = 'cancelled';
1015
- break;
1016
- default:
1017
- ciStatus = 'unknown';
1018
- }
1019
- }
1020
- else if (workflowRuns.some((r) => r.status !== 'completed')) {
1021
- ciStatus = 'pending';
1022
- }
1023
- }
1024
- // Get Kanban summary if project 1 exists (common default)
1025
- let kanbanSummary = null;
1026
- try {
1027
- const kanban = await context.github.getProjectKanban(owner, 1, repo);
1028
- if (kanban) {
1029
- kanbanSummary = {};
1030
- for (const col of kanban.columns) {
1031
- kanbanSummary[col.status] = col.items.length;
1032
- }
1033
- }
1034
- }
1035
- catch {
1036
- // Kanban not available
1037
- }
1038
- // Get milestone summary
1039
- let milestoneSummary = null;
1040
- try {
1041
- const milestones = await context.github.getMilestones(owner, repo, 'open', 5);
1042
- if (milestones.length > 0) {
1043
- milestoneSummary = milestones.map((ms) => {
1044
- const total = ms.openIssues + ms.closedIssues;
1045
- const pct = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0;
1046
- return {
1047
- number: ms.number,
1048
- title: ms.title,
1049
- state: ms.state,
1050
- openIssues: ms.openIssues,
1051
- closedIssues: ms.closedIssues,
1052
- completionPercentage: pct,
1053
- dueOn: ms.dueOn,
1054
- };
1055
- });
1056
- }
1057
- }
1058
- catch {
1059
- // Milestones not available
1060
- }
1061
- return {
1062
- data: {
1063
- repository: `${owner}/${repo}`,
1064
- branch: repoInfo.branch,
1065
- commit: commit?.slice(0, 7) ?? null,
1066
- ci: {
1067
- status: ciStatus,
1068
- latestRun,
1069
- },
1070
- issues: {
1071
- openCount: issues.length,
1072
- items: openIssues,
1073
- },
1074
- pullRequests: {
1075
- openCount: prs.length,
1076
- items: openPrs,
1077
- },
1078
- kanbanSummary,
1079
- milestones: milestoneSummary,
1080
- },
1081
- annotations: { lastModified },
1082
- };
1083
- },
1084
- },
1085
- // Repository insights resource
1086
- {
1087
- uri: 'memory://github/insights',
1088
- name: 'Repository Insights',
1089
- title: 'Repository Stars & Traffic Summary',
1090
- description: 'Compact repo insights: stars, forks, 14-day traffic totals (~150 tokens)',
1091
- mimeType: 'application/json',
1092
- icons: [ICON_ANALYTICS],
1093
- annotations: {
1094
- audience: ['assistant'],
1095
- priority: 0.4, // Lower than status — optional enrichment
1096
- },
1097
- handler: async (_uri, context) => {
1098
- const lastModified = new Date().toISOString();
1099
- if (!context.github) {
1100
- return {
1101
- data: {
1102
- error: 'GitHub integration not available',
1103
- hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
1104
- },
1105
- annotations: { lastModified },
1106
- };
1107
- }
1108
- const repoInfo = await context.github.getRepoInfo();
1109
- const owner = repoInfo.owner;
1110
- const repo = repoInfo.repo;
1111
- if (!owner || !repo) {
1112
- return {
1113
- data: {
1114
- error: 'Could not detect repository',
1115
- hint: 'Set GITHUB_REPO_PATH to your git repository.',
1116
- },
1117
- annotations: { lastModified },
1118
- };
1119
- }
1120
- // Get repo stats (stars, forks)
1121
- const stats = await context.github.getRepoStats(owner, repo);
1122
- // Get traffic data (clones, views) - may fail if token lacks push access
1123
- let traffic = null;
1124
- try {
1125
- const trafficData = await context.github.getTrafficData(owner, repo);
1126
- if (trafficData) {
1127
- traffic = {
1128
- clones14d: trafficData.clones.total,
1129
- views14d: trafficData.views.total,
1130
- };
1131
- }
1132
- }
1133
- catch {
1134
- // Traffic data requires push access - silently skip
1135
- }
1136
- return {
1137
- data: {
1138
- repository: `${owner}/${repo}`,
1139
- stars: stats?.stars ?? null,
1140
- forks: stats?.forks ?? null,
1141
- watchers: stats?.watchers ?? null,
1142
- ...(traffic ?? {}),
1143
- hint: !traffic
1144
- ? 'Traffic data requires push access to the repository.'
1145
- : undefined,
1146
- },
1147
- annotations: { lastModified },
1148
- };
1149
- },
1150
- },
1151
- // Milestone resources
1152
- {
1153
- uri: 'memory://github/milestones',
1154
- name: 'GitHub Milestones',
1155
- title: 'GitHub Repository Milestones',
1156
- description: 'Open GitHub milestones with completion percentages, due dates, and issue counts',
1157
- mimeType: 'application/json',
1158
- icons: [ICON_MILESTONE],
1159
- annotations: {
1160
- audience: ['assistant'],
1161
- priority: 0.6,
1162
- },
1163
- handler: async (_uri, context) => {
1164
- if (!context.github) {
1165
- return {
1166
- error: 'GitHub integration not available',
1167
- hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
1168
- };
1169
- }
1170
- const repoInfo = await context.github.getRepoInfo();
1171
- const owner = repoInfo.owner;
1172
- const repo = repoInfo.repo;
1173
- if (!owner || !repo) {
1174
- return {
1175
- error: 'Could not detect repository',
1176
- hint: 'Set GITHUB_REPO_PATH to your git repository.',
1177
- };
1178
- }
1179
- const milestones = await context.github.getMilestones(owner, repo, 'open', 20);
1180
- const milestonesWithProgress = milestones.map((ms) => {
1181
- const total = ms.openIssues + ms.closedIssues;
1182
- const completionPercentage = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0;
1183
- return { ...ms, completionPercentage };
1184
- });
1185
- return {
1186
- repository: `${owner}/${repo}`,
1187
- milestones: milestonesWithProgress,
1188
- count: milestonesWithProgress.length,
1189
- hint: 'Use get_github_milestones tool for state filtering. Use memory://milestones/{number} for detail.',
1190
- };
1191
- },
1192
- },
1193
- {
1194
- uri: 'memory://milestones/{number}',
1195
- name: 'Milestone Detail',
1196
- title: 'GitHub Milestone Detail',
1197
- description: 'Detailed view of a single GitHub milestone with completion progress and issue counts. Use get_github_issues with the milestone filter for individual issue details.',
1198
- mimeType: 'application/json',
1199
- icons: [ICON_MILESTONE],
1200
- annotations: {
1201
- audience: ['assistant'],
1202
- priority: 0.5,
1203
- },
1204
- handler: async (uri, context) => {
1205
- const match = /memory:\/\/milestones\/(\d+)/.exec(uri);
1206
- const milestoneNumber = match?.[1] ? parseInt(match[1], 10) : null;
1207
- if (milestoneNumber === null) {
1208
- return { error: 'Invalid milestone number' };
1209
- }
1210
- if (!context.github) {
1211
- return {
1212
- error: 'GitHub integration not available',
1213
- hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
1214
- };
1215
- }
1216
- const repoInfo = await context.github.getRepoInfo();
1217
- const owner = repoInfo.owner;
1218
- const repo = repoInfo.repo;
1219
- if (!owner || !repo) {
1220
- return {
1221
- error: 'Could not detect repository',
1222
- hint: 'Set GITHUB_REPO_PATH to your git repository.',
1223
- };
1224
- }
1225
- const milestone = await context.github.getMilestone(owner, repo, milestoneNumber);
1226
- if (!milestone) {
1227
- return { error: `Milestone #${String(milestoneNumber)} not found` };
1228
- }
1229
- const total = milestone.openIssues + milestone.closedIssues;
1230
- const completionPercentage = total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0;
1231
- return {
1232
- repository: `${owner}/${repo}`,
1233
- milestone: { ...milestone, completionPercentage },
1234
- hint: 'Use get_github_issues tool to list issues associated with this milestone.',
1235
- };
1236
- },
1237
- },
1238
- // Kanban board resources (GitHub Projects v2)
1239
- {
1240
- uri: 'memory://kanban/{project_number}',
1241
- name: 'Kanban Board',
1242
- title: 'GitHub Project Kanban Board',
1243
- description: 'View a GitHub Project v2 as a Kanban board with items grouped by Status',
1244
- mimeType: 'application/json',
1245
- annotations: {
1246
- audience: ['assistant'],
1247
- priority: 0.6,
1248
- },
1249
- handler: async (uri, context) => {
1250
- const match = /memory:\/\/kanban\/(\d+)/.exec(uri);
1251
- const projectNumber = match?.[1] ? parseInt(match[1], 10) : null;
1252
- if (projectNumber === null) {
1253
- return { error: 'Invalid project number' };
1254
- }
1255
- if (!context.github) {
1256
- return {
1257
- error: 'GitHub integration not available',
1258
- hint: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
1259
- };
1260
- }
1261
- const repoInfo = await context.github.getRepoInfo();
1262
- const owner = repoInfo.owner;
1263
- const repo = repoInfo.repo ?? undefined;
1264
- if (!owner) {
1265
- return {
1266
- error: 'Could not detect repository owner',
1267
- hint: 'Set GITHUB_REPO_PATH to your git repository.',
1268
- };
1269
- }
1270
- const board = await context.github.getProjectKanban(owner, projectNumber, repo);
1271
- if (!board) {
1272
- return {
1273
- error: `Project #${String(projectNumber)} not found or Status field not configured`,
1274
- projectNumber,
1275
- owner,
1276
- hint: 'Projects can be at user, repository, or organization level.',
1277
- };
1278
- }
1279
- return board;
1280
- },
1281
- },
1282
- {
1283
- uri: 'memory://kanban/{project_number}/diagram',
1284
- name: 'Kanban Diagram',
1285
- title: 'Kanban Board Mermaid Diagram',
1286
- description: 'Mermaid diagram visualization of a GitHub Project Kanban board',
1287
- mimeType: 'text/plain',
1288
- annotations: {
1289
- audience: ['user', 'assistant'],
1290
- priority: 0.5,
1291
- },
1292
- handler: async (uri, context) => {
1293
- const match = /memory:\/\/kanban\/(\d+)\/diagram/.exec(uri);
1294
- const projectNumber = match?.[1] ? parseInt(match[1], 10) : null;
1295
- if (projectNumber === null) {
1296
- return { error: 'Invalid project number' };
1297
- }
1298
- if (!context.github) {
1299
- return {
1300
- format: 'mermaid',
1301
- diagram: 'graph LR\n NoGitHub[GitHub integration not available]',
1302
- message: 'Set GITHUB_TOKEN and GITHUB_REPO_PATH environment variables.',
1303
- };
1304
- }
1305
- const repoInfo = await context.github.getRepoInfo();
1306
- const owner = repoInfo.owner;
1307
- const repo = repoInfo.repo ?? undefined;
1308
- if (!owner) {
1309
- return {
1310
- format: 'mermaid',
1311
- diagram: 'graph LR\n NoOwner[Repository owner not detected]',
1312
- message: 'Set GITHUB_REPO_PATH to your git repository.',
1313
- };
1314
- }
1315
- const board = await context.github.getProjectKanban(owner, projectNumber, repo);
1316
- if (!board) {
1317
- return {
1318
- format: 'mermaid',
1319
- diagram: `graph LR\n NotFound[Project #${String(projectNumber)} not found]`,
1320
- message: 'Ensure the project exists and has a Status field.',
1321
- };
1322
- }
1323
- // Build Mermaid diagram with subgraphs for each column
1324
- const lines = ['graph LR'];
1325
- // Add style definitions
1326
- lines.push(' classDef issue fill:#28a745,color:#fff');
1327
- lines.push(' classDef pr fill:#6f42c1,color:#fff');
1328
- lines.push(' classDef draft fill:#6c757d,color:#fff');
1329
- for (const column of board.columns) {
1330
- const safeStatus = column.status.replace(/["\s]/g, '_');
1331
- lines.push(` subgraph ${safeStatus}["${column.status} (${String(column.items.length)})"]`);
1332
- for (const item of column.items) {
1333
- const safeId = item.id.replace(/[^a-zA-Z0-9]/g, '').slice(-8);
1334
- const label = item.title.slice(0, 25).replace(/["[\]]/g, "'");
1335
- const typeIcon = item.type === 'ISSUE'
1336
- ? '🔵'
1337
- : item.type === 'PULL_REQUEST'
1338
- ? '🟣'
1339
- : '⚪';
1340
- const numberStr = item.number !== undefined && item.number !== 0
1341
- ? `#${String(item.number)}`
1342
- : '';
1343
- lines.push(` I${safeId}["${typeIcon} ${numberStr} ${label}..."]`);
1344
- // Add class based on type
1345
- const typeClass = item.type === 'ISSUE'
1346
- ? 'issue'
1347
- : item.type === 'PULL_REQUEST'
1348
- ? 'pr'
1349
- : 'draft';
1350
- lines.push(` class I${safeId} ${typeClass}`);
1351
- }
1352
- lines.push(' end');
1353
- }
1354
- return {
1355
- format: 'mermaid',
1356
- diagram: lines.join('\n'),
1357
- projectNumber,
1358
- projectTitle: board.projectTitle,
1359
- columnCount: board.columns.length,
1360
- totalItems: board.totalItems,
1361
- legend: {
1362
- '🔵': 'Issue',
1363
- '🟣': 'Pull Request',
1364
- '⚪': 'Draft Issue',
1365
- },
1366
- };
1367
- },
1368
- },
102
+ ...getCoreResourceDefinitions(),
103
+ ...getGraphResourceDefinitions(),
104
+ ...getGitHubResourceDefinitions(),
105
+ ...getTemplateResourceDefinitions(),
106
+ ...getTeamResourceDefinitions(),
1369
107
  ];
1370
108
  }
1371
109
  //# sourceMappingURL=index.js.map