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