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
|
@@ -1,2934 +1,142 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Tool Handler Barrel
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.describe('Include hint when no results found (default: true)'),
|
|
76
|
-
});
|
|
77
|
-
const GetStatisticsSchema = z.object({
|
|
78
|
-
group_by: z.enum(['day', 'week', 'month']).optional().default('week'),
|
|
79
|
-
start_date: z.string().optional(),
|
|
80
|
-
end_date: z.string().optional(),
|
|
81
|
-
project_breakdown: z.boolean().optional().default(false),
|
|
82
|
-
});
|
|
83
|
-
const LinkEntriesSchema = z.object({
|
|
84
|
-
from_entry_id: z.number(),
|
|
85
|
-
to_entry_id: z.number(),
|
|
86
|
-
relationship_type: z
|
|
87
|
-
.enum([
|
|
88
|
-
'evolves_from',
|
|
89
|
-
'references',
|
|
90
|
-
'implements',
|
|
91
|
-
'clarifies',
|
|
92
|
-
'response_to',
|
|
93
|
-
'blocked_by',
|
|
94
|
-
'resolved',
|
|
95
|
-
'caused',
|
|
96
|
-
])
|
|
97
|
-
.optional()
|
|
98
|
-
.default('references'),
|
|
99
|
-
description: z.string().optional(),
|
|
100
|
-
});
|
|
101
|
-
const ExportEntriesSchema = z.object({
|
|
102
|
-
format: z.enum(['json', 'markdown']).optional().default('json'),
|
|
103
|
-
start_date: z.string().optional(),
|
|
104
|
-
end_date: z.string().optional(),
|
|
105
|
-
entry_types: z.array(z.string()).optional(),
|
|
106
|
-
tags: z.array(z.string()).optional(),
|
|
107
|
-
limit: z.number().optional().default(100).describe('Maximum entries to export (default: 100)'),
|
|
108
|
-
});
|
|
109
|
-
const UpdateEntrySchema = z.object({
|
|
110
|
-
entry_id: z.number(),
|
|
111
|
-
content: z.string().optional(),
|
|
112
|
-
entry_type: z.string().optional(),
|
|
113
|
-
is_personal: z.boolean().optional(),
|
|
114
|
-
tags: z.array(z.string()).optional(),
|
|
115
|
-
});
|
|
116
|
-
const DeleteEntrySchema = z.object({
|
|
117
|
-
entry_id: z.number(),
|
|
118
|
-
permanent: z.boolean().optional().default(false),
|
|
119
|
-
});
|
|
120
|
-
// ============================================================================
|
|
121
|
-
// Zod Schemas for Output Validation (MCP 2025-11-25 outputSchema)
|
|
122
|
-
// ============================================================================
|
|
123
|
-
/**
|
|
124
|
-
* Schema for a journal entry in output responses.
|
|
125
|
-
* Uses camelCase to match actual database output format.
|
|
126
|
-
*/
|
|
127
|
-
const EntryOutputSchema = z.object({
|
|
128
|
-
id: z.number(),
|
|
129
|
-
content: z.string(),
|
|
130
|
-
entryType: z.string(),
|
|
131
|
-
isPersonal: z.boolean(),
|
|
132
|
-
timestamp: z.string(),
|
|
133
|
-
tags: z.array(z.string()).optional(),
|
|
134
|
-
significanceType: z.string().nullable().optional(),
|
|
135
|
-
autoContext: z.string().nullable().optional(),
|
|
136
|
-
deletedAt: z.string().nullable().optional(),
|
|
137
|
-
projectNumber: z.number().nullable().optional(),
|
|
138
|
-
projectOwner: z.string().nullable().optional(),
|
|
139
|
-
issueNumber: z.number().nullable().optional(),
|
|
140
|
-
issueUrl: z.string().nullable().optional(),
|
|
141
|
-
prNumber: z.number().nullable().optional(),
|
|
142
|
-
prUrl: z.string().nullable().optional(),
|
|
143
|
-
prStatus: z.string().nullable().optional(),
|
|
144
|
-
workflowRunId: z.number().nullable().optional(),
|
|
145
|
-
workflowName: z.string().nullable().optional(),
|
|
146
|
-
workflowStatus: z.string().nullable().optional(),
|
|
147
|
-
});
|
|
148
|
-
/**
|
|
149
|
-
* Schema for list of entries with count.
|
|
150
|
-
* Used by get_recent_entries, search_entries, search_by_date_range.
|
|
151
|
-
*/
|
|
152
|
-
const EntriesListOutputSchema = z.object({
|
|
153
|
-
entries: z.array(EntryOutputSchema),
|
|
154
|
-
count: z.number(),
|
|
155
|
-
});
|
|
156
|
-
/**
|
|
157
|
-
* Schema for a relationship between entries.
|
|
158
|
-
*/
|
|
159
|
-
const RelationshipOutputSchema = z.object({
|
|
160
|
-
id: z.number(),
|
|
161
|
-
fromEntryId: z.number(),
|
|
162
|
-
toEntryId: z.number(),
|
|
163
|
-
relationshipType: z.string(),
|
|
164
|
-
description: z.string().nullable().optional(),
|
|
165
|
-
createdAt: z.string(),
|
|
166
|
-
});
|
|
167
|
-
/**
|
|
168
|
-
* Schema for get_entry_by_id output (entry with optional relationships).
|
|
169
|
-
* Handles both success (entry found) and error (entry not found) cases.
|
|
170
|
-
*/
|
|
171
|
-
const ImportanceBreakdownSchema = z.object({
|
|
172
|
-
significance: z.number(),
|
|
173
|
-
relationships: z.number(),
|
|
174
|
-
causal: z.number(),
|
|
175
|
-
recency: z.number(),
|
|
176
|
-
});
|
|
177
|
-
const EntryByIdOutputSchema = z.object({
|
|
178
|
-
entry: EntryOutputSchema.optional(),
|
|
179
|
-
relationships: z.array(RelationshipOutputSchema).optional(),
|
|
180
|
-
importance: z.number().nullable().optional(), // Computed importance score (0.0-1.0)
|
|
181
|
-
importanceBreakdown: ImportanceBreakdownSchema.optional(), // Weighted component contributions
|
|
182
|
-
error: z.string().optional(),
|
|
183
|
-
});
|
|
184
|
-
/**
|
|
185
|
-
* Schema for get_statistics output.
|
|
186
|
-
* Matches SqliteAdapter.getStatistics() return type.
|
|
187
|
-
*/
|
|
188
|
-
const StatisticsOutputSchema = z.object({
|
|
189
|
-
groupBy: z.string(),
|
|
190
|
-
totalEntries: z.number(),
|
|
191
|
-
entriesByType: z.record(z.string(), z.number()),
|
|
192
|
-
entriesByPeriod: z.array(z.object({
|
|
193
|
-
period: z.string(),
|
|
194
|
-
count: z.number(),
|
|
195
|
-
})),
|
|
196
|
-
// Enhanced analytics (v4.3.0)
|
|
197
|
-
decisionDensity: z.array(z.object({
|
|
198
|
-
period: z.string(),
|
|
199
|
-
significantCount: z.number(),
|
|
200
|
-
})),
|
|
201
|
-
relationshipComplexity: z.object({
|
|
202
|
-
totalRelationships: z.number(),
|
|
203
|
-
avgPerEntry: z.number(),
|
|
204
|
-
}),
|
|
205
|
-
activityTrend: z.object({
|
|
206
|
-
currentPeriod: z.string(),
|
|
207
|
-
previousPeriod: z.string(),
|
|
208
|
-
growthPercent: z.number().nullable(),
|
|
209
|
-
}),
|
|
210
|
-
causalMetrics: z.object({
|
|
211
|
-
blocked_by: z.number(),
|
|
212
|
-
resolved: z.number(),
|
|
213
|
-
caused: z.number(),
|
|
214
|
-
}),
|
|
215
|
-
});
|
|
216
|
-
// ============================================================================
|
|
217
|
-
// Phase 1: Core Read Tool Output Schemas
|
|
218
|
-
// ============================================================================
|
|
219
|
-
/**
|
|
220
|
-
* Entry with similarity score for semantic search results.
|
|
221
|
-
*/
|
|
222
|
-
const SemanticEntryOutputSchema = EntryOutputSchema.extend({
|
|
223
|
-
similarity: z.number(),
|
|
224
|
-
});
|
|
225
|
-
/**
|
|
226
|
-
* Schema for semantic_search output.
|
|
227
|
-
*/
|
|
228
|
-
const SemanticSearchOutputSchema = z.object({
|
|
229
|
-
query: z.string(),
|
|
230
|
-
entries: z.array(SemanticEntryOutputSchema),
|
|
231
|
-
count: z.number(),
|
|
232
|
-
hint: z.string().optional(),
|
|
233
|
-
error: z.string().optional(),
|
|
234
|
-
});
|
|
235
|
-
/**
|
|
236
|
-
* Tag with usage count.
|
|
237
|
-
*/
|
|
238
|
-
const TagOutputSchema = z.object({
|
|
239
|
-
name: z.string(),
|
|
240
|
-
count: z.number(),
|
|
241
|
-
});
|
|
242
|
-
/**
|
|
243
|
-
* Schema for list_tags output.
|
|
244
|
-
*/
|
|
245
|
-
const TagsListOutputSchema = z.object({
|
|
246
|
-
tags: z.array(TagOutputSchema),
|
|
247
|
-
count: z.number(),
|
|
248
|
-
});
|
|
249
|
-
/**
|
|
250
|
-
* Schema for merge_tags output.
|
|
251
|
-
*/
|
|
252
|
-
const MergeTagsOutputSchema = z.object({
|
|
253
|
-
success: z.boolean(),
|
|
254
|
-
sourceTag: z.string(),
|
|
255
|
-
targetTag: z.string(),
|
|
256
|
-
entriesUpdated: z.number(),
|
|
257
|
-
sourceDeleted: z.boolean(),
|
|
258
|
-
message: z.string(),
|
|
259
|
-
error: z.string().optional(),
|
|
260
|
-
});
|
|
261
|
-
/**
|
|
262
|
-
* Schema for get_vector_index_stats output.
|
|
263
|
-
*/
|
|
264
|
-
const VectorStatsOutputSchema = z.object({
|
|
265
|
-
available: z.boolean(),
|
|
266
|
-
error: z.string().optional(),
|
|
267
|
-
entryCount: z.number().optional(),
|
|
268
|
-
indexSize: z.number().optional(),
|
|
269
|
-
});
|
|
270
|
-
/**
|
|
271
|
-
* Schema for visualize_relationships output.
|
|
272
|
-
*/
|
|
273
|
-
const VisualizationOutputSchema = z.object({
|
|
274
|
-
entry_count: z.number(),
|
|
275
|
-
relationship_count: z.number(),
|
|
276
|
-
root_entry: z.number().nullable(),
|
|
277
|
-
depth: z.number(),
|
|
278
|
-
mermaid: z.string().nullable(),
|
|
279
|
-
message: z.string().optional(),
|
|
280
|
-
legend: z
|
|
281
|
-
.object({
|
|
282
|
-
blue: z.string(),
|
|
283
|
-
orange: z.string(),
|
|
284
|
-
arrows: z.record(z.string(), z.string()),
|
|
285
|
-
})
|
|
286
|
-
.optional(),
|
|
287
|
-
});
|
|
288
|
-
/**
|
|
289
|
-
* Project summary for cross-project insights.
|
|
290
|
-
*/
|
|
291
|
-
const ProjectSummaryOutputSchema = z.object({
|
|
292
|
-
project_number: z.number(),
|
|
293
|
-
entry_count: z.number(),
|
|
294
|
-
first_entry: z.string(),
|
|
295
|
-
last_entry: z.string(),
|
|
296
|
-
active_days: z.number(),
|
|
297
|
-
top_tags: z.array(TagOutputSchema),
|
|
298
|
-
});
|
|
299
|
-
/**
|
|
300
|
-
* Schema for get_cross_project_insights output.
|
|
301
|
-
*/
|
|
302
|
-
const CrossProjectInsightsOutputSchema = z.object({
|
|
303
|
-
project_count: z.number(),
|
|
304
|
-
total_entries: z.number(),
|
|
305
|
-
projects: z.array(ProjectSummaryOutputSchema),
|
|
306
|
-
inactive_projects: z.array(z.object({
|
|
307
|
-
project_number: z.number(),
|
|
308
|
-
last_entry_date: z.string(),
|
|
309
|
-
})),
|
|
310
|
-
inactiveThresholdDays: z.number(), // Cutoff for inactive classification
|
|
311
|
-
time_distribution: z.array(z.object({
|
|
312
|
-
project_number: z.number(),
|
|
313
|
-
percentage: z.string(),
|
|
314
|
-
})),
|
|
315
|
-
message: z.string().optional(),
|
|
316
|
-
});
|
|
317
|
-
// ============================================================================
|
|
318
|
-
// Phase 2: Mutation Tool Output Schemas
|
|
319
|
-
// ============================================================================
|
|
320
|
-
/**
|
|
321
|
-
* Schema for create_entry and create_entry_minimal output.
|
|
322
|
-
*/
|
|
323
|
-
const CreateEntryOutputSchema = z.object({
|
|
324
|
-
success: z.boolean(),
|
|
325
|
-
entry: EntryOutputSchema,
|
|
326
|
-
});
|
|
327
|
-
/**
|
|
328
|
-
* Schema for update_entry output (success or error).
|
|
329
|
-
*/
|
|
330
|
-
const UpdateEntryOutputSchema = z.object({
|
|
331
|
-
success: z.boolean().optional(),
|
|
332
|
-
entry: EntryOutputSchema.optional(),
|
|
333
|
-
error: z.string().optional(),
|
|
334
|
-
});
|
|
335
|
-
/**
|
|
336
|
-
* Schema for delete_entry output.
|
|
337
|
-
*/
|
|
338
|
-
const DeleteEntryOutputSchema = z.object({
|
|
339
|
-
success: z.boolean(),
|
|
340
|
-
entryId: z.number(),
|
|
341
|
-
permanent: z.boolean(),
|
|
342
|
-
error: z.string().optional(),
|
|
343
|
-
});
|
|
344
|
-
/**
|
|
345
|
-
* Schema for link_entries output.
|
|
346
|
-
*/
|
|
347
|
-
const LinkEntriesOutputSchema = z.object({
|
|
348
|
-
success: z.boolean(),
|
|
349
|
-
relationship: RelationshipOutputSchema,
|
|
350
|
-
duplicate: z.boolean().optional().describe('True if relationship already existed'),
|
|
351
|
-
message: z.string().optional().describe('Additional context about the operation'),
|
|
352
|
-
});
|
|
353
|
-
// ============================================================================
|
|
354
|
-
// Phase 3: GitHub Tool Output Schemas
|
|
355
|
-
// ============================================================================
|
|
356
|
-
/**
|
|
357
|
-
* GitHub issue schema (mirrors GitHub API shape).
|
|
358
|
-
*/
|
|
359
|
-
const GitHubIssueOutputSchema = z.object({
|
|
360
|
-
number: z.number(),
|
|
361
|
-
title: z.string(),
|
|
362
|
-
url: z.string(),
|
|
363
|
-
state: z.enum(['OPEN', 'CLOSED']),
|
|
364
|
-
milestone: z
|
|
365
|
-
.object({
|
|
366
|
-
number: z.number(),
|
|
367
|
-
title: z.string(),
|
|
368
|
-
})
|
|
369
|
-
.nullable()
|
|
370
|
-
.optional(),
|
|
371
|
-
});
|
|
372
|
-
/**
|
|
373
|
-
* GitHub issue details schema (extended).
|
|
374
|
-
*/
|
|
375
|
-
const GitHubIssueDetailsOutputSchema = GitHubIssueOutputSchema.extend({
|
|
376
|
-
body: z.string().nullable(),
|
|
377
|
-
labels: z.array(z.string()),
|
|
378
|
-
assignees: z.array(z.string()),
|
|
379
|
-
createdAt: z.string(),
|
|
380
|
-
updatedAt: z.string(),
|
|
381
|
-
closedAt: z.string().nullable(),
|
|
382
|
-
commentsCount: z.number(),
|
|
383
|
-
});
|
|
384
|
-
/**
|
|
385
|
-
* Schema for get_github_issues output.
|
|
386
|
-
*/
|
|
387
|
-
const GitHubIssuesListOutputSchema = z.object({
|
|
388
|
-
owner: z.string(),
|
|
389
|
-
repo: z.string(),
|
|
390
|
-
detectedOwner: z.string().nullable().optional(),
|
|
391
|
-
detectedRepo: z.string().nullable().optional(),
|
|
392
|
-
issues: z.array(GitHubIssueOutputSchema),
|
|
393
|
-
count: z.number(),
|
|
394
|
-
error: z.string().optional(),
|
|
395
|
-
requiresUserInput: z.boolean().optional(),
|
|
396
|
-
instruction: z.string().optional(),
|
|
397
|
-
});
|
|
398
|
-
/**
|
|
399
|
-
* Schema for get_github_issue output.
|
|
400
|
-
*/
|
|
401
|
-
const GitHubIssueResultOutputSchema = z.object({
|
|
402
|
-
issue: GitHubIssueDetailsOutputSchema.optional(),
|
|
403
|
-
owner: z.string().optional(),
|
|
404
|
-
repo: z.string().optional(),
|
|
405
|
-
detectedOwner: z.string().nullable().optional(),
|
|
406
|
-
detectedRepo: z.string().nullable().optional(),
|
|
407
|
-
error: z.string().optional(),
|
|
408
|
-
requiresUserInput: z.boolean().optional(),
|
|
409
|
-
instruction: z.string().optional(),
|
|
410
|
-
});
|
|
411
|
-
/**
|
|
412
|
-
* GitHub pull request schema (mirrors GitHub API shape).
|
|
413
|
-
*/
|
|
414
|
-
const GitHubPullRequestOutputSchema = z.object({
|
|
415
|
-
number: z.number(),
|
|
416
|
-
title: z.string(),
|
|
417
|
-
url: z.string(),
|
|
418
|
-
state: z.enum(['OPEN', 'CLOSED', 'MERGED']),
|
|
419
|
-
});
|
|
420
|
-
/**
|
|
421
|
-
* GitHub PR details schema (extended).
|
|
422
|
-
*/
|
|
423
|
-
const GitHubPRDetailsOutputSchema = GitHubPullRequestOutputSchema.extend({
|
|
424
|
-
body: z.string().nullable(),
|
|
425
|
-
draft: z.boolean(),
|
|
426
|
-
headBranch: z.string(),
|
|
427
|
-
baseBranch: z.string(),
|
|
428
|
-
author: z.string(),
|
|
429
|
-
createdAt: z.string(),
|
|
430
|
-
updatedAt: z.string(),
|
|
431
|
-
mergedAt: z.string().nullable(),
|
|
432
|
-
closedAt: z.string().nullable(),
|
|
433
|
-
additions: z.number(),
|
|
434
|
-
deletions: z.number(),
|
|
435
|
-
changedFiles: z.number(),
|
|
436
|
-
});
|
|
437
|
-
/**
|
|
438
|
-
* Schema for get_github_prs output.
|
|
439
|
-
*/
|
|
440
|
-
const GitHubPRsListOutputSchema = z.object({
|
|
441
|
-
owner: z.string(),
|
|
442
|
-
repo: z.string(),
|
|
443
|
-
detectedOwner: z.string().nullable().optional(),
|
|
444
|
-
detectedRepo: z.string().nullable().optional(),
|
|
445
|
-
pullRequests: z.array(GitHubPullRequestOutputSchema),
|
|
446
|
-
count: z.number(),
|
|
447
|
-
error: z.string().optional(),
|
|
448
|
-
requiresUserInput: z.boolean().optional(),
|
|
449
|
-
instruction: z.string().optional(),
|
|
450
|
-
});
|
|
451
|
-
/**
|
|
452
|
-
* Schema for get_github_pr output.
|
|
453
|
-
*/
|
|
454
|
-
const GitHubPRResultOutputSchema = z.object({
|
|
455
|
-
pullRequest: GitHubPRDetailsOutputSchema.optional(),
|
|
456
|
-
owner: z.string().optional(),
|
|
457
|
-
repo: z.string().optional(),
|
|
458
|
-
detectedOwner: z.string().nullable().optional(),
|
|
459
|
-
detectedRepo: z.string().nullable().optional(),
|
|
460
|
-
error: z.string().optional(),
|
|
461
|
-
requiresUserInput: z.boolean().optional(),
|
|
462
|
-
instruction: z.string().optional(),
|
|
463
|
-
});
|
|
464
|
-
/**
|
|
465
|
-
* Schema for get_github_context output.
|
|
466
|
-
*/
|
|
467
|
-
const GitHubContextOutputSchema = z.object({
|
|
468
|
-
repoName: z.string().nullable(),
|
|
469
|
-
branch: z.string().nullable(),
|
|
470
|
-
commit: z.string().nullable(),
|
|
471
|
-
remoteUrl: z.string().nullable(),
|
|
472
|
-
issues: z.array(GitHubIssueOutputSchema),
|
|
473
|
-
pullRequests: z.array(GitHubPullRequestOutputSchema),
|
|
474
|
-
issueCount: z.number(),
|
|
475
|
-
prCount: z.number(),
|
|
476
|
-
error: z.string().optional(),
|
|
477
|
-
});
|
|
478
|
-
/**
|
|
479
|
-
* Kanban item schema.
|
|
480
|
-
*/
|
|
481
|
-
const KanbanItemOutputSchema = z.object({
|
|
482
|
-
id: z.string(),
|
|
483
|
-
title: z.string(),
|
|
484
|
-
url: z.string(),
|
|
485
|
-
type: z.enum(['ISSUE', 'PULL_REQUEST', 'DRAFT_ISSUE']),
|
|
486
|
-
status: z.string().nullable(),
|
|
487
|
-
number: z.number().optional(),
|
|
488
|
-
labels: z.array(z.string()).optional(),
|
|
489
|
-
assignees: z.array(z.string()).optional(),
|
|
490
|
-
createdAt: z.string(),
|
|
491
|
-
updatedAt: z.string(),
|
|
492
|
-
});
|
|
493
|
-
/**
|
|
494
|
-
* Status option schema.
|
|
495
|
-
*/
|
|
496
|
-
const StatusOptionOutputSchema = z.object({
|
|
497
|
-
id: z.string(),
|
|
498
|
-
name: z.string(),
|
|
499
|
-
color: z.string().optional(),
|
|
500
|
-
});
|
|
501
|
-
/**
|
|
502
|
-
* Kanban column schema.
|
|
503
|
-
*/
|
|
504
|
-
const KanbanColumnOutputSchema = z.object({
|
|
505
|
-
status: z.string(),
|
|
506
|
-
statusOptionId: z.string(),
|
|
507
|
-
items: z.array(KanbanItemOutputSchema),
|
|
508
|
-
});
|
|
509
|
-
/**
|
|
510
|
-
* Schema for get_kanban_board output.
|
|
511
|
-
*/
|
|
512
|
-
const KanbanBoardOutputSchema = z.object({
|
|
513
|
-
projectId: z.string(),
|
|
514
|
-
projectNumber: z.number(),
|
|
515
|
-
projectTitle: z.string(),
|
|
516
|
-
statusFieldId: z.string(),
|
|
517
|
-
statusOptions: z.array(StatusOptionOutputSchema),
|
|
518
|
-
columns: z.array(KanbanColumnOutputSchema),
|
|
519
|
-
totalItems: z.number(),
|
|
520
|
-
owner: z.string().optional(),
|
|
521
|
-
detectedOwner: z.string().nullable().optional(),
|
|
522
|
-
detectedRepo: z.string().nullable().optional(),
|
|
523
|
-
error: z.string().optional(),
|
|
524
|
-
requiresUserInput: z.boolean().optional(),
|
|
525
|
-
hint: z.string().optional(),
|
|
526
|
-
instruction: z.string().optional(),
|
|
527
|
-
});
|
|
528
|
-
// ============================================================================
|
|
529
|
-
// Phase 3b: Repository Insights Output Schema
|
|
530
|
-
// ============================================================================
|
|
531
|
-
/**
|
|
532
|
-
* Schema for get_repo_insights output.
|
|
533
|
-
* Uses optional sections to minimize token usage.
|
|
534
|
-
*/
|
|
535
|
-
const RepoInsightsOutputSchema = z.object({
|
|
536
|
-
owner: z.string().optional(),
|
|
537
|
-
repo: z.string().optional(),
|
|
538
|
-
section: z.string().optional(),
|
|
539
|
-
stars: z.number().optional(),
|
|
540
|
-
forks: z.number().optional(),
|
|
541
|
-
watchers: z.number().optional(),
|
|
542
|
-
openIssues: z.number().optional(),
|
|
543
|
-
size: z.number().optional(),
|
|
544
|
-
defaultBranch: z.string().optional(),
|
|
545
|
-
traffic: z
|
|
546
|
-
.object({
|
|
547
|
-
clones: z.object({
|
|
548
|
-
total: z.number(),
|
|
549
|
-
unique: z.number(),
|
|
550
|
-
dailyAvg: z.number(),
|
|
551
|
-
}),
|
|
552
|
-
views: z.object({
|
|
553
|
-
total: z.number(),
|
|
554
|
-
unique: z.number(),
|
|
555
|
-
dailyAvg: z.number(),
|
|
556
|
-
}),
|
|
557
|
-
period: z.string(),
|
|
558
|
-
})
|
|
559
|
-
.optional(),
|
|
560
|
-
referrers: z
|
|
561
|
-
.array(z.object({
|
|
562
|
-
referrer: z.string(),
|
|
563
|
-
count: z.number(),
|
|
564
|
-
uniques: z.number(),
|
|
565
|
-
}))
|
|
566
|
-
.optional(),
|
|
567
|
-
paths: z
|
|
568
|
-
.array(z.object({
|
|
569
|
-
path: z.string(),
|
|
570
|
-
title: z.string(),
|
|
571
|
-
count: z.number(),
|
|
572
|
-
uniques: z.number(),
|
|
573
|
-
}))
|
|
574
|
-
.optional(),
|
|
575
|
-
error: z.string().optional(),
|
|
576
|
-
requiresUserInput: z.boolean().optional(),
|
|
577
|
-
instruction: z.string().optional(),
|
|
578
|
-
});
|
|
579
|
-
// ============================================================================
|
|
580
|
-
// Phase 4: Backup Tool Output Schemas
|
|
581
|
-
// ============================================================================
|
|
582
|
-
/**
|
|
583
|
-
* Schema for backup_journal output.
|
|
584
|
-
*/
|
|
585
|
-
const BackupResultOutputSchema = z.object({
|
|
586
|
-
success: z.boolean(),
|
|
587
|
-
message: z.string(),
|
|
588
|
-
filename: z.string(),
|
|
589
|
-
path: z.string(),
|
|
590
|
-
sizeBytes: z.number(),
|
|
591
|
-
});
|
|
592
|
-
/**
|
|
593
|
-
* Backup info schema.
|
|
594
|
-
*/
|
|
595
|
-
const BackupInfoOutputSchema = z.object({
|
|
596
|
-
filename: z.string(),
|
|
597
|
-
path: z.string(),
|
|
598
|
-
sizeBytes: z.number(),
|
|
599
|
-
createdAt: z.string(),
|
|
600
|
-
});
|
|
601
|
-
/**
|
|
602
|
-
* Schema for list_backups output.
|
|
603
|
-
*/
|
|
604
|
-
const BackupsListOutputSchema = z.object({
|
|
605
|
-
backups: z.array(BackupInfoOutputSchema),
|
|
606
|
-
total: z.number(),
|
|
607
|
-
backupsDirectory: z.string(),
|
|
608
|
-
hint: z.string().optional(),
|
|
609
|
-
});
|
|
610
|
-
/**
|
|
611
|
-
* Schema for restore_backup output.
|
|
612
|
-
*/
|
|
613
|
-
const RestoreResultOutputSchema = z.object({
|
|
614
|
-
success: z.boolean(),
|
|
615
|
-
message: z.string(),
|
|
616
|
-
restoredFrom: z.string(),
|
|
617
|
-
previousEntryCount: z.number(),
|
|
618
|
-
newEntryCount: z.number(),
|
|
619
|
-
warning: z.string().optional(),
|
|
620
|
-
revertedChanges: z
|
|
621
|
-
.object({
|
|
622
|
-
tagMerges: z.string().optional(),
|
|
623
|
-
entries: z.string().optional(),
|
|
624
|
-
relationships: z.string().optional(),
|
|
625
|
-
})
|
|
626
|
-
.optional(),
|
|
627
|
-
});
|
|
628
|
-
// ============================================================================
|
|
629
|
-
// Phase 5: Remaining Tool Output Schemas
|
|
630
|
-
// ============================================================================
|
|
631
|
-
/**
|
|
632
|
-
* Schema for test_simple output.
|
|
633
|
-
*/
|
|
634
|
-
const TestSimpleOutputSchema = z.object({
|
|
635
|
-
message: z.string(),
|
|
636
|
-
});
|
|
637
|
-
/**
|
|
638
|
-
* Schema for export_entries output.
|
|
639
|
-
*/
|
|
640
|
-
const ExportEntriesOutputSchema = z.object({
|
|
641
|
-
format: z.enum(['json', 'markdown']),
|
|
642
|
-
entries: z.array(EntryOutputSchema).optional(), // For JSON format
|
|
643
|
-
content: z.string().optional(), // For markdown format
|
|
644
|
-
});
|
|
645
|
-
/**
|
|
646
|
-
* Schema for rebuild_vector_index output.
|
|
647
|
-
*/
|
|
648
|
-
const RebuildVectorIndexOutputSchema = z.object({
|
|
649
|
-
success: z.boolean(),
|
|
650
|
-
entriesIndexed: z.number(),
|
|
651
|
-
error: z.string().optional(),
|
|
652
|
-
});
|
|
653
|
-
/**
|
|
654
|
-
* Schema for add_to_vector_index output.
|
|
655
|
-
*/
|
|
656
|
-
const AddToVectorIndexOutputSchema = z.object({
|
|
657
|
-
success: z.boolean(),
|
|
658
|
-
entryId: z.number(),
|
|
659
|
-
error: z.string().optional(),
|
|
660
|
-
});
|
|
661
|
-
/**
|
|
662
|
-
* Schema for move_kanban_item output.
|
|
663
|
-
*/
|
|
664
|
-
const MoveKanbanItemOutputSchema = z.object({
|
|
665
|
-
success: z.boolean().optional(),
|
|
666
|
-
itemId: z.string().optional(),
|
|
667
|
-
newStatus: z.string().optional(),
|
|
668
|
-
projectNumber: z.number().optional(),
|
|
669
|
-
message: z.string().optional(),
|
|
670
|
-
error: z.string().optional(),
|
|
671
|
-
requiresUserInput: z.boolean().optional(),
|
|
672
|
-
hint: z.string().optional(),
|
|
673
|
-
});
|
|
674
|
-
/**
|
|
675
|
-
* Schema for create_github_issue_with_entry output.
|
|
676
|
-
*/
|
|
677
|
-
const CreateGitHubIssueWithEntryOutputSchema = z.object({
|
|
678
|
-
success: z.boolean().optional(),
|
|
679
|
-
issue: z
|
|
680
|
-
.object({
|
|
681
|
-
number: z.number(),
|
|
682
|
-
title: z.string(),
|
|
683
|
-
url: z.string(),
|
|
684
|
-
})
|
|
685
|
-
.optional(),
|
|
686
|
-
project: z
|
|
687
|
-
.object({
|
|
688
|
-
projectNumber: z.number(),
|
|
689
|
-
added: z.boolean(),
|
|
690
|
-
message: z.string(),
|
|
691
|
-
initialStatus: z
|
|
692
|
-
.object({
|
|
693
|
-
status: z.string(),
|
|
694
|
-
set: z.boolean(),
|
|
695
|
-
})
|
|
696
|
-
.optional(),
|
|
697
|
-
})
|
|
698
|
-
.optional(),
|
|
699
|
-
journalEntry: z
|
|
700
|
-
.object({
|
|
701
|
-
id: z.number(),
|
|
702
|
-
linkedToIssue: z.number(),
|
|
703
|
-
})
|
|
704
|
-
.optional(),
|
|
705
|
-
message: z.string().optional(),
|
|
706
|
-
error: z.string().optional(),
|
|
707
|
-
requiresUserInput: z.boolean().optional(),
|
|
708
|
-
instruction: z.string().optional(),
|
|
709
|
-
});
|
|
710
|
-
/**
|
|
711
|
-
* Schema for close_github_issue_with_entry output.
|
|
712
|
-
*/
|
|
713
|
-
const CloseGitHubIssueWithEntryOutputSchema = z.object({
|
|
714
|
-
success: z.boolean().optional(),
|
|
715
|
-
issue: z
|
|
716
|
-
.object({
|
|
717
|
-
number: z.number(),
|
|
718
|
-
title: z.string(),
|
|
719
|
-
url: z.string(),
|
|
720
|
-
previousState: z.string(),
|
|
721
|
-
newState: z.string(),
|
|
722
|
-
})
|
|
723
|
-
.optional(),
|
|
724
|
-
journalEntry: z
|
|
725
|
-
.object({
|
|
726
|
-
id: z.number(),
|
|
727
|
-
linkedToIssue: z.number(),
|
|
728
|
-
significanceType: z.string(),
|
|
729
|
-
})
|
|
730
|
-
.optional(),
|
|
731
|
-
kanban: z
|
|
732
|
-
.object({
|
|
733
|
-
moved: z.boolean(),
|
|
734
|
-
projectNumber: z.number(),
|
|
735
|
-
message: z.string().optional(),
|
|
736
|
-
})
|
|
737
|
-
.optional(),
|
|
738
|
-
message: z.string().optional(),
|
|
739
|
-
error: z.string().optional(),
|
|
740
|
-
requiresUserInput: z.boolean().optional(),
|
|
741
|
-
instruction: z.string().optional(),
|
|
742
|
-
});
|
|
743
|
-
// ============================================================================
|
|
744
|
-
// GitHub Milestone Output Schemas
|
|
745
|
-
// ============================================================================
|
|
746
|
-
/**
|
|
747
|
-
* GitHub milestone schema.
|
|
748
|
-
*/
|
|
749
|
-
const GitHubMilestoneOutputSchema = z.object({
|
|
750
|
-
number: z.number(),
|
|
751
|
-
title: z.string(),
|
|
752
|
-
description: z.string().nullable(),
|
|
753
|
-
state: z.enum(['open', 'closed']),
|
|
754
|
-
url: z.string(),
|
|
755
|
-
dueOn: z.string().nullable(),
|
|
756
|
-
openIssues: z.number(),
|
|
757
|
-
closedIssues: z.number(),
|
|
758
|
-
completionPercentage: z.number().optional(),
|
|
759
|
-
createdAt: z.string(),
|
|
760
|
-
updatedAt: z.string(),
|
|
761
|
-
creator: z.string().nullable(),
|
|
762
|
-
});
|
|
763
|
-
/**
|
|
764
|
-
* Schema for get_github_milestones output.
|
|
765
|
-
*/
|
|
766
|
-
const GitHubMilestonesListOutputSchema = z.object({
|
|
767
|
-
owner: z.string().optional(),
|
|
768
|
-
repo: z.string().optional(),
|
|
769
|
-
detectedOwner: z.string().nullable().optional(),
|
|
770
|
-
detectedRepo: z.string().nullable().optional(),
|
|
771
|
-
milestones: z.array(GitHubMilestoneOutputSchema).optional(),
|
|
772
|
-
count: z.number().optional(),
|
|
773
|
-
error: z.string().optional(),
|
|
774
|
-
requiresUserInput: z.boolean().optional(),
|
|
775
|
-
instruction: z.string().optional(),
|
|
776
|
-
});
|
|
777
|
-
/**
|
|
778
|
-
* Schema for get_github_milestone output.
|
|
779
|
-
*/
|
|
780
|
-
const GitHubMilestoneResultOutputSchema = z.object({
|
|
781
|
-
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
782
|
-
owner: z.string().optional(),
|
|
783
|
-
repo: z.string().optional(),
|
|
784
|
-
detectedOwner: z.string().nullable().optional(),
|
|
785
|
-
detectedRepo: z.string().nullable().optional(),
|
|
786
|
-
error: z.string().optional(),
|
|
787
|
-
requiresUserInput: z.boolean().optional(),
|
|
788
|
-
instruction: z.string().optional(),
|
|
789
|
-
});
|
|
790
|
-
/**
|
|
791
|
-
* Schema for create_github_milestone output.
|
|
792
|
-
*/
|
|
793
|
-
const CreateMilestoneOutputSchema = z.object({
|
|
794
|
-
success: z.boolean().optional(),
|
|
795
|
-
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
796
|
-
message: z.string().optional(),
|
|
797
|
-
error: z.string().optional(),
|
|
798
|
-
requiresUserInput: z.boolean().optional(),
|
|
799
|
-
instruction: z.string().optional(),
|
|
800
|
-
});
|
|
801
|
-
/**
|
|
802
|
-
* Schema for update_github_milestone output.
|
|
803
|
-
*/
|
|
804
|
-
const UpdateMilestoneOutputSchema = z.object({
|
|
805
|
-
success: z.boolean().optional(),
|
|
806
|
-
milestone: GitHubMilestoneOutputSchema.optional(),
|
|
807
|
-
message: z.string().optional(),
|
|
808
|
-
error: z.string().optional(),
|
|
809
|
-
requiresUserInput: z.boolean().optional(),
|
|
810
|
-
instruction: z.string().optional(),
|
|
811
|
-
});
|
|
812
|
-
/**
|
|
813
|
-
* Schema for delete_github_milestone output.
|
|
814
|
-
*/
|
|
815
|
-
const DeleteMilestoneOutputSchema = z.object({
|
|
816
|
-
success: z.boolean().optional(),
|
|
817
|
-
milestoneNumber: z.number().optional(),
|
|
818
|
-
message: z.string().optional(),
|
|
819
|
-
error: z.string().optional(),
|
|
820
|
-
requiresUserInput: z.boolean().optional(),
|
|
821
|
-
instruction: z.string().optional(),
|
|
822
|
-
});
|
|
823
|
-
/**
|
|
824
|
-
* Schema for cleanup_backups output.
|
|
825
|
-
*/
|
|
826
|
-
const CleanupBackupsOutputSchema = z.object({
|
|
827
|
-
success: z.boolean(),
|
|
828
|
-
deleted: z.array(z.string()),
|
|
829
|
-
deletedCount: z.number(),
|
|
830
|
-
keptCount: z.number(),
|
|
831
|
-
message: z.string(),
|
|
832
|
-
});
|
|
4
|
+
* Composes all tool group modules and exposes the public API:
|
|
5
|
+
* - getTools(): returns filtered tool definitions for the MCP server
|
|
6
|
+
* - callTool(): dispatches a tool call by name
|
|
7
|
+
*
|
|
8
|
+
* Each group module owns its:
|
|
9
|
+
* - Input/output Zod schemas
|
|
10
|
+
* - Tool definitions (name, description, group, annotations)
|
|
11
|
+
* - Handler implementations (with try/catch + formatHandlerError)
|
|
12
|
+
*/
|
|
13
|
+
import { getCoreTools } from './core.js';
|
|
14
|
+
import { getSearchTools } from './search.js';
|
|
15
|
+
import { getAnalyticsTools } from './analytics.js';
|
|
16
|
+
import { getRelationshipTools } from './relationships.js';
|
|
17
|
+
import { getExportTools } from './export.js';
|
|
18
|
+
import { getAdminTools } from './admin.js';
|
|
19
|
+
import { getGitHubTools } from './github.js';
|
|
20
|
+
import { getBackupTools } from './backup.js';
|
|
21
|
+
import { getTeamTools } from './team.js';
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Icon Mapping
|
|
24
|
+
// ============================================================================
|
|
25
|
+
function getToolIcon(group) {
|
|
26
|
+
const iconMap = {
|
|
27
|
+
core: {
|
|
28
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/notebook.svg',
|
|
29
|
+
title: 'Journal Core',
|
|
30
|
+
description: 'Core journal operations',
|
|
31
|
+
},
|
|
32
|
+
search: {
|
|
33
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/magnify.svg',
|
|
34
|
+
title: 'Search',
|
|
35
|
+
description: 'Entry search operations',
|
|
36
|
+
},
|
|
37
|
+
analytics: {
|
|
38
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/chart-bar.svg',
|
|
39
|
+
title: 'Analytics',
|
|
40
|
+
description: 'Journal analytics',
|
|
41
|
+
},
|
|
42
|
+
relationships: {
|
|
43
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/graph-outline.svg',
|
|
44
|
+
title: 'Relationships',
|
|
45
|
+
description: 'Entry relationship management',
|
|
46
|
+
},
|
|
47
|
+
export: {
|
|
48
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/export.svg',
|
|
49
|
+
title: 'Export',
|
|
50
|
+
description: 'Data export operations',
|
|
51
|
+
},
|
|
52
|
+
admin: {
|
|
53
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/cog.svg',
|
|
54
|
+
title: 'Admin',
|
|
55
|
+
description: 'Administrative operations',
|
|
56
|
+
},
|
|
57
|
+
github: {
|
|
58
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/github.svg',
|
|
59
|
+
title: 'GitHub',
|
|
60
|
+
description: 'GitHub integration',
|
|
61
|
+
},
|
|
62
|
+
backup: {
|
|
63
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/backup-restore.svg',
|
|
64
|
+
title: 'Backup',
|
|
65
|
+
description: 'Backup and restore',
|
|
66
|
+
},
|
|
67
|
+
team: {
|
|
68
|
+
iconUrl: 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.4.47/svg/account-group.svg',
|
|
69
|
+
title: 'Team',
|
|
70
|
+
description: 'Team collaboration',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
return iconMap[group];
|
|
74
|
+
}
|
|
833
75
|
// ============================================================================
|
|
834
|
-
//
|
|
76
|
+
// Public API
|
|
835
77
|
// ============================================================================
|
|
836
78
|
/**
|
|
837
|
-
* Get all tool definitions
|
|
79
|
+
* Get all tool definitions, optionally filtered by config
|
|
838
80
|
*/
|
|
839
|
-
export function getTools(db, filterConfig, vectorManager, github, config) {
|
|
840
|
-
const context = { db, vectorManager, github, config };
|
|
81
|
+
export function getTools(db, filterConfig, vectorManager, github, config, teamDb) {
|
|
82
|
+
const context = { db, teamDb, vectorManager, github, config };
|
|
841
83
|
const allTools = getAllToolDefinitions(context);
|
|
842
|
-
|
|
843
|
-
if (filterConfig) {
|
|
844
|
-
return allTools
|
|
845
|
-
.filter((t) => filterConfig.enabledTools.has(t.name))
|
|
846
|
-
.map((t) => ({
|
|
847
|
-
name: t.name,
|
|
848
|
-
description: t.description,
|
|
849
|
-
inputSchema: t.inputSchema,
|
|
850
|
-
outputSchema: t.outputSchema, // MCP 2025-11-25
|
|
851
|
-
annotations: t.annotations,
|
|
852
|
-
icons: getToolIcon(t.group), // MCP 2025-11-25 icons
|
|
853
|
-
}));
|
|
854
|
-
}
|
|
855
|
-
return allTools.map((t) => ({
|
|
84
|
+
const mapTool = (t) => ({
|
|
856
85
|
name: t.name,
|
|
857
86
|
description: t.description,
|
|
858
87
|
inputSchema: t.inputSchema,
|
|
859
|
-
outputSchema: t.outputSchema,
|
|
88
|
+
outputSchema: t.outputSchema,
|
|
860
89
|
annotations: t.annotations,
|
|
861
|
-
icons: getToolIcon(t.group),
|
|
862
|
-
})
|
|
90
|
+
icons: getToolIcon(t.group),
|
|
91
|
+
});
|
|
92
|
+
if (filterConfig) {
|
|
93
|
+
return allTools.filter((t) => filterConfig.enabledTools.has(t.name)).map(mapTool);
|
|
94
|
+
}
|
|
95
|
+
return allTools.map(mapTool);
|
|
863
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Cached tool map for O(1) lookup in callTool.
|
|
99
|
+
* Built lazily on first callTool invocation. Invalidates when any
|
|
100
|
+
* context parameter changes (happens in tests with different mocks;
|
|
101
|
+
* in production, all instances are stable).
|
|
102
|
+
*/
|
|
103
|
+
let toolMapCache = null;
|
|
104
|
+
let cachedContextRefs = null;
|
|
864
105
|
/**
|
|
865
106
|
* Call a tool by name
|
|
866
107
|
*/
|
|
867
|
-
export
|
|
868
|
-
const context = { db, vectorManager, github, config, progress };
|
|
869
|
-
|
|
870
|
-
|
|
108
|
+
export function callTool(name, args, db, vectorManager, github, config, progress, teamDb) {
|
|
109
|
+
const context = { db, teamDb, vectorManager, github, config, progress };
|
|
110
|
+
// Build tool map cache on first invocation or when context changes
|
|
111
|
+
if (!toolMapCache ||
|
|
112
|
+
cachedContextRefs?.db !== db ||
|
|
113
|
+
cachedContextRefs.github !== github ||
|
|
114
|
+
cachedContextRefs.vectorManager !== vectorManager ||
|
|
115
|
+
cachedContextRefs.config !== config ||
|
|
116
|
+
cachedContextRefs.teamDb !== teamDb) {
|
|
117
|
+
toolMapCache = new Map(getAllToolDefinitions(context).map((t) => [t.name, t]));
|
|
118
|
+
cachedContextRefs = { db, github, vectorManager, config, teamDb };
|
|
119
|
+
}
|
|
120
|
+
const tool = toolMapCache.get(name);
|
|
871
121
|
if (!tool) {
|
|
872
|
-
|
|
122
|
+
return Promise.reject(new Error(`Unknown tool: ${name}`));
|
|
873
123
|
}
|
|
874
|
-
return tool.handler(args);
|
|
124
|
+
return Promise.resolve(tool.handler(args));
|
|
875
125
|
}
|
|
876
126
|
/**
|
|
877
|
-
*
|
|
127
|
+
* Compose all tool definitions from group modules
|
|
878
128
|
*/
|
|
879
129
|
function getAllToolDefinitions(context) {
|
|
880
|
-
const { db, vectorManager, github, progress } = context;
|
|
881
130
|
return [
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
handler: (params) => {
|
|
892
|
-
const input = CreateEntrySchema.parse(params);
|
|
893
|
-
// Auto-populate issueUrl if issue_number provided without issueUrl
|
|
894
|
-
let resolvedIssueUrl = input.issue_url;
|
|
895
|
-
if (input.issue_number !== undefined && !input.issue_url && github) {
|
|
896
|
-
const cachedRepo = github.getCachedRepoInfo();
|
|
897
|
-
if (cachedRepo?.owner && cachedRepo?.repo) {
|
|
898
|
-
resolvedIssueUrl = `https://github.com/${cachedRepo.owner}/${cachedRepo.repo}/issues/${String(input.issue_number)}`;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const entry = db.createEntry({
|
|
902
|
-
content: input.content,
|
|
903
|
-
entryType: input.entry_type,
|
|
904
|
-
tags: input.tags,
|
|
905
|
-
isPersonal: input.is_personal,
|
|
906
|
-
significanceType: input.significance_type ?? null,
|
|
907
|
-
projectNumber: input.project_number,
|
|
908
|
-
projectOwner: input.project_owner,
|
|
909
|
-
issueNumber: input.issue_number,
|
|
910
|
-
issueUrl: resolvedIssueUrl,
|
|
911
|
-
prNumber: input.pr_number,
|
|
912
|
-
prUrl: input.pr_url,
|
|
913
|
-
prStatus: input.pr_status,
|
|
914
|
-
workflowRunId: input.workflow_run_id,
|
|
915
|
-
workflowName: input.workflow_name,
|
|
916
|
-
workflowStatus: input.workflow_status,
|
|
917
|
-
});
|
|
918
|
-
// Auto-index to vector store for semantic search (fire-and-forget)
|
|
919
|
-
if (vectorManager) {
|
|
920
|
-
vectorManager.addEntry(entry.id, entry.content).catch(() => {
|
|
921
|
-
// Non-critical failure, entry already saved to DB
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
return Promise.resolve({ success: true, entry });
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
{
|
|
928
|
-
name: 'get_entry_by_id',
|
|
929
|
-
title: 'Get Entry by ID',
|
|
930
|
-
description: 'Get a specific journal entry by ID with full details',
|
|
931
|
-
group: 'core',
|
|
932
|
-
inputSchema: GetEntryByIdSchema,
|
|
933
|
-
outputSchema: EntryByIdOutputSchema, // MCP 2025-11-25: structured output
|
|
934
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
935
|
-
handler: (params) => {
|
|
936
|
-
const { entry_id, include_relationships } = GetEntryByIdSchema.parse(params);
|
|
937
|
-
const entry = db.getEntryById(entry_id);
|
|
938
|
-
if (!entry) {
|
|
939
|
-
return Promise.resolve({ error: `Entry ${entry_id} not found` });
|
|
940
|
-
}
|
|
941
|
-
const { score: importance, breakdown: importanceBreakdown } = db.calculateImportance(entry_id);
|
|
942
|
-
const result = {
|
|
943
|
-
entry,
|
|
944
|
-
importance,
|
|
945
|
-
importanceBreakdown,
|
|
946
|
-
};
|
|
947
|
-
if (include_relationships) {
|
|
948
|
-
result['relationships'] = db.getRelationships(entry_id);
|
|
949
|
-
}
|
|
950
|
-
return Promise.resolve(result);
|
|
951
|
-
},
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
name: 'get_recent_entries',
|
|
955
|
-
title: 'Get Recent Entries',
|
|
956
|
-
description: 'Get recent journal entries',
|
|
957
|
-
group: 'core',
|
|
958
|
-
inputSchema: GetRecentEntriesSchema,
|
|
959
|
-
outputSchema: EntriesListOutputSchema, // MCP 2025-11-25: structured output
|
|
960
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
961
|
-
handler: (params) => {
|
|
962
|
-
const { limit, is_personal } = GetRecentEntriesSchema.parse(params);
|
|
963
|
-
const entries = db.getRecentEntries(limit, is_personal);
|
|
964
|
-
return Promise.resolve({ entries, count: entries.length });
|
|
965
|
-
},
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
name: 'create_entry_minimal',
|
|
969
|
-
title: 'Create Entry (Minimal)',
|
|
970
|
-
description: 'Minimal entry creation without context or tags',
|
|
971
|
-
group: 'core',
|
|
972
|
-
inputSchema: CreateEntryMinimalSchema,
|
|
973
|
-
outputSchema: CreateEntryOutputSchema,
|
|
974
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
975
|
-
handler: (params) => {
|
|
976
|
-
const { content } = CreateEntryMinimalSchema.parse(params);
|
|
977
|
-
const entry = db.createEntry({ content });
|
|
978
|
-
// Auto-index to vector store for semantic search (fire-and-forget)
|
|
979
|
-
if (vectorManager) {
|
|
980
|
-
vectorManager.addEntry(entry.id, entry.content).catch(() => {
|
|
981
|
-
// Non-critical failure, entry already saved to DB
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
return Promise.resolve({ success: true, entry });
|
|
985
|
-
},
|
|
986
|
-
},
|
|
987
|
-
{
|
|
988
|
-
name: 'test_simple',
|
|
989
|
-
title: 'Test Simple',
|
|
990
|
-
description: 'Simple test tool that just returns a message',
|
|
991
|
-
group: 'core',
|
|
992
|
-
inputSchema: TestSimpleSchema,
|
|
993
|
-
outputSchema: TestSimpleOutputSchema,
|
|
994
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
995
|
-
handler: (params) => {
|
|
996
|
-
const { message } = TestSimpleSchema.parse(params);
|
|
997
|
-
return Promise.resolve({ message: `Test response: ${message}` });
|
|
998
|
-
},
|
|
999
|
-
},
|
|
1000
|
-
// Search tools
|
|
1001
|
-
{
|
|
1002
|
-
name: 'search_entries',
|
|
1003
|
-
title: 'Search Entries',
|
|
1004
|
-
description: 'Search journal entries with optional filters for GitHub Projects, Issues, PRs, and Actions',
|
|
1005
|
-
group: 'search',
|
|
1006
|
-
inputSchema: SearchEntriesSchema,
|
|
1007
|
-
outputSchema: EntriesListOutputSchema, // MCP 2025-11-25: structured output
|
|
1008
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1009
|
-
handler: (params) => {
|
|
1010
|
-
const input = SearchEntriesSchema.parse(params);
|
|
1011
|
-
// If no query and no filters, validation error usage of getRecentEntries
|
|
1012
|
-
// But we want to allow filtering without text query
|
|
1013
|
-
const hasFilters = input.project_number !== undefined ||
|
|
1014
|
-
input.issue_number !== undefined ||
|
|
1015
|
-
input.pr_number !== undefined ||
|
|
1016
|
-
input.is_personal !== undefined;
|
|
1017
|
-
if (!input.query && !hasFilters) {
|
|
1018
|
-
const entries = db.getRecentEntries(input.limit, input.is_personal);
|
|
1019
|
-
return Promise.resolve({ entries, count: entries.length });
|
|
1020
|
-
}
|
|
1021
|
-
const entries = db.searchEntries(input.query || '', {
|
|
1022
|
-
limit: input.limit,
|
|
1023
|
-
isPersonal: input.is_personal,
|
|
1024
|
-
projectNumber: input.project_number,
|
|
1025
|
-
issueNumber: input.issue_number,
|
|
1026
|
-
prNumber: input.pr_number,
|
|
1027
|
-
});
|
|
1028
|
-
return Promise.resolve({ entries, count: entries.length });
|
|
1029
|
-
},
|
|
1030
|
-
},
|
|
1031
|
-
{
|
|
1032
|
-
name: 'search_by_date_range',
|
|
1033
|
-
title: 'Search by Date Range',
|
|
1034
|
-
description: 'Search journal entries within a date range with optional filters',
|
|
1035
|
-
group: 'search',
|
|
1036
|
-
inputSchema: SearchByDateRangeSchema,
|
|
1037
|
-
outputSchema: EntriesListOutputSchema, // MCP 2025-11-25: structured output
|
|
1038
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1039
|
-
handler: (params) => {
|
|
1040
|
-
const input = SearchByDateRangeSchema.parse(params);
|
|
1041
|
-
const entries = db.searchByDateRange(input.start_date, input.end_date, {
|
|
1042
|
-
entryType: input.entry_type,
|
|
1043
|
-
tags: input.tags,
|
|
1044
|
-
isPersonal: input.is_personal,
|
|
1045
|
-
projectNumber: input.project_number,
|
|
1046
|
-
});
|
|
1047
|
-
return Promise.resolve({ entries, count: entries.length });
|
|
1048
|
-
},
|
|
1049
|
-
},
|
|
1050
|
-
{
|
|
1051
|
-
name: 'semantic_search',
|
|
1052
|
-
title: 'Semantic Search',
|
|
1053
|
-
description: 'Perform semantic/vector search on journal entries using AI embeddings',
|
|
1054
|
-
group: 'search',
|
|
1055
|
-
inputSchema: SemanticSearchSchema,
|
|
1056
|
-
outputSchema: SemanticSearchOutputSchema,
|
|
1057
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1058
|
-
handler: async (params) => {
|
|
1059
|
-
const input = SemanticSearchSchema.parse(params);
|
|
1060
|
-
// Check if vector search is available
|
|
1061
|
-
if (!vectorManager) {
|
|
1062
|
-
return {
|
|
1063
|
-
error: 'Semantic search not initialized. Vector search manager is not available.',
|
|
1064
|
-
query: input.query,
|
|
1065
|
-
entries: [],
|
|
1066
|
-
count: 0,
|
|
1067
|
-
};
|
|
1068
|
-
}
|
|
1069
|
-
// Perform semantic search
|
|
1070
|
-
const results = await vectorManager.search(input.query, input.limit ?? 10, input.similarity_threshold ?? 0.25);
|
|
1071
|
-
// Fetch full entries for matching IDs
|
|
1072
|
-
const entries = results
|
|
1073
|
-
.map((r) => {
|
|
1074
|
-
const entry = db.getEntryById(r.entryId);
|
|
1075
|
-
if (!entry)
|
|
1076
|
-
return null;
|
|
1077
|
-
return {
|
|
1078
|
-
...entry,
|
|
1079
|
-
similarity: Math.round(r.score * 100) / 100,
|
|
1080
|
-
};
|
|
1081
|
-
})
|
|
1082
|
-
.filter((e) => e !== null);
|
|
1083
|
-
// Check index stats to provide accurate hint
|
|
1084
|
-
const stats = await vectorManager.getStats();
|
|
1085
|
-
const isIndexEmpty = stats.itemCount === 0;
|
|
1086
|
-
// hint_on_empty controls whether to show helpful hints (default: true)
|
|
1087
|
-
const includeHint = input.hint_on_empty ?? true;
|
|
1088
|
-
return {
|
|
1089
|
-
query: input.query,
|
|
1090
|
-
entries,
|
|
1091
|
-
count: entries.length,
|
|
1092
|
-
...(includeHint && isIndexEmpty
|
|
1093
|
-
? {
|
|
1094
|
-
hint: 'No entries in vector index. Use rebuild_vector_index to index existing entries.',
|
|
1095
|
-
}
|
|
1096
|
-
: includeHint && entries.length === 0
|
|
1097
|
-
? {
|
|
1098
|
-
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.`,
|
|
1099
|
-
}
|
|
1100
|
-
: {}),
|
|
1101
|
-
};
|
|
1102
|
-
},
|
|
1103
|
-
},
|
|
1104
|
-
// Analytics tools
|
|
1105
|
-
{
|
|
1106
|
-
name: 'get_statistics',
|
|
1107
|
-
title: 'Get Statistics',
|
|
1108
|
-
description: 'Get journal statistics and analytics (Phase 2: includes project breakdown)',
|
|
1109
|
-
group: 'analytics',
|
|
1110
|
-
inputSchema: GetStatisticsSchema,
|
|
1111
|
-
outputSchema: StatisticsOutputSchema, // MCP 2025-11-25: structured output
|
|
1112
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1113
|
-
handler: (params) => {
|
|
1114
|
-
const { group_by } = GetStatisticsSchema.parse(params);
|
|
1115
|
-
const stats = db.getStatistics(group_by);
|
|
1116
|
-
return Promise.resolve({ ...stats, groupBy: group_by });
|
|
1117
|
-
},
|
|
1118
|
-
},
|
|
1119
|
-
{
|
|
1120
|
-
name: 'get_cross_project_insights',
|
|
1121
|
-
title: 'Get Cross-Project Insights',
|
|
1122
|
-
description: 'Analyze patterns across all GitHub Projects tracked in journal entries',
|
|
1123
|
-
group: 'analytics',
|
|
1124
|
-
inputSchema: z.object({
|
|
1125
|
-
start_date: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
1126
|
-
end_date: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
1127
|
-
min_entries: z
|
|
1128
|
-
.number()
|
|
1129
|
-
.optional()
|
|
1130
|
-
.default(3)
|
|
1131
|
-
.describe('Minimum entries to include project'),
|
|
1132
|
-
}),
|
|
1133
|
-
outputSchema: CrossProjectInsightsOutputSchema,
|
|
1134
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1135
|
-
handler: (params) => {
|
|
1136
|
-
const input = z
|
|
1137
|
-
.object({
|
|
1138
|
-
start_date: z.string().optional(),
|
|
1139
|
-
end_date: z.string().optional(),
|
|
1140
|
-
min_entries: z.number().optional().default(3),
|
|
1141
|
-
})
|
|
1142
|
-
.parse(params);
|
|
1143
|
-
const rawDb = db.getRawDb();
|
|
1144
|
-
// Build WHERE clause
|
|
1145
|
-
let where = 'WHERE deleted_at IS NULL AND project_number IS NOT NULL';
|
|
1146
|
-
const sqlParams = [];
|
|
1147
|
-
if (input.start_date) {
|
|
1148
|
-
where += ' AND DATE(timestamp) >= DATE(?)';
|
|
1149
|
-
sqlParams.push(input.start_date);
|
|
1150
|
-
}
|
|
1151
|
-
if (input.end_date) {
|
|
1152
|
-
where += ' AND DATE(timestamp) <= DATE(?)';
|
|
1153
|
-
sqlParams.push(input.end_date);
|
|
1154
|
-
}
|
|
1155
|
-
// Get active projects with stats
|
|
1156
|
-
const projectsResult = rawDb.exec(`
|
|
1157
|
-
SELECT project_number, COUNT(*) as entry_count,
|
|
1158
|
-
MIN(DATE(timestamp)) as first_entry,
|
|
1159
|
-
MAX(DATE(timestamp)) as last_entry,
|
|
1160
|
-
COUNT(DISTINCT DATE(timestamp)) as active_days
|
|
1161
|
-
FROM memory_journal ${where}
|
|
1162
|
-
GROUP BY project_number
|
|
1163
|
-
HAVING entry_count >= ?
|
|
1164
|
-
ORDER BY entry_count DESC
|
|
1165
|
-
`, [...sqlParams, input.min_entries]);
|
|
1166
|
-
if (!projectsResult[0] || projectsResult[0].values.length === 0) {
|
|
1167
|
-
return Promise.resolve({
|
|
1168
|
-
project_count: 0,
|
|
1169
|
-
total_entries: 0,
|
|
1170
|
-
projects: [],
|
|
1171
|
-
inactive_projects: [],
|
|
1172
|
-
inactiveThresholdDays: 7,
|
|
1173
|
-
time_distribution: [],
|
|
1174
|
-
message: `No projects found with at least ${String(input.min_entries)} entries`,
|
|
1175
|
-
});
|
|
1176
|
-
}
|
|
1177
|
-
const columns = projectsResult[0].columns;
|
|
1178
|
-
const projects = projectsResult[0].values.map((row) => {
|
|
1179
|
-
const obj = {};
|
|
1180
|
-
columns.forEach((col, i) => {
|
|
1181
|
-
obj[col] = row[i];
|
|
1182
|
-
});
|
|
1183
|
-
return obj;
|
|
1184
|
-
});
|
|
1185
|
-
// Get top tags per project
|
|
1186
|
-
const projectTags = {};
|
|
1187
|
-
for (const proj of projects) {
|
|
1188
|
-
const projNum = proj['project_number'];
|
|
1189
|
-
const tagsResult = rawDb.exec(`
|
|
1190
|
-
SELECT t.name, COUNT(*) as count
|
|
1191
|
-
FROM tags t
|
|
1192
|
-
JOIN entry_tags et ON t.id = et.tag_id
|
|
1193
|
-
JOIN memory_journal m ON et.entry_id = m.id
|
|
1194
|
-
WHERE m.project_number = ? AND m.deleted_at IS NULL
|
|
1195
|
-
GROUP BY t.name
|
|
1196
|
-
ORDER BY count DESC
|
|
1197
|
-
LIMIT 5
|
|
1198
|
-
`, [projNum]);
|
|
1199
|
-
if (tagsResult[0]) {
|
|
1200
|
-
projectTags[projNum] = tagsResult[0].values.map((row) => ({
|
|
1201
|
-
name: row[0],
|
|
1202
|
-
count: row[1],
|
|
1203
|
-
}));
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
// Find inactive projects (last entry > 7 days ago)
|
|
1207
|
-
const cutoffDate = new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0];
|
|
1208
|
-
const inactiveResult = rawDb.exec(`
|
|
1209
|
-
SELECT project_number, MAX(DATE(timestamp)) as last_entry_date
|
|
1210
|
-
FROM memory_journal
|
|
1211
|
-
WHERE deleted_at IS NULL AND project_number IS NOT NULL
|
|
1212
|
-
GROUP BY project_number
|
|
1213
|
-
HAVING last_entry_date < ?
|
|
1214
|
-
`, [cutoffDate]);
|
|
1215
|
-
const inactiveProjects = inactiveResult[0]?.values.map((row) => ({
|
|
1216
|
-
project_number: row[0],
|
|
1217
|
-
last_entry_date: row[1],
|
|
1218
|
-
})) ?? [];
|
|
1219
|
-
// Calculate time distribution
|
|
1220
|
-
const totalEntries = projects.reduce((sum, p) => sum + p['entry_count'], 0);
|
|
1221
|
-
const distribution = projects.slice(0, 5).map((p) => ({
|
|
1222
|
-
project_number: p['project_number'],
|
|
1223
|
-
percentage: ((p['entry_count'] / totalEntries) * 100).toFixed(1),
|
|
1224
|
-
}));
|
|
1225
|
-
return Promise.resolve({
|
|
1226
|
-
project_count: projects.length,
|
|
1227
|
-
total_entries: totalEntries,
|
|
1228
|
-
projects: projects.map((p) => ({
|
|
1229
|
-
...p,
|
|
1230
|
-
top_tags: projectTags[p['project_number']] ?? [],
|
|
1231
|
-
})),
|
|
1232
|
-
inactive_projects: inactiveProjects,
|
|
1233
|
-
inactiveThresholdDays: 7,
|
|
1234
|
-
time_distribution: distribution,
|
|
1235
|
-
});
|
|
1236
|
-
},
|
|
1237
|
-
},
|
|
1238
|
-
// Relationship tools
|
|
1239
|
-
{
|
|
1240
|
-
name: 'link_entries',
|
|
1241
|
-
title: 'Link Entries',
|
|
1242
|
-
description: 'Create a relationship between two journal entries',
|
|
1243
|
-
group: 'relationships',
|
|
1244
|
-
inputSchema: LinkEntriesSchema,
|
|
1245
|
-
outputSchema: LinkEntriesOutputSchema,
|
|
1246
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
1247
|
-
handler: (params) => {
|
|
1248
|
-
const input = LinkEntriesSchema.parse(params);
|
|
1249
|
-
// Check for existing duplicate relationship
|
|
1250
|
-
const existingRelationships = db.getRelationships(input.from_entry_id);
|
|
1251
|
-
const existing = existingRelationships.find((r) => r.toEntryId === input.to_entry_id &&
|
|
1252
|
-
r.relationshipType === input.relationship_type);
|
|
1253
|
-
if (existing) {
|
|
1254
|
-
return Promise.resolve({
|
|
1255
|
-
success: true,
|
|
1256
|
-
relationship: existing,
|
|
1257
|
-
duplicate: true,
|
|
1258
|
-
message: 'Relationship already exists',
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
// P154: linkEntries throws for nonexistent entries
|
|
1262
|
-
try {
|
|
1263
|
-
const relationship = db.linkEntries(input.from_entry_id, input.to_entry_id, input.relationship_type, input.description);
|
|
1264
|
-
return Promise.resolve({ success: true, relationship });
|
|
1265
|
-
}
|
|
1266
|
-
catch (error) {
|
|
1267
|
-
return Promise.resolve({
|
|
1268
|
-
success: false,
|
|
1269
|
-
relationship: {
|
|
1270
|
-
id: 0,
|
|
1271
|
-
fromEntryId: input.from_entry_id,
|
|
1272
|
-
toEntryId: input.to_entry_id,
|
|
1273
|
-
relationshipType: input.relationship_type,
|
|
1274
|
-
description: input.description ?? null,
|
|
1275
|
-
createdAt: '',
|
|
1276
|
-
},
|
|
1277
|
-
message: error instanceof Error ? error.message : 'Unknown error',
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
},
|
|
1281
|
-
},
|
|
1282
|
-
{
|
|
1283
|
-
name: 'visualize_relationships',
|
|
1284
|
-
title: 'Visualize Relationships',
|
|
1285
|
-
description: 'Generate a Mermaid diagram visualization of entry relationships',
|
|
1286
|
-
group: 'relationships',
|
|
1287
|
-
inputSchema: z.object({
|
|
1288
|
-
entry_id: z
|
|
1289
|
-
.number()
|
|
1290
|
-
.optional()
|
|
1291
|
-
.describe('Specific entry ID to visualize (shows connected entries)'),
|
|
1292
|
-
tags: z.array(z.string()).optional().describe('Filter entries by tags'),
|
|
1293
|
-
depth: z
|
|
1294
|
-
.number()
|
|
1295
|
-
.min(1)
|
|
1296
|
-
.max(3)
|
|
1297
|
-
.optional()
|
|
1298
|
-
.default(2)
|
|
1299
|
-
.describe('Relationship traversal depth'),
|
|
1300
|
-
limit: z.number().optional().default(20).describe('Maximum entries to include'),
|
|
1301
|
-
}),
|
|
1302
|
-
outputSchema: VisualizationOutputSchema,
|
|
1303
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1304
|
-
handler: (params) => {
|
|
1305
|
-
const input = z
|
|
1306
|
-
.object({
|
|
1307
|
-
entry_id: z.number().optional(),
|
|
1308
|
-
tags: z.array(z.string()).optional(),
|
|
1309
|
-
depth: z.number().optional().default(2),
|
|
1310
|
-
limit: z.number().optional().default(20),
|
|
1311
|
-
})
|
|
1312
|
-
.parse(params);
|
|
1313
|
-
const rawDb = db.getRawDb();
|
|
1314
|
-
let entriesResult;
|
|
1315
|
-
if (input.entry_id !== undefined) {
|
|
1316
|
-
// P154: Pre-check entry existence to disambiguate responses
|
|
1317
|
-
const entry = db.getEntryById(input.entry_id);
|
|
1318
|
-
if (!entry) {
|
|
1319
|
-
return Promise.resolve({
|
|
1320
|
-
entry_count: 0,
|
|
1321
|
-
relationship_count: 0,
|
|
1322
|
-
root_entry: input.entry_id,
|
|
1323
|
-
depth: input.depth,
|
|
1324
|
-
mermaid: null,
|
|
1325
|
-
message: `Entry ${String(input.entry_id)} not found`,
|
|
1326
|
-
});
|
|
1327
|
-
}
|
|
1328
|
-
// Use recursive CTE to get connected entries up to depth
|
|
1329
|
-
entriesResult = rawDb.exec(`
|
|
1330
|
-
WITH RECURSIVE connected_entries(id, distance) AS (
|
|
1331
|
-
SELECT id, 0 FROM memory_journal WHERE id = ? AND deleted_at IS NULL
|
|
1332
|
-
UNION
|
|
1333
|
-
SELECT DISTINCT
|
|
1334
|
-
CASE
|
|
1335
|
-
WHEN r.from_entry_id = ce.id THEN r.to_entry_id
|
|
1336
|
-
ELSE r.from_entry_id
|
|
1337
|
-
END,
|
|
1338
|
-
ce.distance + 1
|
|
1339
|
-
FROM connected_entries ce
|
|
1340
|
-
JOIN relationships r ON r.from_entry_id = ce.id OR r.to_entry_id = ce.id
|
|
1341
|
-
WHERE ce.distance < ?
|
|
1342
|
-
)
|
|
1343
|
-
SELECT DISTINCT mj.id, mj.entry_type, mj.content, mj.is_personal
|
|
1344
|
-
FROM memory_journal mj
|
|
1345
|
-
JOIN connected_entries ce ON mj.id = ce.id
|
|
1346
|
-
WHERE mj.deleted_at IS NULL
|
|
1347
|
-
LIMIT ?
|
|
1348
|
-
`, [input.entry_id, input.depth, input.limit]);
|
|
1349
|
-
}
|
|
1350
|
-
else if (input.tags && input.tags.length > 0) {
|
|
1351
|
-
// Filter by tags
|
|
1352
|
-
const placeholders = input.tags.map(() => '?').join(',');
|
|
1353
|
-
entriesResult = rawDb.exec(`
|
|
1354
|
-
SELECT DISTINCT mj.id, mj.entry_type, mj.content, mj.is_personal
|
|
1355
|
-
FROM memory_journal mj
|
|
1356
|
-
WHERE mj.deleted_at IS NULL
|
|
1357
|
-
AND mj.id IN (
|
|
1358
|
-
SELECT et.entry_id FROM entry_tags et
|
|
1359
|
-
JOIN tags t ON et.tag_id = t.id
|
|
1360
|
-
WHERE t.name IN (${placeholders})
|
|
1361
|
-
)
|
|
1362
|
-
LIMIT ?
|
|
1363
|
-
`, [...input.tags, input.limit]);
|
|
1364
|
-
}
|
|
1365
|
-
else {
|
|
1366
|
-
// Get recent entries with relationships
|
|
1367
|
-
entriesResult = rawDb.exec(`
|
|
1368
|
-
SELECT DISTINCT mj.id, mj.entry_type, mj.content, mj.is_personal
|
|
1369
|
-
FROM memory_journal mj
|
|
1370
|
-
WHERE mj.deleted_at IS NULL
|
|
1371
|
-
AND mj.id IN (
|
|
1372
|
-
SELECT DISTINCT from_entry_id FROM relationships
|
|
1373
|
-
UNION
|
|
1374
|
-
SELECT DISTINCT to_entry_id FROM relationships
|
|
1375
|
-
)
|
|
1376
|
-
ORDER BY mj.id DESC
|
|
1377
|
-
LIMIT ?
|
|
1378
|
-
`, [input.limit]);
|
|
1379
|
-
}
|
|
1380
|
-
if (!entriesResult[0] || entriesResult[0].values.length === 0) {
|
|
1381
|
-
return Promise.resolve({
|
|
1382
|
-
entry_count: 0,
|
|
1383
|
-
relationship_count: 0,
|
|
1384
|
-
root_entry: input.entry_id ?? null,
|
|
1385
|
-
depth: input.depth,
|
|
1386
|
-
mermaid: null,
|
|
1387
|
-
message: 'No entries found with relationships matching your criteria',
|
|
1388
|
-
});
|
|
1389
|
-
}
|
|
1390
|
-
// Build entries map
|
|
1391
|
-
const entries = {};
|
|
1392
|
-
const cols = entriesResult[0].columns;
|
|
1393
|
-
for (const row of entriesResult[0].values) {
|
|
1394
|
-
const id = row[cols.indexOf('id')];
|
|
1395
|
-
entries[id] = {
|
|
1396
|
-
id,
|
|
1397
|
-
entry_type: row[cols.indexOf('entry_type')],
|
|
1398
|
-
content: row[cols.indexOf('content')],
|
|
1399
|
-
is_personal: Boolean(row[cols.indexOf('is_personal')]),
|
|
1400
|
-
};
|
|
1401
|
-
}
|
|
1402
|
-
const entryIds = Object.keys(entries).map(Number);
|
|
1403
|
-
const placeholders = entryIds.map(() => '?').join(',');
|
|
1404
|
-
// Get relationships between these entries
|
|
1405
|
-
const relsResult = rawDb.exec(`
|
|
1406
|
-
SELECT from_entry_id, to_entry_id, relationship_type
|
|
1407
|
-
FROM relationships
|
|
1408
|
-
WHERE from_entry_id IN (${placeholders})
|
|
1409
|
-
AND to_entry_id IN (${placeholders})
|
|
1410
|
-
`, [...entryIds, ...entryIds]);
|
|
1411
|
-
const relationships = relsResult[0]?.values ?? [];
|
|
1412
|
-
// Generate Mermaid diagram
|
|
1413
|
-
let mermaid = '```mermaid\\ngraph TD\\n';
|
|
1414
|
-
// Add nodes
|
|
1415
|
-
for (const [idStr, entry] of Object.entries(entries)) {
|
|
1416
|
-
let contentPreview = entry.content.slice(0, 40).replace(/\\n/g, ' ');
|
|
1417
|
-
if (entry.content.length > 40)
|
|
1418
|
-
contentPreview += '...';
|
|
1419
|
-
// Escape for Mermaid
|
|
1420
|
-
contentPreview = contentPreview
|
|
1421
|
-
.replace(/"/g, "'")
|
|
1422
|
-
.replace(/\\[/g, '(').replace(/\\]/g, ')');
|
|
1423
|
-
const entryTypeShort = entry.entry_type.slice(0, 20);
|
|
1424
|
-
mermaid += ` E${idStr}["#${idStr}: ${contentPreview}<br/>${entryTypeShort}"]\\n`;
|
|
1425
|
-
}
|
|
1426
|
-
mermaid += '\\n';
|
|
1427
|
-
// Add relationships with arrows
|
|
1428
|
-
const relSymbols = {
|
|
1429
|
-
references: '-->',
|
|
1430
|
-
implements: '==>',
|
|
1431
|
-
clarifies: '-.->',
|
|
1432
|
-
evolves_from: '-->',
|
|
1433
|
-
response_to: '<-->',
|
|
1434
|
-
// Causal relationship types
|
|
1435
|
-
blocked_by: '--x',
|
|
1436
|
-
resolved: '==>',
|
|
1437
|
-
caused: '-.->',
|
|
1438
|
-
};
|
|
1439
|
-
for (const rel of relationships) {
|
|
1440
|
-
const fromId = rel[0];
|
|
1441
|
-
const toId = rel[1];
|
|
1442
|
-
const relType = rel[2];
|
|
1443
|
-
const arrow = relSymbols[relType] ?? '-->';
|
|
1444
|
-
mermaid += ` E${String(fromId)} ${arrow}|${relType}| E${String(toId)}\\n`;
|
|
1445
|
-
}
|
|
1446
|
-
// Add styling
|
|
1447
|
-
mermaid += '\\n';
|
|
1448
|
-
for (const [idStr, entry] of Object.entries(entries)) {
|
|
1449
|
-
if (entry.is_personal) {
|
|
1450
|
-
mermaid += ` style E${idStr} fill:#E3F2FD\\n`;
|
|
1451
|
-
}
|
|
1452
|
-
else {
|
|
1453
|
-
mermaid += ` style E${idStr} fill:#FFF3E0\\n`;
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
mermaid += '```';
|
|
1457
|
-
return Promise.resolve({
|
|
1458
|
-
entry_count: Object.keys(entries).length,
|
|
1459
|
-
relationship_count: relationships.length,
|
|
1460
|
-
root_entry: input.entry_id ?? null,
|
|
1461
|
-
depth: input.depth,
|
|
1462
|
-
mermaid,
|
|
1463
|
-
legend: {
|
|
1464
|
-
blue: 'Personal entries',
|
|
1465
|
-
orange: 'Project entries',
|
|
1466
|
-
arrows: {
|
|
1467
|
-
'-->': 'references / evolves_from',
|
|
1468
|
-
'==>': 'implements / resolved',
|
|
1469
|
-
'-.->': 'clarifies / caused',
|
|
1470
|
-
'<-->': 'response_to',
|
|
1471
|
-
'--x': 'blocked_by',
|
|
1472
|
-
},
|
|
1473
|
-
},
|
|
1474
|
-
});
|
|
1475
|
-
},
|
|
1476
|
-
},
|
|
1477
|
-
// Export tools
|
|
1478
|
-
{
|
|
1479
|
-
name: 'export_entries',
|
|
1480
|
-
title: 'Export Entries',
|
|
1481
|
-
description: 'Export journal entries to JSON or Markdown format',
|
|
1482
|
-
group: 'export',
|
|
1483
|
-
inputSchema: ExportEntriesSchema,
|
|
1484
|
-
outputSchema: ExportEntriesOutputSchema,
|
|
1485
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1486
|
-
handler: async (params) => {
|
|
1487
|
-
const input = ExportEntriesSchema.parse(params);
|
|
1488
|
-
const limit = input.limit ?? 100;
|
|
1489
|
-
// Send initial progress
|
|
1490
|
-
await sendProgress(progress, 0, 2, 'Fetching entries...');
|
|
1491
|
-
const entries = db.getRecentEntries(limit);
|
|
1492
|
-
// Send processing progress
|
|
1493
|
-
await sendProgress(progress, 1, 2, `Processing ${String(entries.length)} entries...`);
|
|
1494
|
-
if (input.format === 'markdown') {
|
|
1495
|
-
const md = entries
|
|
1496
|
-
.map((e) => `## ${e.timestamp}\n\n**Type:** ${e.entryType}\n\n${e.content}\n\n---`)
|
|
1497
|
-
.join('\n\n');
|
|
1498
|
-
await sendProgress(progress, 2, 2, 'Export complete');
|
|
1499
|
-
return { format: 'markdown', content: md };
|
|
1500
|
-
}
|
|
1501
|
-
await sendProgress(progress, 2, 2, 'Export complete');
|
|
1502
|
-
return { format: 'json', entries };
|
|
1503
|
-
},
|
|
1504
|
-
},
|
|
1505
|
-
// Admin tools
|
|
1506
|
-
{
|
|
1507
|
-
name: 'update_entry',
|
|
1508
|
-
title: 'Update Entry',
|
|
1509
|
-
description: 'Update an existing journal entry',
|
|
1510
|
-
group: 'admin',
|
|
1511
|
-
inputSchema: UpdateEntrySchema,
|
|
1512
|
-
outputSchema: UpdateEntryOutputSchema,
|
|
1513
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
1514
|
-
handler: (params) => {
|
|
1515
|
-
const input = UpdateEntrySchema.parse(params);
|
|
1516
|
-
const entry = db.updateEntry(input.entry_id, {
|
|
1517
|
-
content: input.content,
|
|
1518
|
-
entryType: input.entry_type,
|
|
1519
|
-
isPersonal: input.is_personal,
|
|
1520
|
-
tags: input.tags,
|
|
1521
|
-
});
|
|
1522
|
-
if (!entry) {
|
|
1523
|
-
return Promise.resolve({ error: `Entry ${input.entry_id} not found` });
|
|
1524
|
-
}
|
|
1525
|
-
// Re-index if content changed
|
|
1526
|
-
if (input.content && vectorManager) {
|
|
1527
|
-
vectorManager.addEntry(entry.id, entry.content).catch(() => {
|
|
1528
|
-
// Non-critical failure, entry already updated in DB
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
return Promise.resolve({ success: true, entry });
|
|
1532
|
-
},
|
|
1533
|
-
},
|
|
1534
|
-
{
|
|
1535
|
-
name: 'delete_entry',
|
|
1536
|
-
title: 'Delete Entry',
|
|
1537
|
-
description: 'Delete a journal entry (soft delete with timestamp)',
|
|
1538
|
-
group: 'admin',
|
|
1539
|
-
inputSchema: DeleteEntrySchema,
|
|
1540
|
-
outputSchema: DeleteEntryOutputSchema,
|
|
1541
|
-
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1542
|
-
handler: (params) => {
|
|
1543
|
-
const { entry_id, permanent } = DeleteEntrySchema.parse(params);
|
|
1544
|
-
const success = db.deleteEntry(entry_id, permanent);
|
|
1545
|
-
// P154: Surface structured error for nonexistent entries
|
|
1546
|
-
if (!success) {
|
|
1547
|
-
return Promise.resolve({
|
|
1548
|
-
success: false,
|
|
1549
|
-
entryId: entry_id,
|
|
1550
|
-
permanent,
|
|
1551
|
-
error: `Entry ${String(entry_id)} not found`,
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
// Remove from vector index (non-critical if fails)
|
|
1555
|
-
if (vectorManager) {
|
|
1556
|
-
vectorManager.removeEntry(entry_id).catch(() => {
|
|
1557
|
-
// Non-critical failure, entry already deleted from DB
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
return Promise.resolve({ success, entryId: entry_id, permanent });
|
|
1561
|
-
},
|
|
1562
|
-
},
|
|
1563
|
-
// Utility tools
|
|
1564
|
-
{
|
|
1565
|
-
name: 'list_tags',
|
|
1566
|
-
title: 'List Tags',
|
|
1567
|
-
description: 'List all available tags',
|
|
1568
|
-
group: 'core',
|
|
1569
|
-
inputSchema: z.object({}),
|
|
1570
|
-
outputSchema: TagsListOutputSchema,
|
|
1571
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1572
|
-
handler: (_params) => {
|
|
1573
|
-
const rawTags = db.listTags();
|
|
1574
|
-
const tags = rawTags.map((t) => ({ name: t.name, count: t.usageCount }));
|
|
1575
|
-
return Promise.resolve({ tags, count: tags.length });
|
|
1576
|
-
},
|
|
1577
|
-
},
|
|
1578
|
-
{
|
|
1579
|
-
name: 'merge_tags',
|
|
1580
|
-
title: 'Merge Tags',
|
|
1581
|
-
description: 'Merge one tag into another to consolidate similar tags (e.g., merge "phase-2" into "phase2"). The source tag is deleted after merge.',
|
|
1582
|
-
group: 'admin',
|
|
1583
|
-
inputSchema: z.object({
|
|
1584
|
-
source_tag: z.string().min(1).describe('Tag to merge from (will be deleted)'),
|
|
1585
|
-
target_tag: z
|
|
1586
|
-
.string()
|
|
1587
|
-
.min(1)
|
|
1588
|
-
.describe('Tag to merge into (will be created if not exists)'),
|
|
1589
|
-
}),
|
|
1590
|
-
outputSchema: MergeTagsOutputSchema,
|
|
1591
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
1592
|
-
handler: (params) => {
|
|
1593
|
-
const { source_tag, target_tag } = z
|
|
1594
|
-
.object({
|
|
1595
|
-
source_tag: z.string().min(1),
|
|
1596
|
-
target_tag: z.string().min(1),
|
|
1597
|
-
})
|
|
1598
|
-
.parse(params);
|
|
1599
|
-
if (source_tag === target_tag) {
|
|
1600
|
-
return Promise.resolve({
|
|
1601
|
-
success: false,
|
|
1602
|
-
sourceTag: source_tag,
|
|
1603
|
-
targetTag: target_tag,
|
|
1604
|
-
entriesUpdated: 0,
|
|
1605
|
-
sourceDeleted: false,
|
|
1606
|
-
message: 'Source and target tags cannot be the same',
|
|
1607
|
-
error: 'Source and target tags must be different',
|
|
1608
|
-
});
|
|
1609
|
-
}
|
|
1610
|
-
try {
|
|
1611
|
-
const result = db.mergeTags(source_tag, target_tag);
|
|
1612
|
-
return Promise.resolve({
|
|
1613
|
-
success: true,
|
|
1614
|
-
sourceTag: source_tag,
|
|
1615
|
-
targetTag: target_tag,
|
|
1616
|
-
entriesUpdated: result.entriesUpdated,
|
|
1617
|
-
sourceDeleted: result.sourceDeleted,
|
|
1618
|
-
message: `Merged "${source_tag}" into "${target_tag}". Updated ${String(result.entriesUpdated)} entries.`,
|
|
1619
|
-
});
|
|
1620
|
-
}
|
|
1621
|
-
catch (error) {
|
|
1622
|
-
return Promise.resolve({
|
|
1623
|
-
success: false,
|
|
1624
|
-
sourceTag: source_tag,
|
|
1625
|
-
targetTag: target_tag,
|
|
1626
|
-
entriesUpdated: 0,
|
|
1627
|
-
sourceDeleted: false,
|
|
1628
|
-
message: 'Tag merge failed',
|
|
1629
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
},
|
|
1633
|
-
},
|
|
1634
|
-
// Vector index management tools
|
|
1635
|
-
{
|
|
1636
|
-
name: 'rebuild_vector_index',
|
|
1637
|
-
title: 'Rebuild Vector Index',
|
|
1638
|
-
description: 'Rebuild the semantic search vector index from all existing entries',
|
|
1639
|
-
group: 'admin',
|
|
1640
|
-
inputSchema: z.object({}),
|
|
1641
|
-
outputSchema: RebuildVectorIndexOutputSchema,
|
|
1642
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
1643
|
-
handler: async (_params) => {
|
|
1644
|
-
if (!vectorManager) {
|
|
1645
|
-
return { error: 'Vector search not available' };
|
|
1646
|
-
}
|
|
1647
|
-
const indexed = await vectorManager.rebuildIndex(db, progress);
|
|
1648
|
-
return { success: true, entriesIndexed: indexed };
|
|
1649
|
-
},
|
|
1650
|
-
},
|
|
1651
|
-
{
|
|
1652
|
-
name: 'add_to_vector_index',
|
|
1653
|
-
title: 'Add Entry to Vector Index',
|
|
1654
|
-
description: 'Add a specific entry to the semantic search vector index',
|
|
1655
|
-
group: 'admin',
|
|
1656
|
-
inputSchema: z.object({ entry_id: z.number() }),
|
|
1657
|
-
outputSchema: AddToVectorIndexOutputSchema,
|
|
1658
|
-
annotations: { readOnlyHint: false, idempotentHint: true },
|
|
1659
|
-
handler: async (params) => {
|
|
1660
|
-
const { entry_id } = z.object({ entry_id: z.number() }).parse(params);
|
|
1661
|
-
if (!vectorManager) {
|
|
1662
|
-
return { error: 'Vector search not available' };
|
|
1663
|
-
}
|
|
1664
|
-
const entry = db.getEntryById(entry_id);
|
|
1665
|
-
if (!entry) {
|
|
1666
|
-
return { error: `Entry ${String(entry_id)} not found` };
|
|
1667
|
-
}
|
|
1668
|
-
const success = await vectorManager.addEntry(entry_id, entry.content);
|
|
1669
|
-
return { success, entryId: entry_id };
|
|
1670
|
-
},
|
|
1671
|
-
},
|
|
1672
|
-
{
|
|
1673
|
-
name: 'get_vector_index_stats',
|
|
1674
|
-
title: 'Get Vector Index Stats',
|
|
1675
|
-
description: 'Get statistics about the semantic search vector index',
|
|
1676
|
-
group: 'search',
|
|
1677
|
-
inputSchema: z.object({}),
|
|
1678
|
-
outputSchema: VectorStatsOutputSchema,
|
|
1679
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
1680
|
-
handler: async (_params) => {
|
|
1681
|
-
if (!vectorManager) {
|
|
1682
|
-
return { available: false, error: 'Vector search not available' };
|
|
1683
|
-
}
|
|
1684
|
-
const stats = await vectorManager.getStats();
|
|
1685
|
-
return { available: true, ...stats };
|
|
1686
|
-
},
|
|
1687
|
-
},
|
|
1688
|
-
// GitHub integration tools
|
|
1689
|
-
{
|
|
1690
|
-
name: 'get_github_issues',
|
|
1691
|
-
title: 'Get GitHub Issues',
|
|
1692
|
-
description: 'List issues from a GitHub repository. IMPORTANT: Do NOT guess owner/repo values - leave them empty to auto-detect from the current git repository.',
|
|
1693
|
-
group: 'github',
|
|
1694
|
-
inputSchema: z.object({
|
|
1695
|
-
owner: z
|
|
1696
|
-
.string()
|
|
1697
|
-
.optional()
|
|
1698
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect from git. Only specify if user explicitly provides.'),
|
|
1699
|
-
repo: z
|
|
1700
|
-
.string()
|
|
1701
|
-
.optional()
|
|
1702
|
-
.describe('Repository name - LEAVE EMPTY to auto-detect from git. Only specify if user explicitly provides.'),
|
|
1703
|
-
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
1704
|
-
limit: z.number().optional().default(20),
|
|
1705
|
-
}),
|
|
1706
|
-
outputSchema: GitHubIssuesListOutputSchema,
|
|
1707
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1708
|
-
handler: async (params) => {
|
|
1709
|
-
const input = z
|
|
1710
|
-
.object({
|
|
1711
|
-
owner: z.string().optional(),
|
|
1712
|
-
repo: z.string().optional(),
|
|
1713
|
-
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
1714
|
-
limit: z.number().optional().default(20),
|
|
1715
|
-
})
|
|
1716
|
-
.parse(params);
|
|
1717
|
-
if (!github) {
|
|
1718
|
-
return { error: 'GitHub integration not available' };
|
|
1719
|
-
}
|
|
1720
|
-
// Get owner/repo from input or from current repo
|
|
1721
|
-
const repoInfo = await github.getRepoInfo();
|
|
1722
|
-
const detectedOwner = repoInfo.owner;
|
|
1723
|
-
const detectedRepo = repoInfo.repo;
|
|
1724
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
1725
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
1726
|
-
if (!owner || !repo) {
|
|
1727
|
-
return {
|
|
1728
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
1729
|
-
requiresUserInput: true,
|
|
1730
|
-
detectedOwner,
|
|
1731
|
-
detectedRepo,
|
|
1732
|
-
instruction: 'Ask the user: "What GitHub repository would you like to query? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
1733
|
-
};
|
|
1734
|
-
}
|
|
1735
|
-
const issues = await github.getIssues(owner, repo, input.state, input.limit);
|
|
1736
|
-
return { owner, repo, detectedOwner, detectedRepo, issues, count: issues.length };
|
|
1737
|
-
},
|
|
1738
|
-
},
|
|
1739
|
-
{
|
|
1740
|
-
name: 'get_github_prs',
|
|
1741
|
-
title: 'Get GitHub Pull Requests',
|
|
1742
|
-
description: 'List pull requests from a GitHub repository. IMPORTANT: Do NOT guess owner/repo values - leave them empty to auto-detect from the current git repository.',
|
|
1743
|
-
group: 'github',
|
|
1744
|
-
inputSchema: z.object({
|
|
1745
|
-
owner: z
|
|
1746
|
-
.string()
|
|
1747
|
-
.optional()
|
|
1748
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect from git. Only specify if user explicitly provides.'),
|
|
1749
|
-
repo: z
|
|
1750
|
-
.string()
|
|
1751
|
-
.optional()
|
|
1752
|
-
.describe('Repository name - LEAVE EMPTY to auto-detect from git. Only specify if user explicitly provides.'),
|
|
1753
|
-
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
1754
|
-
limit: z.number().optional().default(20),
|
|
1755
|
-
}),
|
|
1756
|
-
outputSchema: GitHubPRsListOutputSchema,
|
|
1757
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1758
|
-
handler: async (params) => {
|
|
1759
|
-
const input = z
|
|
1760
|
-
.object({
|
|
1761
|
-
owner: z.string().optional(),
|
|
1762
|
-
repo: z.string().optional(),
|
|
1763
|
-
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
1764
|
-
limit: z.number().optional().default(20),
|
|
1765
|
-
})
|
|
1766
|
-
.parse(params);
|
|
1767
|
-
if (!github) {
|
|
1768
|
-
return { error: 'GitHub integration not available' };
|
|
1769
|
-
}
|
|
1770
|
-
const repoInfo = await github.getRepoInfo();
|
|
1771
|
-
const detectedOwner = repoInfo.owner;
|
|
1772
|
-
const detectedRepo = repoInfo.repo;
|
|
1773
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
1774
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
1775
|
-
if (!owner || !repo) {
|
|
1776
|
-
return {
|
|
1777
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
1778
|
-
requiresUserInput: true,
|
|
1779
|
-
detectedOwner,
|
|
1780
|
-
detectedRepo,
|
|
1781
|
-
instruction: 'Ask the user: "What GitHub repository would you like to query? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
1782
|
-
};
|
|
1783
|
-
}
|
|
1784
|
-
const pullRequests = await github.getPullRequests(owner, repo, input.state, input.limit);
|
|
1785
|
-
return {
|
|
1786
|
-
owner,
|
|
1787
|
-
repo,
|
|
1788
|
-
detectedOwner,
|
|
1789
|
-
detectedRepo,
|
|
1790
|
-
pullRequests,
|
|
1791
|
-
count: pullRequests.length,
|
|
1792
|
-
};
|
|
1793
|
-
},
|
|
1794
|
-
},
|
|
1795
|
-
{
|
|
1796
|
-
name: 'get_github_issue',
|
|
1797
|
-
title: 'Get GitHub Issue Details',
|
|
1798
|
-
description: 'Get detailed information about a specific GitHub issue. IMPORTANT: Do NOT guess owner/repo values - leave them empty to auto-detect from the current git repository.',
|
|
1799
|
-
group: 'github',
|
|
1800
|
-
inputSchema: z.object({
|
|
1801
|
-
issue_number: z.number(),
|
|
1802
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
1803
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
1804
|
-
}),
|
|
1805
|
-
outputSchema: GitHubIssueResultOutputSchema,
|
|
1806
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1807
|
-
handler: async (params) => {
|
|
1808
|
-
const input = z
|
|
1809
|
-
.object({
|
|
1810
|
-
issue_number: z.number(),
|
|
1811
|
-
owner: z.string().optional(),
|
|
1812
|
-
repo: z.string().optional(),
|
|
1813
|
-
})
|
|
1814
|
-
.parse(params);
|
|
1815
|
-
if (!github) {
|
|
1816
|
-
return { error: 'GitHub integration not available' };
|
|
1817
|
-
}
|
|
1818
|
-
const repoInfo = await github.getRepoInfo();
|
|
1819
|
-
const detectedOwner = repoInfo.owner;
|
|
1820
|
-
const detectedRepo = repoInfo.repo;
|
|
1821
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
1822
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
1823
|
-
if (!owner || !repo) {
|
|
1824
|
-
return {
|
|
1825
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
1826
|
-
requiresUserInput: true,
|
|
1827
|
-
detectedOwner,
|
|
1828
|
-
detectedRepo,
|
|
1829
|
-
instruction: 'Ask the user: "What GitHub repository is this issue from? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
1830
|
-
};
|
|
1831
|
-
}
|
|
1832
|
-
const issue = await github.getIssue(owner, repo, input.issue_number);
|
|
1833
|
-
if (!issue) {
|
|
1834
|
-
return {
|
|
1835
|
-
error: `Issue #${String(input.issue_number)} not found`,
|
|
1836
|
-
owner,
|
|
1837
|
-
repo,
|
|
1838
|
-
detectedOwner,
|
|
1839
|
-
detectedRepo,
|
|
1840
|
-
};
|
|
1841
|
-
}
|
|
1842
|
-
return { issue, owner, repo, detectedOwner, detectedRepo };
|
|
1843
|
-
},
|
|
1844
|
-
},
|
|
1845
|
-
{
|
|
1846
|
-
name: 'get_github_pr',
|
|
1847
|
-
title: 'Get GitHub PR Details',
|
|
1848
|
-
description: 'Get detailed information about a specific GitHub pull request. IMPORTANT: Do NOT guess owner/repo values - leave them empty to auto-detect from the current git repository.',
|
|
1849
|
-
group: 'github',
|
|
1850
|
-
inputSchema: z.object({
|
|
1851
|
-
pr_number: z.number(),
|
|
1852
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
1853
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
1854
|
-
}),
|
|
1855
|
-
outputSchema: GitHubPRResultOutputSchema,
|
|
1856
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1857
|
-
handler: async (params) => {
|
|
1858
|
-
const input = z
|
|
1859
|
-
.object({
|
|
1860
|
-
pr_number: z.number(),
|
|
1861
|
-
owner: z.string().optional(),
|
|
1862
|
-
repo: z.string().optional(),
|
|
1863
|
-
})
|
|
1864
|
-
.parse(params);
|
|
1865
|
-
if (!github) {
|
|
1866
|
-
return { error: 'GitHub integration not available' };
|
|
1867
|
-
}
|
|
1868
|
-
const repoInfo = await github.getRepoInfo();
|
|
1869
|
-
const detectedOwner = repoInfo.owner;
|
|
1870
|
-
const detectedRepo = repoInfo.repo;
|
|
1871
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
1872
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
1873
|
-
if (!owner || !repo) {
|
|
1874
|
-
return {
|
|
1875
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
1876
|
-
requiresUserInput: true,
|
|
1877
|
-
detectedOwner,
|
|
1878
|
-
detectedRepo,
|
|
1879
|
-
instruction: 'Ask the user: "What GitHub repository is this PR from? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
1880
|
-
};
|
|
1881
|
-
}
|
|
1882
|
-
const pullRequest = await github.getPullRequest(owner, repo, input.pr_number);
|
|
1883
|
-
if (!pullRequest) {
|
|
1884
|
-
return {
|
|
1885
|
-
error: `PR #${String(input.pr_number)} not found`,
|
|
1886
|
-
owner,
|
|
1887
|
-
repo,
|
|
1888
|
-
detectedOwner,
|
|
1889
|
-
detectedRepo,
|
|
1890
|
-
};
|
|
1891
|
-
}
|
|
1892
|
-
return { pullRequest, owner, repo, detectedOwner, detectedRepo };
|
|
1893
|
-
},
|
|
1894
|
-
},
|
|
1895
|
-
{
|
|
1896
|
-
name: 'get_github_context',
|
|
1897
|
-
title: 'Get GitHub Repository Context',
|
|
1898
|
-
description: 'Get current repository context including branch, open issues, and open PRs. Only counts OPEN items (closed items excluded).',
|
|
1899
|
-
group: 'github',
|
|
1900
|
-
inputSchema: z.object({}),
|
|
1901
|
-
outputSchema: GitHubContextOutputSchema,
|
|
1902
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1903
|
-
handler: async (_params) => {
|
|
1904
|
-
if (!github) {
|
|
1905
|
-
return { error: 'GitHub integration not available' };
|
|
1906
|
-
}
|
|
1907
|
-
const context = await github.getRepoContext();
|
|
1908
|
-
return {
|
|
1909
|
-
repoName: context.repoName,
|
|
1910
|
-
branch: context.branch,
|
|
1911
|
-
commit: context.commit,
|
|
1912
|
-
remoteUrl: context.remoteUrl,
|
|
1913
|
-
issues: context.issues,
|
|
1914
|
-
pullRequests: context.pullRequests,
|
|
1915
|
-
issueCount: context.issues.length,
|
|
1916
|
-
prCount: context.pullRequests.length,
|
|
1917
|
-
};
|
|
1918
|
-
},
|
|
1919
|
-
},
|
|
1920
|
-
// Kanban tools (GitHub Projects v2)
|
|
1921
|
-
{
|
|
1922
|
-
name: 'get_kanban_board',
|
|
1923
|
-
title: 'Get Kanban Board',
|
|
1924
|
-
description: 'View a GitHub Project v2 as a Kanban board with items grouped by Status column. Returns all columns with their items.',
|
|
1925
|
-
group: 'github',
|
|
1926
|
-
inputSchema: z.object({
|
|
1927
|
-
project_number: z.number().describe('GitHub Project number (from the project URL)'),
|
|
1928
|
-
owner: z
|
|
1929
|
-
.string()
|
|
1930
|
-
.optional()
|
|
1931
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect from git'),
|
|
1932
|
-
}),
|
|
1933
|
-
outputSchema: KanbanBoardOutputSchema,
|
|
1934
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
1935
|
-
handler: async (params) => {
|
|
1936
|
-
const input = z
|
|
1937
|
-
.object({
|
|
1938
|
-
project_number: z.number(),
|
|
1939
|
-
owner: z.string().optional(),
|
|
1940
|
-
})
|
|
1941
|
-
.parse(params);
|
|
1942
|
-
if (!github) {
|
|
1943
|
-
return { error: 'GitHub integration not available' };
|
|
1944
|
-
}
|
|
1945
|
-
// Get owner from input or from current repo
|
|
1946
|
-
const repoInfo = await github.getRepoInfo();
|
|
1947
|
-
const detectedOwner = repoInfo.owner;
|
|
1948
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
1949
|
-
if (!owner) {
|
|
1950
|
-
return {
|
|
1951
|
-
error: 'STOP: Could not auto-detect repository owner. DO NOT GUESS. You MUST ask the user to provide the GitHub owner.',
|
|
1952
|
-
requiresUserInput: true,
|
|
1953
|
-
detectedOwner,
|
|
1954
|
-
instruction: 'Ask the user: "What GitHub username or organization owns this project?"',
|
|
1955
|
-
};
|
|
1956
|
-
}
|
|
1957
|
-
const repo = repoInfo.repo ?? undefined;
|
|
1958
|
-
const board = await github.getProjectKanban(owner, input.project_number, repo);
|
|
1959
|
-
if (!board) {
|
|
1960
|
-
return {
|
|
1961
|
-
error: `Project #${String(input.project_number)} not found or Status field not configured`,
|
|
1962
|
-
owner,
|
|
1963
|
-
repo,
|
|
1964
|
-
hint: 'Ensure the project exists and has a "Status" single-select field. Projects can be at user, repository, or organization level.',
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
|
-
return {
|
|
1968
|
-
...board,
|
|
1969
|
-
owner,
|
|
1970
|
-
detectedOwner,
|
|
1971
|
-
detectedRepo: repo,
|
|
1972
|
-
};
|
|
1973
|
-
},
|
|
1974
|
-
},
|
|
1975
|
-
{
|
|
1976
|
-
name: 'move_kanban_item',
|
|
1977
|
-
title: 'Move Kanban Item',
|
|
1978
|
-
description: 'Move a project item to a different Status column. Use get_kanban_board first to get the item_id and exact status names. Status matching is case-insensitive.',
|
|
1979
|
-
group: 'github',
|
|
1980
|
-
inputSchema: z.object({
|
|
1981
|
-
project_number: z.number().describe('GitHub Project number'),
|
|
1982
|
-
item_id: z.string().describe('Project item node ID (from get_kanban_board)'),
|
|
1983
|
-
target_status: z
|
|
1984
|
-
.string()
|
|
1985
|
-
.describe('Target status name (e.g., "Done", "In Progress")'),
|
|
1986
|
-
owner: z
|
|
1987
|
-
.string()
|
|
1988
|
-
.optional()
|
|
1989
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
1990
|
-
}),
|
|
1991
|
-
outputSchema: MoveKanbanItemOutputSchema,
|
|
1992
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
1993
|
-
handler: async (params) => {
|
|
1994
|
-
const input = z
|
|
1995
|
-
.object({
|
|
1996
|
-
project_number: z.number(),
|
|
1997
|
-
item_id: z.string(),
|
|
1998
|
-
target_status: z.string(),
|
|
1999
|
-
owner: z.string().optional(),
|
|
2000
|
-
})
|
|
2001
|
-
.parse(params);
|
|
2002
|
-
if (!github) {
|
|
2003
|
-
return { error: 'GitHub integration not available' };
|
|
2004
|
-
}
|
|
2005
|
-
// Get owner from input or from current repo
|
|
2006
|
-
const repoInfo = await github.getRepoInfo();
|
|
2007
|
-
const detectedOwner = repoInfo.owner;
|
|
2008
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
2009
|
-
if (!owner) {
|
|
2010
|
-
return {
|
|
2011
|
-
error: 'STOP: Could not auto-detect repository owner. DO NOT GUESS.',
|
|
2012
|
-
requiresUserInput: true,
|
|
2013
|
-
};
|
|
2014
|
-
}
|
|
2015
|
-
// First, get the board to find projectId, statusFieldId, and target statusOptionId
|
|
2016
|
-
const repo = repoInfo.repo ?? undefined;
|
|
2017
|
-
const board = await github.getProjectKanban(owner, input.project_number, repo);
|
|
2018
|
-
if (!board) {
|
|
2019
|
-
return {
|
|
2020
|
-
error: `Project #${String(input.project_number)} not found`,
|
|
2021
|
-
};
|
|
2022
|
-
}
|
|
2023
|
-
// Find the target status option
|
|
2024
|
-
const targetOption = board.statusOptions.find((opt) => opt.name.toLowerCase() === input.target_status.toLowerCase());
|
|
2025
|
-
if (!targetOption) {
|
|
2026
|
-
return {
|
|
2027
|
-
error: `Status "${input.target_status}" not found in project`,
|
|
2028
|
-
availableStatuses: board.statusOptions.map((opt) => opt.name),
|
|
2029
|
-
hint: 'Use one of the available status names listed above.',
|
|
2030
|
-
};
|
|
2031
|
-
}
|
|
2032
|
-
// Move the item
|
|
2033
|
-
const result = await github.moveProjectItem(board.projectId, input.item_id, board.statusFieldId, targetOption.id);
|
|
2034
|
-
if (!result.success) {
|
|
2035
|
-
return {
|
|
2036
|
-
success: false,
|
|
2037
|
-
error: result.error,
|
|
2038
|
-
targetStatus: input.target_status,
|
|
2039
|
-
};
|
|
2040
|
-
}
|
|
2041
|
-
return {
|
|
2042
|
-
success: true,
|
|
2043
|
-
itemId: input.item_id,
|
|
2044
|
-
newStatus: input.target_status,
|
|
2045
|
-
projectNumber: input.project_number,
|
|
2046
|
-
message: `Item moved to "${input.target_status}"`,
|
|
2047
|
-
};
|
|
2048
|
-
},
|
|
2049
|
-
},
|
|
2050
|
-
{
|
|
2051
|
-
name: 'create_github_issue_with_entry',
|
|
2052
|
-
title: 'Create GitHub Issue with Journal Entry',
|
|
2053
|
-
description: 'Create a GitHub issue AND automatically create a linked journal entry documenting the issue creation.',
|
|
2054
|
-
group: 'github',
|
|
2055
|
-
inputSchema: z.object({
|
|
2056
|
-
title: z.string().min(1).describe('Issue title'),
|
|
2057
|
-
body: z.string().optional().describe('Issue body/description'),
|
|
2058
|
-
labels: z.array(z.string()).optional().describe('Labels to apply'),
|
|
2059
|
-
assignees: z.array(z.string()).optional().describe('Users to assign'),
|
|
2060
|
-
milestone_number: z
|
|
2061
|
-
.number()
|
|
2062
|
-
.optional()
|
|
2063
|
-
.describe('Milestone number to assign this issue to'),
|
|
2064
|
-
project_number: z
|
|
2065
|
-
.number()
|
|
2066
|
-
.optional()
|
|
2067
|
-
.describe('GitHub Project number to add this issue to'),
|
|
2068
|
-
initial_status: z
|
|
2069
|
-
.string()
|
|
2070
|
-
.optional()
|
|
2071
|
-
.describe('Initial status column (e.g., "Backlog", "Ready"). Defaults to "Backlog" when adding to a project.'),
|
|
2072
|
-
owner: z
|
|
2073
|
-
.string()
|
|
2074
|
-
.optional()
|
|
2075
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
2076
|
-
repo: z
|
|
2077
|
-
.string()
|
|
2078
|
-
.optional()
|
|
2079
|
-
.describe('Repository name - LEAVE EMPTY to auto-detect'),
|
|
2080
|
-
entry_content: z
|
|
2081
|
-
.string()
|
|
2082
|
-
.optional()
|
|
2083
|
-
.describe('Custom journal content (defaults to auto-generated summary)'),
|
|
2084
|
-
tags: z.array(z.string()).optional().describe('Journal entry tags'),
|
|
2085
|
-
}),
|
|
2086
|
-
outputSchema: CreateGitHubIssueWithEntryOutputSchema,
|
|
2087
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2088
|
-
handler: async (params) => {
|
|
2089
|
-
const input = z
|
|
2090
|
-
.object({
|
|
2091
|
-
title: z.string().min(1),
|
|
2092
|
-
body: z.string().optional(),
|
|
2093
|
-
labels: z.array(z.string()).optional(),
|
|
2094
|
-
assignees: z.array(z.string()).optional(),
|
|
2095
|
-
milestone_number: z.number().optional(),
|
|
2096
|
-
project_number: z.number().optional(),
|
|
2097
|
-
initial_status: z.string().optional(),
|
|
2098
|
-
owner: z.string().optional(),
|
|
2099
|
-
repo: z.string().optional(),
|
|
2100
|
-
entry_content: z.string().optional(),
|
|
2101
|
-
tags: z.array(z.string()).optional(),
|
|
2102
|
-
})
|
|
2103
|
-
.parse(params);
|
|
2104
|
-
if (!github) {
|
|
2105
|
-
return { error: 'GitHub integration not available' };
|
|
2106
|
-
}
|
|
2107
|
-
// Get owner/repo from input or from current repo
|
|
2108
|
-
const repoInfo = await github.getRepoInfo();
|
|
2109
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2110
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2111
|
-
if (!owner || !repo) {
|
|
2112
|
-
return {
|
|
2113
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2114
|
-
requiresUserInput: true,
|
|
2115
|
-
detected: { owner, repo },
|
|
2116
|
-
};
|
|
2117
|
-
}
|
|
2118
|
-
// Create the GitHub issue
|
|
2119
|
-
const issue = await github.createIssue(owner, repo, input.title, input.body, input.labels, input.assignees, input.milestone_number);
|
|
2120
|
-
if (!issue) {
|
|
2121
|
-
return {
|
|
2122
|
-
error: 'Failed to create GitHub issue. Check GITHUB_TOKEN permissions.',
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
const projectNumber = input.project_number ?? context.config?.defaultProjectNumber;
|
|
2126
|
-
// Add to project if requested or default configured
|
|
2127
|
-
let projectResult = undefined;
|
|
2128
|
-
if (projectNumber !== undefined && issue.nodeId) {
|
|
2129
|
-
try {
|
|
2130
|
-
// Get project ID (needed for mutation)
|
|
2131
|
-
const board = await github.getProjectKanban(owner, projectNumber, repo);
|
|
2132
|
-
if (board) {
|
|
2133
|
-
const added = await github.addProjectItem(board.projectId, issue.nodeId);
|
|
2134
|
-
if (added.success) {
|
|
2135
|
-
// Set initial status if provided
|
|
2136
|
-
let statusResult = undefined;
|
|
2137
|
-
// Default to "Backlog" when adding to project without explicit status
|
|
2138
|
-
const initialStatus = input.initial_status ?? 'Backlog';
|
|
2139
|
-
if (initialStatus && added.itemId) {
|
|
2140
|
-
// Find the status option (case-insensitive)
|
|
2141
|
-
const statusOption = board.statusOptions.find((opt) => opt.name.toLowerCase() === initialStatus.toLowerCase());
|
|
2142
|
-
if (statusOption) {
|
|
2143
|
-
const moveResult = await github.moveProjectItem(board.projectId, added.itemId, board.statusFieldId, statusOption.id);
|
|
2144
|
-
if (moveResult.success) {
|
|
2145
|
-
statusResult = { status: statusOption.name, set: true };
|
|
2146
|
-
}
|
|
2147
|
-
else {
|
|
2148
|
-
statusResult = {
|
|
2149
|
-
status: initialStatus,
|
|
2150
|
-
set: false,
|
|
2151
|
-
error: moveResult.error,
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
else {
|
|
2156
|
-
statusResult = {
|
|
2157
|
-
status: initialStatus,
|
|
2158
|
-
set: false,
|
|
2159
|
-
error: `Status "${initialStatus}" not found. Available: ${board.statusOptions.map((o) => o.name).join(', ')}`,
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
|
-
}
|
|
2163
|
-
projectResult = {
|
|
2164
|
-
projectNumber: projectNumber,
|
|
2165
|
-
added: true,
|
|
2166
|
-
message: `Added to project #${projectNumber}` +
|
|
2167
|
-
(statusResult?.set ? ` (${statusResult.status})` : ''),
|
|
2168
|
-
initialStatus: statusResult,
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
else {
|
|
2172
|
-
projectResult = {
|
|
2173
|
-
projectNumber: projectNumber,
|
|
2174
|
-
added: false,
|
|
2175
|
-
error: added.error,
|
|
2176
|
-
};
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
else {
|
|
2180
|
-
projectResult = {
|
|
2181
|
-
projectNumber: projectNumber,
|
|
2182
|
-
added: false,
|
|
2183
|
-
error: `Project #${projectNumber} not found`,
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2187
|
-
catch (error) {
|
|
2188
|
-
projectResult = {
|
|
2189
|
-
projectNumber: projectNumber,
|
|
2190
|
-
added: false,
|
|
2191
|
-
error: error instanceof Error ? error.message : String(error),
|
|
2192
|
-
};
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
// Create linked journal entry
|
|
2196
|
-
const entryContent = input.entry_content ??
|
|
2197
|
-
`Created GitHub issue #${String(issue.number)}: ${issue.title}\n\n` +
|
|
2198
|
-
`URL: ${issue.url}\n` +
|
|
2199
|
-
(projectNumber !== undefined ? `Project: #${projectNumber}\n` : '') +
|
|
2200
|
-
(input.body
|
|
2201
|
-
? `\nDescription: ${input.body.slice(0, 200)}${input.body.length > 200 ? '...' : ''}`
|
|
2202
|
-
: '');
|
|
2203
|
-
const entry = db.createEntry({
|
|
2204
|
-
content: entryContent,
|
|
2205
|
-
entryType: 'planning',
|
|
2206
|
-
tags: input.tags ?? ['github', 'issue-created'],
|
|
2207
|
-
isPersonal: false,
|
|
2208
|
-
significanceType: null,
|
|
2209
|
-
issueNumber: issue.number,
|
|
2210
|
-
issueUrl: issue.url,
|
|
2211
|
-
projectNumber: projectNumber,
|
|
2212
|
-
});
|
|
2213
|
-
return {
|
|
2214
|
-
success: true,
|
|
2215
|
-
issue: {
|
|
2216
|
-
number: issue.number,
|
|
2217
|
-
title: issue.title,
|
|
2218
|
-
url: issue.url,
|
|
2219
|
-
},
|
|
2220
|
-
project: projectResult,
|
|
2221
|
-
journalEntry: {
|
|
2222
|
-
id: entry.id,
|
|
2223
|
-
linkedToIssue: issue.number,
|
|
2224
|
-
},
|
|
2225
|
-
message: `Created issue #${String(issue.number)}` +
|
|
2226
|
-
(projectResult?.added ? ` (added to Project #${projectNumber})` : '') +
|
|
2227
|
-
` and journal entry #${String(entry.id)}`,
|
|
2228
|
-
};
|
|
2229
|
-
},
|
|
2230
|
-
},
|
|
2231
|
-
{
|
|
2232
|
-
name: 'close_github_issue_with_entry',
|
|
2233
|
-
title: 'Close GitHub Issue with Resolution Entry',
|
|
2234
|
-
description: 'Close a GitHub issue AND create a journal entry documenting the resolution.',
|
|
2235
|
-
group: 'github',
|
|
2236
|
-
inputSchema: z.object({
|
|
2237
|
-
issue_number: z.number().describe('Issue number to close'),
|
|
2238
|
-
resolution_notes: z
|
|
2239
|
-
.string()
|
|
2240
|
-
.optional()
|
|
2241
|
-
.describe('Notes about how the issue was resolved'),
|
|
2242
|
-
comment: z
|
|
2243
|
-
.string()
|
|
2244
|
-
.optional()
|
|
2245
|
-
.describe('Comment to add to the issue before closing'),
|
|
2246
|
-
move_to_done: z
|
|
2247
|
-
.boolean()
|
|
2248
|
-
.optional()
|
|
2249
|
-
.default(false)
|
|
2250
|
-
.describe('Move the associated Kanban item to "Done" column'),
|
|
2251
|
-
project_number: z
|
|
2252
|
-
.number()
|
|
2253
|
-
.optional()
|
|
2254
|
-
.describe('GitHub Project number (required if move_to_done is true, or uses DEFAULT_PROJECT_NUMBER)'),
|
|
2255
|
-
owner: z
|
|
2256
|
-
.string()
|
|
2257
|
-
.optional()
|
|
2258
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
2259
|
-
repo: z
|
|
2260
|
-
.string()
|
|
2261
|
-
.optional()
|
|
2262
|
-
.describe('Repository name - LEAVE EMPTY to auto-detect'),
|
|
2263
|
-
tags: z.array(z.string()).optional().describe('Journal entry tags'),
|
|
2264
|
-
}),
|
|
2265
|
-
outputSchema: CloseGitHubIssueWithEntryOutputSchema,
|
|
2266
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2267
|
-
handler: async (params) => {
|
|
2268
|
-
const input = z
|
|
2269
|
-
.object({
|
|
2270
|
-
issue_number: z.number(),
|
|
2271
|
-
resolution_notes: z.string().optional(),
|
|
2272
|
-
comment: z.string().optional(),
|
|
2273
|
-
move_to_done: z.boolean().optional().default(false),
|
|
2274
|
-
project_number: z.number().optional(),
|
|
2275
|
-
owner: z.string().optional(),
|
|
2276
|
-
repo: z.string().optional(),
|
|
2277
|
-
tags: z.array(z.string()).optional(),
|
|
2278
|
-
})
|
|
2279
|
-
.parse(params);
|
|
2280
|
-
if (!github) {
|
|
2281
|
-
return { error: 'GitHub integration not available' };
|
|
2282
|
-
}
|
|
2283
|
-
// Get owner/repo from input or from current repo
|
|
2284
|
-
const repoInfo = await github.getRepoInfo();
|
|
2285
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2286
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2287
|
-
if (!owner || !repo) {
|
|
2288
|
-
return {
|
|
2289
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2290
|
-
requiresUserInput: true,
|
|
2291
|
-
detected: { owner, repo },
|
|
2292
|
-
};
|
|
2293
|
-
}
|
|
2294
|
-
// Get issue details before closing
|
|
2295
|
-
const issueDetails = await github.getIssue(owner, repo, input.issue_number);
|
|
2296
|
-
if (!issueDetails) {
|
|
2297
|
-
return { error: `Issue #${String(input.issue_number)} not found` };
|
|
2298
|
-
}
|
|
2299
|
-
if (issueDetails.state === 'CLOSED') {
|
|
2300
|
-
return { error: `Issue #${String(input.issue_number)} is already closed` };
|
|
2301
|
-
}
|
|
2302
|
-
// Close the issue
|
|
2303
|
-
const result = await github.closeIssue(owner, repo, input.issue_number, input.comment);
|
|
2304
|
-
if (!result) {
|
|
2305
|
-
return {
|
|
2306
|
-
error: 'Failed to close GitHub issue. Check GITHUB_TOKEN permissions.',
|
|
2307
|
-
};
|
|
2308
|
-
}
|
|
2309
|
-
// Move Kanban item to "Done" if requested
|
|
2310
|
-
let kanbanResult;
|
|
2311
|
-
if (input.move_to_done) {
|
|
2312
|
-
const projectNum = input.project_number ?? context.config?.defaultProjectNumber;
|
|
2313
|
-
if (projectNum === undefined) {
|
|
2314
|
-
kanbanResult = {
|
|
2315
|
-
moved: false,
|
|
2316
|
-
error: 'project_number required when move_to_done is true',
|
|
2317
|
-
};
|
|
2318
|
-
}
|
|
2319
|
-
else {
|
|
2320
|
-
try {
|
|
2321
|
-
const board = await github.getProjectKanban(owner, projectNum, repo);
|
|
2322
|
-
if (!board) {
|
|
2323
|
-
kanbanResult = {
|
|
2324
|
-
moved: false,
|
|
2325
|
-
error: `Project #${projectNum} not found`,
|
|
2326
|
-
projectNumber: projectNum,
|
|
2327
|
-
};
|
|
2328
|
-
}
|
|
2329
|
-
else {
|
|
2330
|
-
// Find the item by issue number
|
|
2331
|
-
const item = board.columns
|
|
2332
|
-
.flatMap((c) => c.items)
|
|
2333
|
-
.find((i) => i.type === 'ISSUE' && i.number === input.issue_number);
|
|
2334
|
-
if (!item) {
|
|
2335
|
-
kanbanResult = {
|
|
2336
|
-
moved: false,
|
|
2337
|
-
error: 'Issue not found on project board',
|
|
2338
|
-
projectNumber: projectNum,
|
|
2339
|
-
};
|
|
2340
|
-
}
|
|
2341
|
-
else {
|
|
2342
|
-
const doneOption = board.statusOptions.find((opt) => opt.name.toLowerCase() === 'done');
|
|
2343
|
-
if (!doneOption) {
|
|
2344
|
-
kanbanResult = {
|
|
2345
|
-
moved: false,
|
|
2346
|
-
error: '"Done" status column not found on board',
|
|
2347
|
-
projectNumber: projectNum,
|
|
2348
|
-
};
|
|
2349
|
-
}
|
|
2350
|
-
else {
|
|
2351
|
-
const moveResult = await github.moveProjectItem(board.projectId, item.id, board.statusFieldId, doneOption.id);
|
|
2352
|
-
kanbanResult = {
|
|
2353
|
-
moved: moveResult.success,
|
|
2354
|
-
error: moveResult.error,
|
|
2355
|
-
projectNumber: projectNum,
|
|
2356
|
-
};
|
|
2357
|
-
}
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
|
-
catch (err) {
|
|
2362
|
-
kanbanResult = {
|
|
2363
|
-
moved: false,
|
|
2364
|
-
error: err instanceof Error ? err.message : String(err),
|
|
2365
|
-
projectNumber: input.project_number ?? context.config?.defaultProjectNumber,
|
|
2366
|
-
};
|
|
2367
|
-
}
|
|
2368
|
-
}
|
|
2369
|
-
}
|
|
2370
|
-
// Create resolution journal entry
|
|
2371
|
-
const entryContent = `Closed GitHub issue #${String(input.issue_number)}: ${issueDetails.title}\n\n` +
|
|
2372
|
-
`URL: ${issueDetails.url}\n` +
|
|
2373
|
-
(input.resolution_notes ? `\nResolution: ${input.resolution_notes}` : '');
|
|
2374
|
-
const entry = db.createEntry({
|
|
2375
|
-
content: entryContent,
|
|
2376
|
-
entryType: 'bug_fix',
|
|
2377
|
-
tags: input.tags ?? ['github', 'issue-closed', 'resolution'],
|
|
2378
|
-
isPersonal: false,
|
|
2379
|
-
significanceType: 'blocker_resolved',
|
|
2380
|
-
issueNumber: input.issue_number,
|
|
2381
|
-
issueUrl: issueDetails.url,
|
|
2382
|
-
});
|
|
2383
|
-
return {
|
|
2384
|
-
success: true,
|
|
2385
|
-
issue: {
|
|
2386
|
-
number: input.issue_number,
|
|
2387
|
-
title: issueDetails.title,
|
|
2388
|
-
url: result.url,
|
|
2389
|
-
previousState: 'OPEN',
|
|
2390
|
-
newState: 'CLOSED',
|
|
2391
|
-
},
|
|
2392
|
-
journalEntry: {
|
|
2393
|
-
id: entry.id,
|
|
2394
|
-
linkedToIssue: input.issue_number,
|
|
2395
|
-
significanceType: 'blocker_resolved',
|
|
2396
|
-
},
|
|
2397
|
-
kanban: kanbanResult,
|
|
2398
|
-
message: `Closed issue #${String(input.issue_number)} and created resolution entry #${String(entry.id)}` +
|
|
2399
|
-
(kanbanResult?.moved ? ' and moved to Done' : ''),
|
|
2400
|
-
};
|
|
2401
|
-
},
|
|
2402
|
-
},
|
|
2403
|
-
// Milestone tools
|
|
2404
|
-
{
|
|
2405
|
-
name: 'get_github_milestones',
|
|
2406
|
-
title: 'List GitHub Milestones',
|
|
2407
|
-
description: 'List GitHub milestones for the repository with completion percentages and due dates.',
|
|
2408
|
-
group: 'github',
|
|
2409
|
-
inputSchema: z.object({
|
|
2410
|
-
state: z
|
|
2411
|
-
.enum(['open', 'closed', 'all'])
|
|
2412
|
-
.optional()
|
|
2413
|
-
.default('open')
|
|
2414
|
-
.describe('Filter by state (default: open)'),
|
|
2415
|
-
limit: z
|
|
2416
|
-
.number()
|
|
2417
|
-
.optional()
|
|
2418
|
-
.default(20)
|
|
2419
|
-
.describe('Max milestones to return (default: 20)'),
|
|
2420
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2421
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2422
|
-
}),
|
|
2423
|
-
outputSchema: GitHubMilestonesListOutputSchema,
|
|
2424
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2425
|
-
handler: async (params) => {
|
|
2426
|
-
const input = z
|
|
2427
|
-
.object({
|
|
2428
|
-
state: z.enum(['open', 'closed', 'all']).optional().default('open'),
|
|
2429
|
-
limit: z.number().optional().default(20),
|
|
2430
|
-
owner: z.string().optional(),
|
|
2431
|
-
repo: z.string().optional(),
|
|
2432
|
-
})
|
|
2433
|
-
.parse(params);
|
|
2434
|
-
if (!github) {
|
|
2435
|
-
return { error: 'GitHub integration not available' };
|
|
2436
|
-
}
|
|
2437
|
-
const repoInfo = await github.getRepoInfo();
|
|
2438
|
-
const detectedOwner = repoInfo.owner;
|
|
2439
|
-
const detectedRepo = repoInfo.repo;
|
|
2440
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
2441
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
2442
|
-
if (!owner || !repo) {
|
|
2443
|
-
return {
|
|
2444
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2445
|
-
requiresUserInput: true,
|
|
2446
|
-
detectedOwner,
|
|
2447
|
-
detectedRepo,
|
|
2448
|
-
instruction: 'Ask the user: "What GitHub repository should I list milestones for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2449
|
-
};
|
|
2450
|
-
}
|
|
2451
|
-
const milestones = await github.getMilestones(owner, repo, input.state, input.limit);
|
|
2452
|
-
const milestonesWithPercentage = milestones.map((ms) => {
|
|
2453
|
-
const total = ms.openIssues + ms.closedIssues;
|
|
2454
|
-
const completionPercentage = total > 0 ? Math.round((ms.closedIssues / total) * 100) : 0;
|
|
2455
|
-
return { ...ms, completionPercentage };
|
|
2456
|
-
});
|
|
2457
|
-
return {
|
|
2458
|
-
milestones: milestonesWithPercentage,
|
|
2459
|
-
count: milestonesWithPercentage.length,
|
|
2460
|
-
owner,
|
|
2461
|
-
repo,
|
|
2462
|
-
detectedOwner,
|
|
2463
|
-
detectedRepo,
|
|
2464
|
-
};
|
|
2465
|
-
},
|
|
2466
|
-
},
|
|
2467
|
-
{
|
|
2468
|
-
name: 'get_github_milestone',
|
|
2469
|
-
title: 'Get GitHub Milestone Details',
|
|
2470
|
-
description: 'Get detailed information about a specific GitHub milestone including progress and linked issue counts.',
|
|
2471
|
-
group: 'github',
|
|
2472
|
-
inputSchema: z.object({
|
|
2473
|
-
milestone_number: z.number().describe('Milestone number'),
|
|
2474
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2475
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect from git'),
|
|
2476
|
-
}),
|
|
2477
|
-
outputSchema: GitHubMilestoneResultOutputSchema,
|
|
2478
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2479
|
-
handler: async (params) => {
|
|
2480
|
-
const input = z
|
|
2481
|
-
.object({
|
|
2482
|
-
milestone_number: z.number(),
|
|
2483
|
-
owner: z.string().optional(),
|
|
2484
|
-
repo: z.string().optional(),
|
|
2485
|
-
})
|
|
2486
|
-
.parse(params);
|
|
2487
|
-
if (!github) {
|
|
2488
|
-
return { error: 'GitHub integration not available' };
|
|
2489
|
-
}
|
|
2490
|
-
const repoInfo = await github.getRepoInfo();
|
|
2491
|
-
const detectedOwner = repoInfo.owner;
|
|
2492
|
-
const detectedRepo = repoInfo.repo;
|
|
2493
|
-
const owner = input.owner ?? detectedOwner ?? undefined;
|
|
2494
|
-
const repo = input.repo ?? detectedRepo ?? undefined;
|
|
2495
|
-
if (!owner || !repo) {
|
|
2496
|
-
return {
|
|
2497
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2498
|
-
requiresUserInput: true,
|
|
2499
|
-
detectedOwner,
|
|
2500
|
-
detectedRepo,
|
|
2501
|
-
instruction: 'Ask the user: "What GitHub repository is this milestone from? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2502
|
-
};
|
|
2503
|
-
}
|
|
2504
|
-
const milestone = await github.getMilestone(owner, repo, input.milestone_number);
|
|
2505
|
-
if (!milestone) {
|
|
2506
|
-
return {
|
|
2507
|
-
error: `Milestone #${String(input.milestone_number)} not found`,
|
|
2508
|
-
owner,
|
|
2509
|
-
repo,
|
|
2510
|
-
detectedOwner,
|
|
2511
|
-
detectedRepo,
|
|
2512
|
-
};
|
|
2513
|
-
}
|
|
2514
|
-
const total = milestone.openIssues + milestone.closedIssues;
|
|
2515
|
-
const completionPercentage = total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0;
|
|
2516
|
-
return {
|
|
2517
|
-
milestone: { ...milestone, completionPercentage },
|
|
2518
|
-
owner,
|
|
2519
|
-
repo,
|
|
2520
|
-
detectedOwner,
|
|
2521
|
-
detectedRepo,
|
|
2522
|
-
};
|
|
2523
|
-
},
|
|
2524
|
-
},
|
|
2525
|
-
{
|
|
2526
|
-
name: 'create_github_milestone',
|
|
2527
|
-
title: 'Create GitHub Milestone',
|
|
2528
|
-
description: 'Create a new GitHub milestone for tracking progress toward a project goal.',
|
|
2529
|
-
group: 'github',
|
|
2530
|
-
inputSchema: z.object({
|
|
2531
|
-
title: z.string().min(1).describe('Milestone title'),
|
|
2532
|
-
description: z.string().optional().describe('Milestone description'),
|
|
2533
|
-
due_on: z.string().optional().describe('Due date in YYYY-MM-DD format (optional)'),
|
|
2534
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2535
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2536
|
-
}),
|
|
2537
|
-
outputSchema: CreateMilestoneOutputSchema,
|
|
2538
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2539
|
-
handler: async (params) => {
|
|
2540
|
-
const input = z
|
|
2541
|
-
.object({
|
|
2542
|
-
title: z.string().min(1),
|
|
2543
|
-
description: z.string().optional(),
|
|
2544
|
-
due_on: z.string().optional(),
|
|
2545
|
-
owner: z.string().optional(),
|
|
2546
|
-
repo: z.string().optional(),
|
|
2547
|
-
})
|
|
2548
|
-
.parse(params);
|
|
2549
|
-
if (!github) {
|
|
2550
|
-
return { error: 'GitHub integration not available' };
|
|
2551
|
-
}
|
|
2552
|
-
const repoInfo = await github.getRepoInfo();
|
|
2553
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2554
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2555
|
-
if (!owner || !repo) {
|
|
2556
|
-
return {
|
|
2557
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2558
|
-
requiresUserInput: true,
|
|
2559
|
-
instruction: 'Ask the user: "What GitHub repository should I create the milestone in?"',
|
|
2560
|
-
};
|
|
2561
|
-
}
|
|
2562
|
-
// Format due_on to ISO 8601 if provided (GitHub expects YYYY-MM-DDTHH:MM:SSZ)
|
|
2563
|
-
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined;
|
|
2564
|
-
const milestone = await github.createMilestone(owner, repo, input.title, input.description, dueOn);
|
|
2565
|
-
if (!milestone) {
|
|
2566
|
-
return {
|
|
2567
|
-
error: 'Failed to create milestone. Check GITHUB_TOKEN permissions.',
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
return {
|
|
2571
|
-
success: true,
|
|
2572
|
-
milestone: { ...milestone, completionPercentage: 0 },
|
|
2573
|
-
message: `Created milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
2574
|
-
};
|
|
2575
|
-
},
|
|
2576
|
-
},
|
|
2577
|
-
{
|
|
2578
|
-
name: 'update_github_milestone',
|
|
2579
|
-
title: 'Update GitHub Milestone',
|
|
2580
|
-
description: 'Update a GitHub milestone (title, description, due date, or state). Use state "closed" to close a completed milestone.',
|
|
2581
|
-
group: 'github',
|
|
2582
|
-
inputSchema: z.object({
|
|
2583
|
-
milestone_number: z.number().describe('Milestone number to update'),
|
|
2584
|
-
title: z.string().optional().describe('New title'),
|
|
2585
|
-
description: z.string().optional().describe('New description'),
|
|
2586
|
-
due_on: z.string().optional().describe('New due date in YYYY-MM-DD format'),
|
|
2587
|
-
state: z
|
|
2588
|
-
.enum(['open', 'closed'])
|
|
2589
|
-
.optional()
|
|
2590
|
-
.describe('Set to "closed" to close the milestone'),
|
|
2591
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2592
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2593
|
-
}),
|
|
2594
|
-
outputSchema: UpdateMilestoneOutputSchema,
|
|
2595
|
-
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: true },
|
|
2596
|
-
handler: async (params) => {
|
|
2597
|
-
const input = z
|
|
2598
|
-
.object({
|
|
2599
|
-
milestone_number: z.number(),
|
|
2600
|
-
title: z.string().optional(),
|
|
2601
|
-
description: z.string().optional(),
|
|
2602
|
-
due_on: z.string().optional(),
|
|
2603
|
-
state: z.enum(['open', 'closed']).optional(),
|
|
2604
|
-
owner: z.string().optional(),
|
|
2605
|
-
repo: z.string().optional(),
|
|
2606
|
-
})
|
|
2607
|
-
.parse(params);
|
|
2608
|
-
if (!github) {
|
|
2609
|
-
return { error: 'GitHub integration not available' };
|
|
2610
|
-
}
|
|
2611
|
-
const repoInfo = await github.getRepoInfo();
|
|
2612
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2613
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2614
|
-
if (!owner || !repo) {
|
|
2615
|
-
return {
|
|
2616
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2617
|
-
requiresUserInput: true,
|
|
2618
|
-
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
2619
|
-
};
|
|
2620
|
-
}
|
|
2621
|
-
const dueOn = input.due_on ? `${input.due_on}T08:00:00Z` : undefined;
|
|
2622
|
-
const milestone = await github.updateMilestone(owner, repo, input.milestone_number, {
|
|
2623
|
-
title: input.title,
|
|
2624
|
-
description: input.description,
|
|
2625
|
-
dueOn,
|
|
2626
|
-
state: input.state,
|
|
2627
|
-
});
|
|
2628
|
-
if (!milestone) {
|
|
2629
|
-
return {
|
|
2630
|
-
error: `Failed to update milestone #${String(input.milestone_number)}. Check that it exists and GITHUB_TOKEN has permissions.`,
|
|
2631
|
-
};
|
|
2632
|
-
}
|
|
2633
|
-
const total = milestone.openIssues + milestone.closedIssues;
|
|
2634
|
-
const completionPercentage = total > 0 ? Math.round((milestone.closedIssues / total) * 100) : 0;
|
|
2635
|
-
return {
|
|
2636
|
-
success: true,
|
|
2637
|
-
milestone: { ...milestone, completionPercentage },
|
|
2638
|
-
message: `Updated milestone #${String(milestone.number)}: ${milestone.title}`,
|
|
2639
|
-
};
|
|
2640
|
-
},
|
|
2641
|
-
},
|
|
2642
|
-
{
|
|
2643
|
-
name: 'delete_github_milestone',
|
|
2644
|
-
title: 'Delete GitHub Milestone',
|
|
2645
|
-
description: 'Permanently delete a GitHub milestone. Issues assigned to the milestone will be un-assigned but not deleted.',
|
|
2646
|
-
group: 'github',
|
|
2647
|
-
inputSchema: z.object({
|
|
2648
|
-
milestone_number: z.number().describe('Milestone number to delete'),
|
|
2649
|
-
confirm: z.literal(true).describe('Must be set to true to confirm deletion'),
|
|
2650
|
-
owner: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2651
|
-
repo: z.string().optional().describe('LEAVE EMPTY to auto-detect'),
|
|
2652
|
-
}),
|
|
2653
|
-
outputSchema: DeleteMilestoneOutputSchema,
|
|
2654
|
-
annotations: {
|
|
2655
|
-
readOnlyHint: false,
|
|
2656
|
-
idempotentHint: false,
|
|
2657
|
-
destructiveHint: true,
|
|
2658
|
-
openWorldHint: true,
|
|
2659
|
-
},
|
|
2660
|
-
handler: async (params) => {
|
|
2661
|
-
const input = z
|
|
2662
|
-
.object({
|
|
2663
|
-
milestone_number: z.number(),
|
|
2664
|
-
confirm: z.literal(true),
|
|
2665
|
-
owner: z.string().optional(),
|
|
2666
|
-
repo: z.string().optional(),
|
|
2667
|
-
})
|
|
2668
|
-
.parse(params);
|
|
2669
|
-
if (!github) {
|
|
2670
|
-
return { error: 'GitHub integration not available' };
|
|
2671
|
-
}
|
|
2672
|
-
const repoInfo = await github.getRepoInfo();
|
|
2673
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2674
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2675
|
-
if (!owner || !repo) {
|
|
2676
|
-
return {
|
|
2677
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS.',
|
|
2678
|
-
requiresUserInput: true,
|
|
2679
|
-
instruction: 'Ask the user: "What GitHub repository is this milestone in?"',
|
|
2680
|
-
};
|
|
2681
|
-
}
|
|
2682
|
-
const result = await github.deleteMilestone(owner, repo, input.milestone_number);
|
|
2683
|
-
if (!result.success) {
|
|
2684
|
-
return {
|
|
2685
|
-
success: false,
|
|
2686
|
-
milestoneNumber: input.milestone_number,
|
|
2687
|
-
message: `Failed to delete milestone #${String(input.milestone_number)}`,
|
|
2688
|
-
error: result.error ?? undefined,
|
|
2689
|
-
};
|
|
2690
|
-
}
|
|
2691
|
-
return {
|
|
2692
|
-
success: true,
|
|
2693
|
-
milestoneNumber: input.milestone_number,
|
|
2694
|
-
message: `Deleted milestone #${String(input.milestone_number)}`,
|
|
2695
|
-
};
|
|
2696
|
-
},
|
|
2697
|
-
},
|
|
2698
|
-
// Repository insights tool
|
|
2699
|
-
{
|
|
2700
|
-
name: 'get_repo_insights',
|
|
2701
|
-
title: 'Repository Insights',
|
|
2702
|
-
description: 'Get repository insights: stars, forks, traffic (clones/views), referrers, and popular paths. Use "sections" to control token usage: stars (~50 tokens), traffic (~100), referrers (~100), paths (~100), or all (~350).',
|
|
2703
|
-
group: 'github',
|
|
2704
|
-
inputSchema: z.object({
|
|
2705
|
-
sections: z
|
|
2706
|
-
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
2707
|
-
.optional()
|
|
2708
|
-
.default('stars')
|
|
2709
|
-
.describe('Data section to return (default: stars). Use "all" for full payload.'),
|
|
2710
|
-
owner: z
|
|
2711
|
-
.string()
|
|
2712
|
-
.optional()
|
|
2713
|
-
.describe('Repository owner - LEAVE EMPTY to auto-detect'),
|
|
2714
|
-
repo: z
|
|
2715
|
-
.string()
|
|
2716
|
-
.optional()
|
|
2717
|
-
.describe('Repository name - LEAVE EMPTY to auto-detect'),
|
|
2718
|
-
}),
|
|
2719
|
-
outputSchema: RepoInsightsOutputSchema,
|
|
2720
|
-
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
|
|
2721
|
-
handler: async (params) => {
|
|
2722
|
-
const input = z
|
|
2723
|
-
.object({
|
|
2724
|
-
sections: z
|
|
2725
|
-
.enum(['stars', 'traffic', 'referrers', 'paths', 'all'])
|
|
2726
|
-
.optional()
|
|
2727
|
-
.default('stars'),
|
|
2728
|
-
owner: z.string().optional(),
|
|
2729
|
-
repo: z.string().optional(),
|
|
2730
|
-
})
|
|
2731
|
-
.parse(params);
|
|
2732
|
-
if (!github) {
|
|
2733
|
-
return { error: 'GitHub integration not available' };
|
|
2734
|
-
}
|
|
2735
|
-
const repoInfo = await github.getRepoInfo();
|
|
2736
|
-
const owner = input.owner ?? repoInfo.owner ?? undefined;
|
|
2737
|
-
const repo = input.repo ?? repoInfo.repo ?? undefined;
|
|
2738
|
-
if (!owner || !repo) {
|
|
2739
|
-
return {
|
|
2740
|
-
error: 'STOP: Could not auto-detect repository. DO NOT GUESS. You MUST ask the user to provide the GitHub owner and repository name.',
|
|
2741
|
-
requiresUserInput: true,
|
|
2742
|
-
instruction: 'Ask the user: "What GitHub repository should I get insights for? Please provide the owner and repo name (e.g., owner/repo)."',
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
const section = input.sections;
|
|
2746
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- building response dynamically
|
|
2747
|
-
const result = {
|
|
2748
|
-
owner,
|
|
2749
|
-
repo,
|
|
2750
|
-
section,
|
|
2751
|
-
};
|
|
2752
|
-
// Stars section (default)
|
|
2753
|
-
if (section === 'stars' || section === 'all') {
|
|
2754
|
-
const stats = await github.getRepoStats(owner, repo);
|
|
2755
|
-
if (stats) {
|
|
2756
|
-
result['stars'] = stats.stars;
|
|
2757
|
-
result['forks'] = stats.forks;
|
|
2758
|
-
result['watchers'] = stats.watchers;
|
|
2759
|
-
result['openIssues'] = stats.openIssues;
|
|
2760
|
-
if (section === 'all') {
|
|
2761
|
-
result['size'] = stats.size;
|
|
2762
|
-
result['defaultBranch'] = stats.defaultBranch;
|
|
2763
|
-
}
|
|
2764
|
-
}
|
|
2765
|
-
}
|
|
2766
|
-
// Traffic section
|
|
2767
|
-
if (section === 'traffic' || section === 'all') {
|
|
2768
|
-
const traffic = await github.getTrafficData(owner, repo);
|
|
2769
|
-
if (traffic) {
|
|
2770
|
-
result['traffic'] = traffic;
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
// Referrers section
|
|
2774
|
-
if (section === 'referrers' || section === 'all') {
|
|
2775
|
-
const referrers = await github.getTopReferrers(owner, repo, 5);
|
|
2776
|
-
result['referrers'] = referrers;
|
|
2777
|
-
}
|
|
2778
|
-
// Paths section
|
|
2779
|
-
if (section === 'paths' || section === 'all') {
|
|
2780
|
-
const paths = await github.getPopularPaths(owner, repo, 5);
|
|
2781
|
-
result['paths'] = paths;
|
|
2782
|
-
}
|
|
2783
|
-
return result;
|
|
2784
|
-
},
|
|
2785
|
-
},
|
|
2786
|
-
// Backup tools
|
|
2787
|
-
{
|
|
2788
|
-
name: 'backup_journal',
|
|
2789
|
-
title: 'Backup Journal Database',
|
|
2790
|
-
description: 'Create a timestamped backup of the journal database. Backups are stored in the backups/ directory.',
|
|
2791
|
-
group: 'backup',
|
|
2792
|
-
inputSchema: z.object({
|
|
2793
|
-
name: z
|
|
2794
|
-
.string()
|
|
2795
|
-
.optional()
|
|
2796
|
-
.describe('Custom backup name (optional, defaults to timestamp)'),
|
|
2797
|
-
}),
|
|
2798
|
-
outputSchema: BackupResultOutputSchema,
|
|
2799
|
-
annotations: { readOnlyHint: false, idempotentHint: true },
|
|
2800
|
-
handler: (params) => {
|
|
2801
|
-
const input = z
|
|
2802
|
-
.object({
|
|
2803
|
-
name: z.string().optional(),
|
|
2804
|
-
})
|
|
2805
|
-
.parse(params);
|
|
2806
|
-
const result = db.exportToFile(input.name);
|
|
2807
|
-
return Promise.resolve({
|
|
2808
|
-
success: true,
|
|
2809
|
-
message: `Backup created successfully`,
|
|
2810
|
-
filename: result.filename,
|
|
2811
|
-
path: result.path,
|
|
2812
|
-
sizeBytes: result.sizeBytes,
|
|
2813
|
-
});
|
|
2814
|
-
},
|
|
2815
|
-
},
|
|
2816
|
-
{
|
|
2817
|
-
name: 'list_backups',
|
|
2818
|
-
title: 'List Journal Backups',
|
|
2819
|
-
description: 'List all available backup files with their sizes and creation dates',
|
|
2820
|
-
group: 'backup',
|
|
2821
|
-
inputSchema: z.object({}),
|
|
2822
|
-
outputSchema: BackupsListOutputSchema,
|
|
2823
|
-
annotations: { readOnlyHint: true, idempotentHint: true },
|
|
2824
|
-
handler: (_params) => {
|
|
2825
|
-
const backups = db.listBackups();
|
|
2826
|
-
return Promise.resolve({
|
|
2827
|
-
backups,
|
|
2828
|
-
total: backups.length,
|
|
2829
|
-
backupsDirectory: db.getBackupsDir(),
|
|
2830
|
-
hint: backups.length === 0
|
|
2831
|
-
? 'No backups found. Use backup_journal to create one.'
|
|
2832
|
-
: undefined,
|
|
2833
|
-
});
|
|
2834
|
-
},
|
|
2835
|
-
},
|
|
2836
|
-
{
|
|
2837
|
-
name: 'restore_backup',
|
|
2838
|
-
title: 'Restore Journal from Backup',
|
|
2839
|
-
description: 'Restore the journal database from a backup file. WARNING: This replaces all current data. An automatic backup is created before restore.',
|
|
2840
|
-
group: 'backup',
|
|
2841
|
-
inputSchema: z.object({
|
|
2842
|
-
filename: z
|
|
2843
|
-
.string()
|
|
2844
|
-
.describe('Backup filename to restore from (e.g., backup_2025-01-01.db)'),
|
|
2845
|
-
confirm: z
|
|
2846
|
-
.literal(true)
|
|
2847
|
-
.describe('Must be set to true to confirm the restore operation'),
|
|
2848
|
-
}),
|
|
2849
|
-
outputSchema: RestoreResultOutputSchema,
|
|
2850
|
-
annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true },
|
|
2851
|
-
handler: async (params) => {
|
|
2852
|
-
const input = z
|
|
2853
|
-
.object({
|
|
2854
|
-
filename: z.string(),
|
|
2855
|
-
confirm: z.literal(true),
|
|
2856
|
-
})
|
|
2857
|
-
.parse(params);
|
|
2858
|
-
// Capture progress context values BEFORE any async operations
|
|
2859
|
-
// This prevents any possible reference corruption during db reinitialization
|
|
2860
|
-
const progressServer = progress?.server;
|
|
2861
|
-
const progressTokenValue = progress?.progressToken;
|
|
2862
|
-
// Phase 1: Notify that we're starting
|
|
2863
|
-
await sendProgress(progress, 1, 3, 'Preparing restore...');
|
|
2864
|
-
// Phase 2: Restoring database (restoreFromFile creates backup internally)
|
|
2865
|
-
await sendProgress(progress, 2, 3, 'Restoring database from backup...');
|
|
2866
|
-
const result = await db.restoreFromFile(input.filename);
|
|
2867
|
-
// Phase 3: Complete - send directly using captured primitives
|
|
2868
|
-
// The db.restoreFromFile() reinitializes the database which can corrupt
|
|
2869
|
-
// the progress context, so we use our captured values
|
|
2870
|
-
if (progressServer !== undefined && progressTokenValue !== undefined) {
|
|
2871
|
-
try {
|
|
2872
|
-
await progressServer.notification({
|
|
2873
|
-
method: 'notifications/progress',
|
|
2874
|
-
params: {
|
|
2875
|
-
progressToken: progressTokenValue,
|
|
2876
|
-
progress: 3,
|
|
2877
|
-
total: 3,
|
|
2878
|
-
message: 'Restore complete',
|
|
2879
|
-
},
|
|
2880
|
-
});
|
|
2881
|
-
}
|
|
2882
|
-
catch {
|
|
2883
|
-
// Best-effort notification
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
return {
|
|
2887
|
-
success: true,
|
|
2888
|
-
message: `Database restored from ${input.filename}`,
|
|
2889
|
-
restoredFrom: result.restoredFrom,
|
|
2890
|
-
previousEntryCount: result.previousEntryCount,
|
|
2891
|
-
newEntryCount: result.newEntryCount,
|
|
2892
|
-
warning: 'A pre-restore backup was automatically created. Any changes made since this backup (including tag merges, new entries, and relationships) have been reverted.',
|
|
2893
|
-
revertedChanges: {
|
|
2894
|
-
tagMerges: 'Any merge_tags operations since this backup are reverted. Previously merged tags will reappear as separate tags.',
|
|
2895
|
-
entries: result.previousEntryCount !== result.newEntryCount
|
|
2896
|
-
? `Entry count changed from ${String(result.previousEntryCount)} to ${String(result.newEntryCount)}`
|
|
2897
|
-
: undefined,
|
|
2898
|
-
},
|
|
2899
|
-
};
|
|
2900
|
-
},
|
|
2901
|
-
},
|
|
2902
|
-
{
|
|
2903
|
-
name: 'cleanup_backups',
|
|
2904
|
-
title: 'Cleanup Old Backups',
|
|
2905
|
-
description: 'Delete old backup files, keeping only the most recent N backups. Use list_backups to preview before cleanup.',
|
|
2906
|
-
group: 'backup',
|
|
2907
|
-
inputSchema: z.object({
|
|
2908
|
-
keep_count: z
|
|
2909
|
-
.number()
|
|
2910
|
-
.min(1)
|
|
2911
|
-
.default(5)
|
|
2912
|
-
.describe('Number of most recent backups to keep (default: 5)'),
|
|
2913
|
-
}),
|
|
2914
|
-
outputSchema: CleanupBackupsOutputSchema,
|
|
2915
|
-
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
2916
|
-
handler: (params) => {
|
|
2917
|
-
const { keep_count } = z
|
|
2918
|
-
.object({ keep_count: z.number().min(1).default(5) })
|
|
2919
|
-
.parse(params);
|
|
2920
|
-
const result = db.deleteOldBackups(keep_count);
|
|
2921
|
-
return Promise.resolve({
|
|
2922
|
-
success: true,
|
|
2923
|
-
deleted: result.deleted,
|
|
2924
|
-
deletedCount: result.deleted.length,
|
|
2925
|
-
keptCount: result.kept,
|
|
2926
|
-
message: result.deleted.length > 0
|
|
2927
|
-
? `Deleted ${String(result.deleted.length)} old backup(s), kept ${String(result.kept)}`
|
|
2928
|
-
: `No backups to delete. Currently have ${String(result.kept)} backup(s).`,
|
|
2929
|
-
});
|
|
2930
|
-
},
|
|
2931
|
-
},
|
|
131
|
+
...getCoreTools(context),
|
|
132
|
+
...getSearchTools(context),
|
|
133
|
+
...getAnalyticsTools(context),
|
|
134
|
+
...getRelationshipTools(context),
|
|
135
|
+
...getExportTools(context),
|
|
136
|
+
...getAdminTools(context),
|
|
137
|
+
...getGitHubTools(context),
|
|
138
|
+
...getBackupTools(context),
|
|
139
|
+
...getTeamTools(context),
|
|
2932
140
|
];
|
|
2933
141
|
}
|
|
2934
142
|
//# sourceMappingURL=index.js.map
|