memory-journal-mcp 4.3.1 → 4.4.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/.dockerignore +131 -122
- package/.gitattributes +29 -0
- package/.github/workflows/docker-publish.yml +1 -1
- package/.github/workflows/lint-and-test.yml +1 -2
- package/.github/workflows/secrets-scanning.yml +0 -1
- package/.github/workflows/security-update.yml +6 -6
- package/.vscode/settings.json +17 -15
- package/CHANGELOG.md +1065 -11
- package/DOCKER_README.md +51 -33
- package/Dockerfile +12 -11
- package/README.md +68 -33
- package/SECURITY.md +225 -158
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/constants/ServerInstructions.d.ts +1 -1
- package/dist/constants/ServerInstructions.d.ts.map +1 -1
- package/dist/constants/ServerInstructions.js +70 -26
- package/dist/constants/ServerInstructions.js.map +1 -1
- package/dist/constants/icons.d.ts +2 -0
- package/dist/constants/icons.d.ts.map +1 -1
- package/dist/constants/icons.js +6 -0
- package/dist/constants/icons.js.map +1 -1
- package/dist/database/SqliteAdapter.d.ts +51 -10
- package/dist/database/SqliteAdapter.d.ts.map +1 -1
- package/dist/database/SqliteAdapter.js +140 -31
- package/dist/database/SqliteAdapter.js.map +1 -1
- package/dist/filtering/ToolFilter.d.ts +1 -1
- package/dist/filtering/ToolFilter.d.ts.map +1 -1
- package/dist/filtering/ToolFilter.js +7 -1
- package/dist/filtering/ToolFilter.js.map +1 -1
- package/dist/github/GitHubIntegration.d.ts +74 -2
- package/dist/github/GitHubIntegration.d.ts.map +1 -1
- package/dist/github/GitHubIntegration.js +508 -7
- package/dist/github/GitHubIntegration.js.map +1 -1
- package/dist/handlers/prompts/index.js +1 -0
- package/dist/handlers/prompts/index.js.map +1 -1
- package/dist/handlers/resources/index.d.ts.map +1 -1
- package/dist/handlers/resources/index.js +257 -13
- package/dist/handlers/resources/index.js.map +1 -1
- package/dist/handlers/tools/index.d.ts.map +1 -1
- package/dist/handlers/tools/index.js +590 -7
- package/dist/handlers/tools/index.js.map +1 -1
- package/dist/server/McpServer.d.ts +2 -0
- package/dist/server/McpServer.d.ts.map +1 -1
- package/dist/server/McpServer.js +69 -26
- package/dist/server/McpServer.js.map +1 -1
- package/dist/types/index.d.ts +97 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -1
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/security-utils.d.ts +8 -0
- package/dist/utils/security-utils.d.ts.map +1 -1
- package/dist/utils/security-utils.js +32 -0
- package/dist/utils/security-utils.js.map +1 -1
- package/dist/vector/VectorSearchManager.d.ts +2 -1
- package/dist/vector/VectorSearchManager.d.ts.map +1 -1
- package/dist/vector/VectorSearchManager.js +100 -34
- package/dist/vector/VectorSearchManager.js.map +1 -1
- package/docker-compose.yml +46 -37
- package/mcp-config-example.json +0 -2
- package/package.json +12 -11
- package/releases/v4.3.1.md +69 -0
- package/releases/v4.4.0.md +120 -0
- package/server.json +3 -3
- package/src/cli.ts +11 -0
- package/src/constants/ServerInstructions.ts +70 -26
- package/src/constants/icons.ts +7 -0
- package/src/database/SqliteAdapter.ts +162 -32
- package/src/filtering/ToolFilter.ts +7 -1
- package/src/github/GitHubIntegration.ts +588 -8
- package/src/handlers/prompts/index.ts +1 -0
- package/src/handlers/resources/index.ts +318 -12
- package/src/handlers/tools/index.ts +681 -12
- package/src/server/McpServer.ts +79 -37
- package/src/types/index.ts +98 -0
- package/src/utils/logger.ts +10 -1
- package/src/utils/security-utils.ts +35 -0
- package/src/vector/VectorSearchManager.ts +110 -39
- package/tests/constants/icons.test.ts +102 -0
- package/tests/constants/server-instructions.test.ts +549 -0
- package/tests/database/sqlite-adapter.bench.ts +63 -0
- package/tests/database/sqlite-adapter.test.ts +555 -0
- package/tests/filtering/tool-filter.test.ts +266 -0
- package/tests/github/github-integration.test.ts +1024 -0
- package/tests/handlers/github-resource-handlers.test.ts +473 -0
- package/tests/handlers/github-tool-handlers.test.ts +556 -0
- package/tests/handlers/prompt-handlers.test.ts +91 -0
- package/tests/handlers/resource-handlers.test.ts +339 -0
- package/tests/handlers/tool-handlers.test.ts +497 -0
- package/tests/handlers/vector-tool-handlers.test.ts +238 -0
- package/tests/server/mcp-server.bench.ts +55 -0
- package/tests/server/mcp-server.test.ts +675 -0
- package/tests/utils/logger.test.ts +180 -0
- package/tests/utils/mcp-logger.test.ts +212 -0
- package/tests/utils/progress-utils.test.ts +156 -0
- package/tests/utils/security-utils.test.ts +82 -0
- package/tests/vector/vector-search-manager.test.ts +335 -0
- package/tests/vector/vector-search.bench.ts +53 -0
- package/.github/workflows/DOCKER_DEPLOYMENT_SETUP.md +0 -387
- package/.github/workflows/dependabot-auto-merge.yml +0 -42
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These instructions are automatically sent to MCP clients during initialization,
|
|
5
5
|
* providing guidance for AI agents on tool usage.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Unreleased: Optimized for token efficiency with tiered instruction levels.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { ToolGroup } from '../types/index.js'
|
|
@@ -80,6 +80,7 @@ const GITHUB_INSTRUCTIONS = `
|
|
|
80
80
|
- After closing issue/merging PR → create summary entry with learnings
|
|
81
81
|
- CI failures → \`actions-failure-digest\` prompt or \`memory://actions/recent\`
|
|
82
82
|
- Kanban: \`get_kanban_board(project_number)\` → \`move_kanban_item\` → document completion
|
|
83
|
+
- Milestones: \`get_github_milestones\` → track project progress, \`memory://github/milestones\`
|
|
83
84
|
- GitHub tools auto-detect owner/repo from git context; specify explicitly if null
|
|
84
85
|
`
|
|
85
86
|
|
|
@@ -122,10 +123,10 @@ const TOOL_PARAMETER_REFERENCE = `
|
|
|
122
123
|
### Entry Operations
|
|
123
124
|
| Tool | Required Parameters | Optional Parameters |
|
|
124
125
|
|------|---------------------|---------------------|
|
|
125
|
-
| \`create_entry\` | \`content\` (string) | \`entry_type\`, \`tags\` (array), \`is_personal\`
|
|
126
|
+
| \`create_entry\` | \`content\` (string) | \`entry_type\`, \`tags\` (array), \`is_personal\`, \`significance_type\`, \`share_with_team\`, \`auto_context\`, \`issue_number\`, \`issue_url\`, \`pr_number\`, \`pr_url\`, \`pr_status\`, \`project_number\`, \`project_owner\`, \`workflow_run_id\`, \`workflow_name\`, \`workflow_status\` |
|
|
126
127
|
| \`create_entry_minimal\` | \`content\` (string) | none |
|
|
127
|
-
| \`get_entry_by_id\` | \`entry_id\` (number) |
|
|
128
|
-
| \`get_recent_entries\` | none | \`limit\` (default
|
|
128
|
+
| \`get_entry_by_id\` | \`entry_id\` (number) | \`include_relationships\` (bool, default true) |
|
|
129
|
+
| \`get_recent_entries\` | none | \`limit\` (default 5), \`is_personal\` (bool) |
|
|
129
130
|
| \`update_entry\` | \`entry_id\` (number) | \`content\`, \`tags\`, \`entry_type\`, \`is_personal\` |
|
|
130
131
|
| \`delete_entry\` | \`entry_id\` (number) | \`permanent\` (bool, default false) |
|
|
131
132
|
| \`list_tags\` | none | none |
|
|
@@ -133,33 +134,40 @@ const TOOL_PARAMETER_REFERENCE = `
|
|
|
133
134
|
### Search Tools
|
|
134
135
|
| Tool | Required Parameters | Optional Parameters |
|
|
135
136
|
|------|---------------------|---------------------|
|
|
136
|
-
| \`search_entries\` | \`query\`
|
|
137
|
-
| \`search_by_date_range\` | \`start_date\`, \`end_date\` (YYYY-MM-DD) | \`tags\`, \`entry_type\` |
|
|
138
|
-
| \`semantic_search\` | \`query\` (string) | \`limit\`, \`similarity_threshold\` (default 0.
|
|
137
|
+
| \`search_entries\` | none | \`query\`, \`limit\`, \`is_personal\`, \`issue_number\`, \`pr_number\`, \`pr_status\`, \`project_number\`, \`workflow_run_id\` |
|
|
138
|
+
| \`search_by_date_range\` | \`start_date\`, \`end_date\` (YYYY-MM-DD) | \`tags\`, \`entry_type\`, \`is_personal\`, \`issue_number\`, \`pr_number\`, \`project_number\`, \`workflow_run_id\` |
|
|
139
|
+
| \`semantic_search\` | \`query\` (string) | \`limit\`, \`similarity_threshold\` (default 0.25), \`is_personal\`, \`hint_on_empty\` (bool, default true) |
|
|
139
140
|
| \`get_vector_index_stats\` | none | none |
|
|
140
141
|
|
|
141
142
|
### Relationship Tools
|
|
142
143
|
| Tool | Required Parameters | Notes |
|
|
143
144
|
|------|---------------------|-------|
|
|
144
|
-
| \`link_entries\` | \`from_entry_id\`, \`to_entry_id\` (numbers) | Types: \`evolves_from\`, \`references\`, \`implements\`, \`clarifies\`, \`response_to\`, \`blocked_by\`, \`resolved\`, \`caused\` |
|
|
145
|
-
| \`visualize_relationships\` | \`entry_id\` (
|
|
145
|
+
| \`link_entries\` | \`from_entry_id\`, \`to_entry_id\` (numbers) | Types: \`evolves_from\`, \`references\`, \`implements\`, \`clarifies\`, \`response_to\`, \`blocked_by\`, \`resolved\`, \`caused\`. Optional \`description\`. |
|
|
146
|
+
| \`visualize_relationships\` | none | Optional \`entry_id\`, \`tags\` (array), \`depth\` (1-3, default 2), \`limit\` (default 20). Returns Mermaid diagram. |
|
|
146
147
|
|
|
147
148
|
### GitHub Tools
|
|
148
149
|
| Tool | Required Parameters | Notes |
|
|
149
150
|
|------|---------------------|-------|
|
|
150
|
-
| \`get_github_context\` | none | Returns repo info, open issues/PRs |
|
|
151
|
-
| \`get_github_issues\` | none | Optional \`state\` (open/closed/all), \`limit\` |
|
|
152
|
-
| \`get_github_prs\` | none | Optional \`state\`, \`limit\` |
|
|
153
|
-
| \`get_github_issue\` | \`issue_number\` (number) | Fetches single issue details |
|
|
154
|
-
| \`get_github_pr\` | \`pr_number\` (number) | Fetches single PR details |
|
|
151
|
+
| \`get_github_context\` | none | Returns repo info, open issues/PRs. Only counts OPEN items. |
|
|
152
|
+
| \`get_github_issues\` | none | Optional \`state\` (open/closed/all), \`limit\`, \`owner\`, \`repo\` |
|
|
153
|
+
| \`get_github_prs\` | none | Optional \`state\`, \`limit\`, \`owner\`, \`repo\` |
|
|
154
|
+
| \`get_github_issue\` | \`issue_number\` (number) | Optional \`owner\`, \`repo\`. Fetches single issue details. |
|
|
155
|
+
| \`get_github_pr\` | \`pr_number\` (number) | Optional \`owner\`, \`repo\`. Fetches single PR details. |
|
|
156
|
+
| \`get_repo_insights\` | none | Optional \`sections\` (stars/traffic/referrers/paths/all, default "stars"), \`owner\`, \`repo\`. Requires push access for traffic. |
|
|
155
157
|
|
|
156
158
|
GitHub tools auto-detect owner/repo from GITHUB_REPO_PATH. If \`detectedOwner\`/\`detectedRepo\` are null in response, specify \`owner\` and \`repo\` parameters explicitly.
|
|
157
159
|
|
|
160
|
+
### Issue Lifecycle Tools
|
|
161
|
+
| Tool | Required Parameters | Notes |
|
|
162
|
+
|------|---------------------|-------|
|
|
163
|
+
| \`create_github_issue_with_entry\` | \`title\` (string) | Optional \`body\`, \`labels\` (array), \`assignees\` (array), \`project_number\`, \`initial_status\`, \`milestone_number\`, \`entry_content\`, \`tags\`, \`owner\`, \`repo\` |
|
|
164
|
+
| \`close_github_issue_with_entry\` | \`issue_number\` (number) | Optional \`comment\`, \`resolution_notes\`, \`tags\`, \`move_to_done\` (bool), \`project_number\`, \`owner\`, \`repo\` |
|
|
165
|
+
|
|
158
166
|
### Kanban Tools (GitHub Projects v2)
|
|
159
167
|
| Tool | Required Parameters | Notes |
|
|
160
168
|
|------|---------------------|-------|
|
|
161
|
-
| \`get_kanban_board\` | \`project_number\` (number) | Returns columns with items grouped by Status |
|
|
162
|
-
| \`move_kanban_item\` | \`project_number\`, \`item_id\` (string), \`target_status\` (string) | \`item_id\` is the GraphQL node ID from board items |
|
|
169
|
+
| \`get_kanban_board\` | \`project_number\` (number) | Optional \`owner\`. Returns columns with items grouped by Status |
|
|
170
|
+
| \`move_kanban_item\` | \`project_number\`, \`item_id\` (string), \`target_status\` (string) | Optional \`owner\`. \`item_id\` is the GraphQL node ID from board items. Status matching is case-insensitive. |
|
|
163
171
|
|
|
164
172
|
**Finding the right project**: User may have multiple projects. Use \`get_kanban_board\` with different project numbers (1, 2, 3...) to find the correct one by checking \`projectTitle\`.
|
|
165
173
|
|
|
@@ -176,33 +184,53 @@ Kanban resources:
|
|
|
176
184
|
- \`memory://kanban/{project_number}\` - JSON board data
|
|
177
185
|
- \`memory://kanban/{project_number}/diagram\` - Mermaid visualization
|
|
178
186
|
|
|
187
|
+
### Milestone Tools
|
|
188
|
+
| Tool | Required Parameters | Notes |
|
|
189
|
+
|------|---------------------|-------|
|
|
190
|
+
| \`get_github_milestones\` | none | Optional \`state\` (open/closed/all), \`limit\`, \`owner\`, \`repo\` |
|
|
191
|
+
| \`get_github_milestone\` | \`milestone_number\` (number) | Optional \`owner\`, \`repo\`. Single milestone with completion %. |
|
|
192
|
+
| \`create_github_milestone\` | \`title\` (string) | Optional \`description\`, \`due_on\` (YYYY-MM-DD), \`owner\`, \`repo\` |
|
|
193
|
+
| \`update_github_milestone\` | \`milestone_number\` (number) | Optional \`title\`, \`description\`, \`due_on\`, \`state\` (open/closed), \`owner\`, \`repo\` |
|
|
194
|
+
| \`delete_github_milestone\` | \`milestone_number\`, \`confirm: true\` | Optional \`owner\`, \`repo\`. Permanent deletion. |
|
|
195
|
+
|
|
196
|
+
Milestone resources:
|
|
197
|
+
- \`memory://github/milestones\` - Open milestones with completion %
|
|
198
|
+
- \`memory://milestones/{number}\` - Single milestone detail
|
|
199
|
+
|
|
179
200
|
### Admin Tools
|
|
180
201
|
| Tool | Required Parameters | Notes |
|
|
181
202
|
|------|---------------------|-------|
|
|
182
|
-
| \`backup_journal\` | none | Optional \`
|
|
203
|
+
| \`backup_journal\` | none | Optional \`name\` (custom backup name) |
|
|
183
204
|
| \`list_backups\` | none | Returns available backup files |
|
|
184
|
-
| \`
|
|
205
|
+
| \`cleanup_backups\` | none | Optional \`keep_count\` (default 5). Deletes old backups, keeps N most recent. |
|
|
206
|
+
| \`restore_backup\` | \`filename\`, \`confirm: true\` | Creates auto-backup before restore |
|
|
185
207
|
| \`add_to_vector_index\` | \`entry_id\` (single number) | Indexes one entry for semantic search |
|
|
186
208
|
| \`rebuild_vector_index\` | none | Re-indexes all entries |
|
|
187
209
|
|
|
188
210
|
### Export Tools
|
|
189
211
|
| Tool | Required Parameters | Notes |
|
|
190
212
|
|------|---------------------|-------|
|
|
191
|
-
| \`export_entries\` | none | Optional \`format\` (json/markdown), \`limit
|
|
213
|
+
| \`export_entries\` | none | Optional \`format\` (json/markdown), \`limit\` (default 100), \`tags\`, \`start_date\`, \`end_date\`, \`entry_types\` |
|
|
192
214
|
|
|
193
215
|
## Entry Types
|
|
194
216
|
Valid values for \`entry_type\` parameter:
|
|
195
217
|
- \`personal_reflection\` (default) - Personal thoughts and notes
|
|
196
|
-
- \`
|
|
218
|
+
- \`project_decision\` - Architectural and team decisions
|
|
219
|
+
- \`technical_achievement\` - Milestones and breakthroughs
|
|
197
220
|
- \`bug_fix\` - Bug fixes and resolutions
|
|
198
|
-
- \`
|
|
221
|
+
- \`feature_implementation\` - Feature builds and rollouts
|
|
199
222
|
- \`code_review\` - Code review notes
|
|
200
|
-
- \`
|
|
201
|
-
- \`
|
|
223
|
+
- \`meeting_notes\` - Meeting summaries
|
|
224
|
+
- \`learning\` - Learning and research insights
|
|
225
|
+
- \`research\` - Research and exploration
|
|
226
|
+
- \`planning\` - Planning sessions and roadmaps (\`create_github_issue_with_entry\` uses this type)
|
|
227
|
+
- \`retrospective\` - Sprint and project retrospectives
|
|
228
|
+
- \`standup\` - Daily standup notes
|
|
229
|
+
- \`other\` - Miscellaneous
|
|
202
230
|
|
|
203
231
|
## Field Notes
|
|
204
232
|
- **\`autoContext\`**: Reserved for future automatic context capture. Currently always \`null\`.
|
|
205
|
-
- **\`memory://tags\` vs \`list_tags\`**: Resource includes \`id\`, \`name\`, \`count\`; tool returns only \`name\`, \`count\`.
|
|
233
|
+
- **\`memory://tags\` vs \`list_tags\`**: Resource includes \`id\`, \`name\`, \`count\`; tool returns only \`name\`, \`count\`. Neither returns orphan tags with zero usage.
|
|
206
234
|
- **Tag naming**: Use lowercase with dashes (e.g., \`bug-fix\`, \`phase-2\`). Use \`merge_tags\` to consolidate duplicates (e.g., merge \`phase2\` into \`phase-2\`).
|
|
207
235
|
- **\`merge_tags\` behavior**: Only updates non-deleted entries. Deleted entries retain their original tags.
|
|
208
236
|
- **\`prStatus\` in entries**: Reflects PR state at entry creation time, not current state. Use \`get_github_pr\` for live status.
|
|
@@ -211,19 +239,35 @@ Valid values for \`entry_type\` parameter:
|
|
|
211
239
|
- **\`semantic_search\` thresholds**: Default similarity threshold is 0.25. For broader matches, try 0.15-0.2. Higher values (0.4+) return only very close semantic matches.
|
|
212
240
|
- **Causal relationship types**: Use \`blocked_by\` (A was blocked by B), \`resolved\` (A resolved B), \`caused\` (A caused B) for decision tracing and failure analysis. Visualizations use distinct arrow styles for causal types.
|
|
213
241
|
- **Enhanced analytics**: \`get_statistics\` returns \`decisionDensity\` (significant entries per period), \`relationshipComplexity\` (avg relationships per entry), \`activityTrend\` (period-over-period growth %), and \`causalMetrics\` (counts for blocked_by/resolved/caused).
|
|
214
|
-
- **Importance scores**: \`get_entry_by_id\` returns
|
|
242
|
+
- **Importance scores**: \`get_entry_by_id\` returns \`importance\` (0.0-1.0) and \`importanceBreakdown\` showing weighted components: significance (30%), relationships (35%), causal (20%), recency (15%). \`memory://significant\` sorts entries by importance.
|
|
243
|
+
- **\`inactiveThresholdDays\`**: \`get_cross_project_insights\` includes \`inactiveThresholdDays: 7\` in output, documenting the inactive project classification cutoff.
|
|
244
|
+
- **GitHub metadata in entries**: Entry output includes 10 GitHub fields (\`issueNumber\`, \`issueUrl\`, \`prNumber\`, \`prUrl\`, \`prStatus\`, \`projectNumber\`, \`projectOwner\`, \`workflowRunId\`, \`workflowName\`, \`workflowStatus\`) in all tool responses.
|
|
245
|
+
- **\`delete_entry\` on soft-deleted**: \`delete_entry(id, permanent: true)\` works on previously soft-deleted entries. Returns \`success: false\` for nonexistent entries.
|
|
215
246
|
|
|
216
247
|
## Key Resources
|
|
217
248
|
| URI | Description |
|
|
218
249
|
|-----|-------------|
|
|
219
250
|
| \`memory://health\` | Server health, DB stats, tool filter status |
|
|
220
251
|
| \`memory://briefing\` | Session context with userMessage to show user |
|
|
252
|
+
| \`memory://instructions\` | Full server instructions and tool reference |
|
|
221
253
|
| \`memory://statistics\` | Entry counts by type and period |
|
|
222
254
|
| \`memory://recent\` | 10 most recent entries |
|
|
223
255
|
| \`memory://tags\` | All tags with usage counts |
|
|
224
|
-
| \`memory://significant\` | Entries
|
|
256
|
+
| \`memory://significant\` | Entries sorted by importance score |
|
|
225
257
|
| \`memory://graph/recent\` | Mermaid diagram of recent relationships |
|
|
258
|
+
| \`memory://graph/actions\` | CI/CD narrative graph |
|
|
259
|
+
| \`memory://actions/recent\` | Recent workflow runs |
|
|
260
|
+
| \`memory://team/recent\` | Team-shared entries |
|
|
261
|
+
| \`memory://github/status\` | GitHub repo overview (CI, issues, PRs, milestones) |
|
|
262
|
+
| \`memory://github/milestones\` | Open milestones with completion % |
|
|
263
|
+
| \`memory://github/insights\` | Stars, forks, and 14-day traffic summary |
|
|
226
264
|
| \`memory://kanban/{n}\` | Kanban board for project number n |
|
|
265
|
+
| \`memory://kanban/{n}/diagram\` | Mermaid Kanban visualization |
|
|
266
|
+
| \`memory://milestones/{n}\` | Single milestone detail + progress |
|
|
267
|
+
| \`memory://projects/{n}/timeline\` | Project entries timeline |
|
|
268
|
+
| \`memory://issues/{n}/entries\` | Entries linked to issue n |
|
|
269
|
+
| \`memory://prs/{n}/entries\` | Entries linked to PR n |
|
|
270
|
+
| \`memory://prs/{n}/timeline\` | PR lifecycle and linked entries |
|
|
227
271
|
`
|
|
228
272
|
|
|
229
273
|
/**
|
package/src/constants/icons.ts
CHANGED
|
@@ -143,6 +143,13 @@ export const ICON_PR: McpIcon = {
|
|
|
143
143
|
sizes: ['24x24'],
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/** Flag for milestone tracking */
|
|
147
|
+
export const ICON_MILESTONE: McpIcon = {
|
|
148
|
+
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/%3E%3Cline x1="4" y1="22" x2="4" y2="15"/%3E%3C/svg%3E',
|
|
149
|
+
mimeType: 'image/svg+xml',
|
|
150
|
+
sizes: ['24x24'],
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
/** Message bubble for prompts */
|
|
147
154
|
export const ICON_PROMPT: McpIcon = {
|
|
148
155
|
src: 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"%3E%3Cpath d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/%3E%3C/svg%3E',
|
|
@@ -17,6 +17,8 @@ import type {
|
|
|
17
17
|
EntryType,
|
|
18
18
|
SignificanceType,
|
|
19
19
|
RelationshipType,
|
|
20
|
+
ImportanceBreakdown,
|
|
21
|
+
ImportanceResult,
|
|
20
22
|
} from '../types/index.js'
|
|
21
23
|
|
|
22
24
|
// Schema SQL for initialization
|
|
@@ -125,6 +127,12 @@ export class SqliteAdapter {
|
|
|
125
127
|
private readonly dbPath: string
|
|
126
128
|
private initialized = false
|
|
127
129
|
|
|
130
|
+
/** Timer handle for debounced save */
|
|
131
|
+
private saveTimer: ReturnType<typeof setTimeout> | null = null
|
|
132
|
+
|
|
133
|
+
/** Debounce interval for batching disk writes (ms) */
|
|
134
|
+
private static readonly SAVE_DEBOUNCE_MS = 500
|
|
135
|
+
|
|
128
136
|
constructor(dbPath: string) {
|
|
129
137
|
this.dbPath = dbPath
|
|
130
138
|
}
|
|
@@ -164,14 +172,34 @@ export class SqliteAdapter {
|
|
|
164
172
|
|
|
165
173
|
logger.info('Database opened', { module: 'SqliteAdapter', dbPath: this.dbPath })
|
|
166
174
|
|
|
167
|
-
//
|
|
168
|
-
this.
|
|
175
|
+
// Immediate flush after initialization to persist schema
|
|
176
|
+
this.flushSave()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Schedule a debounced save to disk.
|
|
181
|
+
* Batches rapid mutations into a single write after SAVE_DEBOUNCE_MS.
|
|
182
|
+
* Used by all mutation methods (createEntry, updateEntry, etc.).
|
|
183
|
+
*/
|
|
184
|
+
private scheduleSave(): void {
|
|
185
|
+
if (this.saveTimer !== null) {
|
|
186
|
+
clearTimeout(this.saveTimer)
|
|
187
|
+
}
|
|
188
|
+
this.saveTimer = setTimeout(() => {
|
|
189
|
+
this.flushSave()
|
|
190
|
+
}, SqliteAdapter.SAVE_DEBOUNCE_MS)
|
|
169
191
|
}
|
|
170
192
|
|
|
171
193
|
/**
|
|
172
|
-
*
|
|
194
|
+
* Immediately flush the database to disk (synchronous).
|
|
195
|
+
* Cancels any pending debounced save.
|
|
196
|
+
* Used by close() and initialize() for guaranteed persistence.
|
|
173
197
|
*/
|
|
174
|
-
|
|
198
|
+
flushSave(): void {
|
|
199
|
+
if (this.saveTimer !== null) {
|
|
200
|
+
clearTimeout(this.saveTimer)
|
|
201
|
+
this.saveTimer = null
|
|
202
|
+
}
|
|
175
203
|
if (!this.db) return
|
|
176
204
|
const data = this.db.export()
|
|
177
205
|
const buffer = Buffer.from(data)
|
|
@@ -179,11 +207,12 @@ export class SqliteAdapter {
|
|
|
179
207
|
}
|
|
180
208
|
|
|
181
209
|
/**
|
|
182
|
-
* Close database connection
|
|
210
|
+
* Close database connection.
|
|
211
|
+
* Flushes any pending writes immediately before closing.
|
|
183
212
|
*/
|
|
184
213
|
close(): void {
|
|
185
214
|
if (this.db) {
|
|
186
|
-
this.
|
|
215
|
+
this.flushSave()
|
|
187
216
|
this.db.close()
|
|
188
217
|
this.db = null
|
|
189
218
|
}
|
|
@@ -264,7 +293,7 @@ export class SqliteAdapter {
|
|
|
264
293
|
this.linkTagsToEntry(entryId, tags)
|
|
265
294
|
}
|
|
266
295
|
|
|
267
|
-
this.
|
|
296
|
+
this.scheduleSave()
|
|
268
297
|
|
|
269
298
|
logger.info('Entry created', {
|
|
270
299
|
module: 'SqliteAdapter',
|
|
@@ -297,30 +326,65 @@ export class SqliteAdapter {
|
|
|
297
326
|
return this.rowToEntry(row)
|
|
298
327
|
}
|
|
299
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Get entry by ID, including soft-deleted entries.
|
|
331
|
+
* Used for permanent deletion of previously soft-deleted entries.
|
|
332
|
+
*/
|
|
333
|
+
getEntryByIdIncludeDeleted(id: number): JournalEntry | null {
|
|
334
|
+
const db = this.ensureDb()
|
|
335
|
+
const result = db.exec(`SELECT * FROM memory_journal WHERE id = ?`, [id])
|
|
336
|
+
|
|
337
|
+
if (result.length === 0 || result[0]?.values.length === 0) return null
|
|
338
|
+
|
|
339
|
+
const columns = result[0]?.columns ?? []
|
|
340
|
+
const values = result[0]?.values[0] ?? []
|
|
341
|
+
const row = this.rowToObject(columns, values)
|
|
342
|
+
|
|
343
|
+
return this.rowToEntry(row)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Importance score result with scoring breakdown
|
|
348
|
+
*/
|
|
349
|
+
static readonly IMPORTANCE_WEIGHTS = {
|
|
350
|
+
significance: 0.3,
|
|
351
|
+
relationships: 0.35,
|
|
352
|
+
causal: 0.2,
|
|
353
|
+
recency: 0.15,
|
|
354
|
+
} as const
|
|
355
|
+
|
|
300
356
|
/**
|
|
301
357
|
* Calculate importance score for an entry (0.0-1.0)
|
|
302
358
|
*
|
|
303
359
|
* Formula:
|
|
304
|
-
* -
|
|
305
|
-
* -
|
|
306
|
-
* -
|
|
307
|
-
* -
|
|
360
|
+
* - significance (0.30): 1.0 if significanceType set, else 0.0
|
|
361
|
+
* - relationships (0.35): min(relCount / 5, 1.0)
|
|
362
|
+
* - causal (0.20): min(causalCount / 3, 1.0)
|
|
363
|
+
* - recency (0.15): max(0, 1 - daysSince / 90)
|
|
364
|
+
*
|
|
365
|
+
* Returns ImportanceResult with score and component breakdown.
|
|
308
366
|
*/
|
|
309
|
-
calculateImportance(entryId: number):
|
|
367
|
+
calculateImportance(entryId: number): ImportanceResult {
|
|
310
368
|
const db = this.ensureDb()
|
|
369
|
+
const round2 = (n: number): number => Math.round(n * 100) / 100
|
|
311
370
|
|
|
312
371
|
// Get entry data
|
|
313
372
|
const entryResult = db.exec(
|
|
314
373
|
`SELECT significance_type, timestamp FROM memory_journal WHERE id = ? AND deleted_at IS NULL`,
|
|
315
374
|
[entryId]
|
|
316
375
|
)
|
|
317
|
-
if (entryResult.length === 0 || entryResult[0]?.values.length === 0)
|
|
376
|
+
if (entryResult.length === 0 || entryResult[0]?.values.length === 0) {
|
|
377
|
+
return {
|
|
378
|
+
score: 0,
|
|
379
|
+
breakdown: { significance: 0, relationships: 0, causal: 0, recency: 0 },
|
|
380
|
+
}
|
|
381
|
+
}
|
|
318
382
|
|
|
319
383
|
const significanceType = entryResult[0]?.values[0]?.[0] as string | null
|
|
320
384
|
const timestamp = entryResult[0]?.values[0]?.[1] as string
|
|
321
385
|
|
|
322
386
|
// Significance weight: 1.0 if set, else 0.0
|
|
323
|
-
const
|
|
387
|
+
const significanceRaw = significanceType ? 1.0 : 0.0
|
|
324
388
|
|
|
325
389
|
// Relationship count (total relationships involving this entry)
|
|
326
390
|
const relResult = db.exec(
|
|
@@ -328,7 +392,7 @@ export class SqliteAdapter {
|
|
|
328
392
|
[entryId, entryId]
|
|
329
393
|
)
|
|
330
394
|
const relCount = (relResult[0]?.values[0]?.[0] as number) ?? 0
|
|
331
|
-
const
|
|
395
|
+
const relationshipsRaw = Math.min(relCount / 5, 1.0)
|
|
332
396
|
|
|
333
397
|
// Causal relationships count
|
|
334
398
|
const causalResult = db.exec(
|
|
@@ -338,23 +402,33 @@ export class SqliteAdapter {
|
|
|
338
402
|
[entryId, entryId]
|
|
339
403
|
)
|
|
340
404
|
const causalCount = (causalResult[0]?.values[0]?.[0] as number) ?? 0
|
|
341
|
-
const
|
|
405
|
+
const causalRaw = Math.min(causalCount / 3, 1.0)
|
|
342
406
|
|
|
343
407
|
// Recency weight: decays over 90 days
|
|
344
408
|
const entryDate = new Date(timestamp)
|
|
345
409
|
const now = new Date()
|
|
346
410
|
const daysSince = Math.floor((now.getTime() - entryDate.getTime()) / (1000 * 60 * 60 * 24))
|
|
347
|
-
const
|
|
411
|
+
const recencyRaw = Math.max(0, 1 - daysSince / 90)
|
|
412
|
+
|
|
413
|
+
const w = SqliteAdapter.IMPORTANCE_WEIGHTS
|
|
348
414
|
|
|
349
|
-
// Weighted
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
415
|
+
// Weighted contributions
|
|
416
|
+
const breakdown: ImportanceBreakdown = {
|
|
417
|
+
significance: round2(significanceRaw * w.significance),
|
|
418
|
+
relationships: round2(relationshipsRaw * w.relationships),
|
|
419
|
+
causal: round2(causalRaw * w.causal),
|
|
420
|
+
recency: round2(recencyRaw * w.recency),
|
|
421
|
+
}
|
|
355
422
|
|
|
356
|
-
//
|
|
357
|
-
|
|
423
|
+
// Total score
|
|
424
|
+
const score = round2(
|
|
425
|
+
significanceRaw * w.significance +
|
|
426
|
+
relationshipsRaw * w.relationships +
|
|
427
|
+
causalRaw * w.causal +
|
|
428
|
+
recencyRaw * w.recency
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return { score, breakdown }
|
|
358
432
|
}
|
|
359
433
|
|
|
360
434
|
/**
|
|
@@ -382,6 +456,34 @@ export class SqliteAdapter {
|
|
|
382
456
|
)
|
|
383
457
|
}
|
|
384
458
|
|
|
459
|
+
/**
|
|
460
|
+
* Get a page of active entries for batch processing (e.g., vector index rebuild).
|
|
461
|
+
* Returns entries ordered by ID ascending for deterministic pagination.
|
|
462
|
+
*/
|
|
463
|
+
getEntriesPage(offset: number, limit: number): JournalEntry[] {
|
|
464
|
+
const db = this.ensureDb()
|
|
465
|
+
const result = db.exec(
|
|
466
|
+
`SELECT * FROM memory_journal WHERE deleted_at IS NULL ORDER BY id ASC LIMIT ? OFFSET ?`,
|
|
467
|
+
[limit, offset]
|
|
468
|
+
)
|
|
469
|
+
if (result.length === 0) return []
|
|
470
|
+
|
|
471
|
+
const columns = result[0]?.columns ?? []
|
|
472
|
+
return (result[0]?.values ?? []).map((values) =>
|
|
473
|
+
this.rowToEntry(this.rowToObject(columns, values))
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Get total count of active (non-deleted) entries.
|
|
479
|
+
* Used for progress reporting and pagination bounds.
|
|
480
|
+
*/
|
|
481
|
+
getActiveEntryCount(): number {
|
|
482
|
+
const db = this.ensureDb()
|
|
483
|
+
const result = db.exec(`SELECT COUNT(*) FROM memory_journal WHERE deleted_at IS NULL`)
|
|
484
|
+
return (result[0]?.values[0]?.[0] as number) ?? 0
|
|
485
|
+
}
|
|
486
|
+
|
|
385
487
|
/**
|
|
386
488
|
* Update an entry
|
|
387
489
|
*/
|
|
@@ -425,7 +527,7 @@ export class SqliteAdapter {
|
|
|
425
527
|
this.linkTagsToEntry(id, updates.tags)
|
|
426
528
|
}
|
|
427
529
|
|
|
428
|
-
this.
|
|
530
|
+
this.scheduleSave()
|
|
429
531
|
|
|
430
532
|
logger.info('Entry updated', {
|
|
431
533
|
module: 'SqliteAdapter',
|
|
@@ -438,17 +540,23 @@ export class SqliteAdapter {
|
|
|
438
540
|
|
|
439
541
|
/**
|
|
440
542
|
* Soft delete an entry
|
|
543
|
+
* Returns false if entry does not exist (P154: Proactive Object Existence Verification)
|
|
441
544
|
*/
|
|
442
545
|
deleteEntry(id: number, permanent = false): boolean {
|
|
443
546
|
const db = this.ensureDb()
|
|
444
547
|
|
|
548
|
+
// P154: Pre-check entry existence before mutation
|
|
549
|
+
// For permanent deletion, also look through soft-deleted entries
|
|
550
|
+
const entry = permanent ? this.getEntryByIdIncludeDeleted(id) : this.getEntryById(id)
|
|
551
|
+
if (!entry) return false
|
|
552
|
+
|
|
445
553
|
if (permanent) {
|
|
446
554
|
db.run('DELETE FROM memory_journal WHERE id = ?', [id])
|
|
447
555
|
} else {
|
|
448
556
|
db.run(`UPDATE memory_journal SET deleted_at = datetime('now') WHERE id = ?`, [id])
|
|
449
557
|
}
|
|
450
558
|
|
|
451
|
-
this.
|
|
559
|
+
this.scheduleSave()
|
|
452
560
|
return true
|
|
453
561
|
}
|
|
454
562
|
|
|
@@ -610,11 +718,11 @@ export class SqliteAdapter {
|
|
|
610
718
|
}
|
|
611
719
|
|
|
612
720
|
/**
|
|
613
|
-
* List all tags
|
|
721
|
+
* List all tags with at least one usage
|
|
614
722
|
*/
|
|
615
723
|
listTags(): Tag[] {
|
|
616
724
|
const db = this.ensureDb()
|
|
617
|
-
const result = db.exec('SELECT * FROM tags ORDER BY usage_count DESC')
|
|
725
|
+
const result = db.exec('SELECT * FROM tags WHERE usage_count > 0 ORDER BY usage_count DESC')
|
|
618
726
|
|
|
619
727
|
if (result.length === 0) return []
|
|
620
728
|
|
|
@@ -691,7 +799,7 @@ export class SqliteAdapter {
|
|
|
691
799
|
db.run('DELETE FROM entry_tags WHERE tag_id = ?', [sourceTagId])
|
|
692
800
|
db.run('DELETE FROM tags WHERE id = ?', [sourceTagId])
|
|
693
801
|
|
|
694
|
-
this.
|
|
802
|
+
this.scheduleSave()
|
|
695
803
|
|
|
696
804
|
logger.info('Tags merged', {
|
|
697
805
|
module: 'SqliteAdapter',
|
|
@@ -708,6 +816,7 @@ export class SqliteAdapter {
|
|
|
708
816
|
|
|
709
817
|
/**
|
|
710
818
|
* Link two entries
|
|
819
|
+
* Throws if either entry does not exist (P154: Proactive Object Existence Verification)
|
|
711
820
|
*/
|
|
712
821
|
linkEntries(
|
|
713
822
|
fromEntryId: number,
|
|
@@ -717,6 +826,16 @@ export class SqliteAdapter {
|
|
|
717
826
|
): Relationship {
|
|
718
827
|
const db = this.ensureDb()
|
|
719
828
|
|
|
829
|
+
// P154: Pre-check both entries exist before creating relationship
|
|
830
|
+
const fromEntry = this.getEntryById(fromEntryId)
|
|
831
|
+
if (!fromEntry) {
|
|
832
|
+
throw new Error(`Source entry ${String(fromEntryId)} not found`)
|
|
833
|
+
}
|
|
834
|
+
const toEntry = this.getEntryById(toEntryId)
|
|
835
|
+
if (!toEntry) {
|
|
836
|
+
throw new Error(`Target entry ${String(toEntryId)} not found`)
|
|
837
|
+
}
|
|
838
|
+
|
|
720
839
|
db.run(
|
|
721
840
|
`
|
|
722
841
|
INSERT INTO relationships (from_entry_id, to_entry_id, relationship_type, description)
|
|
@@ -728,7 +847,7 @@ export class SqliteAdapter {
|
|
|
728
847
|
const result = db.exec('SELECT last_insert_rowid() as id')
|
|
729
848
|
const id = result[0]?.values[0]?.[0] as number
|
|
730
849
|
|
|
731
|
-
this.
|
|
850
|
+
this.scheduleSave()
|
|
732
851
|
|
|
733
852
|
return {
|
|
734
853
|
id,
|
|
@@ -1077,7 +1196,7 @@ export class SqliteAdapter {
|
|
|
1077
1196
|
const newEntryCount = (newCountResult[0]?.values[0]?.[0] as number) ?? 0
|
|
1078
1197
|
|
|
1079
1198
|
// Save to main database path
|
|
1080
|
-
this.
|
|
1199
|
+
this.flushSave()
|
|
1081
1200
|
|
|
1082
1201
|
logger.info('Database restored from backup', {
|
|
1083
1202
|
module: 'SqliteAdapter',
|
|
@@ -1195,6 +1314,17 @@ export class SqliteAdapter {
|
|
|
1195
1314
|
autoContext: row['auto_context'] as string | null,
|
|
1196
1315
|
deletedAt: row['deleted_at'] as string | null,
|
|
1197
1316
|
tags: this.getTagsForEntry(id),
|
|
1317
|
+
// GitHub integration fields
|
|
1318
|
+
projectNumber: (row['project_number'] as number | null) ?? null,
|
|
1319
|
+
projectOwner: (row['project_owner'] as string | null) ?? null,
|
|
1320
|
+
issueNumber: (row['issue_number'] as number | null) ?? null,
|
|
1321
|
+
issueUrl: (row['issue_url'] as string | null) ?? null,
|
|
1322
|
+
prNumber: (row['pr_number'] as number | null) ?? null,
|
|
1323
|
+
prUrl: (row['pr_url'] as string | null) ?? null,
|
|
1324
|
+
prStatus: (row['pr_status'] as string | null) ?? null,
|
|
1325
|
+
workflowRunId: (row['workflow_run_id'] as number | null) ?? null,
|
|
1326
|
+
workflowName: (row['workflow_name'] as string | null) ?? null,
|
|
1327
|
+
workflowStatus: (row['workflow_status'] as string | null) ?? null,
|
|
1198
1328
|
}
|
|
1199
1329
|
}
|
|
1200
1330
|
|
|
@@ -13,7 +13,7 @@ export type { ToolFilterConfig } from '../types/index.js'
|
|
|
13
13
|
/**
|
|
14
14
|
* Tool group definitions mapping group names to tool names
|
|
15
15
|
*
|
|
16
|
-
* All
|
|
16
|
+
* All 39 tools are categorized here for filtering support.
|
|
17
17
|
*/
|
|
18
18
|
export const TOOL_GROUPS: Record<ToolGroup, string[]> = {
|
|
19
19
|
core: [
|
|
@@ -45,6 +45,12 @@ export const TOOL_GROUPS: Record<ToolGroup, string[]> = {
|
|
|
45
45
|
'move_kanban_item',
|
|
46
46
|
'create_github_issue_with_entry',
|
|
47
47
|
'close_github_issue_with_entry',
|
|
48
|
+
'get_github_milestones',
|
|
49
|
+
'get_github_milestone',
|
|
50
|
+
'create_github_milestone',
|
|
51
|
+
'update_github_milestone',
|
|
52
|
+
'delete_github_milestone',
|
|
53
|
+
'get_repo_insights',
|
|
48
54
|
],
|
|
49
55
|
backup: ['backup_journal', 'list_backups', 'restore_backup', 'cleanup_backups'],
|
|
50
56
|
}
|