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,343 @@
1
+ /**
2
+ * Search Tool Group - 4 tools
3
+ *
4
+ * Tools: search_entries, search_by_date_range, semantic_search, get_vector_index_stats
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 {
11
+ ENTRY_TYPES,
12
+ DATE_FORMAT_REGEX,
13
+ DATE_FORMAT_MESSAGE,
14
+ EntryOutputSchema,
15
+ EntriesListOutputSchema,
16
+ } from './schemas.js'
17
+
18
+ // ============================================================================
19
+ // Input Schemas
20
+ // ============================================================================
21
+
22
+ /** Strict schema — used inside handler for structured Zod errors */
23
+ const SearchEntriesSchema = z.object({
24
+ query: z.string().optional(),
25
+ limit: z.number().max(500).optional().default(10),
26
+ is_personal: z.boolean().optional(),
27
+ project_number: z.number().optional(),
28
+ issue_number: z.number().optional(),
29
+ pr_number: z.number().optional(),
30
+ pr_status: z.enum(['draft', 'open', 'merged', 'closed']).optional(),
31
+ workflow_run_id: z.number().optional(),
32
+ })
33
+
34
+ /** Relaxed schema — passed to SDK inputSchema so Zod enum errors reach the handler */
35
+ const SearchEntriesSchemaMcp = z.object({
36
+ query: z.string().optional(),
37
+ limit: z.number().max(500).optional().default(10),
38
+ is_personal: z.boolean().optional(),
39
+ project_number: z.number().optional(),
40
+ issue_number: z.number().optional(),
41
+ pr_number: z.number().optional(),
42
+ pr_status: z.string().optional(),
43
+ workflow_run_id: z.number().optional(),
44
+ })
45
+
46
+ /** Strict schema — used inside handler for structured Zod errors */
47
+ const SearchByDateRangeSchema = z.object({
48
+ start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE),
49
+ end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE),
50
+ entry_type: z.enum(ENTRY_TYPES).optional(),
51
+ tags: z.array(z.string()).optional(),
52
+ is_personal: z.boolean().optional(),
53
+ project_number: z.number().optional(),
54
+ issue_number: z.number().optional(),
55
+ pr_number: z.number().optional(),
56
+ workflow_run_id: z.number().optional(),
57
+ limit: z.number().max(500).optional().default(500),
58
+ })
59
+
60
+ /** Relaxed schema — passed to SDK inputSchema so Zod errors reach the handler */
61
+ const SearchByDateRangeSchemaMcp = z.object({
62
+ start_date: z.string(),
63
+ end_date: z.string(),
64
+ entry_type: z.string().optional(),
65
+ tags: z.array(z.string()).optional(),
66
+ is_personal: z.boolean().optional(),
67
+ project_number: z.number().optional(),
68
+ issue_number: z.number().optional(),
69
+ pr_number: z.number().optional(),
70
+ workflow_run_id: z.number().optional(),
71
+ limit: z.number().max(500).optional().default(500),
72
+ })
73
+
74
+ const SemanticSearchSchema = z.object({
75
+ query: z.string(),
76
+ limit: z.number().max(500).optional().default(10),
77
+ similarity_threshold: z.number().optional().default(0.25),
78
+ is_personal: z.boolean().optional(),
79
+ hint_on_empty: z
80
+ .boolean()
81
+ .optional()
82
+ .default(true)
83
+ .describe('Include hint when no results found (default: true)'),
84
+ })
85
+
86
+ // ============================================================================
87
+ // Output Schemas
88
+ // ============================================================================
89
+
90
+ const SemanticEntryOutputSchema = EntryOutputSchema.extend({
91
+ similarity: z.number(),
92
+ })
93
+
94
+ const SemanticSearchOutputSchema = z.object({
95
+ query: z.string().optional(),
96
+ entries: z.array(SemanticEntryOutputSchema).optional(),
97
+ count: z.number().optional(),
98
+ hint: z.string().optional(),
99
+ success: z.boolean().optional(),
100
+ error: z.string().optional(),
101
+ })
102
+
103
+ const VectorStatsOutputSchema = z.object({
104
+ available: z.boolean(),
105
+ error: z.string().optional(),
106
+ entryCount: z.number().optional(),
107
+ indexSize: z.number().optional(),
108
+ })
109
+
110
+ // ============================================================================
111
+ // Tool Definitions
112
+ // ============================================================================
113
+
114
+ export function getSearchTools(context: ToolContext): ToolDefinition[] {
115
+ const { db, teamDb, vectorManager } = context
116
+ return [
117
+ {
118
+ name: 'search_entries',
119
+ title: 'Search Entries',
120
+ description:
121
+ 'Search journal entries with optional filters for GitHub Projects, Issues, PRs, and Actions',
122
+ group: 'search',
123
+ inputSchema: SearchEntriesSchemaMcp,
124
+ outputSchema: EntriesListOutputSchema,
125
+ annotations: { readOnlyHint: true, idempotentHint: true },
126
+ handler: (params: unknown) => {
127
+ try {
128
+ const input = SearchEntriesSchema.parse(params)
129
+ const hasFilters =
130
+ input.project_number !== undefined ||
131
+ input.issue_number !== undefined ||
132
+ input.pr_number !== undefined ||
133
+ input.is_personal !== undefined
134
+
135
+ let personalEntries
136
+ if (!input.query && !hasFilters) {
137
+ personalEntries = db.getRecentEntries(input.limit, input.is_personal)
138
+ } else {
139
+ personalEntries = db.searchEntries(input.query || '', {
140
+ limit: input.limit,
141
+ isPersonal: input.is_personal,
142
+ projectNumber: input.project_number,
143
+ issueNumber: input.issue_number,
144
+ prNumber: input.pr_number,
145
+ })
146
+ }
147
+
148
+ // Cross-database merge when team DB is available
149
+ if (teamDb) {
150
+ let teamEntries
151
+ if (!input.query && !hasFilters) {
152
+ teamEntries = teamDb.getRecentEntries(input.limit)
153
+ } else {
154
+ teamEntries = teamDb.searchEntries(input.query || '', {
155
+ limit: input.limit,
156
+ projectNumber: input.project_number,
157
+ issueNumber: input.issue_number,
158
+ prNumber: input.pr_number,
159
+ })
160
+ }
161
+ const merged = mergeAndDedup(
162
+ personalEntries.map((e) => ({ ...e, source: 'personal' as const })),
163
+ teamEntries.map((e) => ({ ...e, source: 'team' as const })),
164
+ input.limit
165
+ )
166
+ return { entries: merged, count: merged.length }
167
+ }
168
+
169
+ return { entries: personalEntries, count: personalEntries.length }
170
+ } catch (err) {
171
+ return formatHandlerError(err)
172
+ }
173
+ },
174
+ },
175
+ {
176
+ name: 'search_by_date_range',
177
+ title: 'Search by Date Range',
178
+ description: 'Search journal entries within a date range with optional filters',
179
+ group: 'search',
180
+ inputSchema: SearchByDateRangeSchemaMcp,
181
+ outputSchema: EntriesListOutputSchema,
182
+ annotations: { readOnlyHint: true, idempotentHint: true },
183
+ handler: (params: unknown) => {
184
+ try {
185
+ const input = SearchByDateRangeSchema.parse(params)
186
+ const personalEntries = db.searchByDateRange(input.start_date, input.end_date, {
187
+ entryType: input.entry_type,
188
+ tags: input.tags,
189
+ isPersonal: input.is_personal,
190
+ projectNumber: input.project_number,
191
+ limit: input.limit,
192
+ })
193
+
194
+ // Cross-database merge when team DB is available
195
+ if (teamDb) {
196
+ const teamEntries = teamDb.searchByDateRange(
197
+ input.start_date,
198
+ input.end_date,
199
+ {
200
+ entryType: input.entry_type,
201
+ tags: input.tags,
202
+ projectNumber: input.project_number,
203
+ limit: input.limit,
204
+ }
205
+ )
206
+ const merged = mergeAndDedup(
207
+ personalEntries.map((e) => ({ ...e, source: 'personal' as const })),
208
+ teamEntries.map((e) => ({ ...e, source: 'team' as const })),
209
+ input.limit
210
+ )
211
+ return { entries: merged, count: merged.length }
212
+ }
213
+
214
+ return { entries: personalEntries, count: personalEntries.length }
215
+ } catch (err) {
216
+ return formatHandlerError(err)
217
+ }
218
+ },
219
+ },
220
+ {
221
+ name: 'semantic_search',
222
+ title: 'Semantic Search',
223
+ description: 'Perform semantic/vector search on journal entries using AI embeddings',
224
+ group: 'search',
225
+ inputSchema: SemanticSearchSchema,
226
+ outputSchema: SemanticSearchOutputSchema,
227
+ annotations: { readOnlyHint: true, idempotentHint: true },
228
+ handler: async (params: unknown) => {
229
+ try {
230
+ const input = SemanticSearchSchema.parse(params)
231
+
232
+ if (!vectorManager) {
233
+ return {
234
+ success: false,
235
+ error: 'Semantic search not initialized. Vector search manager is not available.',
236
+ query: input.query,
237
+ entries: [],
238
+ count: 0,
239
+ }
240
+ }
241
+
242
+ const results = await vectorManager.search(
243
+ input.query,
244
+ input.limit ?? 10,
245
+ input.similarity_threshold ?? 0.25
246
+ )
247
+
248
+ const entries = results
249
+ .map((r) => {
250
+ const entry = db.getEntryById(r.entryId)
251
+ if (!entry) return null
252
+ return {
253
+ ...entry,
254
+ similarity: Math.round(r.score * 100) / 100,
255
+ }
256
+ })
257
+ .filter((e): e is NonNullable<typeof e> => e !== null)
258
+
259
+ const stats = await vectorManager.getStats()
260
+ const isIndexEmpty = stats.itemCount === 0
261
+ const includeHint = input.hint_on_empty ?? true
262
+
263
+ return {
264
+ query: input.query,
265
+ entries,
266
+ count: entries.length,
267
+ ...(includeHint && isIndexEmpty
268
+ ? {
269
+ hint: 'No entries in vector index. Use rebuild_vector_index to index existing entries.',
270
+ }
271
+ : includeHint && entries.length === 0
272
+ ? {
273
+ hint: `No entries matched your query above the similarity threshold (${String(input.similarity_threshold ?? 0.25)}). Try lowering similarity_threshold (e.g., 0.15) for broader matches.`,
274
+ }
275
+ : {}),
276
+ }
277
+ } catch (err) {
278
+ return formatHandlerError(err)
279
+ }
280
+ },
281
+ },
282
+ {
283
+ name: 'get_vector_index_stats',
284
+ title: 'Get Vector Index Stats',
285
+ description: 'Get statistics about the semantic search vector index',
286
+ group: 'search',
287
+ inputSchema: z.object({}),
288
+ outputSchema: VectorStatsOutputSchema,
289
+ annotations: { readOnlyHint: true, idempotentHint: true },
290
+ handler: async (_params: unknown) => {
291
+ try {
292
+ if (!vectorManager) {
293
+ return { available: false, error: 'Vector search not available' }
294
+ }
295
+ const stats = await vectorManager.getStats()
296
+ return { available: true, ...stats }
297
+ } catch (err) {
298
+ return formatHandlerError(err)
299
+ }
300
+ },
301
+ },
302
+ ]
303
+ }
304
+
305
+ // ============================================================================
306
+ // Helpers
307
+ // ============================================================================
308
+
309
+ interface EntryWithSource {
310
+ content: string
311
+ timestamp: string
312
+ source: 'personal' | 'team'
313
+ [key: string]: unknown
314
+ }
315
+
316
+ /**
317
+ * Merge personal and team results, deduplicate by content,
318
+ * and sort by timestamp descending.
319
+ */
320
+ function mergeAndDedup(
321
+ personal: EntryWithSource[],
322
+ team: EntryWithSource[],
323
+ limit?: number
324
+ ): EntryWithSource[] {
325
+ const seen = new Set<string>()
326
+ const merged: EntryWithSource[] = []
327
+
328
+ // Concat and sort by timestamp descending
329
+ const all = [...personal, ...team].sort(
330
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
331
+ )
332
+
333
+ for (const entry of all) {
334
+ // Deduplicate by content (same entry shared to team)
335
+ const key = entry.content.slice(0, 200)
336
+ if (!seen.has(key)) {
337
+ seen.add(key)
338
+ merged.push(entry)
339
+ }
340
+ }
341
+
342
+ return limit !== undefined ? merged.slice(0, limit) : merged
343
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Team Tool Group - 3 tools
3
+ *
4
+ * Tools: team_create_entry, team_get_recent, team_search
5
+ *
6
+ * Requires TEAM_DB_PATH to be configured. All tools return structured
7
+ * errors when the team database is not available.
8
+ */
9
+
10
+ import { z } from 'zod'
11
+ import { execFileSync } from 'node:child_process'
12
+ import type { ToolDefinition, ToolContext } from '../../types/index.js'
13
+ import { formatHandlerError } from '../../utils/error-helpers.js'
14
+ import { ENTRY_TYPES, SIGNIFICANCE_TYPES, EntryOutputSchema } from './schemas.js'
15
+
16
+ // ============================================================================
17
+ // Author Detection
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Resolve the author name for team entries.
22
+ * Priority: TEAM_AUTHOR env > git config user.name > 'unknown'
23
+ */
24
+ function resolveAuthor(): string {
25
+ // 1. Explicit env var
26
+ const envAuthor = process.env['TEAM_AUTHOR']?.trim().replace(/"/g, '')
27
+ if (envAuthor) return envAuthor
28
+
29
+ // 2. Git config
30
+ try {
31
+ const gitUser = execFileSync('git', ['config', 'user.name'], {
32
+ encoding: 'utf-8',
33
+ timeout: 3000,
34
+ })
35
+ .trim()
36
+ .replace(/"/g, '')
37
+ if (gitUser) return gitUser
38
+ } catch {
39
+ // Git not available or not configured
40
+ }
41
+
42
+ return 'unknown'
43
+ }
44
+
45
+ // ============================================================================
46
+ // Input Schemas
47
+ // ============================================================================
48
+
49
+ /** Strict schema for team entry creation */
50
+ const TeamCreateEntrySchema = z.object({
51
+ content: z.string().min(1).max(50000),
52
+ entry_type: z.enum(ENTRY_TYPES).optional().default('personal_reflection'),
53
+ tags: z.array(z.string()).optional().default([]),
54
+ significance_type: z.enum(SIGNIFICANCE_TYPES).optional(),
55
+ project_number: z.number().optional(),
56
+ project_owner: z.string().optional(),
57
+ issue_number: z.number().optional(),
58
+ issue_url: z.string().optional(),
59
+ pr_number: z.number().optional(),
60
+ pr_url: z.string().optional(),
61
+ pr_status: z.enum(['draft', 'open', 'merged', 'closed']).optional(),
62
+ author: z.string().optional(),
63
+ })
64
+
65
+ /** Relaxed schema for MCP SDK */
66
+ const TeamCreateEntrySchemaMcp = z.object({
67
+ content: z.string().min(1).max(50000),
68
+ entry_type: z.string().optional().default('personal_reflection'),
69
+ tags: z.array(z.string()).optional().default([]),
70
+ significance_type: z.string().optional(),
71
+ project_number: z.number().optional(),
72
+ project_owner: z.string().optional(),
73
+ issue_number: z.number().optional(),
74
+ issue_url: z.string().optional(),
75
+ pr_number: z.number().optional(),
76
+ pr_url: z.string().optional(),
77
+ pr_status: z.string().optional(),
78
+ author: z.string().optional(),
79
+ })
80
+
81
+ const TeamGetRecentSchema = z.object({
82
+ limit: z.number().max(500).optional().default(10),
83
+ })
84
+
85
+ const TeamSearchSchema = z.object({
86
+ query: z.string().optional(),
87
+ tags: z.array(z.string()).optional(),
88
+ limit: z.number().max(500).optional().default(10),
89
+ })
90
+
91
+ // ============================================================================
92
+ // Output Schemas
93
+ // ============================================================================
94
+
95
+ const TeamEntryOutputSchema = EntryOutputSchema.extend({
96
+ author: z.string().nullable().optional(),
97
+ })
98
+
99
+ const TeamCreateOutputSchema = z.object({
100
+ success: z.boolean().optional(),
101
+ entry: TeamEntryOutputSchema.optional(),
102
+ author: z.string().optional(),
103
+ error: z.string().optional(),
104
+ })
105
+
106
+ const TeamEntriesListOutputSchema = z.object({
107
+ entries: z.array(TeamEntryOutputSchema).optional(),
108
+ count: z.number().optional(),
109
+ success: z.boolean().optional(),
110
+ error: z.string().optional(),
111
+ })
112
+
113
+ // ============================================================================
114
+ // Constants
115
+ // ============================================================================
116
+
117
+ const TEAM_DB_NOT_CONFIGURED =
118
+ 'Team database not configured. Set TEAM_DB_PATH environment variable to enable team collaboration.'
119
+
120
+ // ============================================================================
121
+ // Tool Definitions
122
+ // ============================================================================
123
+
124
+ export function getTeamTools(context: ToolContext): ToolDefinition[] {
125
+ const { teamDb, github } = context
126
+
127
+ return [
128
+ {
129
+ name: 'team_create_entry',
130
+ title: 'Create Team Entry',
131
+ description:
132
+ 'Create an entry in the team database for sharing with collaborators. Requires TEAM_DB_PATH.',
133
+ group: 'team',
134
+ inputSchema: TeamCreateEntrySchemaMcp,
135
+ outputSchema: TeamCreateOutputSchema,
136
+ annotations: { readOnlyHint: false, idempotentHint: false },
137
+ handler: (params: unknown) => {
138
+ try {
139
+ if (!teamDb) {
140
+ return { success: false, error: TEAM_DB_NOT_CONFIGURED }
141
+ }
142
+
143
+ const input = TeamCreateEntrySchema.parse(params)
144
+ const author = input.author ?? resolveAuthor()
145
+
146
+ // Auto-populate issueUrl if issue_number provided
147
+ let resolvedIssueUrl = input.issue_url
148
+ if (input.issue_number !== undefined && !input.issue_url && github) {
149
+ const cachedRepo = github.getCachedRepoInfo()
150
+ if (cachedRepo?.owner && cachedRepo?.repo) {
151
+ resolvedIssueUrl = `https://github.com/${cachedRepo.owner}/${cachedRepo.repo}/issues/${String(input.issue_number)}`
152
+ }
153
+ }
154
+
155
+ const entry = teamDb.createEntry({
156
+ content: input.content,
157
+ entryType: input.entry_type,
158
+ tags: input.tags,
159
+ isPersonal: false, // Team entries are always project-level
160
+ significanceType: input.significance_type ?? null,
161
+ autoContext: JSON.stringify({ author }),
162
+ projectNumber: input.project_number,
163
+ projectOwner: input.project_owner,
164
+ issueNumber: input.issue_number,
165
+ issueUrl: resolvedIssueUrl,
166
+ prNumber: input.pr_number,
167
+ prUrl: input.pr_url,
168
+ prStatus: input.pr_status,
169
+ })
170
+
171
+ // Write author to the dedicated column
172
+ const rawDb = teamDb.getRawDb()
173
+ rawDb.run('UPDATE memory_journal SET author = ? WHERE id = ?', [
174
+ author,
175
+ entry.id,
176
+ ])
177
+ teamDb.flushSave()
178
+
179
+ return {
180
+ success: true,
181
+ entry: { ...entry, author },
182
+ author,
183
+ }
184
+ } catch (err) {
185
+ return formatHandlerError(err)
186
+ }
187
+ },
188
+ },
189
+ {
190
+ name: 'team_get_recent',
191
+ title: 'Get Recent Team Entries',
192
+ description: 'Get recent entries from the team database. Requires TEAM_DB_PATH.',
193
+ group: 'team',
194
+ inputSchema: TeamGetRecentSchema,
195
+ outputSchema: TeamEntriesListOutputSchema,
196
+ annotations: { readOnlyHint: true, idempotentHint: true },
197
+ handler: (params: unknown) => {
198
+ try {
199
+ if (!teamDb) {
200
+ return { success: false, error: TEAM_DB_NOT_CONFIGURED }
201
+ }
202
+
203
+ const { limit } = TeamGetRecentSchema.parse(params)
204
+ const entries = teamDb.getRecentEntries(limit)
205
+
206
+ // Enrich entries with author column
207
+ const rawDb = teamDb.getRawDb()
208
+ const enriched = entries.map((e) => {
209
+ const authorResult = rawDb.exec(
210
+ 'SELECT author FROM memory_journal WHERE id = ?',
211
+ [e.id]
212
+ )
213
+ const author = (authorResult[0]?.values[0]?.[0] as string) ?? null
214
+ return { ...e, author }
215
+ })
216
+
217
+ return { entries: enriched, count: enriched.length }
218
+ } catch (err) {
219
+ return formatHandlerError(err)
220
+ }
221
+ },
222
+ },
223
+ {
224
+ name: 'team_search',
225
+ title: 'Search Team Entries',
226
+ description:
227
+ 'Search entries in the team database by text and/or tags. Requires TEAM_DB_PATH.',
228
+ group: 'team',
229
+ inputSchema: TeamSearchSchema,
230
+ outputSchema: TeamEntriesListOutputSchema,
231
+ annotations: { readOnlyHint: true, idempotentHint: true },
232
+ handler: (params: unknown) => {
233
+ try {
234
+ if (!teamDb) {
235
+ return { success: false, error: TEAM_DB_NOT_CONFIGURED }
236
+ }
237
+
238
+ const { query, tags, limit } = TeamSearchSchema.parse(params)
239
+
240
+ let entries
241
+ if (query) {
242
+ entries = teamDb.searchEntries(query, { limit })
243
+ } else {
244
+ entries = teamDb.getRecentEntries(limit)
245
+ }
246
+
247
+ // Filter by tags if provided
248
+ if (tags && tags.length > 0) {
249
+ entries = entries.filter((e) => {
250
+ const entryTags = teamDb.getTagsForEntry(e.id)
251
+ return tags.some((t) => entryTags.includes(t))
252
+ })
253
+ }
254
+
255
+ // Enrich with author
256
+ const rawDb = teamDb.getRawDb()
257
+ const enriched = entries.map((e) => {
258
+ const authorResult = rawDb.exec(
259
+ 'SELECT author FROM memory_journal WHERE id = ?',
260
+ [e.id]
261
+ )
262
+ const author = (authorResult[0]?.values[0]?.[0] as string) ?? null
263
+ return { ...e, author }
264
+ })
265
+
266
+ return { entries: enriched, count: enriched.length }
267
+ } catch (err) {
268
+ return formatHandlerError(err)
269
+ }
270
+ },
271
+ },
272
+ ]
273
+ }