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
@@ -0,0 +1,439 @@
1
+ /**
2
+ * GitHub Issue Tools - 2 tools
3
+ *
4
+ * Tools: create_github_issue_with_entry, close_github_issue_with_entry
5
+ */
6
+
7
+ import { z } from 'zod'
8
+ import type {
9
+ ToolDefinition,
10
+ ToolContext,
11
+ EntryType,
12
+ SignificanceType,
13
+ } from '../../../types/index.js'
14
+ import { formatHandlerError } from '../../../utils/error-helpers.js'
15
+ import {
16
+ CreateGitHubIssueWithEntryOutputSchema,
17
+ CloseGitHubIssueWithEntryOutputSchema,
18
+ } from './schemas.js'
19
+ import { resolveOwnerRepo } from './helpers.js'
20
+
21
+ export function getGitHubIssueTools(context: ToolContext): ToolDefinition[] {
22
+ const { db } = context
23
+ return [
24
+ {
25
+ name: 'create_github_issue_with_entry',
26
+ title: 'Create GitHub Issue with Journal Entry',
27
+ description:
28
+ 'Create a GitHub issue AND automatically create a linked journal entry documenting the issue creation.',
29
+ group: 'github',
30
+ inputSchema: z.object({
31
+ title: z.string().min(1).describe('Issue title'),
32
+ body: z.string().optional().describe('Issue body/description'),
33
+ labels: z.array(z.string()).optional().describe('Labels to apply'),
34
+ assignees: z.array(z.string()).optional().describe('Users to assign'),
35
+ milestone_number: z
36
+ .number()
37
+ .optional()
38
+ .describe('Milestone number to assign this issue to'),
39
+ project_number: z
40
+ .number()
41
+ .optional()
42
+ .describe('GitHub Project number to add this issue to'),
43
+ initial_status: z
44
+ .string()
45
+ .optional()
46
+ .describe(
47
+ 'Initial status column (e.g., "Backlog", "Ready"). Defaults to "Backlog" when adding to a project.'
48
+ ),
49
+ owner: z
50
+ .string()
51
+ .optional()
52
+ .describe('Repository owner - LEAVE EMPTY to auto-detect'),
53
+ repo: z
54
+ .string()
55
+ .optional()
56
+ .describe('Repository name - LEAVE EMPTY to auto-detect'),
57
+ entry_content: z
58
+ .string()
59
+ .optional()
60
+ .describe('Custom journal content (defaults to auto-generated summary)'),
61
+ tags: z.array(z.string()).optional().describe('Journal entry tags'),
62
+ }),
63
+ outputSchema: CreateGitHubIssueWithEntryOutputSchema,
64
+ annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
65
+ handler: async (params: unknown) => {
66
+ try {
67
+ const input = z
68
+ .object({
69
+ title: z.string().min(1),
70
+ body: z.string().optional(),
71
+ labels: z.array(z.string()).optional(),
72
+ assignees: z.array(z.string()).optional(),
73
+ milestone_number: z.number().optional(),
74
+ project_number: z.number().optional(),
75
+ initial_status: z.string().optional(),
76
+ owner: z.string().optional(),
77
+ repo: z.string().optional(),
78
+ entry_content: z.string().optional(),
79
+ tags: z.array(z.string()).optional(),
80
+ })
81
+ .parse(params)
82
+
83
+ const resolved = await resolveOwnerRepo(context, input)
84
+ if ('error' in resolved) return resolved.response
85
+
86
+ const issue = await resolved.github.createIssue(
87
+ resolved.owner,
88
+ resolved.repo,
89
+ input.title,
90
+ input.body,
91
+ input.labels,
92
+ input.assignees,
93
+ input.milestone_number
94
+ )
95
+
96
+ if (!issue) {
97
+ return {
98
+ success: false,
99
+ error: 'Failed to create GitHub issue. Check GITHUB_TOKEN permissions.',
100
+ }
101
+ }
102
+
103
+ const projectNumber =
104
+ input.project_number ?? context.config?.defaultProjectNumber
105
+
106
+ let projectResult = undefined
107
+ if (projectNumber !== undefined && issue.nodeId) {
108
+ try {
109
+ const board = await resolved.github.getProjectKanban(
110
+ resolved.owner,
111
+ projectNumber,
112
+ resolved.repo
113
+ )
114
+ if (board) {
115
+ const added = await resolved.github.addProjectItem(
116
+ board.projectId,
117
+ issue.nodeId
118
+ )
119
+ if (added.success) {
120
+ let statusResult:
121
+ | { status: string; set: boolean; error?: string }
122
+ | undefined = undefined
123
+ const initialStatus = input.initial_status ?? 'Backlog'
124
+ if (initialStatus && added.itemId) {
125
+ const statusOption = board.statusOptions.find(
126
+ (opt) =>
127
+ opt.name.toLowerCase() ===
128
+ initialStatus.toLowerCase()
129
+ )
130
+ if (statusOption) {
131
+ const moveResult =
132
+ await resolved.github.moveProjectItem(
133
+ board.projectId,
134
+ added.itemId,
135
+ board.statusFieldId,
136
+ statusOption.id
137
+ )
138
+ if (moveResult.success) {
139
+ statusResult = {
140
+ status: statusOption.name,
141
+ set: true,
142
+ }
143
+ } else {
144
+ statusResult = {
145
+ status: initialStatus,
146
+ set: false,
147
+ error: moveResult.error,
148
+ }
149
+ }
150
+ } else {
151
+ statusResult = {
152
+ status: initialStatus,
153
+ set: false,
154
+ error: `Status "${initialStatus}" not found. Available: ${board.statusOptions.map((o) => o.name).join(', ')}`,
155
+ }
156
+ }
157
+ }
158
+
159
+ projectResult = {
160
+ projectNumber: projectNumber,
161
+ added: true,
162
+ message:
163
+ `Added to project #${projectNumber}` +
164
+ (statusResult?.set ? ` (${statusResult.status})` : ''),
165
+ initialStatus: statusResult,
166
+ }
167
+ } else {
168
+ projectResult = {
169
+ projectNumber: projectNumber,
170
+ added: false,
171
+ error: added.error,
172
+ }
173
+ }
174
+ } else {
175
+ projectResult = {
176
+ projectNumber: projectNumber,
177
+ added: false,
178
+ error: `Project #${projectNumber} not found`,
179
+ }
180
+ }
181
+ } catch (error) {
182
+ projectResult = {
183
+ projectNumber: projectNumber,
184
+ added: false,
185
+ error: error instanceof Error ? error.message : String(error),
186
+ }
187
+ }
188
+ }
189
+
190
+ const entryContent =
191
+ input.entry_content ??
192
+ `Created GitHub issue #${String(issue.number)}: ${issue.title}\n\n` +
193
+ `URL: ${issue.url}\n` +
194
+ (projectNumber !== undefined ? `Project: #${projectNumber}\n` : '') +
195
+ (input.body
196
+ ? `\nDescription: ${input.body.slice(0, 200)}${input.body.length > 200 ? '...' : ''}`
197
+ : '')
198
+
199
+ const entry = db.createEntry({
200
+ content: entryContent,
201
+ entryType: 'planning' as EntryType,
202
+ tags: input.tags ?? ['github', 'issue-created'],
203
+ isPersonal: false,
204
+ significanceType: null,
205
+ issueNumber: issue.number,
206
+ issueUrl: issue.url,
207
+ projectNumber: projectNumber,
208
+ })
209
+
210
+ return {
211
+ success: true,
212
+ issue: {
213
+ number: issue.number,
214
+ title: issue.title,
215
+ url: issue.url,
216
+ },
217
+ project: projectResult,
218
+ journalEntry: {
219
+ id: entry.id,
220
+ linkedToIssue: issue.number,
221
+ },
222
+ message:
223
+ `Created issue #${String(issue.number)}` +
224
+ (projectResult?.added ? ` (added to Project #${projectNumber})` : '') +
225
+ ` and journal entry #${String(entry.id)}`,
226
+ }
227
+ } catch (err) {
228
+ return formatHandlerError(err)
229
+ }
230
+ },
231
+ },
232
+ {
233
+ name: 'close_github_issue_with_entry',
234
+ title: 'Close GitHub Issue with Resolution Entry',
235
+ description:
236
+ 'Close a GitHub issue AND create a journal entry documenting the resolution.',
237
+ group: 'github',
238
+ inputSchema: z.object({
239
+ issue_number: z.number().describe('Issue number to close'),
240
+ resolution_notes: z
241
+ .string()
242
+ .optional()
243
+ .describe('Notes about how the issue was resolved'),
244
+ comment: z
245
+ .string()
246
+ .optional()
247
+ .describe('Comment to add to the issue before closing'),
248
+ move_to_done: z
249
+ .boolean()
250
+ .optional()
251
+ .default(false)
252
+ .describe('Move the associated Kanban item to "Done" column'),
253
+ project_number: z
254
+ .number()
255
+ .optional()
256
+ .describe(
257
+ 'GitHub Project number (required if move_to_done is true, or uses DEFAULT_PROJECT_NUMBER)'
258
+ ),
259
+ owner: z
260
+ .string()
261
+ .optional()
262
+ .describe('Repository owner - LEAVE EMPTY to auto-detect'),
263
+ repo: z
264
+ .string()
265
+ .optional()
266
+ .describe('Repository name - LEAVE EMPTY to auto-detect'),
267
+ tags: z.array(z.string()).optional().describe('Journal entry tags'),
268
+ }),
269
+ outputSchema: CloseGitHubIssueWithEntryOutputSchema,
270
+ annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
271
+ handler: async (params: unknown) => {
272
+ try {
273
+ const input = z
274
+ .object({
275
+ issue_number: z.number(),
276
+ resolution_notes: z.string().optional(),
277
+ comment: z.string().optional(),
278
+ move_to_done: z.boolean().optional().default(false),
279
+ project_number: z.number().optional(),
280
+ owner: z.string().optional(),
281
+ repo: z.string().optional(),
282
+ tags: z.array(z.string()).optional(),
283
+ })
284
+ .parse(params)
285
+
286
+ const resolved = await resolveOwnerRepo(context, input)
287
+ if ('error' in resolved) return resolved.response
288
+
289
+ const issueDetails = await resolved.github.getIssue(
290
+ resolved.owner,
291
+ resolved.repo,
292
+ input.issue_number
293
+ )
294
+ if (!issueDetails) {
295
+ return {
296
+ success: false,
297
+ error: `Issue #${String(input.issue_number)} not found`,
298
+ }
299
+ }
300
+
301
+ if (issueDetails.state === 'CLOSED') {
302
+ return {
303
+ success: false,
304
+ error: `Issue #${String(input.issue_number)} is already closed`,
305
+ }
306
+ }
307
+
308
+ const result = await resolved.github.closeIssue(
309
+ resolved.owner,
310
+ resolved.repo,
311
+ input.issue_number,
312
+ input.comment
313
+ )
314
+
315
+ if (!result) {
316
+ return {
317
+ success: false,
318
+ error: 'Failed to close GitHub issue. Check GITHUB_TOKEN permissions.',
319
+ }
320
+ }
321
+
322
+ // Move Kanban item to "Done" if requested
323
+ let kanbanResult:
324
+ | { moved: boolean; error?: string; projectNumber?: number }
325
+ | undefined
326
+ if (input.move_to_done) {
327
+ const projectNum =
328
+ input.project_number ?? context.config?.defaultProjectNumber
329
+ if (projectNum === undefined) {
330
+ kanbanResult = {
331
+ moved: false,
332
+ error: 'project_number required when move_to_done is true',
333
+ }
334
+ } else {
335
+ try {
336
+ const board = await resolved.github.getProjectKanban(
337
+ resolved.owner,
338
+ projectNum,
339
+ resolved.repo
340
+ )
341
+ if (!board) {
342
+ kanbanResult = {
343
+ moved: false,
344
+ error: `Project #${projectNum} not found`,
345
+ projectNumber: projectNum,
346
+ }
347
+ } else {
348
+ const item = board.columns
349
+ .flatMap((c) => c.items)
350
+ .find(
351
+ (i) =>
352
+ i.type === 'ISSUE' &&
353
+ i.number === input.issue_number
354
+ )
355
+ if (!item) {
356
+ kanbanResult = {
357
+ moved: false,
358
+ error: 'Issue not found on project board',
359
+ projectNumber: projectNum,
360
+ }
361
+ } else {
362
+ const doneOption = board.statusOptions.find(
363
+ (opt) => opt.name.toLowerCase() === 'done'
364
+ )
365
+ if (!doneOption) {
366
+ kanbanResult = {
367
+ moved: false,
368
+ error: '"Done" status column not found on board',
369
+ projectNumber: projectNum,
370
+ }
371
+ } else {
372
+ const moveResult =
373
+ await resolved.github.moveProjectItem(
374
+ board.projectId,
375
+ item.id,
376
+ board.statusFieldId,
377
+ doneOption.id
378
+ )
379
+ kanbanResult = {
380
+ moved: moveResult.success,
381
+ error: moveResult.error,
382
+ projectNumber: projectNum,
383
+ }
384
+ }
385
+ }
386
+ }
387
+ } catch (err) {
388
+ kanbanResult = {
389
+ moved: false,
390
+ error: err instanceof Error ? err.message : String(err),
391
+ projectNumber:
392
+ input.project_number ??
393
+ context.config?.defaultProjectNumber,
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ const entryContent =
400
+ `Closed GitHub issue #${String(input.issue_number)}: ${issueDetails.title}\n\n` +
401
+ `URL: ${issueDetails.url}\n` +
402
+ (input.resolution_notes ? `\nResolution: ${input.resolution_notes}` : '')
403
+
404
+ const entry = db.createEntry({
405
+ content: entryContent,
406
+ entryType: 'bug_fix' as EntryType,
407
+ tags: input.tags ?? ['github', 'issue-closed', 'resolution'],
408
+ isPersonal: false,
409
+ significanceType: 'blocker_resolved' as SignificanceType,
410
+ issueNumber: input.issue_number,
411
+ issueUrl: issueDetails.url,
412
+ })
413
+
414
+ return {
415
+ success: true,
416
+ issue: {
417
+ number: input.issue_number,
418
+ title: issueDetails.title,
419
+ url: result.url,
420
+ previousState: 'OPEN',
421
+ newState: 'CLOSED',
422
+ },
423
+ journalEntry: {
424
+ id: entry.id,
425
+ linkedToIssue: input.issue_number,
426
+ significanceType: 'blocker_resolved',
427
+ },
428
+ kanban: kanbanResult,
429
+ message:
430
+ `Closed issue #${String(input.issue_number)} and created resolution entry #${String(entry.id)}` +
431
+ (kanbanResult?.moved ? ' and moved to Done' : ''),
432
+ }
433
+ } catch (err) {
434
+ return formatHandlerError(err)
435
+ }
436
+ },
437
+ },
438
+ ]
439
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * GitHub Kanban Tools - 2 tools
3
+ *
4
+ * Tools: get_kanban_board, move_kanban_item
5
+ */
6
+
7
+ import { z } from 'zod'
8
+ import type { ToolDefinition, ToolContext } from '../../../types/index.js'
9
+ import { formatHandlerError } from '../../../utils/error-helpers.js'
10
+ import { KanbanBoardOutputSchema, MoveKanbanItemOutputSchema } from './schemas.js'
11
+ import { resolveOwner } from './helpers.js'
12
+
13
+ export function getKanbanTools(context: ToolContext): ToolDefinition[] {
14
+ return [
15
+ {
16
+ name: 'get_kanban_board',
17
+ title: 'Get Kanban Board',
18
+ description:
19
+ 'View a GitHub Project v2 as a Kanban board with items grouped by Status column. Returns all columns with their items.',
20
+ group: 'github',
21
+ inputSchema: z.object({
22
+ project_number: z.number().describe('GitHub Project number'),
23
+ owner: z.string().optional().describe('Project owner - LEAVE EMPTY to auto-detect'),
24
+ }),
25
+ outputSchema: KanbanBoardOutputSchema,
26
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
27
+ handler: async (params: unknown) => {
28
+ try {
29
+ const input = z
30
+ .object({
31
+ project_number: z.number(),
32
+ owner: z.string().optional(),
33
+ })
34
+ .parse(params)
35
+
36
+ const resolved = await resolveOwner(context, input.owner)
37
+ if ('error' in resolved) return resolved.response
38
+
39
+ const board = await resolved.github.getProjectKanban(
40
+ resolved.owner,
41
+ input.project_number,
42
+ resolved.repo
43
+ )
44
+
45
+ if (!board) {
46
+ return {
47
+ error: `Project #${String(input.project_number)} not found or Status field not configured`,
48
+ projectNumber: input.project_number,
49
+ owner: resolved.owner,
50
+ hint: 'Projects can be at user, repository, or organization level.',
51
+ }
52
+ }
53
+
54
+ return board
55
+ } catch (err) {
56
+ return formatHandlerError(err)
57
+ }
58
+ },
59
+ },
60
+ {
61
+ name: 'move_kanban_item',
62
+ title: 'Move Kanban Item',
63
+ description:
64
+ 'Move a Kanban item to a different status column. Requires the project board to have a Status field.',
65
+ group: 'github',
66
+ inputSchema: z.object({
67
+ project_number: z.number().describe('GitHub Project number'),
68
+ item_id: z.string().describe('Project item ID (from get_kanban_board)'),
69
+ target_status: z
70
+ .string()
71
+ .describe('Target status column name (e.g., "In Progress", "Done")'),
72
+ owner: z.string().optional().describe('Project owner - LEAVE EMPTY to auto-detect'),
73
+ }),
74
+ outputSchema: MoveKanbanItemOutputSchema,
75
+ annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: true },
76
+ handler: async (params: unknown) => {
77
+ try {
78
+ const input = z
79
+ .object({
80
+ project_number: z.number(),
81
+ item_id: z.string(),
82
+ target_status: z.string(),
83
+ owner: z.string().optional(),
84
+ })
85
+ .parse(params)
86
+
87
+ const resolved = await resolveOwner(context, input.owner)
88
+ if ('error' in resolved) return resolved.response
89
+
90
+ const board = await resolved.github.getProjectKanban(
91
+ resolved.owner,
92
+ input.project_number,
93
+ resolved.repo
94
+ )
95
+
96
+ if (!board) {
97
+ return {
98
+ success: false,
99
+ error: `Project #${String(input.project_number)} not found`,
100
+ }
101
+ }
102
+
103
+ // Find target status option
104
+ const statusOption = board.statusOptions.find(
105
+ (opt) => opt.name.toLowerCase() === input.target_status.toLowerCase()
106
+ )
107
+
108
+ if (!statusOption) {
109
+ return {
110
+ success: false,
111
+ error: `Status "${input.target_status}" not found`,
112
+ availableStatuses: board.statusOptions.map((o) => o.name),
113
+ }
114
+ }
115
+
116
+ const result = await resolved.github.moveProjectItem(
117
+ board.projectId,
118
+ input.item_id,
119
+ board.statusFieldId,
120
+ statusOption.id
121
+ )
122
+
123
+ return {
124
+ success: result.success,
125
+ newStatus: statusOption.name,
126
+ error: result.error,
127
+ }
128
+ } catch (err) {
129
+ return formatHandlerError(err)
130
+ }
131
+ },
132
+ },
133
+ ]
134
+ }