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.
- package/.github/workflows/codeql.yml +1 -6
- package/.github/workflows/docker-publish.yml +15 -49
- package/.github/workflows/lint-and-test.yml +1 -1
- package/.github/workflows/secrets-scanning.yml +4 -3
- package/.github/workflows/security-update.yml +3 -3
- package/CHANGELOG.md +213 -0
- package/CONTRIBUTING.md +132 -97
- package/DOCKER_README.md +184 -235
- package/Dockerfile +27 -24
- package/README.md +218 -190
- package/SECURITY.md +27 -35
- package/dist/cli.js +16 -1
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +5 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +133 -73
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -2
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +7 -6
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +37 -24
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +319 -157
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/database/schema.d.ts +45 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +92 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +13 -2
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +1 -3
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/github.d.ts +12 -0
- package/dist/handlers/prompts/github.d.ts.map +1 -0
- package/dist/handlers/prompts/github.js +178 -0
- package/dist/handlers/prompts/github.js.map +1 -0
- package/dist/handlers/prompts/index.d.ts +23 -2
- package/dist/handlers/prompts/index.d.ts.map +1 -1
- package/dist/handlers/prompts/index.js +7 -432
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/prompts/workflow.d.ts +12 -0
- package/dist/handlers/prompts/workflow.d.ts.map +1 -0
- package/dist/handlers/prompts/workflow.js +277 -0
- package/dist/handlers/prompts/workflow.js.map +1 -0
- package/dist/handlers/resources/core.d.ts +11 -0
- package/dist/handlers/resources/core.d.ts.map +1 -0
- package/dist/handlers/resources/core.js +433 -0
- package/dist/handlers/resources/core.js.map +1 -0
- package/dist/handlers/resources/github.d.ts +11 -0
- package/dist/handlers/resources/github.d.ts.map +1 -0
- package/dist/handlers/resources/github.js +314 -0
- package/dist/handlers/resources/github.js.map +1 -0
- package/dist/handlers/resources/graph.d.ts +11 -0
- package/dist/handlers/resources/graph.d.ts.map +1 -0
- package/dist/handlers/resources/graph.js +204 -0
- package/dist/handlers/resources/graph.js.map +1 -0
- package/dist/handlers/resources/index.d.ts +5 -20
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +16 -1278
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/resources/shared.d.ts +60 -0
- package/dist/handlers/resources/shared.d.ts.map +1 -0
- package/dist/handlers/resources/shared.js +49 -0
- package/dist/handlers/resources/shared.js.map +1 -0
- package/dist/handlers/resources/team.d.ts +13 -0
- package/dist/handlers/resources/team.d.ts.map +1 -0
- package/dist/handlers/resources/team.js +119 -0
- package/dist/handlers/resources/team.js.map +1 -0
- package/dist/handlers/resources/templates.d.ts +13 -0
- package/dist/handlers/resources/templates.d.ts.map +1 -0
- package/dist/handlers/resources/templates.js +310 -0
- package/dist/handlers/resources/templates.js.map +1 -0
- package/dist/handlers/tools/admin.d.ts +8 -0
- package/dist/handlers/tools/admin.d.ts.map +1 -0
- package/dist/handlers/tools/admin.js +270 -0
- package/dist/handlers/tools/admin.js.map +1 -0
- package/dist/handlers/tools/analytics.d.ts +8 -0
- package/dist/handlers/tools/analytics.d.ts.map +1 -0
- package/dist/handlers/tools/analytics.js +256 -0
- package/dist/handlers/tools/analytics.js.map +1 -0
- package/dist/handlers/tools/backup.d.ts +8 -0
- package/dist/handlers/tools/backup.d.ts.map +1 -0
- package/dist/handlers/tools/backup.js +224 -0
- package/dist/handlers/tools/backup.js.map +1 -0
- package/dist/handlers/tools/core.d.ts +9 -0
- package/dist/handlers/tools/core.d.ts.map +1 -0
- package/dist/handlers/tools/core.js +326 -0
- package/dist/handlers/tools/core.js.map +1 -0
- package/dist/handlers/tools/export.d.ts +8 -0
- package/dist/handlers/tools/export.d.ts.map +1 -0
- package/dist/handlers/tools/export.js +89 -0
- package/dist/handlers/tools/export.js.map +1 -0
- package/dist/handlers/tools/github/helpers.d.ts +34 -0
- package/dist/handlers/tools/github/helpers.d.ts.map +1 -0
- package/dist/handlers/tools/github/helpers.js +52 -0
- package/dist/handlers/tools/github/helpers.js.map +1 -0
- package/dist/handlers/tools/github/insights-tools.d.ts +8 -0
- package/dist/handlers/tools/github/insights-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/insights-tools.js +104 -0
- package/dist/handlers/tools/github/insights-tools.js.map +1 -0
- package/dist/handlers/tools/github/issue-tools.d.ts +8 -0
- package/dist/handlers/tools/github/issue-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/issue-tools.js +359 -0
- package/dist/handlers/tools/github/issue-tools.js.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts +8 -0
- package/dist/handlers/tools/github/kanban-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/kanban-tools.js +108 -0
- package/dist/handlers/tools/github/kanban-tools.js.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts +9 -0
- package/dist/handlers/tools/github/milestone-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/milestone-tools.js +302 -0
- package/dist/handlers/tools/github/milestone-tools.js.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts +12 -0
- package/dist/handlers/tools/github/mutation-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/mutation-tools.js +15 -0
- package/dist/handlers/tools/github/mutation-tools.js.map +1 -0
- package/dist/handlers/tools/github/read-tools.d.ts +8 -0
- package/dist/handlers/tools/github/read-tools.d.ts.map +1 -0
- package/dist/handlers/tools/github/read-tools.js +260 -0
- package/dist/handlers/tools/github/read-tools.js.map +1 -0
- package/dist/handlers/tools/github/schemas.d.ts +467 -0
- package/dist/handlers/tools/github/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/github/schemas.js +335 -0
- package/dist/handlers/tools/github/schemas.js.map +1 -0
- package/dist/handlers/tools/github.d.ts +14 -0
- package/dist/handlers/tools/github.d.ts.map +1 -0
- package/dist/handlers/tools/github.js +28 -0
- package/dist/handlers/tools/github.js.map +1 -0
- package/dist/handlers/tools/index.d.ts +15 -20
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +117 -2909
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/handlers/tools/relationships.d.ts +8 -0
- package/dist/handlers/tools/relationships.d.ts.map +1 -0
- package/dist/handlers/tools/relationships.js +308 -0
- package/dist/handlers/tools/relationships.js.map +1 -0
- package/dist/handlers/tools/schemas.d.ts +108 -0
- package/dist/handlers/tools/schemas.d.ts.map +1 -0
- package/dist/handlers/tools/schemas.js +122 -0
- package/dist/handlers/tools/schemas.js.map +1 -0
- package/dist/handlers/tools/search.d.ts +8 -0
- package/dist/handlers/tools/search.d.ts.map +1 -0
- package/dist/handlers/tools/search.js +282 -0
- package/dist/handlers/tools/search.js.map +1 -0
- package/dist/handlers/tools/team.d.ts +11 -0
- package/dist/handlers/tools/team.d.ts.map +1 -0
- package/dist/handlers/tools/team.js +239 -0
- package/dist/handlers/tools/team.js.map +1 -0
- package/dist/server/McpServer.d.ts +4 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +48 -297
- package/dist/server/McpServer.js.map +1 -1
- package/dist/server/Scheduler.d.ts +91 -0
- package/dist/server/Scheduler.d.ts.map +1 -0
- package/dist/server/Scheduler.js +201 -0
- package/dist/server/Scheduler.js.map +1 -0
- package/dist/transports/http.d.ts +66 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +519 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/types/entities.d.ts +101 -0
- package/dist/types/entities.d.ts.map +1 -0
- package/dist/types/entities.js +5 -0
- package/dist/types/entities.js.map +1 -0
- package/dist/types/filtering.d.ts +34 -0
- package/dist/types/filtering.d.ts.map +1 -0
- package/dist/types/filtering.js +5 -0
- package/dist/types/filtering.js.map +1 -0
- package/dist/types/github.d.ts +166 -0
- package/dist/types/github.d.ts.map +1 -0
- package/dist/types/github.js +5 -0
- package/dist/types/github.js.map +1 -0
- package/dist/types/index.d.ts +35 -292
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-helpers.d.ts +37 -0
- package/dist/utils/error-helpers.d.ts.map +1 -0
- package/dist/utils/error-helpers.js +47 -0
- package/dist/utils/error-helpers.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +6 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +0 -21
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +0 -47
- package/dist/utils/security-utils.js.map +1 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +9 -32
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +11 -2
- package/hooks/README.md +107 -0
- package/hooks/cursor/hooks.json +10 -0
- package/hooks/cursor/memory-journal.mdc +22 -0
- package/hooks/cursor/session-end.sh +19 -0
- package/hooks/kilo-code/session-end-mode.json +11 -0
- package/hooks/kiro/session-end.md +13 -0
- package/mcp-config-example.json +1 -0
- package/package.json +11 -9
- package/playwright.config.ts +29 -0
- package/releases/v4.5.0.md +116 -0
- package/releases/v5.0.0.md +105 -0
- package/scripts/generate-server-instructions.ts +176 -0
- package/scripts/server-instructions-function-body.ts +77 -0
- package/server.json +3 -3
- package/src/cli.ts +45 -1
- package/src/constants/ServerInstructions.ts +133 -73
- package/src/constants/icons.ts +8 -7
- package/src/constants/server-instructions.md +268 -0
- package/src/database/SqliteAdapter.ts +358 -192
- package/src/database/schema.ts +125 -0
- package/src/filtering/ToolFilter.ts +13 -2
- package/src/github/GitHubIntegration.ts +1 -3
- package/src/handlers/prompts/github.ts +209 -0
- package/src/handlers/prompts/index.ts +10 -499
- package/src/handlers/prompts/workflow.ts +314 -0
- package/src/handlers/resources/core.ts +528 -0
- package/src/handlers/resources/github.ts +358 -0
- package/src/handlers/resources/graph.ts +254 -0
- package/src/handlers/resources/index.ts +23 -1570
- package/src/handlers/resources/shared.ts +103 -0
- package/src/handlers/resources/team.ts +133 -0
- package/src/handlers/resources/templates.ts +374 -0
- package/src/handlers/tools/admin.ts +285 -0
- package/src/handlers/tools/analytics.ts +301 -0
- package/src/handlers/tools/backup.ts +242 -0
- package/src/handlers/tools/core.ts +350 -0
- package/src/handlers/tools/export.ts +115 -0
- package/src/handlers/tools/github/helpers.ts +86 -0
- package/src/handlers/tools/github/insights-tools.ts +119 -0
- package/src/handlers/tools/github/issue-tools.ts +439 -0
- package/src/handlers/tools/github/kanban-tools.ts +134 -0
- package/src/handlers/tools/github/milestone-tools.ts +392 -0
- package/src/handlers/tools/github/mutation-tools.ts +17 -0
- package/src/handlers/tools/github/read-tools.ts +328 -0
- package/src/handlers/tools/github/schemas.ts +369 -0
- package/src/handlers/tools/github.ts +36 -0
- package/src/handlers/tools/index.ts +144 -3325
- package/src/handlers/tools/relationships.ts +358 -0
- package/src/handlers/tools/schemas.ts +132 -0
- package/src/handlers/tools/search.ts +343 -0
- package/src/handlers/tools/team.ts +273 -0
- package/src/server/McpServer.ts +63 -358
- package/src/server/Scheduler.ts +278 -0
- package/src/transports/http.ts +635 -0
- package/src/types/entities.ts +145 -0
- package/src/types/filtering.ts +54 -0
- package/src/types/github.ts +180 -0
- package/src/types/index.ts +67 -375
- package/src/utils/error-helpers.ts +52 -0
- package/src/utils/logger.ts +6 -3
- package/src/utils/security-utils.ts +0 -52
- package/src/vector/VectorSearchManager.ts +9 -33
- package/tests/constants/icons.test.ts +1 -2
- package/tests/constants/server-instructions.test.ts +30 -4
- package/tests/database/sqlite-adapter.test.ts +91 -7
- package/tests/e2e/auth.spec.ts +154 -0
- package/tests/e2e/health.spec.ts +63 -0
- package/tests/e2e/protocols.spec.ts +134 -0
- package/tests/e2e/resources.spec.ts +103 -0
- package/tests/e2e/scheduler.spec.ts +79 -0
- package/tests/e2e/security.spec.ts +91 -0
- package/tests/e2e/sessions.spec.ts +95 -0
- package/tests/e2e/stateless.spec.ts +121 -0
- package/tests/e2e/tools.spec.ts +111 -0
- package/tests/filtering/tool-filter.test.ts +46 -0
- package/tests/handlers/error-path-coverage.test.ts +324 -0
- package/tests/handlers/github-resource-handlers.test.ts +453 -0
- package/tests/handlers/github-tool-handlers.test.ts +899 -0
- package/tests/handlers/prompt-handler-coverage.test.ts +106 -0
- package/tests/handlers/prompt-handlers.test.ts +40 -0
- package/tests/handlers/resource-handler-coverage.test.ts +181 -0
- package/tests/handlers/resource-handlers.test.ts +33 -9
- package/tests/handlers/search-tool-handlers.test.ts +272 -0
- package/tests/handlers/targeted-gap-closure.test.ts +387 -0
- package/tests/handlers/team-resource-handlers.test.ts +156 -0
- package/tests/handlers/team-tool-handlers.test.ts +301 -0
- package/tests/handlers/tool-handler-coverage.test.ts +469 -0
- package/tests/handlers/tool-handlers.test.ts +2 -2
- package/tests/security/sql-injection.test.ts +3 -54
- package/tests/server/mcp-server.test.ts +503 -8
- package/tests/server/scheduler.test.ts +400 -0
- package/tests/transports/http-transport.test.ts +620 -0
- package/tests/vector/vector-search-manager.test.ts +60 -0
- package/vitest.config.ts +4 -1
- package/.memory-journal-team.db +0 -0
- package/.vscode/settings.json +0 -84
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin Tool Group - 5 tools
|
|
3
|
+
*
|
|
4
|
+
* Tools: update_entry, delete_entry, merge_tags, rebuild_vector_index, add_to_vector_index
|
|
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 { ENTRY_TYPES, EntryOutputSchema } from './schemas.js'
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Input Schemas
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/** Strict schema — used inside handler for structured Zod errors */
|
|
17
|
+
const UpdateEntrySchema = z.object({
|
|
18
|
+
entry_id: z.number(),
|
|
19
|
+
content: z.string().optional(),
|
|
20
|
+
entry_type: z.enum(ENTRY_TYPES).optional(),
|
|
21
|
+
is_personal: z.boolean().optional(),
|
|
22
|
+
tags: z.array(z.string()).optional(),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
/** Relaxed schema — passed to SDK inputSchema so Zod enum errors reach the handler */
|
|
26
|
+
const UpdateEntrySchemaMcp = z.object({
|
|
27
|
+
entry_id: z.number(),
|
|
28
|
+
content: z.string().optional(),
|
|
29
|
+
entry_type: z.string().optional(),
|
|
30
|
+
is_personal: z.boolean().optional(),
|
|
31
|
+
tags: z.array(z.string()).optional(),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const DeleteEntrySchema = z.object({
|
|
35
|
+
entry_id: z.number(),
|
|
36
|
+
permanent: z.boolean().optional().default(false),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Output Schemas
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
const UpdateEntryOutputSchema = z.object({
|
|
44
|
+
success: z.boolean().optional(),
|
|
45
|
+
entry: EntryOutputSchema.optional(),
|
|
46
|
+
error: z.string().optional(),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const DeleteEntryOutputSchema = z.object({
|
|
50
|
+
success: z.boolean().optional(),
|
|
51
|
+
entryId: z.number().optional(),
|
|
52
|
+
permanent: z.boolean().optional(),
|
|
53
|
+
error: z.string().optional(),
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const MergeTagsOutputSchema = z.object({
|
|
57
|
+
success: z.boolean().optional(),
|
|
58
|
+
sourceTag: z.string().optional(),
|
|
59
|
+
targetTag: z.string().optional(),
|
|
60
|
+
entriesUpdated: z.number().optional(),
|
|
61
|
+
sourceDeleted: z.boolean().optional(),
|
|
62
|
+
message: z.string().optional(),
|
|
63
|
+
error: z.string().optional(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const RebuildVectorIndexOutputSchema = z.object({
|
|
67
|
+
success: z.boolean().optional(),
|
|
68
|
+
entriesIndexed: z.number().optional(),
|
|
69
|
+
error: z.string().optional(),
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const AddToVectorIndexOutputSchema = z.object({
|
|
73
|
+
success: z.boolean().optional(),
|
|
74
|
+
entryId: z.number().optional(),
|
|
75
|
+
error: z.string().optional(),
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Tool Definitions
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
export function getAdminTools(context: ToolContext): ToolDefinition[] {
|
|
83
|
+
const { db, vectorManager, progress } = context
|
|
84
|
+
return [
|
|
85
|
+
{
|
|
86
|
+
name: 'update_entry',
|
|
87
|
+
title: 'Update Entry',
|
|
88
|
+
description: 'Update an existing journal entry',
|
|
89
|
+
group: 'admin',
|
|
90
|
+
inputSchema: UpdateEntrySchemaMcp,
|
|
91
|
+
outputSchema: UpdateEntryOutputSchema,
|
|
92
|
+
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
93
|
+
handler: (params: unknown) => {
|
|
94
|
+
try {
|
|
95
|
+
const input = UpdateEntrySchema.parse(params)
|
|
96
|
+
const entry = db.updateEntry(input.entry_id, {
|
|
97
|
+
content: input.content,
|
|
98
|
+
entryType: input.entry_type,
|
|
99
|
+
isPersonal: input.is_personal,
|
|
100
|
+
tags: input.tags,
|
|
101
|
+
})
|
|
102
|
+
if (!entry) {
|
|
103
|
+
return {
|
|
104
|
+
success: false,
|
|
105
|
+
error: `Entry ${String(input.entry_id)} not found`,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Re-index if content changed
|
|
110
|
+
if (input.content && vectorManager) {
|
|
111
|
+
vectorManager.addEntry(entry.id, entry.content).catch(() => {
|
|
112
|
+
// Non-critical failure, entry already updated in DB
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { success: true, entry }
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return formatHandlerError(err)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'delete_entry',
|
|
124
|
+
title: 'Delete Entry',
|
|
125
|
+
description: 'Delete a journal entry (soft delete with timestamp)',
|
|
126
|
+
group: 'admin',
|
|
127
|
+
inputSchema: DeleteEntrySchema,
|
|
128
|
+
outputSchema: DeleteEntryOutputSchema,
|
|
129
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
130
|
+
handler: (params: unknown) => {
|
|
131
|
+
try {
|
|
132
|
+
const { entry_id, permanent } = DeleteEntrySchema.parse(params)
|
|
133
|
+
const success = db.deleteEntry(entry_id, permanent)
|
|
134
|
+
|
|
135
|
+
if (!success) {
|
|
136
|
+
return {
|
|
137
|
+
success: false,
|
|
138
|
+
entryId: entry_id,
|
|
139
|
+
permanent,
|
|
140
|
+
error: `Entry ${String(entry_id)} not found`,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Remove from vector index (non-critical if fails)
|
|
145
|
+
if (vectorManager) {
|
|
146
|
+
vectorManager.removeEntry(entry_id).catch(() => {
|
|
147
|
+
// Non-critical failure, entry already deleted from DB
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { success, entryId: entry_id, permanent }
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return formatHandlerError(err)
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'merge_tags',
|
|
159
|
+
title: 'Merge Tags',
|
|
160
|
+
description:
|
|
161
|
+
'Merge one tag into another to consolidate similar tags (e.g., merge "phase-2" into "phase2"). The source tag is deleted after merge.',
|
|
162
|
+
group: 'admin',
|
|
163
|
+
inputSchema: z.object({
|
|
164
|
+
source_tag: z.string().min(1).describe('Tag to merge from (will be deleted)'),
|
|
165
|
+
target_tag: z
|
|
166
|
+
.string()
|
|
167
|
+
.min(1)
|
|
168
|
+
.describe('Tag to merge into (will be created if not exists)'),
|
|
169
|
+
}),
|
|
170
|
+
outputSchema: MergeTagsOutputSchema,
|
|
171
|
+
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
172
|
+
handler: (params: unknown) => {
|
|
173
|
+
try {
|
|
174
|
+
const { source_tag, target_tag } = z
|
|
175
|
+
.object({
|
|
176
|
+
source_tag: z.string().min(1),
|
|
177
|
+
target_tag: z.string().min(1),
|
|
178
|
+
})
|
|
179
|
+
.parse(params)
|
|
180
|
+
|
|
181
|
+
if (source_tag === target_tag) {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
sourceTag: source_tag,
|
|
185
|
+
targetTag: target_tag,
|
|
186
|
+
entriesUpdated: 0,
|
|
187
|
+
sourceDeleted: false,
|
|
188
|
+
message: 'Source and target tags cannot be the same',
|
|
189
|
+
error: 'Source and target tags must be different',
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const result = db.mergeTags(source_tag, target_tag)
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
sourceTag: source_tag,
|
|
197
|
+
targetTag: target_tag,
|
|
198
|
+
entriesUpdated: result.entriesUpdated,
|
|
199
|
+
sourceDeleted: result.sourceDeleted,
|
|
200
|
+
message: `Merged "${source_tag}" into "${target_tag}". Updated ${String(result.entriesUpdated)} entries.`,
|
|
201
|
+
}
|
|
202
|
+
} catch (error) {
|
|
203
|
+
// Zod or domain error
|
|
204
|
+
if (error instanceof z.ZodError) {
|
|
205
|
+
return formatHandlerError(error)
|
|
206
|
+
}
|
|
207
|
+
// Domain error from db.mergeTags — try to preserve schema shape
|
|
208
|
+
try {
|
|
209
|
+
const parsed = z
|
|
210
|
+
.object({ source_tag: z.string(), target_tag: z.string() })
|
|
211
|
+
.parse(params)
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
sourceTag: parsed.source_tag,
|
|
215
|
+
targetTag: parsed.target_tag,
|
|
216
|
+
entriesUpdated: 0,
|
|
217
|
+
sourceDeleted: false,
|
|
218
|
+
message: 'Tag merge failed',
|
|
219
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
return formatHandlerError(error)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'rebuild_vector_index',
|
|
229
|
+
title: 'Rebuild Vector Index',
|
|
230
|
+
description: 'Rebuild the semantic search vector index from all existing entries',
|
|
231
|
+
group: 'admin',
|
|
232
|
+
inputSchema: z.object({}),
|
|
233
|
+
outputSchema: RebuildVectorIndexOutputSchema,
|
|
234
|
+
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
235
|
+
handler: async (_params: unknown) => {
|
|
236
|
+
try {
|
|
237
|
+
if (!vectorManager) {
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
entriesIndexed: 0,
|
|
241
|
+
error: 'Vector search not available',
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const indexed = await vectorManager.rebuildIndex(db, progress)
|
|
245
|
+
return { success: true, entriesIndexed: indexed }
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return formatHandlerError(err)
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'add_to_vector_index',
|
|
253
|
+
title: 'Add Entry to Vector Index',
|
|
254
|
+
description: 'Add a specific entry to the semantic search vector index',
|
|
255
|
+
group: 'admin',
|
|
256
|
+
inputSchema: z.object({ entry_id: z.number() }),
|
|
257
|
+
outputSchema: AddToVectorIndexOutputSchema,
|
|
258
|
+
annotations: { readOnlyHint: false, idempotentHint: true },
|
|
259
|
+
handler: async (params: unknown) => {
|
|
260
|
+
try {
|
|
261
|
+
const { entry_id } = z.object({ entry_id: z.number() }).parse(params)
|
|
262
|
+
if (!vectorManager) {
|
|
263
|
+
return {
|
|
264
|
+
success: false,
|
|
265
|
+
entryId: entry_id,
|
|
266
|
+
error: 'Vector search not available',
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const entry = db.getEntryById(entry_id)
|
|
270
|
+
if (!entry) {
|
|
271
|
+
return {
|
|
272
|
+
success: false,
|
|
273
|
+
entryId: entry_id,
|
|
274
|
+
error: `Entry ${String(entry_id)} not found`,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const success = await vectorManager.addEntry(entry_id, entry.content)
|
|
278
|
+
return { success, entryId: entry_id }
|
|
279
|
+
} catch (err) {
|
|
280
|
+
return formatHandlerError(err)
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
]
|
|
285
|
+
}
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics Tool Group - 2 tools
|
|
3
|
+
*
|
|
4
|
+
* Tools: get_statistics, get_cross_project_insights
|
|
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 { DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE, TagOutputSchema } from './schemas.js'
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Output Schemas
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
const StatisticsOutputSchema = z.object({
|
|
17
|
+
groupBy: z.string().optional(),
|
|
18
|
+
totalEntries: z.number().optional(),
|
|
19
|
+
entriesByType: z.record(z.string(), z.number()).optional(),
|
|
20
|
+
entriesByPeriod: z
|
|
21
|
+
.array(
|
|
22
|
+
z.object({
|
|
23
|
+
period: z.string(),
|
|
24
|
+
count: z.number(),
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
.optional(),
|
|
28
|
+
decisionDensity: z
|
|
29
|
+
.array(
|
|
30
|
+
z.object({
|
|
31
|
+
period: z.string(),
|
|
32
|
+
significantCount: z.number(),
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
.optional(),
|
|
36
|
+
relationshipComplexity: z
|
|
37
|
+
.object({
|
|
38
|
+
totalRelationships: z.number(),
|
|
39
|
+
avgPerEntry: z.number(),
|
|
40
|
+
})
|
|
41
|
+
.optional(),
|
|
42
|
+
activityTrend: z
|
|
43
|
+
.object({
|
|
44
|
+
currentPeriod: z.string(),
|
|
45
|
+
previousPeriod: z.string(),
|
|
46
|
+
growthPercent: z.number().nullable(),
|
|
47
|
+
})
|
|
48
|
+
.optional(),
|
|
49
|
+
causalMetrics: z
|
|
50
|
+
.object({
|
|
51
|
+
blocked_by: z.number(),
|
|
52
|
+
resolved: z.number(),
|
|
53
|
+
caused: z.number(),
|
|
54
|
+
})
|
|
55
|
+
.optional(),
|
|
56
|
+
success: z.boolean().optional(),
|
|
57
|
+
error: z.string().optional(),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const ProjectSummaryOutputSchema = z.object({
|
|
61
|
+
project_number: z.number(),
|
|
62
|
+
entry_count: z.number(),
|
|
63
|
+
first_entry: z.string(),
|
|
64
|
+
last_entry: z.string(),
|
|
65
|
+
active_days: z.number(),
|
|
66
|
+
top_tags: z.array(TagOutputSchema),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const CrossProjectInsightsOutputSchema = z.object({
|
|
70
|
+
project_count: z.number().optional(),
|
|
71
|
+
total_entries: z.number().optional(),
|
|
72
|
+
projects: z.array(ProjectSummaryOutputSchema).optional(),
|
|
73
|
+
inactive_projects: z
|
|
74
|
+
.array(
|
|
75
|
+
z.object({
|
|
76
|
+
project_number: z.number(),
|
|
77
|
+
last_entry_date: z.string(),
|
|
78
|
+
})
|
|
79
|
+
)
|
|
80
|
+
.optional(),
|
|
81
|
+
inactiveThresholdDays: z.number().optional(),
|
|
82
|
+
time_distribution: z
|
|
83
|
+
.array(
|
|
84
|
+
z.object({
|
|
85
|
+
project_number: z.number(),
|
|
86
|
+
percentage: z.string(),
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
.optional(),
|
|
90
|
+
message: z.string().optional(),
|
|
91
|
+
success: z.boolean().optional(),
|
|
92
|
+
error: z.string().optional(),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Input Schemas
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/** Strict schema — used inside handler for structured Zod errors */
|
|
100
|
+
const GetStatisticsSchema = z.object({
|
|
101
|
+
group_by: z.enum(['day', 'week', 'month']).optional().default('week'),
|
|
102
|
+
start_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
|
|
103
|
+
end_date: z.string().regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE).optional(),
|
|
104
|
+
project_breakdown: z.boolean().optional().default(false),
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
/** Relaxed schema — passed to SDK inputSchema so Zod errors reach the handler */
|
|
108
|
+
const GetStatisticsSchemaMcp = z.object({
|
|
109
|
+
group_by: z.string().optional().default('week'),
|
|
110
|
+
start_date: z.string().optional(),
|
|
111
|
+
end_date: z.string().optional(),
|
|
112
|
+
project_breakdown: z.boolean().optional().default(false),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/** Strict schema — used inside handler for structured Zod errors */
|
|
116
|
+
const CrossProjectInsightsInputSchema = z.object({
|
|
117
|
+
start_date: z
|
|
118
|
+
.string()
|
|
119
|
+
.regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE)
|
|
120
|
+
.optional()
|
|
121
|
+
.describe('Start date (YYYY-MM-DD)'),
|
|
122
|
+
end_date: z
|
|
123
|
+
.string()
|
|
124
|
+
.regex(DATE_FORMAT_REGEX, DATE_FORMAT_MESSAGE)
|
|
125
|
+
.optional()
|
|
126
|
+
.describe('End date (YYYY-MM-DD)'),
|
|
127
|
+
min_entries: z.number().optional().default(3).describe('Minimum entries to include project'),
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
/** Relaxed schema — passed to SDK inputSchema so Zod errors reach the handler */
|
|
131
|
+
const CrossProjectInsightsInputSchemaMcp = z.object({
|
|
132
|
+
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
133
|
+
end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
134
|
+
min_entries: z.number().optional().default(3).describe('Minimum entries to include project'),
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// Tool Definitions
|
|
139
|
+
// ============================================================================
|
|
140
|
+
|
|
141
|
+
export function getAnalyticsTools(context: ToolContext): ToolDefinition[] {
|
|
142
|
+
const { db } = context
|
|
143
|
+
return [
|
|
144
|
+
{
|
|
145
|
+
name: 'get_statistics',
|
|
146
|
+
title: 'Get Statistics',
|
|
147
|
+
description:
|
|
148
|
+
'Get journal statistics and analytics (Phase 2: includes project breakdown)',
|
|
149
|
+
group: 'analytics',
|
|
150
|
+
inputSchema: GetStatisticsSchemaMcp,
|
|
151
|
+
outputSchema: StatisticsOutputSchema,
|
|
152
|
+
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
153
|
+
handler: (params: unknown) => {
|
|
154
|
+
try {
|
|
155
|
+
const { group_by } = GetStatisticsSchema.parse(params)
|
|
156
|
+
const stats = db.getStatistics(group_by)
|
|
157
|
+
return { ...stats, groupBy: group_by }
|
|
158
|
+
} catch (err) {
|
|
159
|
+
return formatHandlerError(err)
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'get_cross_project_insights',
|
|
165
|
+
title: 'Get Cross-Project Insights',
|
|
166
|
+
description: 'Analyze patterns across all GitHub Projects tracked in journal entries',
|
|
167
|
+
group: 'analytics',
|
|
168
|
+
inputSchema: CrossProjectInsightsInputSchemaMcp,
|
|
169
|
+
outputSchema: CrossProjectInsightsOutputSchema,
|
|
170
|
+
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
171
|
+
handler: (params: unknown) => {
|
|
172
|
+
try {
|
|
173
|
+
const input = CrossProjectInsightsInputSchema.parse(params)
|
|
174
|
+
|
|
175
|
+
const rawDb = db.getRawDb()
|
|
176
|
+
|
|
177
|
+
// Build WHERE clause
|
|
178
|
+
let where = 'WHERE deleted_at IS NULL AND project_number IS NOT NULL'
|
|
179
|
+
const sqlParams: unknown[] = []
|
|
180
|
+
|
|
181
|
+
if (input.start_date) {
|
|
182
|
+
where += ' AND DATE(timestamp) >= DATE(?)'
|
|
183
|
+
sqlParams.push(input.start_date)
|
|
184
|
+
}
|
|
185
|
+
if (input.end_date) {
|
|
186
|
+
where += ' AND DATE(timestamp) <= DATE(?)'
|
|
187
|
+
sqlParams.push(input.end_date)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Get active projects with stats
|
|
191
|
+
const projectsResult = rawDb.exec(
|
|
192
|
+
`
|
|
193
|
+
SELECT project_number, COUNT(*) as entry_count,
|
|
194
|
+
MIN(DATE(timestamp)) as first_entry,
|
|
195
|
+
MAX(DATE(timestamp)) as last_entry,
|
|
196
|
+
COUNT(DISTINCT DATE(timestamp)) as active_days
|
|
197
|
+
FROM memory_journal ${where}
|
|
198
|
+
GROUP BY project_number
|
|
199
|
+
HAVING entry_count >= ?
|
|
200
|
+
ORDER BY entry_count DESC
|
|
201
|
+
`,
|
|
202
|
+
[...sqlParams, input.min_entries]
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if (!projectsResult[0] || projectsResult[0].values.length === 0) {
|
|
206
|
+
return {
|
|
207
|
+
project_count: 0,
|
|
208
|
+
total_entries: 0,
|
|
209
|
+
projects: [],
|
|
210
|
+
inactive_projects: [],
|
|
211
|
+
inactiveThresholdDays: 7,
|
|
212
|
+
time_distribution: [],
|
|
213
|
+
message: `No projects found with at least ${String(input.min_entries)} entries`,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const columns = projectsResult[0].columns
|
|
218
|
+
const projects = projectsResult[0].values.map((row) => {
|
|
219
|
+
const obj: Record<string, unknown> = {}
|
|
220
|
+
columns.forEach((col, i) => {
|
|
221
|
+
obj[col] = row[i]
|
|
222
|
+
})
|
|
223
|
+
return obj
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// Get top tags per project
|
|
227
|
+
const projectTags: Record<number, { name: string; count: number }[]> = {}
|
|
228
|
+
for (const proj of projects) {
|
|
229
|
+
const projNum = proj['project_number'] as number
|
|
230
|
+
const tagsResult = rawDb.exec(
|
|
231
|
+
`
|
|
232
|
+
SELECT t.name, COUNT(*) as count
|
|
233
|
+
FROM tags t
|
|
234
|
+
JOIN entry_tags et ON t.id = et.tag_id
|
|
235
|
+
JOIN memory_journal m ON et.entry_id = m.id
|
|
236
|
+
WHERE m.project_number = ? AND m.deleted_at IS NULL
|
|
237
|
+
GROUP BY t.name
|
|
238
|
+
ORDER BY count DESC
|
|
239
|
+
LIMIT 5
|
|
240
|
+
`,
|
|
241
|
+
[projNum]
|
|
242
|
+
)
|
|
243
|
+
if (tagsResult[0]) {
|
|
244
|
+
projectTags[projNum] = tagsResult[0].values.map((row) => ({
|
|
245
|
+
name: row[0] as string,
|
|
246
|
+
count: row[1] as number,
|
|
247
|
+
}))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Find inactive projects (last entry > 7 days ago)
|
|
252
|
+
const cutoffDate = new Date(Date.now() - 7 * 86400000)
|
|
253
|
+
.toISOString()
|
|
254
|
+
.split('T')[0]
|
|
255
|
+
const inactiveResult = rawDb.exec(
|
|
256
|
+
`
|
|
257
|
+
SELECT project_number, MAX(DATE(timestamp)) as last_entry_date
|
|
258
|
+
FROM memory_journal
|
|
259
|
+
WHERE deleted_at IS NULL AND project_number IS NOT NULL
|
|
260
|
+
GROUP BY project_number
|
|
261
|
+
HAVING last_entry_date < ?
|
|
262
|
+
`,
|
|
263
|
+
[cutoffDate]
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
const inactiveProjects =
|
|
267
|
+
inactiveResult[0]?.values.map((row) => ({
|
|
268
|
+
project_number: row[0] as number,
|
|
269
|
+
last_entry_date: row[1] as string,
|
|
270
|
+
})) ?? []
|
|
271
|
+
|
|
272
|
+
// Calculate time distribution
|
|
273
|
+
const totalEntries = projects.reduce(
|
|
274
|
+
(sum, p) => sum + (p['entry_count'] as number),
|
|
275
|
+
0
|
|
276
|
+
)
|
|
277
|
+
const distribution = projects.slice(0, 5).map((p) => ({
|
|
278
|
+
project_number: p['project_number'],
|
|
279
|
+
percentage: (((p['entry_count'] as number) / totalEntries) * 100).toFixed(
|
|
280
|
+
1
|
|
281
|
+
),
|
|
282
|
+
}))
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
project_count: projects.length,
|
|
286
|
+
total_entries: totalEntries,
|
|
287
|
+
projects: projects.map((p) => ({
|
|
288
|
+
...p,
|
|
289
|
+
top_tags: projectTags[p['project_number'] as number] ?? [],
|
|
290
|
+
})),
|
|
291
|
+
inactive_projects: inactiveProjects,
|
|
292
|
+
inactiveThresholdDays: 7,
|
|
293
|
+
time_distribution: distribution,
|
|
294
|
+
}
|
|
295
|
+
} catch (err) {
|
|
296
|
+
return formatHandlerError(err)
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
]
|
|
301
|
+
}
|