@vheins/local-memory-mcp 0.5.32 → 0.6.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/README.md +9 -6
- package/dist/chunk-XIJO63UU.js +3618 -0
- package/dist/dashboard/public/assets/{index-C8FB4maW.css → index-Bd7v94SO.css} +1 -1
- package/dist/dashboard/public/assets/index-Df97JpLg.js +84 -0
- package/dist/dashboard/public/index.html +2 -2
- package/dist/dashboard/server.js +790 -451
- package/dist/mcp/server.js +2121 -305
- package/dist/{mcp/prompts/definitions → prompts}/create-task.md +4 -3
- package/dist/{mcp/prompts/definitions → prompts}/memory-agent-core.md +2 -0
- package/dist/{mcp/prompts/definitions → prompts}/memory-index-policy.md +2 -0
- package/dist/{mcp/prompts/definitions → prompts}/review-and-audit.md +3 -1
- package/dist/{mcp/prompts/definitions → prompts}/review-and-post-issue.md +3 -1
- package/dist/prompts/task-management-guidelines.md +28 -0
- package/dist/{mcp/prompts/definitions → prompts}/task-memory-executor.md +6 -5
- package/package.json +22 -6
- package/dist/capabilities.d.ts +0 -22
- package/dist/capabilities.d.ts.map +0 -1
- package/dist/capabilities.js +0 -38
- package/dist/capabilities.js.map +0 -1
- package/dist/completion.d.ts +0 -25
- package/dist/completion.d.ts.map +0 -1
- package/dist/completion.js +0 -127
- package/dist/completion.js.map +0 -1
- package/dist/dashboard/dashboard.test.d.ts +0 -2
- package/dist/dashboard/dashboard.test.d.ts.map +0 -1
- package/dist/dashboard/dashboard.test.js +0 -370
- package/dist/dashboard/dashboard.test.js.map +0 -1
- package/dist/dashboard/public/assets/index-DIhCu9qA.js +0 -78
- package/dist/dashboard/server.d.ts +0 -3
- package/dist/dashboard/server.d.ts.map +0 -1
- package/dist/dashboard/server.js.map +0 -1
- package/dist/e2e.test.d.ts +0 -2
- package/dist/e2e.test.d.ts.map +0 -1
- package/dist/e2e.test.js +0 -250
- package/dist/e2e.test.js.map +0 -1
- package/dist/mcp/capabilities.d.ts +0 -22
- package/dist/mcp/capabilities.d.ts.map +0 -1
- package/dist/mcp/capabilities.js +0 -38
- package/dist/mcp/capabilities.js.map +0 -1
- package/dist/mcp/client.d.ts +0 -34
- package/dist/mcp/client.d.ts.map +0 -1
- package/dist/mcp/client.js +0 -188
- package/dist/mcp/client.js.map +0 -1
- package/dist/mcp/client.test.d.ts +0 -2
- package/dist/mcp/client.test.d.ts.map +0 -1
- package/dist/mcp/client.test.js +0 -130
- package/dist/mcp/client.test.js.map +0 -1
- package/dist/mcp/completion.d.ts +0 -25
- package/dist/mcp/completion.d.ts.map +0 -1
- package/dist/mcp/completion.js +0 -127
- package/dist/mcp/completion.js.map +0 -1
- package/dist/mcp/elicitation.d.ts +0 -24
- package/dist/mcp/elicitation.d.ts.map +0 -1
- package/dist/mcp/elicitation.js +0 -13
- package/dist/mcp/elicitation.js.map +0 -1
- package/dist/mcp/prompts/definitions/task-management-guidelines.md +0 -30
- package/dist/mcp/prompts/loader.d.ts +0 -10
- package/dist/mcp/prompts/loader.d.ts.map +0 -1
- package/dist/mcp/prompts/loader.js +0 -31
- package/dist/mcp/prompts/loader.js.map +0 -1
- package/dist/mcp/prompts/registry.d.ts +0 -35
- package/dist/mcp/prompts/registry.d.ts.map +0 -1
- package/dist/mcp/prompts/registry.js +0 -95
- package/dist/mcp/prompts/registry.js.map +0 -1
- package/dist/mcp/resources/index.d.ts +0 -68
- package/dist/mcp/resources/index.d.ts.map +0 -1
- package/dist/mcp/resources/index.js +0 -359
- package/dist/mcp/resources/index.js.map +0 -1
- package/dist/mcp/router.d.ts +0 -14
- package/dist/mcp/router.d.ts.map +0 -1
- package/dist/mcp/router.js +0 -255
- package/dist/mcp/router.js.map +0 -1
- package/dist/mcp/sampling.d.ts +0 -69
- package/dist/mcp/sampling.d.ts.map +0 -1
- package/dist/mcp/sampling.js +0 -13
- package/dist/mcp/sampling.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -3
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/session.d.ts +0 -28
- package/dist/mcp/session.d.ts.map +0 -1
- package/dist/mcp/session.js +0 -106
- package/dist/mcp/session.js.map +0 -1
- package/dist/mcp/storage/sqlite.d.ts +0 -87
- package/dist/mcp/storage/sqlite.d.ts.map +0 -1
- package/dist/mcp/storage/sqlite.js +0 -1327
- package/dist/mcp/storage/sqlite.js.map +0 -1
- package/dist/mcp/storage/vectors.d.ts +0 -19
- package/dist/mcp/storage/vectors.d.ts.map +0 -1
- package/dist/mcp/storage/vectors.js +0 -74
- package/dist/mcp/storage/vectors.js.map +0 -1
- package/dist/mcp/storage/vectors.stub.d.ts +0 -12
- package/dist/mcp/storage/vectors.stub.d.ts.map +0 -1
- package/dist/mcp/storage/vectors.stub.js +0 -88
- package/dist/mcp/storage/vectors.stub.js.map +0 -1
- package/dist/mcp/tests/client.test.d.ts +0 -2
- package/dist/mcp/tests/client.test.d.ts.map +0 -1
- package/dist/mcp/tests/client.test.js +0 -130
- package/dist/mcp/tests/client.test.js.map +0 -1
- package/dist/mcp/tests/dashboard.test.d.ts +0 -2
- package/dist/mcp/tests/dashboard.test.d.ts.map +0 -1
- package/dist/mcp/tests/dashboard.test.js +0 -370
- package/dist/mcp/tests/dashboard.test.js.map +0 -1
- package/dist/mcp/tests/detail-tools.test.d.ts +0 -2
- package/dist/mcp/tests/detail-tools.test.d.ts.map +0 -1
- package/dist/mcp/tests/detail-tools.test.js +0 -109
- package/dist/mcp/tests/detail-tools.test.js.map +0 -1
- package/dist/mcp/tests/e2e.test.d.ts +0 -2
- package/dist/mcp/tests/e2e.test.d.ts.map +0 -1
- package/dist/mcp/tests/e2e.test.js +0 -255
- package/dist/mcp/tests/e2e.test.js.map +0 -1
- package/dist/mcp/tests/index.test.d.ts +0 -2
- package/dist/mcp/tests/index.test.d.ts.map +0 -1
- package/dist/mcp/tests/index.test.js +0 -185
- package/dist/mcp/tests/index.test.js.map +0 -1
- package/dist/mcp/tests/logger.test.d.ts +0 -2
- package/dist/mcp/tests/logger.test.d.ts.map +0 -1
- package/dist/mcp/tests/logger.test.js +0 -104
- package/dist/mcp/tests/logger.test.js.map +0 -1
- package/dist/mcp/tests/memory.bulk.test.d.ts +0 -2
- package/dist/mcp/tests/memory.bulk.test.d.ts.map +0 -1
- package/dist/mcp/tests/memory.bulk.test.js +0 -53
- package/dist/mcp/tests/memory.bulk.test.js.map +0 -1
- package/dist/mcp/tests/memory.search.test.d.ts +0 -2
- package/dist/mcp/tests/memory.search.test.d.ts.map +0 -1
- package/dist/mcp/tests/memory.search.test.js +0 -181
- package/dist/mcp/tests/memory.search.test.js.map +0 -1
- package/dist/mcp/tests/normalize.test.d.ts +0 -2
- package/dist/mcp/tests/normalize.test.d.ts.map +0 -1
- package/dist/mcp/tests/normalize.test.js +0 -181
- package/dist/mcp/tests/normalize.test.js.map +0 -1
- package/dist/mcp/tests/query-expander.test.d.ts +0 -2
- package/dist/mcp/tests/query-expander.test.d.ts.map +0 -1
- package/dist/mcp/tests/query-expander.test.js +0 -33
- package/dist/mcp/tests/query-expander.test.js.map +0 -1
- package/dist/mcp/tests/router.test.d.ts +0 -2
- package/dist/mcp/tests/router.test.d.ts.map +0 -1
- package/dist/mcp/tests/router.test.js +0 -481
- package/dist/mcp/tests/router.test.js.map +0 -1
- package/dist/mcp/tests/spec_compliance.test.d.ts +0 -2
- package/dist/mcp/tests/spec_compliance.test.d.ts.map +0 -1
- package/dist/mcp/tests/spec_compliance.test.js +0 -61
- package/dist/mcp/tests/spec_compliance.test.js.map +0 -1
- package/dist/mcp/tests/sqlite.test.d.ts +0 -2
- package/dist/mcp/tests/sqlite.test.d.ts.map +0 -1
- package/dist/mcp/tests/sqlite.test.js +0 -367
- package/dist/mcp/tests/sqlite.test.js.map +0 -1
- package/dist/mcp/tests/tasks-search.test.d.ts +0 -2
- package/dist/mcp/tests/tasks-search.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks-search.test.js +0 -156
- package/dist/mcp/tests/tasks-search.test.js.map +0 -1
- package/dist/mcp/tests/tasks-transition.test.d.ts +0 -2
- package/dist/mcp/tests/tasks-transition.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks-transition.test.js +0 -174
- package/dist/mcp/tests/tasks-transition.test.js.map +0 -1
- package/dist/mcp/tests/tasks.bulk.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.bulk.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.bulk.test.js +0 -410
- package/dist/mcp/tests/tasks.bulk.test.js.map +0 -1
- package/dist/mcp/tests/tasks.e2e.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.e2e.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.e2e.test.js +0 -289
- package/dist/mcp/tests/tasks.e2e.test.js.map +0 -1
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/mcp/tests/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js +0 -72
- package/dist/mcp/tests/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/mcp/tests/v2-features.test.d.ts +0 -2
- package/dist/mcp/tests/v2-features.test.d.ts.map +0 -1
- package/dist/mcp/tests/v2-features.test.js +0 -209
- package/dist/mcp/tests/v2-features.test.js.map +0 -1
- package/dist/mcp/tools/memory.acknowledge.d.ts +0 -4
- package/dist/mcp/tools/memory.acknowledge.d.ts.map +0 -1
- package/dist/mcp/tools/memory.acknowledge.js +0 -32
- package/dist/mcp/tools/memory.acknowledge.js.map +0 -1
- package/dist/mcp/tools/memory.bulk-delete.d.ts +0 -4
- package/dist/mcp/tools/memory.bulk-delete.d.ts.map +0 -1
- package/dist/mcp/tools/memory.bulk-delete.js +0 -40
- package/dist/mcp/tools/memory.bulk-delete.js.map +0 -1
- package/dist/mcp/tools/memory.delete.d.ts +0 -9
- package/dist/mcp/tools/memory.delete.d.ts.map +0 -1
- package/dist/mcp/tools/memory.delete.js +0 -40
- package/dist/mcp/tools/memory.delete.js.map +0 -1
- package/dist/mcp/tools/memory.get.d.ts +0 -3
- package/dist/mcp/tools/memory.get.d.ts.map +0 -1
- package/dist/mcp/tools/memory.get.js +0 -24
- package/dist/mcp/tools/memory.get.js.map +0 -1
- package/dist/mcp/tools/memory.recap.d.ts +0 -4
- package/dist/mcp/tools/memory.recap.d.ts.map +0 -1
- package/dist/mcp/tools/memory.recap.js +0 -52
- package/dist/mcp/tools/memory.recap.js.map +0 -1
- package/dist/mcp/tools/memory.search.d.ts +0 -5
- package/dist/mcp/tools/memory.search.d.ts.map +0 -1
- package/dist/mcp/tools/memory.search.js +0 -144
- package/dist/mcp/tools/memory.search.js.map +0 -1
- package/dist/mcp/tools/memory.store.d.ts +0 -5
- package/dist/mcp/tools/memory.store.d.ts.map +0 -1
- package/dist/mcp/tools/memory.store.js +0 -120
- package/dist/mcp/tools/memory.store.js.map +0 -1
- package/dist/mcp/tools/memory.summarize.d.ts +0 -4
- package/dist/mcp/tools/memory.summarize.d.ts.map +0 -1
- package/dist/mcp/tools/memory.summarize.js +0 -32
- package/dist/mcp/tools/memory.summarize.js.map +0 -1
- package/dist/mcp/tools/memory.synthesize.d.ts +0 -14
- package/dist/mcp/tools/memory.synthesize.d.ts.map +0 -1
- package/dist/mcp/tools/memory.synthesize.js +0 -230
- package/dist/mcp/tools/memory.synthesize.js.map +0 -1
- package/dist/mcp/tools/memory.update.d.ts +0 -5
- package/dist/mcp/tools/memory.update.d.ts.map +0 -1
- package/dist/mcp/tools/memory.update.js +0 -74
- package/dist/mcp/tools/memory.update.js.map +0 -1
- package/dist/mcp/tools/schemas.d.ts +0 -3177
- package/dist/mcp/tools/schemas.d.ts.map +0 -1
- package/dist/mcp/tools/schemas.js +0 -1081
- package/dist/mcp/tools/schemas.js.map +0 -1
- package/dist/mcp/tools/task.bulk-manage.d.ts +0 -4
- package/dist/mcp/tools/task.bulk-manage.d.ts.map +0 -1
- package/dist/mcp/tools/task.bulk-manage.js +0 -190
- package/dist/mcp/tools/task.bulk-manage.js.map +0 -1
- package/dist/mcp/tools/task.get.d.ts +0 -3
- package/dist/mcp/tools/task.get.d.ts.map +0 -1
- package/dist/mcp/tools/task.get.js +0 -32
- package/dist/mcp/tools/task.get.js.map +0 -1
- package/dist/mcp/tools/task.manage.d.ts +0 -18
- package/dist/mcp/tools/task.manage.d.ts.map +0 -1
- package/dist/mcp/tools/task.manage.js +0 -477
- package/dist/mcp/tools/task.manage.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -87
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -3
- package/dist/mcp/types.js.map +0 -1
- package/dist/mcp/utils/completion.d.ts +0 -2
- package/dist/mcp/utils/completion.d.ts.map +0 -1
- package/dist/mcp/utils/completion.js +0 -28
- package/dist/mcp/utils/completion.js.map +0 -1
- package/dist/mcp/utils/git-scope.d.ts +0 -8
- package/dist/mcp/utils/git-scope.d.ts.map +0 -1
- package/dist/mcp/utils/git-scope.js +0 -38
- package/dist/mcp/utils/git-scope.js.map +0 -1
- package/dist/mcp/utils/logger.d.ts +0 -25
- package/dist/mcp/utils/logger.d.ts.map +0 -1
- package/dist/mcp/utils/logger.js +0 -152
- package/dist/mcp/utils/logger.js.map +0 -1
- package/dist/mcp/utils/mcp-response.d.ts +0 -82
- package/dist/mcp/utils/mcp-response.d.ts.map +0 -1
- package/dist/mcp/utils/mcp-response.js +0 -182
- package/dist/mcp/utils/mcp-response.js.map +0 -1
- package/dist/mcp/utils/normalize.d.ts +0 -9
- package/dist/mcp/utils/normalize.d.ts.map +0 -1
- package/dist/mcp/utils/normalize.js +0 -62
- package/dist/mcp/utils/normalize.js.map +0 -1
- package/dist/mcp/utils/pagination.d.ts +0 -6
- package/dist/mcp/utils/pagination.d.ts.map +0 -1
- package/dist/mcp/utils/pagination.js +0 -32
- package/dist/mcp/utils/pagination.js.map +0 -1
- package/dist/mcp/utils/query-expander.d.ts +0 -2
- package/dist/mcp/utils/query-expander.d.ts.map +0 -1
- package/dist/mcp/utils/query-expander.js +0 -29
- package/dist/mcp/utils/query-expander.js.map +0 -1
- package/dist/memory.bulk.test.d.ts +0 -2
- package/dist/memory.bulk.test.d.ts.map +0 -1
- package/dist/memory.bulk.test.js +0 -52
- package/dist/memory.bulk.test.js.map +0 -1
- package/dist/memory.db +0 -0
- package/dist/prompts/registry.d.ts +0 -314
- package/dist/prompts/registry.d.ts.map +0 -1
- package/dist/prompts/registry.js +0 -936
- package/dist/prompts/registry.js.map +0 -1
- package/dist/resources/index.d.ts +0 -68
- package/dist/resources/index.d.ts.map +0 -1
- package/dist/resources/index.js +0 -323
- package/dist/resources/index.js.map +0 -1
- package/dist/resources/index.test.d.ts +0 -2
- package/dist/resources/index.test.d.ts.map +0 -1
- package/dist/resources/index.test.js +0 -105
- package/dist/resources/index.test.js.map +0 -1
- package/dist/router.d.ts +0 -14
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js +0 -242
- package/dist/router.js.map +0 -1
- package/dist/router.test.d.ts +0 -2
- package/dist/router.test.d.ts.map +0 -1
- package/dist/router.test.js +0 -122
- package/dist/router.test.js.map +0 -1
- package/dist/server.d.ts +0 -3
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -338
- package/dist/server.js.map +0 -1
- package/dist/storage/sqlite.d.ts +0 -79
- package/dist/storage/sqlite.d.ts.map +0 -1
- package/dist/storage/sqlite.js +0 -1148
- package/dist/storage/sqlite.js.map +0 -1
- package/dist/storage/sqlite.test.d.ts +0 -2
- package/dist/storage/sqlite.test.d.ts.map +0 -1
- package/dist/storage/sqlite.test.js +0 -367
- package/dist/storage/sqlite.test.js.map +0 -1
- package/dist/storage/vectors.d.ts +0 -19
- package/dist/storage/vectors.d.ts.map +0 -1
- package/dist/storage/vectors.js +0 -74
- package/dist/storage/vectors.js.map +0 -1
- package/dist/storage/vectors.stub.d.ts +0 -12
- package/dist/storage/vectors.stub.d.ts.map +0 -1
- package/dist/storage/vectors.stub.js +0 -88
- package/dist/storage/vectors.stub.js.map +0 -1
- package/dist/tasks.archive.test.d.ts +0 -2
- package/dist/tasks.archive.test.d.ts.map +0 -1
- package/dist/tasks.archive.test.js +0 -75
- package/dist/tasks.archive.test.js.map +0 -1
- package/dist/tasks.bulk.test.d.ts +0 -2
- package/dist/tasks.bulk.test.d.ts.map +0 -1
- package/dist/tasks.bulk.test.js +0 -250
- package/dist/tasks.bulk.test.js.map +0 -1
- package/dist/tasks.e2e.test.d.ts +0 -2
- package/dist/tasks.e2e.test.d.ts.map +0 -1
- package/dist/tasks.e2e.test.js +0 -289
- package/dist/tasks.e2e.test.js.map +0 -1
- package/dist/tests/client.test.d.ts +0 -2
- package/dist/tests/client.test.d.ts.map +0 -1
- package/dist/tests/client.test.js +0 -130
- package/dist/tests/client.test.js.map +0 -1
- package/dist/tests/dashboard.test.d.ts +0 -2
- package/dist/tests/dashboard.test.d.ts.map +0 -1
- package/dist/tests/dashboard.test.js +0 -370
- package/dist/tests/dashboard.test.js.map +0 -1
- package/dist/tests/e2e.test.d.ts +0 -2
- package/dist/tests/e2e.test.d.ts.map +0 -1
- package/dist/tests/e2e.test.js +0 -250
- package/dist/tests/e2e.test.js.map +0 -1
- package/dist/tests/index.test.d.ts +0 -2
- package/dist/tests/index.test.d.ts.map +0 -1
- package/dist/tests/index.test.js +0 -185
- package/dist/tests/index.test.js.map +0 -1
- package/dist/tests/logger.test.d.ts +0 -2
- package/dist/tests/logger.test.d.ts.map +0 -1
- package/dist/tests/logger.test.js +0 -104
- package/dist/tests/logger.test.js.map +0 -1
- package/dist/tests/memory.bulk.test.d.ts +0 -2
- package/dist/tests/memory.bulk.test.d.ts.map +0 -1
- package/dist/tests/memory.bulk.test.js +0 -52
- package/dist/tests/memory.bulk.test.js.map +0 -1
- package/dist/tests/memory.search.test.d.ts +0 -2
- package/dist/tests/memory.search.test.d.ts.map +0 -1
- package/dist/tests/memory.search.test.js +0 -181
- package/dist/tests/memory.search.test.js.map +0 -1
- package/dist/tests/normalize.test.d.ts +0 -2
- package/dist/tests/normalize.test.d.ts.map +0 -1
- package/dist/tests/normalize.test.js +0 -181
- package/dist/tests/normalize.test.js.map +0 -1
- package/dist/tests/query-expander.test.d.ts +0 -2
- package/dist/tests/query-expander.test.d.ts.map +0 -1
- package/dist/tests/query-expander.test.js +0 -33
- package/dist/tests/query-expander.test.js.map +0 -1
- package/dist/tests/router.test.d.ts +0 -2
- package/dist/tests/router.test.d.ts.map +0 -1
- package/dist/tests/router.test.js +0 -470
- package/dist/tests/router.test.js.map +0 -1
- package/dist/tests/sqlite.test.d.ts +0 -2
- package/dist/tests/sqlite.test.d.ts.map +0 -1
- package/dist/tests/sqlite.test.js +0 -367
- package/dist/tests/sqlite.test.js.map +0 -1
- package/dist/tests/tasks-search.test.d.ts +0 -2
- package/dist/tests/tasks-search.test.d.ts.map +0 -1
- package/dist/tests/tasks-search.test.js +0 -154
- package/dist/tests/tasks-search.test.js.map +0 -1
- package/dist/tests/tasks-transition.test.d.ts +0 -2
- package/dist/tests/tasks-transition.test.d.ts.map +0 -1
- package/dist/tests/tasks-transition.test.js +0 -174
- package/dist/tests/tasks-transition.test.js.map +0 -1
- package/dist/tests/tasks.bulk.test.d.ts +0 -2
- package/dist/tests/tasks.bulk.test.d.ts.map +0 -1
- package/dist/tests/tasks.bulk.test.js +0 -254
- package/dist/tests/tasks.bulk.test.js.map +0 -1
- package/dist/tests/tasks.e2e.test.d.ts +0 -2
- package/dist/tests/tasks.e2e.test.d.ts.map +0 -1
- package/dist/tests/tasks.e2e.test.js +0 -289
- package/dist/tests/tasks.e2e.test.js.map +0 -1
- package/dist/tests/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/tests/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/tests/tasks.pending-limit-refined.test.js +0 -72
- package/dist/tests/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/tests/v2-features.test.d.ts +0 -2
- package/dist/tests/v2-features.test.d.ts.map +0 -1
- package/dist/tests/v2-features.test.js +0 -209
- package/dist/tests/v2-features.test.js.map +0 -1
- package/dist/tools/memory.acknowledge.d.ts +0 -4
- package/dist/tools/memory.acknowledge.d.ts.map +0 -1
- package/dist/tools/memory.acknowledge.js +0 -30
- package/dist/tools/memory.acknowledge.js.map +0 -1
- package/dist/tools/memory.bulk-delete.d.ts +0 -4
- package/dist/tools/memory.bulk-delete.d.ts.map +0 -1
- package/dist/tools/memory.bulk-delete.js +0 -39
- package/dist/tools/memory.bulk-delete.js.map +0 -1
- package/dist/tools/memory.delete.d.ts +0 -9
- package/dist/tools/memory.delete.d.ts.map +0 -1
- package/dist/tools/memory.delete.js +0 -39
- package/dist/tools/memory.delete.js.map +0 -1
- package/dist/tools/memory.recap.d.ts +0 -4
- package/dist/tools/memory.recap.d.ts.map +0 -1
- package/dist/tools/memory.recap.js +0 -90
- package/dist/tools/memory.recap.js.map +0 -1
- package/dist/tools/memory.search.d.ts +0 -5
- package/dist/tools/memory.search.d.ts.map +0 -1
- package/dist/tools/memory.search.js +0 -134
- package/dist/tools/memory.search.js.map +0 -1
- package/dist/tools/memory.search.test.d.ts +0 -2
- package/dist/tools/memory.search.test.d.ts.map +0 -1
- package/dist/tools/memory.search.test.js +0 -181
- package/dist/tools/memory.search.test.js.map +0 -1
- package/dist/tools/memory.store.d.ts +0 -5
- package/dist/tools/memory.store.d.ts.map +0 -1
- package/dist/tools/memory.store.js +0 -117
- package/dist/tools/memory.store.js.map +0 -1
- package/dist/tools/memory.summarize.d.ts +0 -4
- package/dist/tools/memory.summarize.d.ts.map +0 -1
- package/dist/tools/memory.summarize.js +0 -31
- package/dist/tools/memory.summarize.js.map +0 -1
- package/dist/tools/memory.synthesize.d.ts +0 -14
- package/dist/tools/memory.synthesize.d.ts.map +0 -1
- package/dist/tools/memory.synthesize.js +0 -228
- package/dist/tools/memory.synthesize.js.map +0 -1
- package/dist/tools/memory.update.d.ts +0 -5
- package/dist/tools/memory.update.d.ts.map +0 -1
- package/dist/tools/memory.update.js +0 -73
- package/dist/tools/memory.update.js.map +0 -1
- package/dist/tools/schemas.d.ts +0 -2762
- package/dist/tools/schemas.d.ts.map +0 -1
- package/dist/tools/schemas.js +0 -952
- package/dist/tools/schemas.js.map +0 -1
- package/dist/tools/task.bulk-manage.d.ts +0 -4
- package/dist/tools/task.bulk-manage.d.ts.map +0 -1
- package/dist/tools/task.bulk-manage.js +0 -146
- package/dist/tools/task.bulk-manage.js.map +0 -1
- package/dist/tools/task.manage.d.ts +0 -16
- package/dist/tools/task.manage.d.ts.map +0 -1
- package/dist/tools/task.manage.js +0 -395
- package/dist/tools/task.manage.js.map +0 -1
- package/dist/tools/tasks-transition.test.d.ts +0 -2
- package/dist/tools/tasks-transition.test.d.ts.map +0 -1
- package/dist/tools/tasks-transition.test.js +0 -174
- package/dist/tools/tasks-transition.test.js.map +0 -1
- package/dist/tools/tasks.pending-limit-refined.test.d.ts +0 -2
- package/dist/tools/tasks.pending-limit-refined.test.d.ts.map +0 -1
- package/dist/tools/tasks.pending-limit-refined.test.js +0 -72
- package/dist/tools/tasks.pending-limit-refined.test.js.map +0 -1
- package/dist/types.d.ts +0 -87
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/utils/completion.d.ts +0 -2
- package/dist/utils/completion.d.ts.map +0 -1
- package/dist/utils/completion.js +0 -28
- package/dist/utils/completion.js.map +0 -1
- package/dist/utils/git-scope.d.ts +0 -8
- package/dist/utils/git-scope.d.ts.map +0 -1
- package/dist/utils/git-scope.js +0 -38
- package/dist/utils/git-scope.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -25
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -152
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/logger.test.d.ts +0 -2
- package/dist/utils/logger.test.d.ts.map +0 -1
- package/dist/utils/logger.test.js +0 -76
- package/dist/utils/logger.test.js.map +0 -1
- package/dist/utils/mcp-response.d.ts +0 -96
- package/dist/utils/mcp-response.d.ts.map +0 -1
- package/dist/utils/mcp-response.js +0 -131
- package/dist/utils/mcp-response.js.map +0 -1
- package/dist/utils/normalize.d.ts +0 -9
- package/dist/utils/normalize.d.ts.map +0 -1
- package/dist/utils/normalize.js +0 -62
- package/dist/utils/normalize.js.map +0 -1
- package/dist/utils/normalize.test.d.ts +0 -2
- package/dist/utils/normalize.test.d.ts.map +0 -1
- package/dist/utils/normalize.test.js +0 -159
- package/dist/utils/normalize.test.js.map +0 -1
- package/dist/utils/pagination.d.ts +0 -6
- package/dist/utils/pagination.d.ts.map +0 -1
- package/dist/utils/pagination.js +0 -32
- package/dist/utils/pagination.js.map +0 -1
- package/dist/utils/query-expander.d.ts +0 -2
- package/dist/utils/query-expander.d.ts.map +0 -1
- package/dist/utils/query-expander.js +0 -29
- package/dist/utils/query-expander.js.map +0 -1
- package/dist/utils/query-expander.test.d.ts +0 -2
- package/dist/utils/query-expander.test.d.ts.map +0 -1
- package/dist/utils/query-expander.test.js +0 -33
- package/dist/utils/query-expander.test.js.map +0 -1
- package/dist/v2-features.test.d.ts +0 -2
- package/dist/v2-features.test.d.ts.map +0 -1
- package/dist/v2-features.test.js +0 -209
- package/dist/v2-features.test.js.map +0 -1
- /package/dist/{mcp/prompts/definitions → prompts}/architecture-design.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/documentation-sync.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/fix-suggestion.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/import-github-issues.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/learning-retrospective.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/memory-guided-review.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/project-briefing.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/root-cause-analysis.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/security-triage.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/senior-code-review.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/session-planner.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/tech-affinity-scout.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/technical-planning.md +0 -0
- /package/dist/{mcp/prompts/definitions → prompts}/tool-usage-guidelines.md +0 -0
|
@@ -0,0 +1,3618 @@
|
|
|
1
|
+
// src/mcp/capabilities.ts
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
var __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
var pkgPath = path.join(__dirname, "../../package.json");
|
|
7
|
+
var pkgVersion = "0.1.0";
|
|
8
|
+
try {
|
|
9
|
+
if (fs.existsSync(pkgPath)) {
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
11
|
+
pkgVersion = pkg.version;
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
}
|
|
15
|
+
var MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
16
|
+
var CAPABILITIES = {
|
|
17
|
+
serverInfo: {
|
|
18
|
+
name: "mcp-memory-local",
|
|
19
|
+
version: pkgVersion
|
|
20
|
+
},
|
|
21
|
+
capabilities: {
|
|
22
|
+
completions: {},
|
|
23
|
+
logging: {},
|
|
24
|
+
resources: {
|
|
25
|
+
subscribe: true,
|
|
26
|
+
listChanged: true
|
|
27
|
+
},
|
|
28
|
+
tools: {
|
|
29
|
+
listChanged: false
|
|
30
|
+
},
|
|
31
|
+
prompts: {
|
|
32
|
+
listChanged: true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/mcp/utils/logger.ts
|
|
38
|
+
var LEVELS = {
|
|
39
|
+
debug: 0,
|
|
40
|
+
info: 1,
|
|
41
|
+
notice: 2,
|
|
42
|
+
warning: 3,
|
|
43
|
+
error: 4,
|
|
44
|
+
critical: 5,
|
|
45
|
+
alert: 6,
|
|
46
|
+
emergency: 7
|
|
47
|
+
};
|
|
48
|
+
var LEVEL_ALIASES = {
|
|
49
|
+
warn: "warning"
|
|
50
|
+
};
|
|
51
|
+
var currentLevel = parseLevel(process.env.LOG_LEVEL?.toLowerCase());
|
|
52
|
+
var sinks = /* @__PURE__ */ new Set();
|
|
53
|
+
function parseLevel(raw) {
|
|
54
|
+
if (!raw) return "info";
|
|
55
|
+
const normalized = LEVEL_ALIASES[raw] || raw;
|
|
56
|
+
if (normalized in LEVELS) return normalized;
|
|
57
|
+
return "info";
|
|
58
|
+
}
|
|
59
|
+
function normalizeMethodLevel(level) {
|
|
60
|
+
return level === "warn" ? "warning" : level;
|
|
61
|
+
}
|
|
62
|
+
function formatContextForStderr(context) {
|
|
63
|
+
if (!context || Object.keys(context).length === 0) return "";
|
|
64
|
+
return ` ${JSON.stringify(context)}`;
|
|
65
|
+
}
|
|
66
|
+
function getMcpIcon(message) {
|
|
67
|
+
if (message.includes("search")) return "\u{1F50D}";
|
|
68
|
+
if (message.includes("store") || message.includes("write")) return "\u{1F4BE}";
|
|
69
|
+
if (message.includes("read") || message.includes("resource")) return "\u{1F4D6}";
|
|
70
|
+
if (message.includes("delete")) return "\u{1F5D1}\uFE0F";
|
|
71
|
+
if (message.includes("update")) return "\u{1F504}";
|
|
72
|
+
if (message.includes("acknowledge")) return "\u2705";
|
|
73
|
+
if (message.includes("recap")) return "\u{1F4CB}";
|
|
74
|
+
if (message.includes("task")) return "\u26A1";
|
|
75
|
+
return "\u{1F916}";
|
|
76
|
+
}
|
|
77
|
+
function deriveLoggerName(message) {
|
|
78
|
+
if (message.startsWith("[Server]")) return "server";
|
|
79
|
+
if (message.startsWith("[Vectors]")) return "vectors";
|
|
80
|
+
if (message.startsWith("[MCP]")) return "mcp";
|
|
81
|
+
if (message.startsWith("[Dashboard]")) return "dashboard";
|
|
82
|
+
return "app";
|
|
83
|
+
}
|
|
84
|
+
function emitToStderr(level, message, context) {
|
|
85
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
86
|
+
if (message.startsWith("[MCP]")) {
|
|
87
|
+
const icon = getMcpIcon(message);
|
|
88
|
+
const action = message.replace("[MCP] ", "").trim();
|
|
89
|
+
process.stderr.write(`${timestamp} ${icon} [MCP] ${action.padEnd(7)}${formatContextForStderr(context)}
|
|
90
|
+
`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const levelStr = level.toUpperCase().padEnd(9);
|
|
94
|
+
process.stderr.write(`${timestamp} [${levelStr}] ${message}${formatContextForStderr(context)}
|
|
95
|
+
`);
|
|
96
|
+
}
|
|
97
|
+
function sanitizeLogData(message, context) {
|
|
98
|
+
return {
|
|
99
|
+
message,
|
|
100
|
+
...context ?? {}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function emitToSinks(level, message, context) {
|
|
104
|
+
const payload = {
|
|
105
|
+
level,
|
|
106
|
+
logger: deriveLoggerName(message),
|
|
107
|
+
data: sanitizeLogData(message, context)
|
|
108
|
+
};
|
|
109
|
+
for (const sink of sinks) {
|
|
110
|
+
try {
|
|
111
|
+
sink(payload);
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function log(level, message, context) {
|
|
117
|
+
const normalizedLevel = normalizeMethodLevel(level);
|
|
118
|
+
if (LEVELS[normalizedLevel] < LEVELS[currentLevel]) return;
|
|
119
|
+
emitToStderr(normalizedLevel, message, context);
|
|
120
|
+
emitToSinks(normalizedLevel, message, context);
|
|
121
|
+
}
|
|
122
|
+
var logger = {
|
|
123
|
+
debug(message, context) {
|
|
124
|
+
log("debug", message, context);
|
|
125
|
+
},
|
|
126
|
+
info(message, context) {
|
|
127
|
+
log("info", message, context);
|
|
128
|
+
},
|
|
129
|
+
notice(message, context) {
|
|
130
|
+
log("notice", message, context);
|
|
131
|
+
},
|
|
132
|
+
warn(message, context) {
|
|
133
|
+
log("warn", message, context);
|
|
134
|
+
},
|
|
135
|
+
warning(message, context) {
|
|
136
|
+
log("warning", message, context);
|
|
137
|
+
},
|
|
138
|
+
error(message, context) {
|
|
139
|
+
log("error", message, context);
|
|
140
|
+
},
|
|
141
|
+
critical(message, context) {
|
|
142
|
+
log("critical", message, context);
|
|
143
|
+
},
|
|
144
|
+
alert(message, context) {
|
|
145
|
+
log("alert", message, context);
|
|
146
|
+
},
|
|
147
|
+
emergency(message, context) {
|
|
148
|
+
log("emergency", message, context);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function setLogLevel(level) {
|
|
152
|
+
const raw = level.toLowerCase();
|
|
153
|
+
const normalized = LEVEL_ALIASES[raw] || raw;
|
|
154
|
+
if (!(normalized in LEVELS)) {
|
|
155
|
+
const error = new Error(`Invalid log level: ${level}`);
|
|
156
|
+
error.code = -32602;
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
currentLevel = normalized;
|
|
160
|
+
return currentLevel;
|
|
161
|
+
}
|
|
162
|
+
function getLogLevel() {
|
|
163
|
+
return currentLevel;
|
|
164
|
+
}
|
|
165
|
+
function addLogSink(sink) {
|
|
166
|
+
sinks.add(sink);
|
|
167
|
+
return () => sinks.delete(sink);
|
|
168
|
+
}
|
|
169
|
+
var LOG_LEVEL_VALUES = Object.keys(LEVELS);
|
|
170
|
+
|
|
171
|
+
// src/mcp/storage/sqlite.ts
|
|
172
|
+
import Database from "better-sqlite3";
|
|
173
|
+
import path2 from "path";
|
|
174
|
+
import fs2 from "fs";
|
|
175
|
+
import os from "os";
|
|
176
|
+
|
|
177
|
+
// src/mcp/storage/migrations.ts
|
|
178
|
+
var MigrationManager = class {
|
|
179
|
+
constructor(db) {
|
|
180
|
+
this.db = db;
|
|
181
|
+
}
|
|
182
|
+
db;
|
|
183
|
+
migrate() {
|
|
184
|
+
this.db.exec(`
|
|
185
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
186
|
+
id TEXT PRIMARY KEY,
|
|
187
|
+
repo TEXT NOT NULL,
|
|
188
|
+
type TEXT NOT NULL,
|
|
189
|
+
title TEXT,
|
|
190
|
+
content TEXT NOT NULL,
|
|
191
|
+
importance INTEGER NOT NULL CHECK (importance BETWEEN 1 AND 5),
|
|
192
|
+
folder TEXT,
|
|
193
|
+
language TEXT,
|
|
194
|
+
created_at TEXT NOT NULL,
|
|
195
|
+
updated_at TEXT NOT NULL,
|
|
196
|
+
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
197
|
+
recall_count INTEGER NOT NULL DEFAULT 0,
|
|
198
|
+
last_used_at TEXT,
|
|
199
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
200
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
201
|
+
model TEXT NOT NULL DEFAULT 'unknown',
|
|
202
|
+
completed_at TEXT
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
CREATE INDEX IF NOT EXISTS idx_memories_repo ON memories(repo);
|
|
206
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
207
|
+
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance);
|
|
208
|
+
CREATE INDEX IF NOT EXISTS idx_memories_hit_count ON memories(hit_count);
|
|
209
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_memories_updated_at ON memories(updated_at);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_memories_repo_created_at ON memories(repo, created_at DESC);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_memories_repo_hit_count ON memories(repo, hit_count DESC);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_memories_title ON memories(title);
|
|
214
|
+
|
|
215
|
+
CREATE TABLE IF NOT EXISTS memory_summary (
|
|
216
|
+
repo TEXT PRIMARY KEY,
|
|
217
|
+
summary TEXT NOT NULL,
|
|
218
|
+
updated_at TEXT NOT NULL
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
CREATE TABLE IF NOT EXISTS memory_vectors (
|
|
222
|
+
memory_id TEXT PRIMARY KEY,
|
|
223
|
+
vector TEXT NOT NULL,
|
|
224
|
+
updated_at TEXT NOT NULL,
|
|
225
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id) ON DELETE CASCADE
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
229
|
+
id TEXT PRIMARY KEY,
|
|
230
|
+
repo TEXT NOT NULL,
|
|
231
|
+
task_code TEXT NOT NULL,
|
|
232
|
+
phase TEXT,
|
|
233
|
+
title TEXT NOT NULL,
|
|
234
|
+
description TEXT,
|
|
235
|
+
status TEXT NOT NULL DEFAULT 'backlog',
|
|
236
|
+
priority INTEGER NOT NULL DEFAULT 3,
|
|
237
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
238
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
239
|
+
doc_path TEXT,
|
|
240
|
+
created_at TEXT NOT NULL,
|
|
241
|
+
updated_at TEXT NOT NULL,
|
|
242
|
+
finished_at TEXT,
|
|
243
|
+
canceled_at TEXT,
|
|
244
|
+
tags TEXT,
|
|
245
|
+
metadata TEXT,
|
|
246
|
+
parent_id TEXT,
|
|
247
|
+
depends_on TEXT,
|
|
248
|
+
est_tokens INTEGER NOT NULL DEFAULT 0,
|
|
249
|
+
in_progress_at TEXT,
|
|
250
|
+
FOREIGN KEY (parent_id) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
251
|
+
FOREIGN KEY (depends_on) REFERENCES tasks(id) ON DELETE SET NULL
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_repo ON tasks(repo);
|
|
255
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_code ON tasks(task_code);
|
|
256
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
257
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_phase ON tasks(phase);
|
|
258
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
|
|
259
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_created_at ON tasks(created_at);
|
|
260
|
+
|
|
261
|
+
CREATE TABLE IF NOT EXISTS task_comments (
|
|
262
|
+
id TEXT PRIMARY KEY,
|
|
263
|
+
task_id TEXT NOT NULL,
|
|
264
|
+
repo TEXT NOT NULL,
|
|
265
|
+
comment TEXT NOT NULL,
|
|
266
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
267
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
268
|
+
model TEXT NOT NULL DEFAULT 'unknown',
|
|
269
|
+
previous_status TEXT,
|
|
270
|
+
next_status TEXT,
|
|
271
|
+
created_at TEXT NOT NULL,
|
|
272
|
+
FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_task_id ON task_comments(task_id);
|
|
276
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_repo ON task_comments(repo);
|
|
277
|
+
CREATE INDEX IF NOT EXISTS idx_task_comments_created_at ON task_comments(created_at DESC);
|
|
278
|
+
|
|
279
|
+
CREATE TABLE IF NOT EXISTS memories_archive (
|
|
280
|
+
id TEXT PRIMARY KEY,
|
|
281
|
+
repo TEXT NOT NULL,
|
|
282
|
+
type TEXT NOT NULL,
|
|
283
|
+
content TEXT NOT NULL,
|
|
284
|
+
importance INTEGER NOT NULL,
|
|
285
|
+
folder TEXT,
|
|
286
|
+
language TEXT,
|
|
287
|
+
created_at TEXT NOT NULL,
|
|
288
|
+
updated_at TEXT NOT NULL,
|
|
289
|
+
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
290
|
+
recall_count INTEGER NOT NULL DEFAULT 0,
|
|
291
|
+
last_used_at TEXT,
|
|
292
|
+
expires_at TEXT,
|
|
293
|
+
archived_at TEXT NOT NULL,
|
|
294
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
295
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
296
|
+
model TEXT NOT NULL DEFAULT 'unknown',
|
|
297
|
+
completed_at TEXT
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
CREATE TABLE IF NOT EXISTS action_log (
|
|
301
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
302
|
+
action TEXT NOT NULL,
|
|
303
|
+
query TEXT,
|
|
304
|
+
response TEXT,
|
|
305
|
+
memory_id TEXT,
|
|
306
|
+
task_id TEXT,
|
|
307
|
+
repo TEXT NOT NULL,
|
|
308
|
+
result_count INTEGER NOT NULL DEFAULT 0,
|
|
309
|
+
created_at TEXT NOT NULL
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
CREATE INDEX IF NOT EXISTS idx_action_log_repo ON action_log(repo);
|
|
313
|
+
CREATE INDEX IF NOT EXISTS idx_action_log_created_at ON action_log(created_at);
|
|
314
|
+
|
|
315
|
+
-- FTS5 Virtual Table for Memories
|
|
316
|
+
-- Note: Only using id, title, and content for search indexing
|
|
317
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
318
|
+
id UNINDEXED,
|
|
319
|
+
repo,
|
|
320
|
+
type,
|
|
321
|
+
title,
|
|
322
|
+
content,
|
|
323
|
+
metadata UNINDEXED,
|
|
324
|
+
content='memories',
|
|
325
|
+
content_rowid='id'
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
-- Triggers to keep FTS index in sync with base table
|
|
329
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
330
|
+
INSERT INTO memories_fts(id, repo, type, title, content, metadata)
|
|
331
|
+
VALUES (new.id, new.repo, new.type, new.title, new.content, new.metadata);
|
|
332
|
+
END;
|
|
333
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
334
|
+
INSERT INTO memories_fts(memories_fts, id, repo, type, title, content, metadata)
|
|
335
|
+
VALUES ('delete', old.id, old.repo, old.type, old.title, old.content, old.metadata);
|
|
336
|
+
END;
|
|
337
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
338
|
+
INSERT INTO memories_fts(memories_fts, id, repo, type, title, content, metadata)
|
|
339
|
+
VALUES ('delete', old.id, old.repo, old.type, old.title, old.content, old.metadata);
|
|
340
|
+
INSERT INTO memories_fts(id, repo, type, title, content, metadata)
|
|
341
|
+
VALUES (new.id, new.repo, new.type, new.title, new.content, new.metadata);
|
|
342
|
+
END;
|
|
343
|
+
`);
|
|
344
|
+
const columnsToAdd = [
|
|
345
|
+
{ name: "title", table: "memories", definition: "ALTER TABLE memories ADD COLUMN title TEXT" },
|
|
346
|
+
{
|
|
347
|
+
name: "hit_count",
|
|
348
|
+
table: "memories",
|
|
349
|
+
definition: "ALTER TABLE memories ADD COLUMN hit_count INTEGER NOT NULL DEFAULT 0"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
name: "recall_count",
|
|
353
|
+
table: "memories",
|
|
354
|
+
definition: "ALTER TABLE memories ADD COLUMN recall_count INTEGER NOT NULL DEFAULT 0"
|
|
355
|
+
},
|
|
356
|
+
{ name: "last_used_at", table: "memories", definition: "ALTER TABLE memories ADD COLUMN last_used_at TEXT" },
|
|
357
|
+
{ name: "expires_at", table: "memories", definition: "ALTER TABLE memories ADD COLUMN expires_at TEXT" },
|
|
358
|
+
{ name: "supersedes", table: "memories", definition: "ALTER TABLE memories ADD COLUMN supersedes TEXT" },
|
|
359
|
+
{
|
|
360
|
+
name: "status",
|
|
361
|
+
table: "memories",
|
|
362
|
+
definition: "ALTER TABLE memories ADD COLUMN status TEXT NOT NULL DEFAULT 'active'"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: "is_global",
|
|
366
|
+
table: "memories",
|
|
367
|
+
definition: "ALTER TABLE memories ADD COLUMN is_global INTEGER NOT NULL DEFAULT 0"
|
|
368
|
+
},
|
|
369
|
+
{ name: "tags", table: "memories", definition: "ALTER TABLE memories ADD COLUMN tags TEXT" },
|
|
370
|
+
{ name: "metadata", table: "memories", definition: "ALTER TABLE memories ADD COLUMN metadata TEXT" },
|
|
371
|
+
{
|
|
372
|
+
name: "vector_version",
|
|
373
|
+
table: "memory_vectors",
|
|
374
|
+
definition: "ALTER TABLE memory_vectors ADD COLUMN vector_version INTEGER NOT NULL DEFAULT 1"
|
|
375
|
+
},
|
|
376
|
+
{ name: "depends_on", table: "tasks", definition: "ALTER TABLE tasks ADD COLUMN depends_on TEXT" },
|
|
377
|
+
{
|
|
378
|
+
name: "est_tokens",
|
|
379
|
+
table: "tasks",
|
|
380
|
+
definition: "ALTER TABLE tasks ADD COLUMN est_tokens INTEGER NOT NULL DEFAULT 0"
|
|
381
|
+
},
|
|
382
|
+
{ name: "in_progress_at", table: "tasks", definition: "ALTER TABLE tasks ADD COLUMN in_progress_at TEXT" },
|
|
383
|
+
{ name: "task_code", table: "tasks", definition: "ALTER TABLE tasks ADD COLUMN task_code TEXT" },
|
|
384
|
+
{ name: "task_id", table: "action_log", definition: "ALTER TABLE action_log ADD COLUMN task_id TEXT" },
|
|
385
|
+
{
|
|
386
|
+
name: "agent",
|
|
387
|
+
table: "memories",
|
|
388
|
+
definition: "ALTER TABLE memories ADD COLUMN agent TEXT NOT NULL DEFAULT 'unknown'"
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "role",
|
|
392
|
+
table: "memories",
|
|
393
|
+
definition: "ALTER TABLE memories ADD COLUMN role TEXT NOT NULL DEFAULT 'unknown'"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "model",
|
|
397
|
+
table: "memories",
|
|
398
|
+
definition: "ALTER TABLE memories ADD COLUMN model TEXT NOT NULL DEFAULT 'unknown'"
|
|
399
|
+
},
|
|
400
|
+
{ name: "completed_at", table: "memories", definition: "ALTER TABLE memories ADD COLUMN completed_at TEXT" },
|
|
401
|
+
{
|
|
402
|
+
name: "agent",
|
|
403
|
+
table: "tasks",
|
|
404
|
+
definition: "ALTER TABLE tasks ADD COLUMN agent TEXT NOT NULL DEFAULT 'unknown'"
|
|
405
|
+
},
|
|
406
|
+
{ name: "role", table: "tasks", definition: "ALTER TABLE tasks ADD COLUMN role TEXT NOT NULL DEFAULT 'unknown'" },
|
|
407
|
+
{ name: "doc_path", table: "tasks", definition: "ALTER TABLE tasks ADD COLUMN doc_path TEXT" },
|
|
408
|
+
{ name: "response", table: "action_log", definition: "ALTER TABLE action_log ADD COLUMN response TEXT" }
|
|
409
|
+
];
|
|
410
|
+
for (const col of columnsToAdd) {
|
|
411
|
+
try {
|
|
412
|
+
const tableInfo = this.db.prepare(`PRAGMA table_info(${col.table})`).all();
|
|
413
|
+
const existingTableColumns = tableInfo.map((c) => c.name);
|
|
414
|
+
if (tableInfo.length > 0 && !existingTableColumns.includes(col.name)) {
|
|
415
|
+
this.db.exec(col.definition);
|
|
416
|
+
}
|
|
417
|
+
} catch (e) {
|
|
418
|
+
logger.error(
|
|
419
|
+
`Migration step failed for ${col.table}.${col.name}: ${e instanceof Error ? e.message : String(e)}`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
this.ensureMemoryTypeConstraint();
|
|
424
|
+
this.ensureTaskStatusConstraintRemoved();
|
|
425
|
+
this.ensureMemoryStatusConstraintRemoved();
|
|
426
|
+
this.db.exec(`
|
|
427
|
+
CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status);
|
|
428
|
+
CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes);
|
|
429
|
+
CREATE INDEX IF NOT EXISTS idx_memories_is_global ON memories(is_global);
|
|
430
|
+
`);
|
|
431
|
+
try {
|
|
432
|
+
this.db.exec("UPDATE tasks SET task_code = substr(id, 1, 8) WHERE task_code IS NULL");
|
|
433
|
+
} catch (e) {
|
|
434
|
+
logger.error(`Legacy backfill task_code failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
ensureMemoryTypeConstraint() {
|
|
438
|
+
const tableSql = this.db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memories'").get();
|
|
439
|
+
if (!tableSql?.sql || !tableSql.sql.includes("CHECK (type IN")) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
this.db.exec(`
|
|
443
|
+
BEGIN TRANSACTION;
|
|
444
|
+
|
|
445
|
+
CREATE TABLE memories__migrated (
|
|
446
|
+
id TEXT PRIMARY KEY,
|
|
447
|
+
repo TEXT NOT NULL,
|
|
448
|
+
type TEXT NOT NULL,
|
|
449
|
+
title TEXT,
|
|
450
|
+
content TEXT NOT NULL,
|
|
451
|
+
importance INTEGER NOT NULL CHECK (importance BETWEEN 1 AND 5),
|
|
452
|
+
folder TEXT,
|
|
453
|
+
language TEXT,
|
|
454
|
+
created_at TEXT NOT NULL,
|
|
455
|
+
updated_at TEXT NOT NULL,
|
|
456
|
+
hit_count INTEGER NOT NULL DEFAULT 0,
|
|
457
|
+
recall_count INTEGER NOT NULL DEFAULT 0,
|
|
458
|
+
last_used_at TEXT,
|
|
459
|
+
expires_at TEXT,
|
|
460
|
+
supersedes TEXT,
|
|
461
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
462
|
+
is_global INTEGER NOT NULL DEFAULT 0,
|
|
463
|
+
tags TEXT,
|
|
464
|
+
metadata TEXT,
|
|
465
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
466
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
467
|
+
model TEXT NOT NULL DEFAULT 'unknown',
|
|
468
|
+
completed_at TEXT
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
INSERT INTO memories__migrated (
|
|
472
|
+
id, repo, type, title, content, importance, folder, language,
|
|
473
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
474
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
475
|
+
)
|
|
476
|
+
SELECT
|
|
477
|
+
id, repo, type, title, content, importance, folder, language,
|
|
478
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
479
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
480
|
+
FROM memories;
|
|
481
|
+
|
|
482
|
+
DROP TABLE memories;
|
|
483
|
+
ALTER TABLE memories__migrated RENAME TO memories;
|
|
484
|
+
|
|
485
|
+
COMMIT;
|
|
486
|
+
`);
|
|
487
|
+
}
|
|
488
|
+
ensureTaskStatusConstraintRemoved() {
|
|
489
|
+
const tableSql = this.db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'tasks'").get();
|
|
490
|
+
if (!tableSql?.sql || !tableSql.sql.includes("CHECK (status IN") && !tableSql.sql.includes("DEFAULT 'pending'")) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this.db.exec(`
|
|
494
|
+
BEGIN TRANSACTION;
|
|
495
|
+
|
|
496
|
+
CREATE TABLE tasks__migrated (
|
|
497
|
+
id TEXT PRIMARY KEY,
|
|
498
|
+
repo TEXT NOT NULL,
|
|
499
|
+
task_code TEXT NOT NULL,
|
|
500
|
+
phase TEXT,
|
|
501
|
+
title TEXT NOT NULL,
|
|
502
|
+
description TEXT,
|
|
503
|
+
status TEXT NOT NULL DEFAULT 'backlog',
|
|
504
|
+
priority INTEGER NOT NULL DEFAULT 3,
|
|
505
|
+
agent TEXT NOT NULL DEFAULT 'unknown',
|
|
506
|
+
role TEXT NOT NULL DEFAULT 'unknown',
|
|
507
|
+
doc_path TEXT,
|
|
508
|
+
created_at TEXT NOT NULL,
|
|
509
|
+
updated_at TEXT NOT NULL,
|
|
510
|
+
finished_at TEXT,
|
|
511
|
+
canceled_at TEXT,
|
|
512
|
+
tags TEXT,
|
|
513
|
+
metadata TEXT,
|
|
514
|
+
parent_id TEXT,
|
|
515
|
+
depends_on TEXT,
|
|
516
|
+
est_tokens INTEGER NOT NULL DEFAULT 0,
|
|
517
|
+
in_progress_at TEXT,
|
|
518
|
+
FOREIGN KEY (parent_id) REFERENCES tasks(id) ON DELETE SET NULL,
|
|
519
|
+
FOREIGN KEY (depends_on) REFERENCES tasks(id) ON DELETE SET NULL
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
INSERT INTO tasks__migrated (
|
|
523
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
524
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
525
|
+
)
|
|
526
|
+
SELECT
|
|
527
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
528
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
529
|
+
FROM tasks;
|
|
530
|
+
|
|
531
|
+
DROP TABLE tasks;
|
|
532
|
+
ALTER TABLE tasks__migrated RENAME TO tasks;
|
|
533
|
+
|
|
534
|
+
COMMIT;
|
|
535
|
+
`);
|
|
536
|
+
}
|
|
537
|
+
ensureMemoryStatusConstraintRemoved() {
|
|
538
|
+
const tableSql = this.db.prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'memories'").get();
|
|
539
|
+
if (tableSql?.sql?.includes("status TEXT NOT NULL DEFAULT 'active' CHECK")) {
|
|
540
|
+
this.ensureMemoryTypeConstraint();
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// src/mcp/utils/normalize.ts
|
|
546
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
547
|
+
// English stopwords
|
|
548
|
+
"a",
|
|
549
|
+
"an",
|
|
550
|
+
"the",
|
|
551
|
+
"and",
|
|
552
|
+
"or",
|
|
553
|
+
"but",
|
|
554
|
+
"in",
|
|
555
|
+
"on",
|
|
556
|
+
"at",
|
|
557
|
+
"to",
|
|
558
|
+
"for",
|
|
559
|
+
"of",
|
|
560
|
+
"with",
|
|
561
|
+
"by",
|
|
562
|
+
"from",
|
|
563
|
+
"up",
|
|
564
|
+
"about",
|
|
565
|
+
"into",
|
|
566
|
+
"through",
|
|
567
|
+
"during",
|
|
568
|
+
"is",
|
|
569
|
+
"are",
|
|
570
|
+
"was",
|
|
571
|
+
"were",
|
|
572
|
+
"be",
|
|
573
|
+
"been",
|
|
574
|
+
"being",
|
|
575
|
+
"have",
|
|
576
|
+
"has",
|
|
577
|
+
"had",
|
|
578
|
+
"do",
|
|
579
|
+
"does",
|
|
580
|
+
"did",
|
|
581
|
+
"will",
|
|
582
|
+
"would",
|
|
583
|
+
"could",
|
|
584
|
+
"should",
|
|
585
|
+
"may",
|
|
586
|
+
"might",
|
|
587
|
+
"shall",
|
|
588
|
+
"can",
|
|
589
|
+
"need",
|
|
590
|
+
"dare",
|
|
591
|
+
"ought",
|
|
592
|
+
"used",
|
|
593
|
+
"i",
|
|
594
|
+
"me",
|
|
595
|
+
"my",
|
|
596
|
+
"myself",
|
|
597
|
+
"we",
|
|
598
|
+
"our",
|
|
599
|
+
"ours",
|
|
600
|
+
"ourselves",
|
|
601
|
+
"you",
|
|
602
|
+
"your",
|
|
603
|
+
"yours",
|
|
604
|
+
"yourself",
|
|
605
|
+
"yourselves",
|
|
606
|
+
"he",
|
|
607
|
+
"him",
|
|
608
|
+
"his",
|
|
609
|
+
"himself",
|
|
610
|
+
"she",
|
|
611
|
+
"her",
|
|
612
|
+
"hers",
|
|
613
|
+
"herself",
|
|
614
|
+
"it",
|
|
615
|
+
"its",
|
|
616
|
+
"itself",
|
|
617
|
+
"they",
|
|
618
|
+
"them",
|
|
619
|
+
"their",
|
|
620
|
+
"theirs",
|
|
621
|
+
"themselves",
|
|
622
|
+
"what",
|
|
623
|
+
"which",
|
|
624
|
+
"who",
|
|
625
|
+
"whom",
|
|
626
|
+
"this",
|
|
627
|
+
"that",
|
|
628
|
+
"these",
|
|
629
|
+
"those",
|
|
630
|
+
"am",
|
|
631
|
+
"if",
|
|
632
|
+
"then",
|
|
633
|
+
"else",
|
|
634
|
+
"when",
|
|
635
|
+
"where",
|
|
636
|
+
"why",
|
|
637
|
+
"how",
|
|
638
|
+
"all",
|
|
639
|
+
"both",
|
|
640
|
+
"each",
|
|
641
|
+
"few",
|
|
642
|
+
"more",
|
|
643
|
+
"most",
|
|
644
|
+
"other",
|
|
645
|
+
"some",
|
|
646
|
+
"such",
|
|
647
|
+
"no",
|
|
648
|
+
"not",
|
|
649
|
+
"only",
|
|
650
|
+
"same",
|
|
651
|
+
"so",
|
|
652
|
+
"than",
|
|
653
|
+
"too",
|
|
654
|
+
"very",
|
|
655
|
+
"just",
|
|
656
|
+
"because",
|
|
657
|
+
"as",
|
|
658
|
+
"until",
|
|
659
|
+
"while",
|
|
660
|
+
"although",
|
|
661
|
+
"though",
|
|
662
|
+
"after",
|
|
663
|
+
"before",
|
|
664
|
+
"since",
|
|
665
|
+
"between",
|
|
666
|
+
"out",
|
|
667
|
+
"off",
|
|
668
|
+
"over",
|
|
669
|
+
"under",
|
|
670
|
+
"again",
|
|
671
|
+
"further",
|
|
672
|
+
"once",
|
|
673
|
+
"here",
|
|
674
|
+
"there",
|
|
675
|
+
"any",
|
|
676
|
+
"also",
|
|
677
|
+
// Indonesian stopwords
|
|
678
|
+
"yang",
|
|
679
|
+
"dan",
|
|
680
|
+
"di",
|
|
681
|
+
"ke",
|
|
682
|
+
"dari",
|
|
683
|
+
"ini",
|
|
684
|
+
"itu",
|
|
685
|
+
"dengan",
|
|
686
|
+
"untuk",
|
|
687
|
+
"pada",
|
|
688
|
+
"adalah",
|
|
689
|
+
"dalam",
|
|
690
|
+
"tidak",
|
|
691
|
+
"akan",
|
|
692
|
+
"juga",
|
|
693
|
+
"ada",
|
|
694
|
+
"saya",
|
|
695
|
+
"kita",
|
|
696
|
+
"kami",
|
|
697
|
+
"mereka",
|
|
698
|
+
"dia",
|
|
699
|
+
"ia",
|
|
700
|
+
"anda",
|
|
701
|
+
"kamu",
|
|
702
|
+
"aku",
|
|
703
|
+
"bisa",
|
|
704
|
+
"dapat",
|
|
705
|
+
"harus",
|
|
706
|
+
"sudah",
|
|
707
|
+
"telah",
|
|
708
|
+
"sedang",
|
|
709
|
+
"masih",
|
|
710
|
+
"lebih",
|
|
711
|
+
"sangat",
|
|
712
|
+
"paling",
|
|
713
|
+
"hanya",
|
|
714
|
+
"jika",
|
|
715
|
+
"kalau",
|
|
716
|
+
"karena",
|
|
717
|
+
"sehingga",
|
|
718
|
+
"namun",
|
|
719
|
+
"tetapi",
|
|
720
|
+
"tapi",
|
|
721
|
+
"atau",
|
|
722
|
+
"maupun",
|
|
723
|
+
"bahwa",
|
|
724
|
+
"oleh",
|
|
725
|
+
"seperti",
|
|
726
|
+
"antara",
|
|
727
|
+
"setelah",
|
|
728
|
+
"sebelum",
|
|
729
|
+
"ketika",
|
|
730
|
+
"saat",
|
|
731
|
+
"semua",
|
|
732
|
+
"setiap",
|
|
733
|
+
"beberapa",
|
|
734
|
+
"banyak",
|
|
735
|
+
"sedikit",
|
|
736
|
+
"lain",
|
|
737
|
+
"lainnya",
|
|
738
|
+
"tersebut",
|
|
739
|
+
"nya",
|
|
740
|
+
"pun",
|
|
741
|
+
"lah",
|
|
742
|
+
"kah",
|
|
743
|
+
"dah",
|
|
744
|
+
"agar",
|
|
745
|
+
"supaya",
|
|
746
|
+
"maka",
|
|
747
|
+
"meski",
|
|
748
|
+
"walaupun",
|
|
749
|
+
"meskipun",
|
|
750
|
+
"selain",
|
|
751
|
+
"selama",
|
|
752
|
+
"sejak",
|
|
753
|
+
"hingga",
|
|
754
|
+
"sampai",
|
|
755
|
+
"tentang",
|
|
756
|
+
"terhadap",
|
|
757
|
+
"melalui",
|
|
758
|
+
"tanpa",
|
|
759
|
+
"bagi",
|
|
760
|
+
"atas",
|
|
761
|
+
"bawah",
|
|
762
|
+
"depan",
|
|
763
|
+
"belakang",
|
|
764
|
+
"kiri",
|
|
765
|
+
"kanan",
|
|
766
|
+
"sini",
|
|
767
|
+
"sana",
|
|
768
|
+
"situ",
|
|
769
|
+
"begitu",
|
|
770
|
+
"begini",
|
|
771
|
+
"demikian",
|
|
772
|
+
"hal",
|
|
773
|
+
"cara",
|
|
774
|
+
"waktu",
|
|
775
|
+
"tempat"
|
|
776
|
+
]);
|
|
777
|
+
function normalize(text) {
|
|
778
|
+
return text.toLowerCase().replace(/[^a-z0-9\s_\-.]/g, " ").replace(/\s+/g, " ").trim();
|
|
779
|
+
}
|
|
780
|
+
function normalizeRepo(repo) {
|
|
781
|
+
if (!repo) return "";
|
|
782
|
+
const parts = repo.split("/");
|
|
783
|
+
return parts[parts.length - 1].trim();
|
|
784
|
+
}
|
|
785
|
+
function tokenize(text) {
|
|
786
|
+
return normalize(text).split(" ").filter((token) => token.length > 0 && !STOPWORDS.has(token));
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/mcp/storage/base.ts
|
|
790
|
+
var BaseEntity = class {
|
|
791
|
+
constructor(db) {
|
|
792
|
+
this.db = db;
|
|
793
|
+
}
|
|
794
|
+
db;
|
|
795
|
+
/**
|
|
796
|
+
* Safe JSON parsing helper to avoid crashes on corrupt data
|
|
797
|
+
*/
|
|
798
|
+
safeJSONParse(json, defaultValue) {
|
|
799
|
+
if (!json) return defaultValue;
|
|
800
|
+
try {
|
|
801
|
+
return JSON.parse(json);
|
|
802
|
+
} catch {
|
|
803
|
+
return defaultValue;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Mapping helper for MemoryEntry
|
|
808
|
+
*/
|
|
809
|
+
rowToMemoryEntry(row) {
|
|
810
|
+
const r = row;
|
|
811
|
+
return {
|
|
812
|
+
id: r.id,
|
|
813
|
+
type: r.type,
|
|
814
|
+
title: r.title || "Untitled",
|
|
815
|
+
content: r.content,
|
|
816
|
+
importance: r.importance,
|
|
817
|
+
agent: r.agent || "unknown",
|
|
818
|
+
role: r.role || "unknown",
|
|
819
|
+
model: r.model || "unknown",
|
|
820
|
+
scope: {
|
|
821
|
+
repo: r.repo,
|
|
822
|
+
folder: r.folder || void 0,
|
|
823
|
+
language: r.language || void 0
|
|
824
|
+
},
|
|
825
|
+
created_at: r.created_at,
|
|
826
|
+
updated_at: r.updated_at,
|
|
827
|
+
completed_at: r.completed_at || null,
|
|
828
|
+
hit_count: r.hit_count ?? 0,
|
|
829
|
+
recall_count: r.recall_count ?? 0,
|
|
830
|
+
last_used_at: r.last_used_at ?? null,
|
|
831
|
+
expires_at: r.expires_at ?? null,
|
|
832
|
+
supersedes: r.supersedes ?? null,
|
|
833
|
+
status: r.status || "active",
|
|
834
|
+
is_global: r.is_global === 1,
|
|
835
|
+
tags: this.safeJSONParse(r.tags, []),
|
|
836
|
+
metadata: this.safeJSONParse(r.metadata, {})
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Mapping helper for Task
|
|
841
|
+
*/
|
|
842
|
+
rowToTask(row) {
|
|
843
|
+
const r = row;
|
|
844
|
+
return {
|
|
845
|
+
id: r.id,
|
|
846
|
+
repo: r.repo,
|
|
847
|
+
task_code: r.task_code,
|
|
848
|
+
phase: r.phase || "",
|
|
849
|
+
title: r.title,
|
|
850
|
+
description: r.description || null,
|
|
851
|
+
status: r.status || "backlog",
|
|
852
|
+
priority: r.priority || 3,
|
|
853
|
+
agent: r.agent || "unknown",
|
|
854
|
+
role: r.role || "unknown",
|
|
855
|
+
doc_path: r.doc_path || null,
|
|
856
|
+
created_at: r.created_at,
|
|
857
|
+
updated_at: r.updated_at,
|
|
858
|
+
in_progress_at: r.in_progress_at || null,
|
|
859
|
+
finished_at: r.finished_at || null,
|
|
860
|
+
canceled_at: r.canceled_at || null,
|
|
861
|
+
est_tokens: r.est_tokens || 0,
|
|
862
|
+
tags: this.safeJSONParse(r.tags, []),
|
|
863
|
+
metadata: this.safeJSONParse(r.metadata, {}),
|
|
864
|
+
parent_id: r.parent_id || null,
|
|
865
|
+
depends_on: r.depends_on || null,
|
|
866
|
+
comments_count: r.comments_count || 0
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Vector math utilities (simple bag-of-words implementation)
|
|
871
|
+
*/
|
|
872
|
+
computeVector(text) {
|
|
873
|
+
const tokens = tokenize(text);
|
|
874
|
+
const vector = {};
|
|
875
|
+
tokens.forEach((token) => {
|
|
876
|
+
vector[token] = (vector[token] || 0) + 1;
|
|
877
|
+
});
|
|
878
|
+
return vector;
|
|
879
|
+
}
|
|
880
|
+
cosineSimilarity(v1, v2) {
|
|
881
|
+
const keys1 = Object.keys(v1);
|
|
882
|
+
const keys2 = Object.keys(v2);
|
|
883
|
+
if (!keys1.length || !keys2.length) return 0;
|
|
884
|
+
let dotProduct = 0;
|
|
885
|
+
for (const key of keys1) {
|
|
886
|
+
if (v2[key]) dotProduct += v1[key] * v2[key];
|
|
887
|
+
}
|
|
888
|
+
let mag1 = 0;
|
|
889
|
+
for (const key of keys1) mag1 += v1[key] * v1[key];
|
|
890
|
+
let mag2 = 0;
|
|
891
|
+
for (const key of keys2) mag2 += v2[key] * v2[key];
|
|
892
|
+
const mag = Math.sqrt(mag1) * Math.sqrt(mag2);
|
|
893
|
+
return mag === 0 ? 0 : dotProduct / mag;
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// src/mcp/entities/memory.ts
|
|
898
|
+
var MemoryEntity = class extends BaseEntity {
|
|
899
|
+
insert(entry) {
|
|
900
|
+
const stmt = this.db.prepare(`
|
|
901
|
+
INSERT INTO memories (
|
|
902
|
+
id, repo, type, title, content, importance, folder, language,
|
|
903
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
904
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
905
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
906
|
+
`);
|
|
907
|
+
stmt.run(
|
|
908
|
+
entry.id,
|
|
909
|
+
entry.scope.repo,
|
|
910
|
+
entry.type,
|
|
911
|
+
entry.title || null,
|
|
912
|
+
entry.content,
|
|
913
|
+
entry.importance,
|
|
914
|
+
entry.scope.folder || null,
|
|
915
|
+
entry.scope.language || null,
|
|
916
|
+
entry.created_at,
|
|
917
|
+
entry.updated_at,
|
|
918
|
+
entry.expires_at ?? null,
|
|
919
|
+
entry.supersedes ?? null,
|
|
920
|
+
entry.status || "active",
|
|
921
|
+
entry.is_global ? 1 : 0,
|
|
922
|
+
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
923
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
924
|
+
entry.agent || "unknown",
|
|
925
|
+
entry.role || "unknown",
|
|
926
|
+
entry.model || "unknown",
|
|
927
|
+
entry.completed_at || null
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
update(id, updates) {
|
|
931
|
+
const fields = [];
|
|
932
|
+
const values = [];
|
|
933
|
+
Object.keys(updates).forEach((key) => {
|
|
934
|
+
const k = key;
|
|
935
|
+
const val = updates[k];
|
|
936
|
+
if (val !== void 0) {
|
|
937
|
+
if (k === "scope") {
|
|
938
|
+
const scope = updates.scope;
|
|
939
|
+
if (scope?.repo) {
|
|
940
|
+
fields.push("repo = ?");
|
|
941
|
+
values.push(scope.repo);
|
|
942
|
+
}
|
|
943
|
+
if (scope?.folder !== void 0) {
|
|
944
|
+
fields.push("folder = ?");
|
|
945
|
+
values.push(scope.folder);
|
|
946
|
+
}
|
|
947
|
+
if (scope?.language !== void 0) {
|
|
948
|
+
fields.push("language = ?");
|
|
949
|
+
values.push(scope.language);
|
|
950
|
+
}
|
|
951
|
+
} else if (k === "tags" || k === "metadata") {
|
|
952
|
+
fields.push(`${k} = ?`);
|
|
953
|
+
values.push(JSON.stringify(val));
|
|
954
|
+
} else if (k === "is_global") {
|
|
955
|
+
fields.push(`${k} = ?`);
|
|
956
|
+
values.push(val ? 1 : 0);
|
|
957
|
+
} else if (k !== "id" && k !== "created_at") {
|
|
958
|
+
fields.push(`${k} = ?`);
|
|
959
|
+
values.push(val);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
if (fields.length === 0) return;
|
|
964
|
+
fields.push("updated_at = ?");
|
|
965
|
+
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
966
|
+
values.push(id);
|
|
967
|
+
const stmt = this.db.prepare(`UPDATE memories SET ${fields.join(", ")} WHERE id = ?`);
|
|
968
|
+
stmt.run(...values);
|
|
969
|
+
}
|
|
970
|
+
delete(id) {
|
|
971
|
+
this.db.prepare("DELETE FROM memories WHERE id = ?").run(id);
|
|
972
|
+
}
|
|
973
|
+
getById(id) {
|
|
974
|
+
const row = this.db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
975
|
+
return row ? this.rowToMemoryEntry(row) : null;
|
|
976
|
+
}
|
|
977
|
+
getByIdWithStats(id) {
|
|
978
|
+
const row = this.db.prepare(
|
|
979
|
+
`
|
|
980
|
+
SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate
|
|
981
|
+
FROM memories WHERE id = ?
|
|
982
|
+
`
|
|
983
|
+
).get(id);
|
|
984
|
+
if (!row) return null;
|
|
985
|
+
return {
|
|
986
|
+
...this.rowToMemoryEntry(row),
|
|
987
|
+
recall_rate: row.recall_rate ?? 0
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
getByIds(ids, options = {}) {
|
|
991
|
+
if (ids.length === 0) return [];
|
|
992
|
+
let sql = `SELECT * FROM memories WHERE id IN (${ids.map(() => "?").join(",")})`;
|
|
993
|
+
const params = [...ids];
|
|
994
|
+
if (options.type) {
|
|
995
|
+
sql += " AND type = ?";
|
|
996
|
+
params.push(options.type);
|
|
997
|
+
}
|
|
998
|
+
if (options.status) {
|
|
999
|
+
sql += " AND status = ?";
|
|
1000
|
+
params.push(options.status);
|
|
1001
|
+
}
|
|
1002
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
1003
|
+
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1004
|
+
}
|
|
1005
|
+
getStats(repo) {
|
|
1006
|
+
let sql = "SELECT type, COUNT(*) as count FROM memories";
|
|
1007
|
+
const params = [];
|
|
1008
|
+
if (repo) {
|
|
1009
|
+
sql += " WHERE repo = ?";
|
|
1010
|
+
params.push(repo);
|
|
1011
|
+
}
|
|
1012
|
+
sql += " GROUP BY type";
|
|
1013
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
1014
|
+
const byType = {};
|
|
1015
|
+
let total = 0;
|
|
1016
|
+
rows.forEach((row) => {
|
|
1017
|
+
byType[row.type] = row.count;
|
|
1018
|
+
total += row.count;
|
|
1019
|
+
});
|
|
1020
|
+
return { total, byType };
|
|
1021
|
+
}
|
|
1022
|
+
searchByRepo(repo, query = "", type, limit = 5) {
|
|
1023
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1024
|
+
if (query && query.length >= 3) {
|
|
1025
|
+
try {
|
|
1026
|
+
let sql2 = `
|
|
1027
|
+
SELECT m.*
|
|
1028
|
+
FROM memories m
|
|
1029
|
+
JOIN memories_fts f ON m.id = f.id
|
|
1030
|
+
WHERE m.repo = ? AND memories_fts MATCH ? AND m.status = 'active'
|
|
1031
|
+
AND (m.expires_at IS NULL OR m.expires_at > ?)
|
|
1032
|
+
`;
|
|
1033
|
+
const params2 = [repo, query, now];
|
|
1034
|
+
if (type) {
|
|
1035
|
+
sql2 += " AND m.type = ?";
|
|
1036
|
+
params2.push(type);
|
|
1037
|
+
}
|
|
1038
|
+
sql2 += " ORDER BY rank, m.importance DESC, m.created_at DESC LIMIT ?";
|
|
1039
|
+
params2.push(limit);
|
|
1040
|
+
const rows2 = this.db.prepare(sql2).all(...params2);
|
|
1041
|
+
return rows2.map((row) => this.rowToMemoryEntry(row));
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
logger.warn("FTS5 similarity search failed, falling back to LIKE", {
|
|
1044
|
+
error: e instanceof Error ? e.message : String(e),
|
|
1045
|
+
repo,
|
|
1046
|
+
query
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
let sql = "SELECT * FROM memories WHERE repo = ? AND (content LIKE ? OR title LIKE ? OR tags LIKE ?) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?)";
|
|
1051
|
+
const params = [repo, `%${query}%`, `%${query}%`, `%${query}%`, now];
|
|
1052
|
+
if (type) {
|
|
1053
|
+
sql += " AND type = ?";
|
|
1054
|
+
params.push(type);
|
|
1055
|
+
}
|
|
1056
|
+
sql += " ORDER BY importance DESC, created_at DESC LIMIT ?";
|
|
1057
|
+
params.push(limit);
|
|
1058
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
1059
|
+
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1060
|
+
}
|
|
1061
|
+
bulkInsertMemories(entries) {
|
|
1062
|
+
const insert = this.db.prepare(`
|
|
1063
|
+
INSERT INTO memories (
|
|
1064
|
+
id, repo, type, title, content, importance, folder, language,
|
|
1065
|
+
created_at, updated_at, hit_count, recall_count, last_used_at, expires_at,
|
|
1066
|
+
supersedes, status, is_global, tags, metadata, agent, role, model, completed_at
|
|
1067
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1068
|
+
`);
|
|
1069
|
+
const insertMany = this.db.transaction((entries2) => {
|
|
1070
|
+
let count = 0;
|
|
1071
|
+
for (const entry of entries2) {
|
|
1072
|
+
insert.run(
|
|
1073
|
+
entry.id,
|
|
1074
|
+
entry.scope.repo,
|
|
1075
|
+
entry.type,
|
|
1076
|
+
entry.title || null,
|
|
1077
|
+
entry.content,
|
|
1078
|
+
entry.importance,
|
|
1079
|
+
entry.scope.folder || null,
|
|
1080
|
+
entry.scope.language || null,
|
|
1081
|
+
entry.created_at,
|
|
1082
|
+
entry.updated_at,
|
|
1083
|
+
entry.expires_at ?? null,
|
|
1084
|
+
entry.supersedes ?? null,
|
|
1085
|
+
entry.status || "active",
|
|
1086
|
+
entry.is_global ? 1 : 0,
|
|
1087
|
+
entry.tags ? JSON.stringify(entry.tags) : null,
|
|
1088
|
+
entry.metadata ? JSON.stringify(entry.metadata) : null,
|
|
1089
|
+
entry.agent || "unknown",
|
|
1090
|
+
entry.role || "unknown",
|
|
1091
|
+
entry.model || "unknown",
|
|
1092
|
+
entry.completed_at || null
|
|
1093
|
+
);
|
|
1094
|
+
count++;
|
|
1095
|
+
}
|
|
1096
|
+
return count;
|
|
1097
|
+
});
|
|
1098
|
+
return insertMany(entries);
|
|
1099
|
+
}
|
|
1100
|
+
bulkUpdateMemories(ids, updates) {
|
|
1101
|
+
if (ids.length === 0) return 0;
|
|
1102
|
+
const fields = [];
|
|
1103
|
+
const values = [];
|
|
1104
|
+
Object.keys(updates).forEach((key) => {
|
|
1105
|
+
const value = updates[key];
|
|
1106
|
+
if (value !== void 0) {
|
|
1107
|
+
if (key === "tags" || key === "metadata") {
|
|
1108
|
+
fields.push(`${key} = ?`);
|
|
1109
|
+
values.push(JSON.stringify(value));
|
|
1110
|
+
} else if (key === "is_global") {
|
|
1111
|
+
fields.push(`${key} = ?`);
|
|
1112
|
+
values.push(value ? 1 : 0);
|
|
1113
|
+
} else {
|
|
1114
|
+
fields.push(`${key} = ?`);
|
|
1115
|
+
values.push(value);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
if (fields.length === 0) return 0;
|
|
1120
|
+
fields.push("updated_at = ?");
|
|
1121
|
+
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
1122
|
+
const updateMany = this.db.transaction((ids2, fields2, values2) => {
|
|
1123
|
+
let count = 0;
|
|
1124
|
+
const chunkSize = 500;
|
|
1125
|
+
for (let i = 0; i < ids2.length; i += chunkSize) {
|
|
1126
|
+
const chunk = ids2.slice(i, i + chunkSize);
|
|
1127
|
+
const stmt = this.db.prepare(
|
|
1128
|
+
`UPDATE memories SET ${fields2.join(", ")} WHERE id IN (${chunk.map(() => "?").join(",")})`
|
|
1129
|
+
);
|
|
1130
|
+
const result = stmt.run(...values2, ...chunk);
|
|
1131
|
+
count += result.changes;
|
|
1132
|
+
}
|
|
1133
|
+
return count;
|
|
1134
|
+
});
|
|
1135
|
+
return updateMany(ids, fields, values);
|
|
1136
|
+
}
|
|
1137
|
+
bulkDeleteMemories(ids) {
|
|
1138
|
+
if (ids.length === 0) return 0;
|
|
1139
|
+
const deleteMany = this.db.transaction((ids2) => {
|
|
1140
|
+
let count = 0;
|
|
1141
|
+
const chunkSize = 500;
|
|
1142
|
+
for (let i = 0; i < ids2.length; i += chunkSize) {
|
|
1143
|
+
const chunk = ids2.slice(i, i + chunkSize);
|
|
1144
|
+
const stmt = this.db.prepare(`DELETE FROM memories WHERE id IN (${chunk.map(() => "?").join(",")})`);
|
|
1145
|
+
const result = stmt.run(...chunk);
|
|
1146
|
+
count += result.changes;
|
|
1147
|
+
}
|
|
1148
|
+
return count;
|
|
1149
|
+
});
|
|
1150
|
+
return deleteMany(ids);
|
|
1151
|
+
}
|
|
1152
|
+
getRecentMemories(repo, limit, offset = 0, includeArchived = false, excludeTypes = []) {
|
|
1153
|
+
let query = "SELECT * FROM memories WHERE repo = ?";
|
|
1154
|
+
const params = [repo];
|
|
1155
|
+
if (!includeArchived) {
|
|
1156
|
+
query += " AND status = 'active'";
|
|
1157
|
+
}
|
|
1158
|
+
if (excludeTypes.length > 0) {
|
|
1159
|
+
query += ` AND type NOT IN (${excludeTypes.map(() => "?").join(",")})`;
|
|
1160
|
+
params.push(...excludeTypes);
|
|
1161
|
+
}
|
|
1162
|
+
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?";
|
|
1163
|
+
params.push(limit, offset);
|
|
1164
|
+
const rows = this.db.prepare(query).all(...params);
|
|
1165
|
+
return rows.map((row) => this.rowToMemoryEntry(row));
|
|
1166
|
+
}
|
|
1167
|
+
getTotalCount(repo, includeArchived = false, excludeTypes = []) {
|
|
1168
|
+
let sql = "SELECT COUNT(*) as count FROM memories WHERE repo = ?";
|
|
1169
|
+
const params = [repo];
|
|
1170
|
+
if (!includeArchived) sql += " AND status = 'active'";
|
|
1171
|
+
if (excludeTypes.length > 0) {
|
|
1172
|
+
sql += ` AND type NOT IN (${excludeTypes.map(() => "?").join(",")})`;
|
|
1173
|
+
params.push(...excludeTypes);
|
|
1174
|
+
}
|
|
1175
|
+
const row = this.db.prepare(sql).get(...params);
|
|
1176
|
+
return row.count;
|
|
1177
|
+
}
|
|
1178
|
+
incrementHitCount(id) {
|
|
1179
|
+
this.db.prepare("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
1180
|
+
}
|
|
1181
|
+
incrementHitCounts(ids) {
|
|
1182
|
+
if (!ids || ids.length === 0) return;
|
|
1183
|
+
const stmt = this.db.prepare("UPDATE memories SET hit_count = hit_count + 1, last_used_at = ? WHERE id = ?");
|
|
1184
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1185
|
+
const transaction = this.db.transaction((idsToUpdate) => {
|
|
1186
|
+
for (const id of idsToUpdate) {
|
|
1187
|
+
stmt.run(now, id);
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
transaction(ids);
|
|
1191
|
+
}
|
|
1192
|
+
incrementRecallCount(id) {
|
|
1193
|
+
this.db.prepare("UPDATE memories SET recall_count = recall_count + 1, last_used_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
1194
|
+
}
|
|
1195
|
+
getVectorCandidates(repo, limit = 100) {
|
|
1196
|
+
let sql = `SELECT mv.memory_id, mv.vector FROM memory_vectors mv JOIN memories m ON mv.memory_id = m.id`;
|
|
1197
|
+
const params = [];
|
|
1198
|
+
if (repo) {
|
|
1199
|
+
sql += " WHERE m.repo = ?";
|
|
1200
|
+
params.push(repo);
|
|
1201
|
+
}
|
|
1202
|
+
sql += " LIMIT ?";
|
|
1203
|
+
params.push(limit);
|
|
1204
|
+
return this.db.prepare(sql).all(...params);
|
|
1205
|
+
}
|
|
1206
|
+
upsertVectorEmbedding(memoryId, vector) {
|
|
1207
|
+
this.db.prepare(
|
|
1208
|
+
`
|
|
1209
|
+
INSERT INTO memory_vectors (memory_id, vector, updated_at) VALUES (?, ?, ?)
|
|
1210
|
+
ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, updated_at = excluded.updated_at
|
|
1211
|
+
`
|
|
1212
|
+
).run(memoryId, JSON.stringify(vector), (/* @__PURE__ */ new Date()).toISOString());
|
|
1213
|
+
}
|
|
1214
|
+
getSummary(repo) {
|
|
1215
|
+
const row = this.db.prepare("SELECT summary, updated_at FROM memory_summary WHERE repo = ?").get(repo);
|
|
1216
|
+
return row;
|
|
1217
|
+
}
|
|
1218
|
+
getAllMemoriesWithStats(repo) {
|
|
1219
|
+
const rows = this.db.prepare(
|
|
1220
|
+
`
|
|
1221
|
+
SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate
|
|
1222
|
+
FROM memories
|
|
1223
|
+
WHERE repo = ?
|
|
1224
|
+
ORDER BY created_at DESC
|
|
1225
|
+
`
|
|
1226
|
+
).all(repo);
|
|
1227
|
+
return rows.map((row) => ({
|
|
1228
|
+
...this.rowToMemoryEntry(row),
|
|
1229
|
+
recall_rate: row.recall_rate || 0
|
|
1230
|
+
}));
|
|
1231
|
+
}
|
|
1232
|
+
upsertSummary(repo, summary) {
|
|
1233
|
+
this.db.prepare(
|
|
1234
|
+
`
|
|
1235
|
+
INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1236
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at
|
|
1237
|
+
`
|
|
1238
|
+
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1239
|
+
}
|
|
1240
|
+
listMemoriesForDashboard(options) {
|
|
1241
|
+
const {
|
|
1242
|
+
repo,
|
|
1243
|
+
type,
|
|
1244
|
+
tag,
|
|
1245
|
+
isGlobal,
|
|
1246
|
+
minImportance,
|
|
1247
|
+
maxImportance,
|
|
1248
|
+
search,
|
|
1249
|
+
offset = 0,
|
|
1250
|
+
limit = 50,
|
|
1251
|
+
sortBy = "created_at",
|
|
1252
|
+
sortOrder = "DESC"
|
|
1253
|
+
} = options;
|
|
1254
|
+
const where = ["1=1"];
|
|
1255
|
+
const params = [];
|
|
1256
|
+
if (repo) {
|
|
1257
|
+
where.push("repo = ?");
|
|
1258
|
+
params.push(repo);
|
|
1259
|
+
}
|
|
1260
|
+
if (type) {
|
|
1261
|
+
where.push("type = ?");
|
|
1262
|
+
params.push(type);
|
|
1263
|
+
}
|
|
1264
|
+
if (tag) {
|
|
1265
|
+
where.push("tags LIKE ?");
|
|
1266
|
+
params.push(`%${tag}%`);
|
|
1267
|
+
}
|
|
1268
|
+
if (isGlobal !== void 0) {
|
|
1269
|
+
where.push("is_global = ?");
|
|
1270
|
+
params.push(isGlobal ? 1 : 0);
|
|
1271
|
+
}
|
|
1272
|
+
if (minImportance !== void 0) {
|
|
1273
|
+
where.push("importance >= ?");
|
|
1274
|
+
params.push(minImportance);
|
|
1275
|
+
}
|
|
1276
|
+
if (maxImportance !== void 0) {
|
|
1277
|
+
where.push("importance <= ?");
|
|
1278
|
+
params.push(maxImportance);
|
|
1279
|
+
}
|
|
1280
|
+
if (search) {
|
|
1281
|
+
where.push("(title LIKE ? OR content LIKE ?)");
|
|
1282
|
+
params.push(`%${search}%`, `%${search}%`);
|
|
1283
|
+
}
|
|
1284
|
+
const countSql = `SELECT COUNT(*) as count FROM memories WHERE ${where.join(" AND ")}`;
|
|
1285
|
+
const total = this.db.prepare(countSql).get(...params).count;
|
|
1286
|
+
const dataSql = `SELECT *, CASE WHEN hit_count > 0 THEN CAST(recall_count AS REAL) / hit_count ELSE 0 END AS recall_rate FROM memories WHERE ${where.join(" AND ")} ORDER BY ${sortBy} ${sortOrder} LIMIT ? OFFSET ?`;
|
|
1287
|
+
const rows = this.db.prepare(dataSql).all(...params, limit, offset);
|
|
1288
|
+
const items = rows.map((row) => ({
|
|
1289
|
+
...this.rowToMemoryEntry(row),
|
|
1290
|
+
recall_rate: row.recall_rate || 0
|
|
1291
|
+
}));
|
|
1292
|
+
return { items, memories: items, total, limit, offset };
|
|
1293
|
+
}
|
|
1294
|
+
searchBySimilarity(query, repo, limit = 10, includeArchived = false, currentTags = []) {
|
|
1295
|
+
const queryVector = this.computeVector(query);
|
|
1296
|
+
const now = /* @__PURE__ */ new Date();
|
|
1297
|
+
const where = ["(repo = ? OR is_global = 1)"];
|
|
1298
|
+
const params = [repo];
|
|
1299
|
+
if (currentTags.length > 0) {
|
|
1300
|
+
const tagConditions = currentTags.map(() => "tags LIKE ?").join(" OR ");
|
|
1301
|
+
where.push(`(${tagConditions})`);
|
|
1302
|
+
currentTags.forEach((tag) => params.push(`%${tag}%`));
|
|
1303
|
+
}
|
|
1304
|
+
let sql = `SELECT * FROM memories WHERE (${where.join(" AND ")}) AND (expires_at IS NULL OR expires_at > ?)`;
|
|
1305
|
+
if (!includeArchived) sql += " AND status = 'active'";
|
|
1306
|
+
sql += ` ORDER BY CASE WHEN repo = ? THEN 0 ELSE 1 END, importance DESC, created_at DESC LIMIT 100`;
|
|
1307
|
+
const candidates = this.db.prepare(sql).all(...params, now.toISOString(), repo);
|
|
1308
|
+
if (candidates.length < 5) {
|
|
1309
|
+
const recentSql = `SELECT * FROM memories WHERE (${where.join(" OR ")}) AND status = 'active' AND (expires_at IS NULL OR expires_at > ?) ORDER BY created_at DESC LIMIT 10`;
|
|
1310
|
+
const recent = this.db.prepare(recentSql).all(...params, now.toISOString());
|
|
1311
|
+
for (const r of recent) {
|
|
1312
|
+
if (!candidates.find((c) => c.id === r.id)) candidates.push(r);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
return candidates.map((row) => {
|
|
1316
|
+
const memory = this.rowToMemoryEntry(row);
|
|
1317
|
+
const isExpired = row.expires_at && new Date(row.expires_at) <= now;
|
|
1318
|
+
const isArchived = row.status === "archived" && !includeArchived;
|
|
1319
|
+
if (isExpired || isArchived) {
|
|
1320
|
+
return { ...memory, similarity: 0 };
|
|
1321
|
+
}
|
|
1322
|
+
const similarity = this.cosineSimilarity(queryVector, this.computeVector(memory.content)) || 0;
|
|
1323
|
+
let score = similarity;
|
|
1324
|
+
if (!score) {
|
|
1325
|
+
score = 0.16;
|
|
1326
|
+
}
|
|
1327
|
+
if (row.repo === repo) score += 0.1;
|
|
1328
|
+
return { ...memory, similarity: score };
|
|
1329
|
+
}).filter((r) => r.similarity > 0).sort((a, b) => b.similarity - a.similarity).slice(0, limit);
|
|
1330
|
+
}
|
|
1331
|
+
async checkConflicts(content, repo, _type, _vectors, threshold = 0.55) {
|
|
1332
|
+
const results = await this.searchBySimilarity(content, repo, 1, false);
|
|
1333
|
+
if (results.length > 0 && results[0].similarity >= threshold) {
|
|
1334
|
+
return results[0];
|
|
1335
|
+
}
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
archiveExpiredMemories(force = false) {
|
|
1339
|
+
if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
|
|
1340
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1341
|
+
const result = this.db.prepare(
|
|
1342
|
+
`
|
|
1343
|
+
UPDATE memories SET status = 'archived', updated_at = ?
|
|
1344
|
+
WHERE expires_at IS NOT NULL AND expires_at <= ? AND status = 'active'
|
|
1345
|
+
`
|
|
1346
|
+
).run(now, now);
|
|
1347
|
+
return result.changes;
|
|
1348
|
+
}
|
|
1349
|
+
archiveLowScoreMemories(force = false) {
|
|
1350
|
+
if (process.env.ENABLE_AUTO_ARCHIVE !== "true" && !force) return 0;
|
|
1351
|
+
const result = this.db.prepare(
|
|
1352
|
+
`
|
|
1353
|
+
UPDATE memories SET status = 'archived', updated_at = ?
|
|
1354
|
+
WHERE status = 'active' AND (
|
|
1355
|
+
(julianday('now') - julianday(COALESCE(last_used_at, created_at)) > 90 AND importance < 3)
|
|
1356
|
+
OR (hit_count > 10 AND recall_count = 0)
|
|
1357
|
+
)
|
|
1358
|
+
`
|
|
1359
|
+
).run((/* @__PURE__ */ new Date()).toISOString());
|
|
1360
|
+
return result.changes;
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
|
|
1364
|
+
// src/mcp/entities/task.ts
|
|
1365
|
+
var TaskEntity = class extends BaseEntity {
|
|
1366
|
+
insertTask(task) {
|
|
1367
|
+
const stmt = this.db.prepare(`
|
|
1368
|
+
INSERT INTO tasks (
|
|
1369
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1370
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1371
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1372
|
+
`);
|
|
1373
|
+
stmt.run(
|
|
1374
|
+
task.id,
|
|
1375
|
+
task.repo,
|
|
1376
|
+
task.task_code,
|
|
1377
|
+
task.phase || null,
|
|
1378
|
+
task.title,
|
|
1379
|
+
task.description || null,
|
|
1380
|
+
task.status || "backlog",
|
|
1381
|
+
task.priority || 3,
|
|
1382
|
+
task.agent || "unknown",
|
|
1383
|
+
task.role || "unknown",
|
|
1384
|
+
task.doc_path || null,
|
|
1385
|
+
task.created_at,
|
|
1386
|
+
task.updated_at,
|
|
1387
|
+
task.finished_at || null,
|
|
1388
|
+
task.canceled_at || null,
|
|
1389
|
+
task.tags ? JSON.stringify(task.tags) : null,
|
|
1390
|
+
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1391
|
+
task.parent_id || null,
|
|
1392
|
+
task.depends_on || null,
|
|
1393
|
+
task.est_tokens || 0,
|
|
1394
|
+
task.in_progress_at || null
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
updateTask(id, updates) {
|
|
1398
|
+
const fields = [];
|
|
1399
|
+
const values = [];
|
|
1400
|
+
const anyUpdates = updates;
|
|
1401
|
+
Object.keys(updates).forEach((key) => {
|
|
1402
|
+
if (key !== "comment" && key !== "model" && anyUpdates[key] !== void 0) {
|
|
1403
|
+
if (key === "tags" || key === "metadata") {
|
|
1404
|
+
fields.push(`${key} = ?`);
|
|
1405
|
+
values.push(JSON.stringify(anyUpdates[key]));
|
|
1406
|
+
} else {
|
|
1407
|
+
fields.push(`${key} = ?`);
|
|
1408
|
+
values.push(anyUpdates[key]);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
if (fields.length === 0) return;
|
|
1413
|
+
fields.push("updated_at = ?");
|
|
1414
|
+
values.push((/* @__PURE__ */ new Date()).toISOString());
|
|
1415
|
+
values.push(id);
|
|
1416
|
+
const stmt = this.db.prepare(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`);
|
|
1417
|
+
stmt.run(...values);
|
|
1418
|
+
}
|
|
1419
|
+
deleteTask(id) {
|
|
1420
|
+
this.db.prepare("DELETE FROM task_comments WHERE task_id = ?").run(id);
|
|
1421
|
+
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
1422
|
+
}
|
|
1423
|
+
getTaskById(id) {
|
|
1424
|
+
const row = this.db.prepare(
|
|
1425
|
+
`
|
|
1426
|
+
SELECT t.*, d.task_code as depends_on_code
|
|
1427
|
+
FROM tasks t
|
|
1428
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1429
|
+
WHERE t.id = ?
|
|
1430
|
+
`
|
|
1431
|
+
).get(id);
|
|
1432
|
+
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(id) } : null;
|
|
1433
|
+
}
|
|
1434
|
+
getTaskByCode(repo, taskCode) {
|
|
1435
|
+
const row = this.db.prepare(
|
|
1436
|
+
`
|
|
1437
|
+
SELECT t.*, d.task_code as depends_on_code
|
|
1438
|
+
FROM tasks t
|
|
1439
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1440
|
+
WHERE t.repo = ? AND t.task_code = ?
|
|
1441
|
+
`
|
|
1442
|
+
).get(repo, taskCode);
|
|
1443
|
+
return row ? { ...this.rowToTask(row), comments: this.getTaskCommentsByTaskId(row.id) } : null;
|
|
1444
|
+
}
|
|
1445
|
+
getTasksByRepo(repo, status, limit, offset, search) {
|
|
1446
|
+
let query = `
|
|
1447
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1448
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1449
|
+
FROM tasks t
|
|
1450
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1451
|
+
WHERE t.repo = ?
|
|
1452
|
+
`;
|
|
1453
|
+
const params = [repo];
|
|
1454
|
+
if (status) {
|
|
1455
|
+
query += " AND t.status = ?";
|
|
1456
|
+
params.push(status);
|
|
1457
|
+
}
|
|
1458
|
+
if (search) {
|
|
1459
|
+
query += " AND (t.title LIKE ? OR t.description LIKE ? OR t.task_code LIKE ?)";
|
|
1460
|
+
const searchPattern = `%${search}%`;
|
|
1461
|
+
params.push(searchPattern, searchPattern, searchPattern);
|
|
1462
|
+
}
|
|
1463
|
+
query += ` ORDER BY
|
|
1464
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1465
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1466
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1467
|
+
WHEN t.status = 'pending' THEN 1
|
|
1468
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1469
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1470
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1471
|
+
ELSE 5 END ASC,
|
|
1472
|
+
t.priority DESC,
|
|
1473
|
+
t.created_at ASC`;
|
|
1474
|
+
if (limit !== void 0) {
|
|
1475
|
+
query += " LIMIT ?";
|
|
1476
|
+
params.push(limit);
|
|
1477
|
+
if (offset !== void 0) {
|
|
1478
|
+
query += " OFFSET ?";
|
|
1479
|
+
params.push(offset);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
const rows = this.db.prepare(query).all(...params);
|
|
1483
|
+
return rows.map((r) => this.rowToTask(r));
|
|
1484
|
+
}
|
|
1485
|
+
listRecentTasks(limit = 50, offset = 0) {
|
|
1486
|
+
const query = `
|
|
1487
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1488
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1489
|
+
FROM tasks t
|
|
1490
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1491
|
+
ORDER BY
|
|
1492
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1493
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1494
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1495
|
+
WHEN t.status = 'pending' THEN 1
|
|
1496
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1497
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1498
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1499
|
+
ELSE 5 END ASC,
|
|
1500
|
+
t.priority DESC,
|
|
1501
|
+
t.created_at ASC
|
|
1502
|
+
LIMIT ? OFFSET ?
|
|
1503
|
+
`;
|
|
1504
|
+
const rows = this.db.prepare(query).all(limit, offset);
|
|
1505
|
+
return rows.map((r) => this.rowToTask(r));
|
|
1506
|
+
}
|
|
1507
|
+
getTasksByMultipleStatuses(repo, statuses, limit, offset, search) {
|
|
1508
|
+
if (!statuses.length) return this.getTasksByRepo(repo, void 0, limit, offset, search);
|
|
1509
|
+
let query = `
|
|
1510
|
+
SELECT t.*, d.task_code as depends_on_code,
|
|
1511
|
+
(SELECT COUNT(*) FROM task_comments WHERE task_id = t.id) as comments_count
|
|
1512
|
+
FROM tasks t
|
|
1513
|
+
LEFT JOIN tasks d ON t.depends_on = d.id
|
|
1514
|
+
WHERE t.repo = ? AND t.status IN (${statuses.map(() => "?").join(",")})
|
|
1515
|
+
`;
|
|
1516
|
+
const params = [repo, ...statuses];
|
|
1517
|
+
if (search) {
|
|
1518
|
+
query += " AND (t.title LIKE ? OR t.description LIKE ? OR t.task_code LIKE ?)";
|
|
1519
|
+
const searchPattern = `%${search}%`;
|
|
1520
|
+
params.push(searchPattern, searchPattern, searchPattern);
|
|
1521
|
+
}
|
|
1522
|
+
query += ` ORDER BY
|
|
1523
|
+
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END ASC,
|
|
1524
|
+
CASE WHEN t.status = 'completed' THEN t.updated_at ELSE NULL END DESC,
|
|
1525
|
+
CASE WHEN t.status = 'in_progress' THEN 0
|
|
1526
|
+
WHEN t.status = 'pending' THEN 1
|
|
1527
|
+
WHEN t.status = 'backlog' THEN 2
|
|
1528
|
+
WHEN t.status = 'blocked' THEN 3
|
|
1529
|
+
WHEN t.status = 'canceled' THEN 4
|
|
1530
|
+
ELSE 5 END ASC,
|
|
1531
|
+
t.priority DESC,
|
|
1532
|
+
t.created_at ASC`;
|
|
1533
|
+
if (limit !== void 0) {
|
|
1534
|
+
query += " LIMIT ?";
|
|
1535
|
+
params.push(limit);
|
|
1536
|
+
if (offset !== void 0) {
|
|
1537
|
+
query += " OFFSET ?";
|
|
1538
|
+
params.push(offset);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
const rows = this.db.prepare(query).all(...params);
|
|
1542
|
+
return rows.map((r) => this.rowToTask(r));
|
|
1543
|
+
}
|
|
1544
|
+
isTaskCodeDuplicate(repo, task_code, excludeId) {
|
|
1545
|
+
let query = "SELECT COUNT(*) as count FROM tasks WHERE repo = ? AND task_code = ?";
|
|
1546
|
+
const params = [repo, task_code];
|
|
1547
|
+
if (excludeId) {
|
|
1548
|
+
query += " AND id != ?";
|
|
1549
|
+
params.push(excludeId);
|
|
1550
|
+
}
|
|
1551
|
+
const row = this.db.prepare(query).get(...params);
|
|
1552
|
+
return row.count > 0;
|
|
1553
|
+
}
|
|
1554
|
+
bulkInsertTasks(tasks) {
|
|
1555
|
+
const insert = this.db.prepare(`
|
|
1556
|
+
INSERT INTO tasks (
|
|
1557
|
+
id, repo, task_code, phase, title, description, status, priority,
|
|
1558
|
+
agent, role, doc_path, created_at, updated_at, finished_at, canceled_at, tags, metadata, parent_id, depends_on, est_tokens, in_progress_at
|
|
1559
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1560
|
+
`);
|
|
1561
|
+
const insertMany = this.db.transaction((tasks2) => {
|
|
1562
|
+
let count = 0;
|
|
1563
|
+
for (const task of tasks2) {
|
|
1564
|
+
insert.run(
|
|
1565
|
+
task.id,
|
|
1566
|
+
task.repo,
|
|
1567
|
+
task.task_code,
|
|
1568
|
+
task.phase || null,
|
|
1569
|
+
task.title,
|
|
1570
|
+
task.description || null,
|
|
1571
|
+
task.status || "backlog",
|
|
1572
|
+
task.priority || 3,
|
|
1573
|
+
task.agent || "unknown",
|
|
1574
|
+
task.role || "unknown",
|
|
1575
|
+
task.doc_path || null,
|
|
1576
|
+
task.created_at,
|
|
1577
|
+
task.updated_at,
|
|
1578
|
+
task.finished_at || null,
|
|
1579
|
+
task.canceled_at || null,
|
|
1580
|
+
task.tags ? JSON.stringify(task.tags) : null,
|
|
1581
|
+
task.metadata ? JSON.stringify(task.metadata) : null,
|
|
1582
|
+
task.parent_id || null,
|
|
1583
|
+
task.depends_on || null,
|
|
1584
|
+
task.est_tokens || 0,
|
|
1585
|
+
task.in_progress_at || null
|
|
1586
|
+
);
|
|
1587
|
+
count++;
|
|
1588
|
+
}
|
|
1589
|
+
return count;
|
|
1590
|
+
});
|
|
1591
|
+
return insertMany(tasks);
|
|
1592
|
+
}
|
|
1593
|
+
insertTaskComment(comment) {
|
|
1594
|
+
this.db.prepare(
|
|
1595
|
+
`
|
|
1596
|
+
INSERT INTO task_comments (
|
|
1597
|
+
id, task_id, repo, comment, agent, role, model, previous_status, next_status, created_at
|
|
1598
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1599
|
+
`
|
|
1600
|
+
).run(
|
|
1601
|
+
comment.id,
|
|
1602
|
+
comment.task_id,
|
|
1603
|
+
comment.repo,
|
|
1604
|
+
comment.comment,
|
|
1605
|
+
comment.agent || "unknown",
|
|
1606
|
+
comment.role || "unknown",
|
|
1607
|
+
comment.model || "unknown",
|
|
1608
|
+
comment.previous_status || null,
|
|
1609
|
+
comment.next_status || null,
|
|
1610
|
+
comment.created_at
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
updateTaskComment(id, updates) {
|
|
1614
|
+
const fields = [];
|
|
1615
|
+
const values = [];
|
|
1616
|
+
const anyUpdates = updates;
|
|
1617
|
+
Object.keys(updates).forEach((key) => {
|
|
1618
|
+
if (anyUpdates[key] !== void 0) {
|
|
1619
|
+
fields.push(`${key} = ?`);
|
|
1620
|
+
values.push(anyUpdates[key]);
|
|
1621
|
+
}
|
|
1622
|
+
});
|
|
1623
|
+
if (fields.length === 0) return;
|
|
1624
|
+
values.push(id);
|
|
1625
|
+
const stmt = this.db.prepare(`UPDATE task_comments SET ${fields.join(", ")} WHERE id = ?`);
|
|
1626
|
+
stmt.run(...values);
|
|
1627
|
+
}
|
|
1628
|
+
deleteTaskComment(id) {
|
|
1629
|
+
this.db.prepare("DELETE FROM task_comments WHERE id = ?").run(id);
|
|
1630
|
+
}
|
|
1631
|
+
getTaskCommentById(id) {
|
|
1632
|
+
return this.db.prepare("SELECT * FROM task_comments WHERE id = ?").get(id);
|
|
1633
|
+
}
|
|
1634
|
+
getTaskCommentsByTaskId(taskId) {
|
|
1635
|
+
return this.db.prepare(
|
|
1636
|
+
`
|
|
1637
|
+
SELECT * FROM task_comments
|
|
1638
|
+
WHERE task_id = ?
|
|
1639
|
+
ORDER BY created_at DESC, id DESC
|
|
1640
|
+
`
|
|
1641
|
+
).all(taskId);
|
|
1642
|
+
}
|
|
1643
|
+
getAllTaskCommentsByRepo(repo) {
|
|
1644
|
+
return this.db.prepare(
|
|
1645
|
+
`
|
|
1646
|
+
SELECT * FROM task_comments
|
|
1647
|
+
WHERE repo = ?
|
|
1648
|
+
ORDER BY created_at DESC, id DESC
|
|
1649
|
+
`
|
|
1650
|
+
).all(repo);
|
|
1651
|
+
}
|
|
1652
|
+
getTaskStats(repo) {
|
|
1653
|
+
const rows = this.db.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status").all(repo);
|
|
1654
|
+
const stats = { total: 0, backlog: 0, todo: 0, inProgress: 0, completed: 0, blocked: 0, canceled: 0 };
|
|
1655
|
+
rows.forEach((r) => {
|
|
1656
|
+
const count = r.count;
|
|
1657
|
+
stats.total += count;
|
|
1658
|
+
if (r.status === "backlog") stats.backlog = count;
|
|
1659
|
+
else if (r.status === "pending") stats.todo = count;
|
|
1660
|
+
else if (r.status === "in_progress") stats.inProgress = count;
|
|
1661
|
+
else if (r.status === "completed") stats.completed = count;
|
|
1662
|
+
else if (r.status === "blocked") stats.blocked = count;
|
|
1663
|
+
else if (r.status === "canceled") stats.canceled = count;
|
|
1664
|
+
});
|
|
1665
|
+
return stats;
|
|
1666
|
+
}
|
|
1667
|
+
getTaskTimeStats(repo, period) {
|
|
1668
|
+
let dateFilter = "";
|
|
1669
|
+
if (period === "daily") dateFilter = "AND date(COALESCE(finished_at, updated_at)) = date('now')";
|
|
1670
|
+
else if (period === "weekly") dateFilter = "AND date(COALESCE(finished_at, updated_at)) >= date('now', '-7 days')";
|
|
1671
|
+
else if (period === "monthly")
|
|
1672
|
+
dateFilter = "AND date(COALESCE(finished_at, updated_at)) >= date('now', '-30 days')";
|
|
1673
|
+
const stats = this.db.prepare(
|
|
1674
|
+
`
|
|
1675
|
+
SELECT
|
|
1676
|
+
COUNT(*) as completed_count,
|
|
1677
|
+
SUM(est_tokens) as total_tokens,
|
|
1678
|
+
AVG(
|
|
1679
|
+
CASE
|
|
1680
|
+
WHEN in_progress_at IS NOT NULL AND finished_at IS NOT NULL
|
|
1681
|
+
THEN (julianday(finished_at) - julianday(in_progress_at)) * 86400.0
|
|
1682
|
+
ELSE NULL
|
|
1683
|
+
END
|
|
1684
|
+
) as avg_duration_seconds
|
|
1685
|
+
FROM tasks
|
|
1686
|
+
WHERE repo = ?
|
|
1687
|
+
AND status = 'completed'
|
|
1688
|
+
${dateFilter}
|
|
1689
|
+
`
|
|
1690
|
+
).get(repo);
|
|
1691
|
+
let addedDateFilter = "";
|
|
1692
|
+
if (period === "daily") addedDateFilter = "AND date(created_at) = date('now')";
|
|
1693
|
+
else if (period === "weekly") addedDateFilter = "AND date(created_at) >= date('now', '-7 days')";
|
|
1694
|
+
else if (period === "monthly") addedDateFilter = "AND date(created_at) >= date('now', '-30 days')";
|
|
1695
|
+
const added = this.db.prepare(
|
|
1696
|
+
`
|
|
1697
|
+
SELECT COUNT(*) as count FROM tasks
|
|
1698
|
+
WHERE repo = ?
|
|
1699
|
+
${addedDateFilter}
|
|
1700
|
+
`
|
|
1701
|
+
).get(repo);
|
|
1702
|
+
return {
|
|
1703
|
+
completed: stats?.completed_count || 0,
|
|
1704
|
+
tokens: stats?.total_tokens || 0,
|
|
1705
|
+
avgDuration: stats?.avg_duration_seconds || 0,
|
|
1706
|
+
added: added?.count || 0
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
getTaskComparisonSeries(repo, period) {
|
|
1710
|
+
let labelFormat;
|
|
1711
|
+
let dateFilter;
|
|
1712
|
+
if (period === "daily") {
|
|
1713
|
+
labelFormat = "%H:00";
|
|
1714
|
+
dateFilter = "date(COALESCE(finished_at, created_at)) = date('now')";
|
|
1715
|
+
} else if (period === "weekly") {
|
|
1716
|
+
labelFormat = "%Y-%m-%d";
|
|
1717
|
+
dateFilter = "date(COALESCE(finished_at, created_at)) >= date('now', '-6 days')";
|
|
1718
|
+
} else if (period === "monthly") {
|
|
1719
|
+
labelFormat = "W%W";
|
|
1720
|
+
dateFilter = "date(COALESCE(finished_at, created_at)) >= date('now', '-30 days')";
|
|
1721
|
+
} else {
|
|
1722
|
+
labelFormat = "%Y-%m";
|
|
1723
|
+
dateFilter = "1=1";
|
|
1724
|
+
}
|
|
1725
|
+
const query = `
|
|
1726
|
+
SELECT label, SUM(created) as created, SUM(completed) as completed
|
|
1727
|
+
FROM (
|
|
1728
|
+
SELECT strftime(?, created_at) as label, 1 as created, 0 as completed
|
|
1729
|
+
FROM tasks
|
|
1730
|
+
WHERE repo = ? AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "created_at")}
|
|
1731
|
+
UNION ALL
|
|
1732
|
+
SELECT strftime(?, COALESCE(finished_at, updated_at)) as label, 0 as created, 1 as completed
|
|
1733
|
+
FROM tasks
|
|
1734
|
+
WHERE repo = ? AND status = 'completed' AND ${dateFilter.replace("COALESCE(finished_at, created_at)", "COALESCE(finished_at, updated_at)")}
|
|
1735
|
+
)
|
|
1736
|
+
GROUP BY label
|
|
1737
|
+
ORDER BY label ASC
|
|
1738
|
+
LIMIT 100
|
|
1739
|
+
`;
|
|
1740
|
+
return this.db.prepare(query).all(labelFormat, repo, labelFormat, repo);
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
|
|
1744
|
+
// src/mcp/entities/action.ts
|
|
1745
|
+
var ActionEntity = class extends BaseEntity {
|
|
1746
|
+
constructor(db) {
|
|
1747
|
+
super(db);
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Log an action to the audit trail.
|
|
1751
|
+
* Supports both object-based options (modern) and positional arguments (legacy/internal).
|
|
1752
|
+
*/
|
|
1753
|
+
logAction(action, repo, optionsOrQuery, response, memoryId, taskId, resultCount = 0) {
|
|
1754
|
+
let query = typeof optionsOrQuery === "string" ? optionsOrQuery : void 0;
|
|
1755
|
+
let finalResponse = response;
|
|
1756
|
+
let finalMemoryId = memoryId;
|
|
1757
|
+
let finalTaskId = taskId;
|
|
1758
|
+
let finalResultCount = resultCount;
|
|
1759
|
+
if (optionsOrQuery && typeof optionsOrQuery === "object") {
|
|
1760
|
+
query = optionsOrQuery.query || query;
|
|
1761
|
+
const res = optionsOrQuery.response;
|
|
1762
|
+
finalResponse = (typeof res === "object" ? JSON.stringify(res) : res) || finalResponse;
|
|
1763
|
+
finalMemoryId = optionsOrQuery.memoryId || finalMemoryId;
|
|
1764
|
+
finalTaskId = optionsOrQuery.taskId || finalTaskId;
|
|
1765
|
+
finalResultCount = optionsOrQuery.resultCount !== void 0 ? optionsOrQuery.resultCount : finalResultCount;
|
|
1766
|
+
}
|
|
1767
|
+
const stmt = this.db.prepare(`
|
|
1768
|
+
INSERT INTO action_log (repo, action, query, response, memory_id, task_id, result_count, created_at)
|
|
1769
|
+
VALUES (:repo, :action, :query, :response, :memory_id, :task_id, :result_count, :created_at)
|
|
1770
|
+
`);
|
|
1771
|
+
stmt.run({
|
|
1772
|
+
repo: repo || "",
|
|
1773
|
+
action: action || "unknown",
|
|
1774
|
+
query: query || null,
|
|
1775
|
+
response: finalResponse ? typeof finalResponse === "string" ? finalResponse : JSON.stringify(finalResponse) : null,
|
|
1776
|
+
memory_id: finalMemoryId || null,
|
|
1777
|
+
task_id: finalTaskId || null,
|
|
1778
|
+
result_count: finalResultCount ?? 0,
|
|
1779
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1780
|
+
});
|
|
1781
|
+
}
|
|
1782
|
+
getLastActionId() {
|
|
1783
|
+
const row = this.db.prepare("SELECT MAX(id) as id FROM action_log").get();
|
|
1784
|
+
return row?.id || 0;
|
|
1785
|
+
}
|
|
1786
|
+
getActionsAfter(id) {
|
|
1787
|
+
return this.db.prepare(
|
|
1788
|
+
`
|
|
1789
|
+
SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1790
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1791
|
+
WHERE a.id > ? ORDER BY a.created_at ASC
|
|
1792
|
+
`
|
|
1793
|
+
).all(id);
|
|
1794
|
+
}
|
|
1795
|
+
getRecentActions(repo, limit = 10, offset = 0) {
|
|
1796
|
+
let query = `
|
|
1797
|
+
SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1798
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1799
|
+
`;
|
|
1800
|
+
const params = [];
|
|
1801
|
+
if (repo) {
|
|
1802
|
+
query += " WHERE a.repo = ?";
|
|
1803
|
+
params.push(repo);
|
|
1804
|
+
}
|
|
1805
|
+
query += " ORDER BY a.created_at DESC, a.id DESC LIMIT ? OFFSET ?";
|
|
1806
|
+
params.push(limit, offset);
|
|
1807
|
+
return this.db.prepare(query).all(...params);
|
|
1808
|
+
}
|
|
1809
|
+
getActionStatsByDate(repo) {
|
|
1810
|
+
return this.db.prepare(
|
|
1811
|
+
`
|
|
1812
|
+
SELECT date(created_at) as date, count(*) as count
|
|
1813
|
+
FROM action_log
|
|
1814
|
+
WHERE repo = ? AND created_at > date('now', '-30 days')
|
|
1815
|
+
GROUP BY date(created_at)
|
|
1816
|
+
ORDER BY date ASC
|
|
1817
|
+
`
|
|
1818
|
+
).all(repo);
|
|
1819
|
+
}
|
|
1820
|
+
getActionDistribution(repo) {
|
|
1821
|
+
return this.db.prepare(
|
|
1822
|
+
`
|
|
1823
|
+
SELECT action, count(*) as count
|
|
1824
|
+
FROM action_log
|
|
1825
|
+
WHERE repo = ?
|
|
1826
|
+
GROUP BY action
|
|
1827
|
+
`
|
|
1828
|
+
).all(repo);
|
|
1829
|
+
}
|
|
1830
|
+
getActionById(id) {
|
|
1831
|
+
return this.db.prepare(
|
|
1832
|
+
`
|
|
1833
|
+
SELECT a.*, m.title as memory_title, m.type as memory_type
|
|
1834
|
+
FROM action_log a LEFT JOIN memories m ON a.memory_id = m.id
|
|
1835
|
+
WHERE a.id = ?
|
|
1836
|
+
`
|
|
1837
|
+
).get(id);
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1841
|
+
// src/mcp/entities/system.ts
|
|
1842
|
+
var SystemEntity = class extends BaseEntity {
|
|
1843
|
+
listRepos() {
|
|
1844
|
+
const rows = this.db.prepare("SELECT DISTINCT repo FROM memories UNION SELECT DISTINCT repo FROM tasks").all();
|
|
1845
|
+
return rows.map((r) => r.repo);
|
|
1846
|
+
}
|
|
1847
|
+
listRepoNavigation() {
|
|
1848
|
+
const repos = this.listRepos();
|
|
1849
|
+
return repos.map((repo) => {
|
|
1850
|
+
const memoryCount = this.db.prepare("SELECT COUNT(*) as count FROM memories WHERE repo = ?").get(repo).count;
|
|
1851
|
+
const taskCount = this.db.prepare("SELECT COUNT(*) as count FROM tasks WHERE repo = ?").get(repo).count;
|
|
1852
|
+
const lastActivity = this.db.prepare(
|
|
1853
|
+
`SELECT MAX(created_at) as last FROM (SELECT created_at FROM memories WHERE repo = ? UNION ALL SELECT created_at FROM tasks WHERE repo = ? UNION ALL SELECT created_at FROM action_log WHERE repo = ?)`
|
|
1854
|
+
).get(repo, repo, repo).last;
|
|
1855
|
+
return {
|
|
1856
|
+
repo,
|
|
1857
|
+
memoryCount,
|
|
1858
|
+
taskCount,
|
|
1859
|
+
lastActivity
|
|
1860
|
+
};
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
getDashboardStats(repo) {
|
|
1864
|
+
const memoryStats = this.db.prepare("SELECT type, COUNT(*) as count FROM memories WHERE repo = ? GROUP BY type").all(repo);
|
|
1865
|
+
const taskStats = this.db.prepare("SELECT status, COUNT(*) as count FROM tasks WHERE repo = ? GROUP BY status").all(repo);
|
|
1866
|
+
const recentMemories = this.db.prepare("SELECT * FROM memories WHERE repo = ? ORDER BY created_at DESC LIMIT 5").all(repo).map((r) => this.rowToMemoryEntry(r));
|
|
1867
|
+
const activeTasks = this.db.prepare(
|
|
1868
|
+
"SELECT * FROM tasks WHERE repo = ? AND status IN ('in_progress', 'pending', 'backlog') ORDER BY priority DESC, created_at ASC LIMIT 5"
|
|
1869
|
+
).all(repo).map((r) => this.rowToTask(r));
|
|
1870
|
+
return {
|
|
1871
|
+
memoryStats,
|
|
1872
|
+
taskStats,
|
|
1873
|
+
recentMemories,
|
|
1874
|
+
activeTasks
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
getGlobalStats() {
|
|
1878
|
+
const totalMemories = this.db.prepare("SELECT COUNT(*) as count FROM memories").get().count;
|
|
1879
|
+
const totalTasks = this.db.prepare("SELECT COUNT(*) as count FROM tasks").get().count;
|
|
1880
|
+
const totalRepos = this.listRepos().length;
|
|
1881
|
+
return {
|
|
1882
|
+
totalMemories,
|
|
1883
|
+
totalTasks,
|
|
1884
|
+
totalRepos
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
getRepoDetails(repo) {
|
|
1888
|
+
const memoryCount = this.db.prepare("SELECT COUNT(*) as count FROM memories WHERE repo = ?").get(repo).count;
|
|
1889
|
+
const taskCount = this.db.prepare("SELECT COUNT(*) as count FROM tasks WHERE repo = ?").get(repo).count;
|
|
1890
|
+
const languages = this.db.prepare("SELECT DISTINCT language FROM memories WHERE repo = ? AND language IS NOT NULL").all(repo).map((r) => r.language);
|
|
1891
|
+
return {
|
|
1892
|
+
repo,
|
|
1893
|
+
memoryCount,
|
|
1894
|
+
taskCount,
|
|
1895
|
+
languages
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
};
|
|
1899
|
+
|
|
1900
|
+
// src/mcp/entities/summary.ts
|
|
1901
|
+
var SummaryEntity = class extends BaseEntity {
|
|
1902
|
+
getSummary(repo) {
|
|
1903
|
+
const row = this.db.prepare("SELECT summary, updated_at FROM memory_summary WHERE repo = ?").get(repo);
|
|
1904
|
+
return row || null;
|
|
1905
|
+
}
|
|
1906
|
+
upsertSummary(repo, summary) {
|
|
1907
|
+
this.db.prepare(
|
|
1908
|
+
`
|
|
1909
|
+
INSERT INTO memory_summary (repo, summary, updated_at) VALUES (?, ?, ?)
|
|
1910
|
+
ON CONFLICT(repo) DO UPDATE SET summary = excluded.summary, updated_at = excluded.updated_at
|
|
1911
|
+
`
|
|
1912
|
+
).run(repo, summary, (/* @__PURE__ */ new Date()).toISOString());
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
|
|
1916
|
+
// src/mcp/storage/sqlite.ts
|
|
1917
|
+
function resolveDbPath() {
|
|
1918
|
+
if (process.env.MEMORY_DB_PATH) return process.env.MEMORY_DB_PATH;
|
|
1919
|
+
const standardConfigDir = process.platform === "win32" ? path2.join(os.homedir(), ".local-memory-mcp") : process.platform === "darwin" ? path2.join(os.homedir(), "Library", "Application Support", "local-memory-mcp") : path2.join(os.homedir(), ".config", "local-memory-mcp");
|
|
1920
|
+
const standardPath = path2.join(standardConfigDir, "memory.db");
|
|
1921
|
+
if (fs2.existsSync(standardPath)) return standardPath;
|
|
1922
|
+
const legacyPath = path2.join(os.homedir(), ".config", "local-memory-mcp", "memory.db");
|
|
1923
|
+
if (fs2.existsSync(legacyPath)) return legacyPath;
|
|
1924
|
+
const localCwdFile = path2.join(process.cwd(), "storage", "memory.db");
|
|
1925
|
+
if (fs2.existsSync(localCwdFile)) return localCwdFile;
|
|
1926
|
+
return standardPath;
|
|
1927
|
+
}
|
|
1928
|
+
var DB_PATH = resolveDbPath();
|
|
1929
|
+
var SQLiteStore = class {
|
|
1930
|
+
db;
|
|
1931
|
+
memories;
|
|
1932
|
+
tasks;
|
|
1933
|
+
actions;
|
|
1934
|
+
system;
|
|
1935
|
+
summaries;
|
|
1936
|
+
constructor(dbPath) {
|
|
1937
|
+
const finalPath = dbPath ?? DB_PATH;
|
|
1938
|
+
const dbDir = path2.dirname(finalPath);
|
|
1939
|
+
if (!fs2.existsSync(dbDir)) {
|
|
1940
|
+
fs2.mkdirSync(dbDir, { recursive: true });
|
|
1941
|
+
}
|
|
1942
|
+
this.db = new Database(finalPath);
|
|
1943
|
+
this.db.pragma("journal_mode = WAL");
|
|
1944
|
+
this.db.pragma("synchronous = NORMAL");
|
|
1945
|
+
this.db.pragma("busy_timeout = 5000");
|
|
1946
|
+
const migrator = new MigrationManager(this.db);
|
|
1947
|
+
migrator.migrate();
|
|
1948
|
+
this.memories = new MemoryEntity(this.db);
|
|
1949
|
+
this.tasks = new TaskEntity(this.db);
|
|
1950
|
+
this.actions = new ActionEntity(this.db);
|
|
1951
|
+
this.system = new SystemEntity(this.db);
|
|
1952
|
+
this.summaries = new SummaryEntity(this.db);
|
|
1953
|
+
logger.info(`SQLiteStore initialized at ${finalPath}`);
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Returns the current database file path.
|
|
1957
|
+
*/
|
|
1958
|
+
getDbPath() {
|
|
1959
|
+
return this.db.name;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Closes the database connection.
|
|
1963
|
+
*/
|
|
1964
|
+
close() {
|
|
1965
|
+
this.db.close();
|
|
1966
|
+
}
|
|
1967
|
+
};
|
|
1968
|
+
|
|
1969
|
+
// src/mcp/session.ts
|
|
1970
|
+
import path3 from "path";
|
|
1971
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1972
|
+
function createSessionContext() {
|
|
1973
|
+
return {
|
|
1974
|
+
roots: [],
|
|
1975
|
+
supportsRoots: false,
|
|
1976
|
+
supportsSampling: false,
|
|
1977
|
+
supportsSamplingTools: false,
|
|
1978
|
+
supportsElicitation: false,
|
|
1979
|
+
supportsElicitationForm: false,
|
|
1980
|
+
supportsElicitationUrl: false
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function updateSessionFromInitialize(session, params) {
|
|
1984
|
+
const capabilities = params?.capabilities || {};
|
|
1985
|
+
session.clientInfo = params?.clientInfo;
|
|
1986
|
+
session.clientCapabilities = capabilities;
|
|
1987
|
+
session.supportsRoots = Boolean(capabilities.roots);
|
|
1988
|
+
session.supportsSampling = Boolean(capabilities.sampling);
|
|
1989
|
+
session.supportsSamplingTools = Boolean(capabilities.sampling?.tools);
|
|
1990
|
+
session.supportsElicitation = Boolean(capabilities.elicitation);
|
|
1991
|
+
session.supportsElicitationForm = supportsElicitationMode(capabilities.elicitation, "form");
|
|
1992
|
+
session.supportsElicitationUrl = supportsElicitationMode(capabilities.elicitation, "url");
|
|
1993
|
+
}
|
|
1994
|
+
function supportsElicitationMode(capability, mode) {
|
|
1995
|
+
if (!capability || typeof capability !== "object") {
|
|
1996
|
+
return false;
|
|
1997
|
+
}
|
|
1998
|
+
const cap = capability;
|
|
1999
|
+
if (mode === "form") {
|
|
2000
|
+
return Object.keys(cap).length === 0 || typeof cap.form === "object";
|
|
2001
|
+
}
|
|
2002
|
+
return typeof cap.url === "object";
|
|
2003
|
+
}
|
|
2004
|
+
function updateSessionRoots(session, roots) {
|
|
2005
|
+
const normalized = normalizeRoots(roots);
|
|
2006
|
+
const previous = JSON.stringify(session.roots);
|
|
2007
|
+
const next = JSON.stringify(normalized);
|
|
2008
|
+
session.roots = normalized;
|
|
2009
|
+
return previous !== next;
|
|
2010
|
+
}
|
|
2011
|
+
function normalizeRoots(roots) {
|
|
2012
|
+
if (!Array.isArray(roots)) return [];
|
|
2013
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2014
|
+
const normalized = [];
|
|
2015
|
+
for (const root of roots) {
|
|
2016
|
+
if (!root || typeof root !== "object") continue;
|
|
2017
|
+
const r = root;
|
|
2018
|
+
const uri = typeof r.uri === "string" ? r.uri : void 0;
|
|
2019
|
+
const name = typeof r.name === "string" ? r.name : void 0;
|
|
2020
|
+
if (!uri || seen.has(uri)) continue;
|
|
2021
|
+
seen.add(uri);
|
|
2022
|
+
normalized.push({ uri, name });
|
|
2023
|
+
}
|
|
2024
|
+
return normalized;
|
|
2025
|
+
}
|
|
2026
|
+
function extractRootsFromResult(result) {
|
|
2027
|
+
return normalizeRoots(result?.roots);
|
|
2028
|
+
}
|
|
2029
|
+
function getFilesystemRoots(session) {
|
|
2030
|
+
if (!session) return [];
|
|
2031
|
+
const resolved = [];
|
|
2032
|
+
for (const root of session.roots) {
|
|
2033
|
+
if (!root.uri.startsWith("file://")) continue;
|
|
2034
|
+
try {
|
|
2035
|
+
resolved.push(path3.resolve(fileURLToPath2(root.uri)));
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
return resolved;
|
|
2040
|
+
}
|
|
2041
|
+
function isPathWithinRoots(targetPath, session) {
|
|
2042
|
+
const roots = getFilesystemRoots(session);
|
|
2043
|
+
if (roots.length === 0) return true;
|
|
2044
|
+
const normalizedTarget = path3.resolve(targetPath);
|
|
2045
|
+
return roots.some((rootPath) => {
|
|
2046
|
+
const relative = path3.relative(rootPath, normalizedTarget);
|
|
2047
|
+
return relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative);
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
function findContainingRoot(targetPath, session) {
|
|
2051
|
+
const roots = getFilesystemRoots(session);
|
|
2052
|
+
if (roots.length === 0) return null;
|
|
2053
|
+
const normalizedTarget = path3.resolve(targetPath);
|
|
2054
|
+
for (const rootPath of roots) {
|
|
2055
|
+
const relative = path3.relative(rootPath, normalizedTarget);
|
|
2056
|
+
if (relative === "" || !relative.startsWith("..") && !path3.isAbsolute(relative)) {
|
|
2057
|
+
return rootPath;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
return null;
|
|
2061
|
+
}
|
|
2062
|
+
function inferRepoFromSession(session) {
|
|
2063
|
+
const roots = getFilesystemRoots(session);
|
|
2064
|
+
if (roots.length === 1) {
|
|
2065
|
+
return path3.basename(roots[0]);
|
|
2066
|
+
}
|
|
2067
|
+
return void 0;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/mcp/tools/schemas.ts
|
|
2071
|
+
import { z } from "zod";
|
|
2072
|
+
var MemoryScopeSchema = z.object({
|
|
2073
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2074
|
+
branch: z.string().optional(),
|
|
2075
|
+
folder: z.string().optional(),
|
|
2076
|
+
language: z.string().optional()
|
|
2077
|
+
});
|
|
2078
|
+
var MemoryTypeSchema = z.enum([
|
|
2079
|
+
"code_fact",
|
|
2080
|
+
"decision",
|
|
2081
|
+
"mistake",
|
|
2082
|
+
"pattern",
|
|
2083
|
+
"agent_handoff",
|
|
2084
|
+
"agent_registered",
|
|
2085
|
+
"file_claim",
|
|
2086
|
+
"task_archive"
|
|
2087
|
+
]);
|
|
2088
|
+
var MemoryStoreSchema = z.object({
|
|
2089
|
+
type: MemoryTypeSchema,
|
|
2090
|
+
title: z.string().min(3).max(100),
|
|
2091
|
+
content: z.string().min(10),
|
|
2092
|
+
importance: z.number().min(1).max(5),
|
|
2093
|
+
agent: z.string().min(1),
|
|
2094
|
+
role: z.string().optional().default("unknown"),
|
|
2095
|
+
model: z.string().min(1),
|
|
2096
|
+
scope: MemoryScopeSchema,
|
|
2097
|
+
ttlDays: z.number().min(1).optional(),
|
|
2098
|
+
supersedes: z.string().uuid().optional(),
|
|
2099
|
+
tags: z.array(z.string()).optional(),
|
|
2100
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
2101
|
+
is_global: z.boolean().default(false)
|
|
2102
|
+
});
|
|
2103
|
+
var MemoryUpdateSchema = z.object({
|
|
2104
|
+
id: z.string().uuid(),
|
|
2105
|
+
type: MemoryTypeSchema.optional(),
|
|
2106
|
+
title: z.string().min(3).max(100).optional(),
|
|
2107
|
+
content: z.string().min(10).optional(),
|
|
2108
|
+
importance: z.number().min(1).max(5).optional(),
|
|
2109
|
+
agent: z.string().optional(),
|
|
2110
|
+
role: z.string().optional(),
|
|
2111
|
+
status: z.enum(["active", "archived"]).optional(),
|
|
2112
|
+
supersedes: z.string().uuid().optional(),
|
|
2113
|
+
tags: z.array(z.string()).optional(),
|
|
2114
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
2115
|
+
is_global: z.boolean().optional(),
|
|
2116
|
+
completed_at: z.string().optional()
|
|
2117
|
+
}).refine(
|
|
2118
|
+
(data) => data.type !== void 0 || data.content !== void 0 || data.title !== void 0 || data.importance !== void 0 || data.status !== void 0 || data.supersedes !== void 0 || data.tags !== void 0 || data.metadata !== void 0 || data.is_global !== void 0 || data.agent !== void 0 || data.role !== void 0 || data.completed_at !== void 0,
|
|
2119
|
+
{ message: "At least one field must be provided for update" }
|
|
2120
|
+
);
|
|
2121
|
+
var MemorySearchSchema = z.object({
|
|
2122
|
+
query: z.string().min(3),
|
|
2123
|
+
prompt: z.string().optional(),
|
|
2124
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2125
|
+
types: z.array(MemoryTypeSchema).optional(),
|
|
2126
|
+
minImportance: z.number().min(1).max(5).optional(),
|
|
2127
|
+
limit: z.number().min(1).max(100).default(5),
|
|
2128
|
+
offset: z.number().min(0).default(0),
|
|
2129
|
+
includeRecap: z.boolean().default(false),
|
|
2130
|
+
current_file_path: z.string().optional(),
|
|
2131
|
+
include_archived: z.boolean().default(false),
|
|
2132
|
+
current_tags: z.array(z.string()).optional(),
|
|
2133
|
+
scope: MemoryScopeSchema.partial().optional()
|
|
2134
|
+
});
|
|
2135
|
+
var MemoryAcknowledgeSchema = z.object({
|
|
2136
|
+
memory_id: z.string().uuid(),
|
|
2137
|
+
status: z.enum(["used", "irrelevant", "contradictory"]),
|
|
2138
|
+
application_context: z.string().min(10).optional()
|
|
2139
|
+
});
|
|
2140
|
+
var MemoryRecapSchema = z.object({
|
|
2141
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2142
|
+
limit: z.number().min(1).max(50).default(20),
|
|
2143
|
+
offset: z.number().min(0).default(0)
|
|
2144
|
+
});
|
|
2145
|
+
var MemoryDeleteSchema = z.object({
|
|
2146
|
+
repo: z.string().min(1).transform(normalizeRepo).optional(),
|
|
2147
|
+
id: z.string().uuid().optional(),
|
|
2148
|
+
ids: z.array(z.string().uuid()).min(1).optional()
|
|
2149
|
+
}).refine((data) => data.id !== void 0 || data.ids !== void 0, {
|
|
2150
|
+
message: "Either 'id' or 'ids' must be provided for deletion"
|
|
2151
|
+
});
|
|
2152
|
+
var MemorySummarizeSchema = z.object({
|
|
2153
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2154
|
+
signals: z.array(z.string().max(200)).min(1)
|
|
2155
|
+
});
|
|
2156
|
+
var MemorySynthesizeSchema = z.object({
|
|
2157
|
+
repo: z.string().min(1).transform(normalizeRepo).optional(),
|
|
2158
|
+
objective: z.string().min(5),
|
|
2159
|
+
current_file_path: z.string().optional(),
|
|
2160
|
+
include_summary: z.boolean().default(true),
|
|
2161
|
+
include_tasks: z.boolean().default(true),
|
|
2162
|
+
use_tools: z.boolean().default(true),
|
|
2163
|
+
max_iterations: z.number().int().min(1).max(5).default(3),
|
|
2164
|
+
max_tokens: z.number().int().min(128).max(4e3).default(1200)
|
|
2165
|
+
});
|
|
2166
|
+
var TaskStatusSchema = z.enum(["backlog", "pending", "in_progress", "completed", "canceled", "blocked"]);
|
|
2167
|
+
var TaskPrioritySchema = z.number().min(1).max(5);
|
|
2168
|
+
var SingleTaskCreateSchema = z.object({
|
|
2169
|
+
task_code: z.string().min(1),
|
|
2170
|
+
phase: z.string().min(1),
|
|
2171
|
+
title: z.string().min(3).max(100),
|
|
2172
|
+
description: z.string().min(1),
|
|
2173
|
+
status: TaskStatusSchema.default("backlog"),
|
|
2174
|
+
priority: TaskPrioritySchema.default(3),
|
|
2175
|
+
agent: z.string().optional(),
|
|
2176
|
+
role: z.string().optional(),
|
|
2177
|
+
doc_path: z.string().optional(),
|
|
2178
|
+
tags: z.array(z.string()).optional(),
|
|
2179
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
2180
|
+
parent_id: z.string().uuid().optional(),
|
|
2181
|
+
depends_on: z.string().uuid().optional(),
|
|
2182
|
+
est_tokens: z.number().int().min(0).optional()
|
|
2183
|
+
});
|
|
2184
|
+
var TaskCreateSchema = z.object({
|
|
2185
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2186
|
+
// Allow single task fields at top level (backward compatibility & single use)
|
|
2187
|
+
task_code: z.string().min(1).optional(),
|
|
2188
|
+
phase: z.string().min(1).optional(),
|
|
2189
|
+
title: z.string().min(3).max(100).optional(),
|
|
2190
|
+
description: z.string().min(1).optional(),
|
|
2191
|
+
status: TaskStatusSchema.optional(),
|
|
2192
|
+
priority: TaskPrioritySchema.optional(),
|
|
2193
|
+
agent: z.string().optional(),
|
|
2194
|
+
role: z.string().optional(),
|
|
2195
|
+
doc_path: z.string().optional(),
|
|
2196
|
+
tags: z.array(z.string()).optional(),
|
|
2197
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
2198
|
+
parent_id: z.string().uuid().optional(),
|
|
2199
|
+
depends_on: z.string().uuid().optional(),
|
|
2200
|
+
est_tokens: z.number().int().min(0).optional(),
|
|
2201
|
+
// Allow bulk tasks
|
|
2202
|
+
tasks: z.array(SingleTaskCreateSchema).min(1).optional()
|
|
2203
|
+
}).refine((data) => {
|
|
2204
|
+
if (data.tasks) return true;
|
|
2205
|
+
return !!(data.task_code && data.phase && data.title && data.description);
|
|
2206
|
+
}, { message: "Either 'tasks' array or single task fields (task_code, phase, title, description) must be provided" });
|
|
2207
|
+
var TaskCreateInteractiveSchema = SingleTaskCreateSchema.partial().extend({
|
|
2208
|
+
repo: z.string().min(1).transform(normalizeRepo).optional()
|
|
2209
|
+
});
|
|
2210
|
+
var TaskUpdateSchema = z.object({
|
|
2211
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2212
|
+
id: z.string().uuid().optional(),
|
|
2213
|
+
ids: z.array(z.string().uuid()).min(1).optional(),
|
|
2214
|
+
task_code: z.string().optional(),
|
|
2215
|
+
phase: z.string().optional(),
|
|
2216
|
+
title: z.string().min(3).max(100).optional(),
|
|
2217
|
+
description: z.string().optional(),
|
|
2218
|
+
status: TaskStatusSchema.optional(),
|
|
2219
|
+
priority: TaskPrioritySchema.optional(),
|
|
2220
|
+
agent: z.string().min(1, "agent name is required").optional(),
|
|
2221
|
+
role: z.string().min(1, "agent role is required").optional(),
|
|
2222
|
+
model: z.string().optional(),
|
|
2223
|
+
comment: z.string().min(1).optional(),
|
|
2224
|
+
doc_path: z.string().optional(),
|
|
2225
|
+
tags: z.array(z.string()).optional(),
|
|
2226
|
+
metadata: z.record(z.string(), z.any()).optional(),
|
|
2227
|
+
parent_id: z.string().uuid().optional(),
|
|
2228
|
+
depends_on: z.string().uuid().optional(),
|
|
2229
|
+
est_tokens: z.number().int().min(0).optional(),
|
|
2230
|
+
force: z.boolean().optional()
|
|
2231
|
+
}).refine((data) => data.id !== void 0 || data.ids !== void 0, {
|
|
2232
|
+
message: "Either 'id' or 'ids' must be provided for update"
|
|
2233
|
+
}).refine((data) => Object.keys(data).length > 2, {
|
|
2234
|
+
message: "At least one field besides repo and id/ids must be provided for update"
|
|
2235
|
+
});
|
|
2236
|
+
var TaskListSchema = z.object({
|
|
2237
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2238
|
+
status: z.string().optional(),
|
|
2239
|
+
phase: z.string().optional(),
|
|
2240
|
+
query: z.string().optional(),
|
|
2241
|
+
limit: z.number().min(1).max(100).default(15),
|
|
2242
|
+
offset: z.number().min(0).default(0)
|
|
2243
|
+
});
|
|
2244
|
+
var TaskSearchSchema = z.object({
|
|
2245
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2246
|
+
query: z.string().min(1),
|
|
2247
|
+
status: z.string().optional(),
|
|
2248
|
+
limit: z.number().min(1).max(100).default(10),
|
|
2249
|
+
offset: z.number().min(0).default(0)
|
|
2250
|
+
});
|
|
2251
|
+
var TaskDeleteSchema = z.object({
|
|
2252
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2253
|
+
id: z.string().uuid().optional(),
|
|
2254
|
+
ids: z.array(z.string().uuid()).min(1).optional()
|
|
2255
|
+
}).refine((data) => data.id !== void 0 || data.ids !== void 0, {
|
|
2256
|
+
message: "Either 'id' or 'ids' must be provided for deletion"
|
|
2257
|
+
});
|
|
2258
|
+
var MemoryDetailSchema = z.object({
|
|
2259
|
+
id: z.string().uuid()
|
|
2260
|
+
});
|
|
2261
|
+
var TaskGetSchema = z.object({
|
|
2262
|
+
repo: z.string().min(1).transform(normalizeRepo),
|
|
2263
|
+
id: z.string().uuid().optional(),
|
|
2264
|
+
task_code: z.string().optional()
|
|
2265
|
+
}).refine((data) => data.id !== void 0 || data.task_code !== void 0, {
|
|
2266
|
+
message: "Either id or task_code must be provided"
|
|
2267
|
+
});
|
|
2268
|
+
var TOOL_DEFINITIONS = [
|
|
2269
|
+
{
|
|
2270
|
+
name: "memory-synthesize",
|
|
2271
|
+
title: "Memory Synthesize",
|
|
2272
|
+
description: "Use client sampling to synthesize a grounded answer from local memory and tasks. Best for project briefings, tradeoff summaries, and context-aware answers.",
|
|
2273
|
+
annotations: {
|
|
2274
|
+
readOnlyHint: true,
|
|
2275
|
+
idempotentHint: true,
|
|
2276
|
+
openWorldHint: false
|
|
2277
|
+
},
|
|
2278
|
+
execution: {
|
|
2279
|
+
taskSupport: "optional"
|
|
2280
|
+
},
|
|
2281
|
+
inputSchema: {
|
|
2282
|
+
type: "object",
|
|
2283
|
+
properties: {
|
|
2284
|
+
repo: { type: "string", description: "Repository name. Optional when a single MCP root is active." },
|
|
2285
|
+
objective: { type: "string", minLength: 5, description: "Question or synthesis objective." },
|
|
2286
|
+
current_file_path: {
|
|
2287
|
+
type: "string",
|
|
2288
|
+
description: "Optional absolute file path for workspace-local grounding."
|
|
2289
|
+
},
|
|
2290
|
+
include_summary: { type: "boolean", default: true },
|
|
2291
|
+
include_tasks: { type: "boolean", default: true },
|
|
2292
|
+
use_tools: {
|
|
2293
|
+
type: "boolean",
|
|
2294
|
+
default: true,
|
|
2295
|
+
description: "Allow the sampled model to call local memory/task tools during synthesis when the client supports sampling.tools."
|
|
2296
|
+
},
|
|
2297
|
+
max_iterations: { type: "number", minimum: 1, maximum: 5, default: 3 },
|
|
2298
|
+
max_tokens: { type: "number", minimum: 128, maximum: 4e3, default: 1200 }
|
|
2299
|
+
},
|
|
2300
|
+
required: ["objective"]
|
|
2301
|
+
},
|
|
2302
|
+
outputSchema: {
|
|
2303
|
+
type: "object",
|
|
2304
|
+
properties: {
|
|
2305
|
+
repo: { type: "string" },
|
|
2306
|
+
objective: { type: "string" },
|
|
2307
|
+
answer: { type: "string" },
|
|
2308
|
+
model: { type: "string" },
|
|
2309
|
+
stopReason: { type: "string" },
|
|
2310
|
+
iterations: { type: "number" },
|
|
2311
|
+
toolCalls: { type: "number" }
|
|
2312
|
+
},
|
|
2313
|
+
required: ["repo", "objective", "answer", "iterations", "toolCalls"]
|
|
2314
|
+
}
|
|
2315
|
+
},
|
|
2316
|
+
{
|
|
2317
|
+
name: "task-create-interactive",
|
|
2318
|
+
title: "Interactive Task Create",
|
|
2319
|
+
description: "Create a task with MCP elicitation fallback for any missing required fields. Best when an agent knows a task is needed but still needs user confirmation for repo, title, or phase.",
|
|
2320
|
+
annotations: {
|
|
2321
|
+
readOnlyHint: false,
|
|
2322
|
+
idempotentHint: false,
|
|
2323
|
+
destructiveHint: false,
|
|
2324
|
+
openWorldHint: false
|
|
2325
|
+
},
|
|
2326
|
+
inputSchema: {
|
|
2327
|
+
type: "object",
|
|
2328
|
+
properties: {
|
|
2329
|
+
repo: {
|
|
2330
|
+
type: "string",
|
|
2331
|
+
description: "Repository name. Optional when it can be inferred from MCP roots or elicited from the user."
|
|
2332
|
+
},
|
|
2333
|
+
task_code: { type: "string" },
|
|
2334
|
+
phase: { type: "string" },
|
|
2335
|
+
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
2336
|
+
description: { type: "string", minLength: 1 },
|
|
2337
|
+
status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
|
|
2338
|
+
priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
|
|
2339
|
+
agent: { type: "string" },
|
|
2340
|
+
role: { type: "string" },
|
|
2341
|
+
doc_path: { type: "string" }
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
outputSchema: {
|
|
2345
|
+
type: "object",
|
|
2346
|
+
properties: {
|
|
2347
|
+
repo: { type: "string" },
|
|
2348
|
+
task_code: { type: "string" },
|
|
2349
|
+
phase: { type: "string" },
|
|
2350
|
+
title: { type: "string" },
|
|
2351
|
+
status: { type: "string" },
|
|
2352
|
+
priority: { type: "number" }
|
|
2353
|
+
},
|
|
2354
|
+
required: ["repo", "task_code", "phase", "title", "status", "priority"]
|
|
2355
|
+
}
|
|
2356
|
+
},
|
|
2357
|
+
{
|
|
2358
|
+
name: "memory-detail",
|
|
2359
|
+
title: "Memory Detail",
|
|
2360
|
+
description: "Fetch full details of a specific memory by ID. Use this when you have a memory ID (e.g. from search results) and need to read the full content.",
|
|
2361
|
+
inputSchema: {
|
|
2362
|
+
type: "object",
|
|
2363
|
+
properties: {
|
|
2364
|
+
id: { type: "string", format: "uuid", description: "Memory entry ID" }
|
|
2365
|
+
},
|
|
2366
|
+
required: ["id"]
|
|
2367
|
+
}
|
|
2368
|
+
},
|
|
2369
|
+
{
|
|
2370
|
+
name: "task-detail",
|
|
2371
|
+
title: "Task Detail",
|
|
2372
|
+
description: "Fetch full details of a specific task by ID or task code. Use this when you have a task ID or code and need to read the full description and comments.",
|
|
2373
|
+
inputSchema: {
|
|
2374
|
+
type: "object",
|
|
2375
|
+
properties: {
|
|
2376
|
+
repo: { type: "string", description: "Repository name" },
|
|
2377
|
+
id: { type: "string", format: "uuid", description: "Task ID (optional if task_code is provided)" },
|
|
2378
|
+
task_code: { type: "string", description: "Task code (e.g. TASK-001) (optional if id is provided)" }
|
|
2379
|
+
},
|
|
2380
|
+
required: ["repo"]
|
|
2381
|
+
}
|
|
2382
|
+
},
|
|
2383
|
+
{
|
|
2384
|
+
name: "memory-store",
|
|
2385
|
+
title: "Memory Store",
|
|
2386
|
+
description: "Store a new memory entry. Keep 'title' concise and human-readable; do not embed agent/role/date metadata in the title. Put auxiliary context into 'metadata'. Use 'tags' for tech-stack and 'is_global' for universal rules.",
|
|
2387
|
+
annotations: {
|
|
2388
|
+
readOnlyHint: false,
|
|
2389
|
+
idempotentHint: false,
|
|
2390
|
+
destructiveHint: false,
|
|
2391
|
+
openWorldHint: false
|
|
2392
|
+
},
|
|
2393
|
+
inputSchema: {
|
|
2394
|
+
type: "object",
|
|
2395
|
+
properties: {
|
|
2396
|
+
type: {
|
|
2397
|
+
type: "string",
|
|
2398
|
+
enum: [
|
|
2399
|
+
"code_fact",
|
|
2400
|
+
"decision",
|
|
2401
|
+
"mistake",
|
|
2402
|
+
"pattern",
|
|
2403
|
+
"agent_handoff",
|
|
2404
|
+
"agent_registered",
|
|
2405
|
+
"file_claim",
|
|
2406
|
+
"task_archive"
|
|
2407
|
+
],
|
|
2408
|
+
description: "Type of memory being stored"
|
|
2409
|
+
},
|
|
2410
|
+
title: {
|
|
2411
|
+
type: "string",
|
|
2412
|
+
minLength: 3,
|
|
2413
|
+
maxLength: 100,
|
|
2414
|
+
description: "Short human-readable title for the memory. Do not embed bracketed metadata like agent/role/date prefixes here."
|
|
2415
|
+
},
|
|
2416
|
+
content: {
|
|
2417
|
+
type: "string",
|
|
2418
|
+
minLength: 10,
|
|
2419
|
+
description: "The memory content"
|
|
2420
|
+
},
|
|
2421
|
+
importance: {
|
|
2422
|
+
type: "number",
|
|
2423
|
+
minimum: 1,
|
|
2424
|
+
maximum: 5,
|
|
2425
|
+
description: "Importance score (1-5)"
|
|
2426
|
+
},
|
|
2427
|
+
agent: {
|
|
2428
|
+
type: "string",
|
|
2429
|
+
description: "Name of the agent creating this memory"
|
|
2430
|
+
},
|
|
2431
|
+
role: {
|
|
2432
|
+
type: "string",
|
|
2433
|
+
description: "Role of the agent creating this memory"
|
|
2434
|
+
},
|
|
2435
|
+
model: {
|
|
2436
|
+
type: "string",
|
|
2437
|
+
description: "AI model used by the agent"
|
|
2438
|
+
},
|
|
2439
|
+
scope: {
|
|
2440
|
+
type: "object",
|
|
2441
|
+
properties: {
|
|
2442
|
+
repo: { type: "string", description: "Repository name" },
|
|
2443
|
+
branch: { type: "string" },
|
|
2444
|
+
folder: { type: "string" },
|
|
2445
|
+
language: { type: "string" }
|
|
2446
|
+
},
|
|
2447
|
+
required: ["repo"]
|
|
2448
|
+
},
|
|
2449
|
+
tags: {
|
|
2450
|
+
type: "array",
|
|
2451
|
+
items: { type: "string" },
|
|
2452
|
+
description: "Technology stack tags (e.g., ['filament', 'laravel'])"
|
|
2453
|
+
},
|
|
2454
|
+
metadata: {
|
|
2455
|
+
type: "object",
|
|
2456
|
+
description: "Structured metadata for non-title context such as source agent, claim fields, or timestamps"
|
|
2457
|
+
},
|
|
2458
|
+
is_global: {
|
|
2459
|
+
type: "boolean",
|
|
2460
|
+
description: "If true, this memory is shared across all repositories"
|
|
2461
|
+
},
|
|
2462
|
+
ttlDays: { type: "number", minimum: 1 },
|
|
2463
|
+
supersedes: { type: "string", format: "uuid" }
|
|
2464
|
+
},
|
|
2465
|
+
required: ["type", "title", "content", "importance", "scope", "agent", "model"]
|
|
2466
|
+
},
|
|
2467
|
+
outputSchema: {
|
|
2468
|
+
type: "object",
|
|
2469
|
+
properties: {
|
|
2470
|
+
success: { type: "boolean" },
|
|
2471
|
+
id: { type: "string" },
|
|
2472
|
+
repo: { type: "string" },
|
|
2473
|
+
type: { type: "string" },
|
|
2474
|
+
title: { type: "string" },
|
|
2475
|
+
error: { type: "string" },
|
|
2476
|
+
message: { type: "string" }
|
|
2477
|
+
},
|
|
2478
|
+
required: ["success"]
|
|
2479
|
+
}
|
|
2480
|
+
},
|
|
2481
|
+
{
|
|
2482
|
+
name: "memory-acknowledge",
|
|
2483
|
+
title: "Memory Acknowledge",
|
|
2484
|
+
description: "Acknowledge the use of a memory or report its irrelevance/contradiction. Mandatory after using memory to generate code.",
|
|
2485
|
+
annotations: {
|
|
2486
|
+
readOnlyHint: false,
|
|
2487
|
+
idempotentHint: false,
|
|
2488
|
+
openWorldHint: false
|
|
2489
|
+
},
|
|
2490
|
+
inputSchema: {
|
|
2491
|
+
type: "object",
|
|
2492
|
+
properties: {
|
|
2493
|
+
memory_id: { type: "string", format: "uuid" },
|
|
2494
|
+
status: { type: "string", enum: ["used", "irrelevant", "contradictory"] },
|
|
2495
|
+
application_context: { type: "string", minLength: 10 }
|
|
2496
|
+
},
|
|
2497
|
+
required: ["memory_id", "status"]
|
|
2498
|
+
},
|
|
2499
|
+
outputSchema: {
|
|
2500
|
+
type: "object",
|
|
2501
|
+
properties: {
|
|
2502
|
+
success: { type: "boolean" },
|
|
2503
|
+
id: { type: "string" },
|
|
2504
|
+
status: { type: "string" }
|
|
2505
|
+
},
|
|
2506
|
+
required: ["success", "id", "status"]
|
|
2507
|
+
}
|
|
2508
|
+
},
|
|
2509
|
+
{
|
|
2510
|
+
name: "memory-update",
|
|
2511
|
+
title: "Memory Update",
|
|
2512
|
+
description: "Update an existing memory entry. Keep 'title' concise and move agent/role/date or claim context into 'metadata' instead of the title.",
|
|
2513
|
+
annotations: {
|
|
2514
|
+
readOnlyHint: false,
|
|
2515
|
+
idempotentHint: false,
|
|
2516
|
+
destructiveHint: false,
|
|
2517
|
+
openWorldHint: false
|
|
2518
|
+
},
|
|
2519
|
+
inputSchema: {
|
|
2520
|
+
type: "object",
|
|
2521
|
+
properties: {
|
|
2522
|
+
id: { type: "string", format: "uuid" },
|
|
2523
|
+
type: {
|
|
2524
|
+
type: "string",
|
|
2525
|
+
enum: [
|
|
2526
|
+
"code_fact",
|
|
2527
|
+
"decision",
|
|
2528
|
+
"mistake",
|
|
2529
|
+
"pattern",
|
|
2530
|
+
"agent_handoff",
|
|
2531
|
+
"agent_registered",
|
|
2532
|
+
"file_claim",
|
|
2533
|
+
"task_archive"
|
|
2534
|
+
]
|
|
2535
|
+
},
|
|
2536
|
+
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
2537
|
+
content: { type: "string", minLength: 10 },
|
|
2538
|
+
importance: { type: "number", minimum: 1, maximum: 5 },
|
|
2539
|
+
agent: { type: "string" },
|
|
2540
|
+
role: { type: "string" },
|
|
2541
|
+
status: { type: "string", enum: ["active", "archived"] },
|
|
2542
|
+
supersedes: { type: "string", format: "uuid" },
|
|
2543
|
+
tags: { type: "array", items: { type: "string" } },
|
|
2544
|
+
metadata: { type: "object" },
|
|
2545
|
+
is_global: { type: "boolean" },
|
|
2546
|
+
completed_at: { type: "string" }
|
|
2547
|
+
},
|
|
2548
|
+
required: ["id"]
|
|
2549
|
+
},
|
|
2550
|
+
outputSchema: {
|
|
2551
|
+
type: "object",
|
|
2552
|
+
properties: {
|
|
2553
|
+
success: { type: "boolean" },
|
|
2554
|
+
id: { type: "string" },
|
|
2555
|
+
repo: { type: "string" },
|
|
2556
|
+
updatedFields: {
|
|
2557
|
+
type: "array",
|
|
2558
|
+
items: { type: "string" }
|
|
2559
|
+
}
|
|
2560
|
+
},
|
|
2561
|
+
required: ["success", "id", "repo", "updatedFields"]
|
|
2562
|
+
}
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
name: "memory-search",
|
|
2566
|
+
title: "Memory Search",
|
|
2567
|
+
description: "NAVIGATION LAYER: Returns a pointer table of matching memory IDs only. Returns columns [id, title, type, importance] \u2014 NO content. Retrieve full memory via memory-detail. Use 'current_tags' to find tech-stack specific knowledge from other projects.",
|
|
2568
|
+
annotations: {
|
|
2569
|
+
readOnlyHint: true,
|
|
2570
|
+
idempotentHint: true,
|
|
2571
|
+
openWorldHint: false
|
|
2572
|
+
},
|
|
2573
|
+
inputSchema: {
|
|
2574
|
+
type: "object",
|
|
2575
|
+
properties: {
|
|
2576
|
+
query: { type: "string", minLength: 3 },
|
|
2577
|
+
prompt: { type: "string" },
|
|
2578
|
+
repo: { type: "string" },
|
|
2579
|
+
current_tags: {
|
|
2580
|
+
type: "array",
|
|
2581
|
+
items: { type: "string" },
|
|
2582
|
+
description: "Active tech stack tags (e.g., ['filament', 'react'])"
|
|
2583
|
+
},
|
|
2584
|
+
types: {
|
|
2585
|
+
type: "array",
|
|
2586
|
+
items: {
|
|
2587
|
+
type: "string",
|
|
2588
|
+
enum: [
|
|
2589
|
+
"code_fact",
|
|
2590
|
+
"decision",
|
|
2591
|
+
"mistake",
|
|
2592
|
+
"pattern",
|
|
2593
|
+
"agent_handoff",
|
|
2594
|
+
"agent_registered",
|
|
2595
|
+
"file_claim",
|
|
2596
|
+
"task_archive"
|
|
2597
|
+
]
|
|
2598
|
+
}
|
|
2599
|
+
},
|
|
2600
|
+
minImportance: { type: "number", minimum: 1, maximum: 5 },
|
|
2601
|
+
limit: { type: "number", minimum: 1, maximum: 100, default: 5 },
|
|
2602
|
+
offset: { type: "number", minimum: 0, default: 0 },
|
|
2603
|
+
includeRecap: { type: "boolean", default: false },
|
|
2604
|
+
current_file_path: { type: "string" },
|
|
2605
|
+
include_archived: { type: "boolean", default: false },
|
|
2606
|
+
scope: {
|
|
2607
|
+
type: "object",
|
|
2608
|
+
properties: {
|
|
2609
|
+
repo: { type: "string" },
|
|
2610
|
+
branch: { type: "string" },
|
|
2611
|
+
folder: { type: "string" },
|
|
2612
|
+
language: { type: "string" }
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
},
|
|
2616
|
+
required: ["query", "repo"]
|
|
2617
|
+
},
|
|
2618
|
+
outputSchema: {
|
|
2619
|
+
type: "object",
|
|
2620
|
+
properties: {
|
|
2621
|
+
schema: { type: "string", enum: ["memory-search"] },
|
|
2622
|
+
query: { type: "string" },
|
|
2623
|
+
count: { type: "number", description: "Number of rows returned" },
|
|
2624
|
+
total: { type: "number", description: "Total matching memories" },
|
|
2625
|
+
offset: { type: "number" },
|
|
2626
|
+
limit: { type: "number" },
|
|
2627
|
+
results: {
|
|
2628
|
+
type: "object",
|
|
2629
|
+
properties: {
|
|
2630
|
+
columns: {
|
|
2631
|
+
type: "array",
|
|
2632
|
+
items: { type: "string" },
|
|
2633
|
+
description: "Column names: [id, title, type, importance]"
|
|
2634
|
+
},
|
|
2635
|
+
rows: {
|
|
2636
|
+
type: "array",
|
|
2637
|
+
items: { type: "array" },
|
|
2638
|
+
description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
|
|
2639
|
+
}
|
|
2640
|
+
},
|
|
2641
|
+
required: ["columns", "rows"]
|
|
2642
|
+
}
|
|
2643
|
+
},
|
|
2644
|
+
required: ["schema", "query", "count", "total", "offset", "limit", "results"]
|
|
2645
|
+
}
|
|
2646
|
+
},
|
|
2647
|
+
{
|
|
2648
|
+
name: "memory-summarize",
|
|
2649
|
+
title: "Memory Summarize",
|
|
2650
|
+
description: "Update the summary for a repository",
|
|
2651
|
+
annotations: {
|
|
2652
|
+
readOnlyHint: false,
|
|
2653
|
+
idempotentHint: false,
|
|
2654
|
+
openWorldHint: false
|
|
2655
|
+
},
|
|
2656
|
+
inputSchema: {
|
|
2657
|
+
type: "object",
|
|
2658
|
+
properties: {
|
|
2659
|
+
repo: { type: "string", description: "Repository name" },
|
|
2660
|
+
signals: {
|
|
2661
|
+
type: "array",
|
|
2662
|
+
items: { type: "string", maxLength: 200 },
|
|
2663
|
+
minItems: 1,
|
|
2664
|
+
description: "High-level signals to include in summary"
|
|
2665
|
+
}
|
|
2666
|
+
},
|
|
2667
|
+
required: ["repo", "signals"]
|
|
2668
|
+
},
|
|
2669
|
+
outputSchema: {
|
|
2670
|
+
type: "object",
|
|
2671
|
+
properties: {
|
|
2672
|
+
success: { type: "boolean" },
|
|
2673
|
+
repo: { type: "string" },
|
|
2674
|
+
summary: { type: "string" },
|
|
2675
|
+
signalCount: { type: "number" }
|
|
2676
|
+
},
|
|
2677
|
+
required: ["success", "repo", "summary", "signalCount"]
|
|
2678
|
+
}
|
|
2679
|
+
},
|
|
2680
|
+
{
|
|
2681
|
+
name: "memory-delete",
|
|
2682
|
+
title: "Memory Delete",
|
|
2683
|
+
description: "Soft-delete one or more memory entries. Supports single 'id' or bulk 'ids'.",
|
|
2684
|
+
annotations: {
|
|
2685
|
+
readOnlyHint: false,
|
|
2686
|
+
idempotentHint: false,
|
|
2687
|
+
destructiveHint: true,
|
|
2688
|
+
openWorldHint: false
|
|
2689
|
+
},
|
|
2690
|
+
inputSchema: {
|
|
2691
|
+
type: "object",
|
|
2692
|
+
properties: {
|
|
2693
|
+
repo: { type: "string", description: "Repository name (optional for single id)" },
|
|
2694
|
+
id: { type: "string", format: "uuid", description: "Memory entry ID to delete" },
|
|
2695
|
+
ids: {
|
|
2696
|
+
type: "array",
|
|
2697
|
+
items: { type: "string", format: "uuid" },
|
|
2698
|
+
minItems: 1,
|
|
2699
|
+
description: "Array of memory IDs to delete"
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
},
|
|
2703
|
+
outputSchema: {
|
|
2704
|
+
type: "object",
|
|
2705
|
+
properties: {
|
|
2706
|
+
success: { type: "boolean" },
|
|
2707
|
+
id: { type: "string" },
|
|
2708
|
+
ids: { type: "array", items: { type: "string" } },
|
|
2709
|
+
repo: { type: "string" },
|
|
2710
|
+
deletedCount: { type: "number" }
|
|
2711
|
+
},
|
|
2712
|
+
required: ["success"]
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
{
|
|
2716
|
+
name: "memory-recap",
|
|
2717
|
+
title: "Memory Recap",
|
|
2718
|
+
description: "AGGREGATED OVERVIEW LAYER: Returns stats (counts by type) and a pointer table of top memories [id, title, type, importance]. NO content. Use for orientation only \u2014 retrieve full memory via memory-detail.",
|
|
2719
|
+
annotations: {
|
|
2720
|
+
readOnlyHint: true,
|
|
2721
|
+
idempotentHint: true,
|
|
2722
|
+
openWorldHint: false
|
|
2723
|
+
},
|
|
2724
|
+
inputSchema: {
|
|
2725
|
+
type: "object",
|
|
2726
|
+
properties: {
|
|
2727
|
+
repo: { type: "string", description: "Repository name (required)" },
|
|
2728
|
+
limit: {
|
|
2729
|
+
type: "number",
|
|
2730
|
+
minimum: 1,
|
|
2731
|
+
maximum: 50,
|
|
2732
|
+
default: 20,
|
|
2733
|
+
description: "Maximum number of top memories to return in the pointer table"
|
|
2734
|
+
},
|
|
2735
|
+
offset: {
|
|
2736
|
+
type: "number",
|
|
2737
|
+
minimum: 0,
|
|
2738
|
+
default: 0,
|
|
2739
|
+
description: "Number of memories to skip for pagination (optional, default 0)"
|
|
2740
|
+
}
|
|
2741
|
+
},
|
|
2742
|
+
required: ["repo"]
|
|
2743
|
+
},
|
|
2744
|
+
outputSchema: {
|
|
2745
|
+
type: "object",
|
|
2746
|
+
properties: {
|
|
2747
|
+
schema: { type: "string", enum: ["memory-recap"] },
|
|
2748
|
+
repo: { type: "string" },
|
|
2749
|
+
count: { type: "number", description: "Number of rows in the top pointer table" },
|
|
2750
|
+
total: { type: "number", description: "Total active memories in repo" },
|
|
2751
|
+
offset: { type: "number" },
|
|
2752
|
+
limit: { type: "number" },
|
|
2753
|
+
stats: {
|
|
2754
|
+
type: "object",
|
|
2755
|
+
properties: {
|
|
2756
|
+
by_type: {
|
|
2757
|
+
type: "object",
|
|
2758
|
+
description: "Count of active memories per type (e.g. { decision: 3, code_fact: 7 })"
|
|
2759
|
+
}
|
|
2760
|
+
},
|
|
2761
|
+
required: ["by_type"]
|
|
2762
|
+
},
|
|
2763
|
+
top: {
|
|
2764
|
+
type: "object",
|
|
2765
|
+
properties: {
|
|
2766
|
+
columns: {
|
|
2767
|
+
type: "array",
|
|
2768
|
+
items: { type: "string" },
|
|
2769
|
+
description: "Column names: [id, title, type, importance]"
|
|
2770
|
+
},
|
|
2771
|
+
rows: {
|
|
2772
|
+
type: "array",
|
|
2773
|
+
items: { type: "array" },
|
|
2774
|
+
description: "Each row: [id, title, type, importance]. Fetch full content via memory-detail"
|
|
2775
|
+
}
|
|
2776
|
+
},
|
|
2777
|
+
required: ["columns", "rows"]
|
|
2778
|
+
}
|
|
2779
|
+
},
|
|
2780
|
+
required: ["schema", "repo", "count", "total", "offset", "limit", "stats", "top"]
|
|
2781
|
+
}
|
|
2782
|
+
},
|
|
2783
|
+
{
|
|
2784
|
+
name: "task-create",
|
|
2785
|
+
title: "Task Create",
|
|
2786
|
+
description: "Register one or more new tasks in a repository. task_code must be unique within the repository. Supports single task object or an array of tasks for bulk creation.",
|
|
2787
|
+
annotations: {
|
|
2788
|
+
readOnlyHint: false,
|
|
2789
|
+
idempotentHint: false,
|
|
2790
|
+
openWorldHint: false
|
|
2791
|
+
},
|
|
2792
|
+
inputSchema: {
|
|
2793
|
+
type: "object",
|
|
2794
|
+
properties: {
|
|
2795
|
+
repo: { type: "string", description: "Repository name" },
|
|
2796
|
+
task_code: { type: "string", description: "Unique task code (e.g. TASK-001) (Required for single task)" },
|
|
2797
|
+
phase: { type: "string", description: "Project phase (Required for single task)" },
|
|
2798
|
+
title: { type: "string", minLength: 3, maxLength: 100, description: "Task objective (Required for single task)" },
|
|
2799
|
+
description: { type: "string", description: "Detailed description (Required for single task)" },
|
|
2800
|
+
status: {
|
|
2801
|
+
type: "string",
|
|
2802
|
+
enum: ["backlog", "pending"],
|
|
2803
|
+
default: "backlog",
|
|
2804
|
+
description: "New tasks MUST start in 'backlog' if there are already 10 pending tasks. Otherwise can start in 'pending'."
|
|
2805
|
+
},
|
|
2806
|
+
priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
|
|
2807
|
+
agent: { type: "string" },
|
|
2808
|
+
role: { type: "string" },
|
|
2809
|
+
doc_path: { type: "string" },
|
|
2810
|
+
tags: { type: "array", items: { type: "string" } },
|
|
2811
|
+
metadata: { type: "object" },
|
|
2812
|
+
parent_id: { type: "string", format: "uuid" },
|
|
2813
|
+
depends_on: { type: "string", format: "uuid" },
|
|
2814
|
+
est_tokens: { type: "number", minimum: 0, description: "Estimated tokens budget for this task" },
|
|
2815
|
+
tasks: {
|
|
2816
|
+
type: "array",
|
|
2817
|
+
items: {
|
|
2818
|
+
type: "object",
|
|
2819
|
+
properties: {
|
|
2820
|
+
task_code: { type: "string" },
|
|
2821
|
+
phase: { type: "string" },
|
|
2822
|
+
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
2823
|
+
description: { type: "string" },
|
|
2824
|
+
status: { type: "string", enum: ["backlog", "pending"], default: "backlog" },
|
|
2825
|
+
priority: { type: "number", minimum: 1, maximum: 5, default: 3 },
|
|
2826
|
+
agent: { type: "string" },
|
|
2827
|
+
role: { type: "string" },
|
|
2828
|
+
doc_path: { type: "string" },
|
|
2829
|
+
tags: { type: "array", items: { type: "string" } },
|
|
2830
|
+
metadata: { type: "object" },
|
|
2831
|
+
parent_id: { type: "string", format: "uuid" },
|
|
2832
|
+
depends_on: { type: "string", format: "uuid" },
|
|
2833
|
+
est_tokens: { type: "number", minimum: 0 }
|
|
2834
|
+
},
|
|
2835
|
+
required: ["task_code", "phase", "title", "description"]
|
|
2836
|
+
},
|
|
2837
|
+
description: "Array of tasks for bulk creation"
|
|
2838
|
+
}
|
|
2839
|
+
},
|
|
2840
|
+
required: ["repo"]
|
|
2841
|
+
},
|
|
2842
|
+
outputSchema: {
|
|
2843
|
+
type: "object",
|
|
2844
|
+
properties: {
|
|
2845
|
+
success: { type: "boolean" },
|
|
2846
|
+
id: { type: "string" },
|
|
2847
|
+
repo: { type: "string" },
|
|
2848
|
+
task_code: { type: "string" },
|
|
2849
|
+
phase: { type: "string" },
|
|
2850
|
+
title: { type: "string" },
|
|
2851
|
+
status: { type: "string" },
|
|
2852
|
+
priority: { type: "number" },
|
|
2853
|
+
createdCount: { type: "number" },
|
|
2854
|
+
taskCodes: { type: "array", items: { type: "string" } }
|
|
2855
|
+
},
|
|
2856
|
+
required: ["success", "repo"]
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
{
|
|
2860
|
+
name: "task-update",
|
|
2861
|
+
title: "Task Update",
|
|
2862
|
+
description: "Update one or more tasks. Supports single update via 'id' or bulk update via 'ids'. Provide only the fields that need to be changed. MANDATORY WORKFLOW: You cannot move a task from 'pending' or 'blocked' directly to 'completed'. You MUST move it to 'in_progress' first. When changing status to 'completed', include 'est_tokens' with the estimated total tokens actually used for the task.",
|
|
2863
|
+
annotations: {
|
|
2864
|
+
readOnlyHint: false,
|
|
2865
|
+
idempotentHint: false,
|
|
2866
|
+
openWorldHint: false
|
|
2867
|
+
},
|
|
2868
|
+
inputSchema: {
|
|
2869
|
+
type: "object",
|
|
2870
|
+
properties: {
|
|
2871
|
+
repo: { type: "string", description: "Repository name" },
|
|
2872
|
+
id: { type: "string", format: "uuid", description: "Task ID (for single update)" },
|
|
2873
|
+
ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk update)" },
|
|
2874
|
+
task_code: { type: "string" },
|
|
2875
|
+
phase: { type: "string" },
|
|
2876
|
+
title: { type: "string", minLength: 3, maxLength: 100 },
|
|
2877
|
+
description: { type: "string" },
|
|
2878
|
+
status: {
|
|
2879
|
+
type: "string",
|
|
2880
|
+
enum: ["backlog", "pending", "in_progress", "completed", "canceled", "blocked"],
|
|
2881
|
+
description: "New status. Transitions from 'backlog', 'pending' or 'blocked' to 'completed' are NOT allowed."
|
|
2882
|
+
},
|
|
2883
|
+
priority: { type: "number", minimum: 1, maximum: 5 },
|
|
2884
|
+
agent: { type: "string" },
|
|
2885
|
+
role: { type: "string" },
|
|
2886
|
+
model: { type: "string" },
|
|
2887
|
+
comment: {
|
|
2888
|
+
type: "string",
|
|
2889
|
+
description: "REQUIRED when changing task status. Explain WHY the status is changing (e.g., 'Starting implementation', 'Blocked by missing API docs', 'Verified fix')."
|
|
2890
|
+
},
|
|
2891
|
+
doc_path: { type: "string" },
|
|
2892
|
+
tags: { type: "array", items: { type: "string" } },
|
|
2893
|
+
metadata: { type: "object" },
|
|
2894
|
+
parent_id: { type: "string", format: "uuid" },
|
|
2895
|
+
depends_on: { type: "string", format: "uuid" },
|
|
2896
|
+
est_tokens: {
|
|
2897
|
+
type: "number",
|
|
2898
|
+
minimum: 0,
|
|
2899
|
+
description: "Estimated total tokens actually used for this task. Required when status changes to 'completed'."
|
|
2900
|
+
},
|
|
2901
|
+
force: { type: "boolean", description: "If true, bypasses status transition validation (e.g. pending -> completed)." }
|
|
2902
|
+
},
|
|
2903
|
+
required: ["repo"]
|
|
2904
|
+
},
|
|
2905
|
+
outputSchema: {
|
|
2906
|
+
type: "object",
|
|
2907
|
+
properties: {
|
|
2908
|
+
success: { type: "boolean" },
|
|
2909
|
+
id: { type: "string" },
|
|
2910
|
+
ids: { type: "array", items: { type: "string" } },
|
|
2911
|
+
repo: { type: "string" },
|
|
2912
|
+
status: { type: "string" },
|
|
2913
|
+
archivedToMemory: { type: "boolean" },
|
|
2914
|
+
updatedFields: {
|
|
2915
|
+
type: "array",
|
|
2916
|
+
items: { type: "string" }
|
|
2917
|
+
},
|
|
2918
|
+
updatedCount: { type: "number" }
|
|
2919
|
+
},
|
|
2920
|
+
required: ["success", "repo"]
|
|
2921
|
+
}
|
|
2922
|
+
},
|
|
2923
|
+
{
|
|
2924
|
+
name: "task-delete",
|
|
2925
|
+
title: "Task Delete",
|
|
2926
|
+
description: "Delete one or more tasks from a repository. Supports single 'id' or bulk 'ids'.",
|
|
2927
|
+
annotations: {
|
|
2928
|
+
readOnlyHint: false,
|
|
2929
|
+
idempotentHint: false,
|
|
2930
|
+
destructiveHint: true,
|
|
2931
|
+
openWorldHint: false
|
|
2932
|
+
},
|
|
2933
|
+
inputSchema: {
|
|
2934
|
+
type: "object",
|
|
2935
|
+
properties: {
|
|
2936
|
+
repo: { type: "string", description: "Repository name" },
|
|
2937
|
+
id: { type: "string", format: "uuid", description: "Task ID (for single deletion)" },
|
|
2938
|
+
ids: { type: "array", items: { type: "string", format: "uuid" }, description: "Task IDs (for bulk deletion)" }
|
|
2939
|
+
},
|
|
2940
|
+
required: ["repo"]
|
|
2941
|
+
},
|
|
2942
|
+
outputSchema: {
|
|
2943
|
+
type: "object",
|
|
2944
|
+
properties: {
|
|
2945
|
+
success: { type: "boolean" },
|
|
2946
|
+
id: { type: "string" },
|
|
2947
|
+
ids: { type: "array", items: { type: "string" } },
|
|
2948
|
+
repo: { type: "string" },
|
|
2949
|
+
deletedCount: { type: "number" }
|
|
2950
|
+
},
|
|
2951
|
+
required: ["success", "repo"]
|
|
2952
|
+
}
|
|
2953
|
+
},
|
|
2954
|
+
{
|
|
2955
|
+
name: "task-list",
|
|
2956
|
+
title: "Task List",
|
|
2957
|
+
description: "PRIMARY navigation and search tool for tasks. Returns a compact tabular list of tasks (id, task_code, title, status, priority). Defaults to in_progress and pending tasks. Use 'query' to filter by code, title, or description. Use 'status' (comma-separated) for specific filters. AGENTS: call this once at start, pick ONE task, then call task-detail.",
|
|
2958
|
+
annotations: {
|
|
2959
|
+
readOnlyHint: true,
|
|
2960
|
+
idempotentHint: true,
|
|
2961
|
+
openWorldHint: false
|
|
2962
|
+
},
|
|
2963
|
+
inputSchema: {
|
|
2964
|
+
type: "object",
|
|
2965
|
+
properties: {
|
|
2966
|
+
repo: {
|
|
2967
|
+
type: "string",
|
|
2968
|
+
description: "Repository name"
|
|
2969
|
+
},
|
|
2970
|
+
status: {
|
|
2971
|
+
type: "string",
|
|
2972
|
+
default: "in_progress,pending",
|
|
2973
|
+
description: "Comma-separated status filter (backlog, pending, in_progress, completed, canceled, blocked). Defaults to 'in_progress,pending'."
|
|
2974
|
+
},
|
|
2975
|
+
phase: {
|
|
2976
|
+
type: "string",
|
|
2977
|
+
description: "Filter by phase (e.g., 'research', 'implementation')"
|
|
2978
|
+
},
|
|
2979
|
+
query: {
|
|
2980
|
+
type: "string",
|
|
2981
|
+
description: "Search keyword matching task code, title, or description"
|
|
2982
|
+
},
|
|
2983
|
+
limit: {
|
|
2984
|
+
type: "number",
|
|
2985
|
+
minimum: 1,
|
|
2986
|
+
maximum: 100,
|
|
2987
|
+
default: 5,
|
|
2988
|
+
description: "Maximum rows to return (default 5)"
|
|
2989
|
+
},
|
|
2990
|
+
offset: {
|
|
2991
|
+
type: "number",
|
|
2992
|
+
minimum: 0,
|
|
2993
|
+
default: 0,
|
|
2994
|
+
description: "Offset for pagination"
|
|
2995
|
+
}
|
|
2996
|
+
},
|
|
2997
|
+
required: ["repo"]
|
|
2998
|
+
},
|
|
2999
|
+
outputSchema: {
|
|
3000
|
+
type: "object",
|
|
3001
|
+
properties: {
|
|
3002
|
+
schema: { type: "string", enum: ["task-list"] },
|
|
3003
|
+
tasks: {
|
|
3004
|
+
type: "object",
|
|
3005
|
+
properties: {
|
|
3006
|
+
columns: {
|
|
3007
|
+
type: "array",
|
|
3008
|
+
items: { type: "string" },
|
|
3009
|
+
description: "Column names in order: id, task_code, title, status, priority, comments_count"
|
|
3010
|
+
},
|
|
3011
|
+
rows: {
|
|
3012
|
+
type: "array",
|
|
3013
|
+
items: { type: "array" },
|
|
3014
|
+
description: "Each row: [id, task_code, title, status, priority, comments_count]. Use task-detail to fetch full task."
|
|
3015
|
+
}
|
|
3016
|
+
},
|
|
3017
|
+
required: ["columns", "rows"]
|
|
3018
|
+
},
|
|
3019
|
+
count: { type: "number" },
|
|
3020
|
+
offset: { type: "number" }
|
|
3021
|
+
},
|
|
3022
|
+
required: ["schema", "tasks", "count"]
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
];
|
|
3026
|
+
|
|
3027
|
+
// src/mcp/utils/pagination.ts
|
|
3028
|
+
function encodeCursor(offset) {
|
|
3029
|
+
return Buffer.from(String(offset), "utf8").toString("base64");
|
|
3030
|
+
}
|
|
3031
|
+
function decodeCursor(cursor) {
|
|
3032
|
+
if (cursor === void 0 || cursor === null || cursor === "") {
|
|
3033
|
+
return 0;
|
|
3034
|
+
}
|
|
3035
|
+
if (typeof cursor !== "string" || cursor.trim() === "") {
|
|
3036
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
3037
|
+
}
|
|
3038
|
+
let decoded;
|
|
3039
|
+
try {
|
|
3040
|
+
decoded = Buffer.from(cursor, "base64").toString("utf8");
|
|
3041
|
+
} catch {
|
|
3042
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
3043
|
+
}
|
|
3044
|
+
if (!/^\d+$/.test(decoded)) {
|
|
3045
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
3046
|
+
}
|
|
3047
|
+
const offset = Number.parseInt(decoded, 10);
|
|
3048
|
+
if (!Number.isFinite(offset) || offset < 0) {
|
|
3049
|
+
throw invalidPaginationParams("Invalid cursor");
|
|
3050
|
+
}
|
|
3051
|
+
return offset;
|
|
3052
|
+
}
|
|
3053
|
+
function invalidPaginationParams(message) {
|
|
3054
|
+
const error = new Error(message);
|
|
3055
|
+
error.code = -32602;
|
|
3056
|
+
return error;
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// src/mcp/utils/completion.ts
|
|
3060
|
+
var MAX_COMPLETION_VALUES = 100;
|
|
3061
|
+
function rankCompletionValues(candidates, input) {
|
|
3062
|
+
const unique = [...new Set(candidates.filter(Boolean))];
|
|
3063
|
+
const needle = input.trim().toLowerCase();
|
|
3064
|
+
if (!needle) {
|
|
3065
|
+
return unique.slice(0, MAX_COMPLETION_VALUES);
|
|
3066
|
+
}
|
|
3067
|
+
return unique.map((value) => ({ value, score: scoreCompletionValue(value, needle) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.value.localeCompare(b.value)).map((entry) => entry.value);
|
|
3068
|
+
}
|
|
3069
|
+
function scoreCompletionValue(value, needle) {
|
|
3070
|
+
const haystack = value.toLowerCase();
|
|
3071
|
+
if (haystack === needle) return 100;
|
|
3072
|
+
if (haystack.startsWith(needle)) return 75;
|
|
3073
|
+
if (haystack.includes(needle)) return 50;
|
|
3074
|
+
const compactNeedle = needle.replace(/[\s_-]+/g, "");
|
|
3075
|
+
const compactHaystack = haystack.replace(/[\s_-]+/g, "");
|
|
3076
|
+
if (compactNeedle && compactHaystack.includes(compactNeedle)) return 25;
|
|
3077
|
+
return 0;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// src/mcp/resources/index.ts
|
|
3081
|
+
var DEFAULT_PAGE_SIZE = 25;
|
|
3082
|
+
var MAX_PAGE_SIZE = 100;
|
|
3083
|
+
function listResources(session, params) {
|
|
3084
|
+
const resources = [
|
|
3085
|
+
{
|
|
3086
|
+
uri: "repository://index",
|
|
3087
|
+
name: "Repository Index",
|
|
3088
|
+
title: "Repository Index",
|
|
3089
|
+
description: "List of all known repositories with memory/task counts and last activity",
|
|
3090
|
+
mimeType: "application/json",
|
|
3091
|
+
annotations: {
|
|
3092
|
+
audience: ["assistant"],
|
|
3093
|
+
priority: 1,
|
|
3094
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
3095
|
+
}
|
|
3096
|
+
},
|
|
3097
|
+
{
|
|
3098
|
+
uri: "session://roots",
|
|
3099
|
+
name: "Session Roots",
|
|
3100
|
+
title: "Session Roots",
|
|
3101
|
+
description: session?.roots.length ? "Active workspace roots provided by the MCP client" : "No active workspace roots were provided by the MCP client",
|
|
3102
|
+
mimeType: "application/json",
|
|
3103
|
+
size: Buffer.byteLength(JSON.stringify({ roots: session?.roots ?? [] }), "utf8"),
|
|
3104
|
+
annotations: {
|
|
3105
|
+
audience: ["assistant"],
|
|
3106
|
+
priority: 0.95,
|
|
3107
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
];
|
|
3111
|
+
return paginateEntries("resources", resources, params);
|
|
3112
|
+
}
|
|
3113
|
+
function listResourceTemplates(params) {
|
|
3114
|
+
const templates = [
|
|
3115
|
+
// ── Memory ──────────────────────────────────────────────────────────────
|
|
3116
|
+
{
|
|
3117
|
+
uriTemplate: "repository://{name}/memories",
|
|
3118
|
+
name: "Repository Memories",
|
|
3119
|
+
title: "Repository Memories",
|
|
3120
|
+
description: "All active memory entries for a specific repository",
|
|
3121
|
+
mimeType: "application/json",
|
|
3122
|
+
annotations: { audience: ["assistant"], priority: 0.85 }
|
|
3123
|
+
},
|
|
3124
|
+
{
|
|
3125
|
+
uriTemplate: "repository://{name}/memories?search={search}&type={type}&tag={tag}",
|
|
3126
|
+
name: "Filtered Repository Memories",
|
|
3127
|
+
title: "Filtered Repository Memories",
|
|
3128
|
+
description: "Filter or search memories within a repository by keyword, type, or tag",
|
|
3129
|
+
mimeType: "application/json",
|
|
3130
|
+
annotations: { audience: ["assistant"], priority: 0.8 }
|
|
3131
|
+
},
|
|
3132
|
+
{
|
|
3133
|
+
uriTemplate: "memory://{id}",
|
|
3134
|
+
name: "Memory Detail",
|
|
3135
|
+
title: "Memory Detail",
|
|
3136
|
+
description: "Full content and statistics for a specific memory UUID",
|
|
3137
|
+
mimeType: "application/json",
|
|
3138
|
+
annotations: { audience: ["assistant"], priority: 0.75 }
|
|
3139
|
+
},
|
|
3140
|
+
// ── Tasks ────────────────────────────────────────────────────────────────
|
|
3141
|
+
{
|
|
3142
|
+
uriTemplate: "repository://{name}/tasks",
|
|
3143
|
+
name: "Repository Tasks",
|
|
3144
|
+
title: "Repository Tasks",
|
|
3145
|
+
description: "All active tasks for a specific repository",
|
|
3146
|
+
mimeType: "application/json",
|
|
3147
|
+
annotations: { audience: ["assistant"], priority: 0.9 }
|
|
3148
|
+
},
|
|
3149
|
+
{
|
|
3150
|
+
uriTemplate: "repository://{name}/tasks?status={status}&priority={priority}",
|
|
3151
|
+
name: "Filtered Repository Tasks",
|
|
3152
|
+
title: "Filtered Repository Tasks",
|
|
3153
|
+
description: "Filter tasks within a repository by status or priority level",
|
|
3154
|
+
mimeType: "application/json",
|
|
3155
|
+
annotations: { audience: ["assistant"], priority: 0.85 }
|
|
3156
|
+
},
|
|
3157
|
+
{
|
|
3158
|
+
uriTemplate: "task://{id}",
|
|
3159
|
+
name: "Task Detail",
|
|
3160
|
+
title: "Task Detail",
|
|
3161
|
+
description: "Full content and comments for a specific task UUID",
|
|
3162
|
+
mimeType: "application/json",
|
|
3163
|
+
annotations: { audience: ["assistant"], priority: 0.8 }
|
|
3164
|
+
},
|
|
3165
|
+
// ── Repository extras ────────────────────────────────────────────────────
|
|
3166
|
+
{
|
|
3167
|
+
uriTemplate: "repository://{name}/summary",
|
|
3168
|
+
name: "Repository Summary",
|
|
3169
|
+
title: "Repository Summary",
|
|
3170
|
+
description: "High-level architectural summary for a repository",
|
|
3171
|
+
mimeType: "text/plain",
|
|
3172
|
+
annotations: { audience: ["assistant"], priority: 0.95 }
|
|
3173
|
+
},
|
|
3174
|
+
{
|
|
3175
|
+
uriTemplate: "repository://{name}/actions",
|
|
3176
|
+
name: "Repository Actions",
|
|
3177
|
+
title: "Repository Actions",
|
|
3178
|
+
description: "Audit log of agent tool actions scoped to a repository",
|
|
3179
|
+
mimeType: "application/json",
|
|
3180
|
+
annotations: { audience: ["assistant"], priority: 0.6 }
|
|
3181
|
+
},
|
|
3182
|
+
// ── Action detail ────────────────────────────────────────────────────────
|
|
3183
|
+
{
|
|
3184
|
+
uriTemplate: "action://{id}",
|
|
3185
|
+
name: "Action Detail",
|
|
3186
|
+
title: "Action Detail",
|
|
3187
|
+
description: "Full details of a specific audit log entry by integer ID",
|
|
3188
|
+
mimeType: "application/json",
|
|
3189
|
+
annotations: { audience: ["assistant"], priority: 0.55 }
|
|
3190
|
+
}
|
|
3191
|
+
];
|
|
3192
|
+
return paginateEntries("resourceTemplates", templates, params);
|
|
3193
|
+
}
|
|
3194
|
+
function completeResourceArgument(resourceUri, argumentName, argumentValue, _contextArguments, dataSources) {
|
|
3195
|
+
if (resourceUri === "repository://{name}/memories" || resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}" || resourceUri === "repository://{name}/tasks" || resourceUri === "repository://{name}/tasks?status={status}&priority={priority}" || resourceUri === "repository://{name}/summary" || resourceUri === "repository://{name}/actions") {
|
|
3196
|
+
if (argumentName === "name") {
|
|
3197
|
+
return rankCompletionValues(dataSources.repos, argumentValue);
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
if (resourceUri === "repository://{name}/memories?search={search}&type={type}&tag={tag}") {
|
|
3201
|
+
if (argumentName === "tag") {
|
|
3202
|
+
return rankCompletionValues(dataSources.tags, argumentValue);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3205
|
+
throw invalidCompletionParams(`Unknown resource template or argument: ${resourceUri} (${argumentName})`);
|
|
3206
|
+
}
|
|
3207
|
+
function readResource(uri, db, session) {
|
|
3208
|
+
logger.info("[MCP] resource.read", { uri });
|
|
3209
|
+
if (uri === "repository://index") {
|
|
3210
|
+
const repos = db.system.listRepoNavigation();
|
|
3211
|
+
const payload = JSON.stringify(repos, null, 2);
|
|
3212
|
+
return {
|
|
3213
|
+
contents: [
|
|
3214
|
+
{
|
|
3215
|
+
uri,
|
|
3216
|
+
mimeType: "application/json",
|
|
3217
|
+
text: payload,
|
|
3218
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3219
|
+
annotations: {
|
|
3220
|
+
audience: ["assistant"],
|
|
3221
|
+
priority: 1,
|
|
3222
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
]
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
if (uri === "session://roots") {
|
|
3229
|
+
const payload = JSON.stringify({ roots: session?.roots ?? [] }, null, 2);
|
|
3230
|
+
return {
|
|
3231
|
+
contents: [
|
|
3232
|
+
{
|
|
3233
|
+
uri,
|
|
3234
|
+
mimeType: "application/json",
|
|
3235
|
+
text: payload,
|
|
3236
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3237
|
+
annotations: {
|
|
3238
|
+
audience: ["assistant"],
|
|
3239
|
+
priority: 0.95,
|
|
3240
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString()
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
]
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
const memoryIdMatch = uri.match(/^memory:\/\/([0-9a-f-]{36})$/i);
|
|
3247
|
+
if (memoryIdMatch) {
|
|
3248
|
+
const id = memoryIdMatch[1];
|
|
3249
|
+
const entry = db.memories.getByIdWithStats(id);
|
|
3250
|
+
if (!entry) throw resourceNotFound(`Memory with ID ${id} not found.`, uri);
|
|
3251
|
+
const payload = JSON.stringify(entry, null, 2);
|
|
3252
|
+
return {
|
|
3253
|
+
contents: [
|
|
3254
|
+
{
|
|
3255
|
+
uri,
|
|
3256
|
+
mimeType: "application/json",
|
|
3257
|
+
text: payload,
|
|
3258
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3259
|
+
annotations: {
|
|
3260
|
+
audience: ["assistant"],
|
|
3261
|
+
priority: 0.75,
|
|
3262
|
+
lastModified: entry.updated_at || entry.created_at
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
]
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
const taskIdMatch = uri.match(/^task:\/\/([0-9a-f-]{36})$/i);
|
|
3269
|
+
if (taskIdMatch) {
|
|
3270
|
+
const id = taskIdMatch[1];
|
|
3271
|
+
const task = db.tasks.getTaskById(id);
|
|
3272
|
+
if (!task) throw resourceNotFound(`Task with ID ${id} not found.`, uri);
|
|
3273
|
+
const payload = JSON.stringify(task, null, 2);
|
|
3274
|
+
return {
|
|
3275
|
+
contents: [
|
|
3276
|
+
{
|
|
3277
|
+
uri,
|
|
3278
|
+
mimeType: "application/json",
|
|
3279
|
+
text: payload,
|
|
3280
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3281
|
+
annotations: {
|
|
3282
|
+
audience: ["assistant"],
|
|
3283
|
+
priority: 0.8,
|
|
3284
|
+
lastModified: task.updated_at || task.created_at
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
]
|
|
3288
|
+
};
|
|
3289
|
+
}
|
|
3290
|
+
const repoBase = parseRepoUri(uri);
|
|
3291
|
+
if (repoBase) {
|
|
3292
|
+
const { name, path: repoPath, query } = repoBase;
|
|
3293
|
+
if (repoPath === "summary") {
|
|
3294
|
+
const summary = db.summaries.getSummary(name);
|
|
3295
|
+
const text = summary?.summary || `No summary available for repository: ${name}`;
|
|
3296
|
+
return {
|
|
3297
|
+
contents: [
|
|
3298
|
+
{
|
|
3299
|
+
uri,
|
|
3300
|
+
mimeType: "text/plain",
|
|
3301
|
+
text,
|
|
3302
|
+
size: Buffer.byteLength(text, "utf8"),
|
|
3303
|
+
annotations: {
|
|
3304
|
+
audience: ["assistant"],
|
|
3305
|
+
priority: 0.95,
|
|
3306
|
+
lastModified: summary?.updated_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
]
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
3312
|
+
if (repoPath === "memories") {
|
|
3313
|
+
const search = query.get("search") || "";
|
|
3314
|
+
const type = query.get("type");
|
|
3315
|
+
const tag = query.get("tag");
|
|
3316
|
+
const result = db.memories.listMemoriesForDashboard({
|
|
3317
|
+
repo: name,
|
|
3318
|
+
type: type || void 0,
|
|
3319
|
+
tag: tag || void 0,
|
|
3320
|
+
search: search || void 0,
|
|
3321
|
+
limit: 50
|
|
3322
|
+
});
|
|
3323
|
+
const entries = result.items;
|
|
3324
|
+
const payload = JSON.stringify(entries, null, 2);
|
|
3325
|
+
return {
|
|
3326
|
+
contents: [
|
|
3327
|
+
{
|
|
3328
|
+
uri,
|
|
3329
|
+
mimeType: "application/json",
|
|
3330
|
+
text: payload,
|
|
3331
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3332
|
+
annotations: {
|
|
3333
|
+
audience: ["assistant"],
|
|
3334
|
+
priority: 0.85,
|
|
3335
|
+
lastModified: deriveLastModifiedFromCollection(
|
|
3336
|
+
entries.map((e) => e.updated_at || e.created_at)
|
|
3337
|
+
)
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
]
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
if (repoPath === "tasks") {
|
|
3344
|
+
const status = query.get("status");
|
|
3345
|
+
const priority = query.get("priority");
|
|
3346
|
+
let tasks;
|
|
3347
|
+
if (status && status !== "all") {
|
|
3348
|
+
const statuses = status.split(",").map((s) => s.trim());
|
|
3349
|
+
tasks = db.tasks.getTasksByMultipleStatuses(name, statuses);
|
|
3350
|
+
} else {
|
|
3351
|
+
tasks = db.tasks.getTasksByMultipleStatuses(name, ["backlog", "pending", "in_progress", "blocked"]);
|
|
3352
|
+
}
|
|
3353
|
+
if (priority) {
|
|
3354
|
+
const p = Number(priority);
|
|
3355
|
+
if (!isNaN(p)) {
|
|
3356
|
+
tasks = tasks.filter((t) => t.priority === p);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
const payload = JSON.stringify(tasks, null, 2);
|
|
3360
|
+
return {
|
|
3361
|
+
contents: [
|
|
3362
|
+
{
|
|
3363
|
+
uri,
|
|
3364
|
+
mimeType: "application/json",
|
|
3365
|
+
text: payload,
|
|
3366
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3367
|
+
annotations: {
|
|
3368
|
+
audience: ["assistant"],
|
|
3369
|
+
priority: 0.9,
|
|
3370
|
+
lastModified: deriveLastModifiedFromCollection(tasks.map((t) => t.updated_at))
|
|
3371
|
+
}
|
|
3372
|
+
}
|
|
3373
|
+
]
|
|
3374
|
+
};
|
|
3375
|
+
}
|
|
3376
|
+
if (repoPath === "actions") {
|
|
3377
|
+
const actions = db.actions.getRecentActions(name, 100);
|
|
3378
|
+
const payload = JSON.stringify(actions, null, 2);
|
|
3379
|
+
return {
|
|
3380
|
+
contents: [
|
|
3381
|
+
{
|
|
3382
|
+
uri,
|
|
3383
|
+
mimeType: "application/json",
|
|
3384
|
+
text: payload,
|
|
3385
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3386
|
+
annotations: {
|
|
3387
|
+
audience: ["assistant"],
|
|
3388
|
+
priority: 0.6,
|
|
3389
|
+
lastModified: deriveLastModifiedFromCollection(actions.map((a) => a.created_at))
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
]
|
|
3393
|
+
};
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
const actionIdMatch = uri.match(/^action:\/\/(\d+)$/);
|
|
3397
|
+
if (actionIdMatch) {
|
|
3398
|
+
const id = Number(actionIdMatch[1]);
|
|
3399
|
+
const action = db.actions.getActionById(id);
|
|
3400
|
+
if (!action) throw resourceNotFound(`Action with ID ${id} not found.`, uri);
|
|
3401
|
+
const payload = JSON.stringify(action, null, 2);
|
|
3402
|
+
return {
|
|
3403
|
+
contents: [
|
|
3404
|
+
{
|
|
3405
|
+
uri,
|
|
3406
|
+
mimeType: "application/json",
|
|
3407
|
+
text: payload,
|
|
3408
|
+
size: Buffer.byteLength(payload, "utf8"),
|
|
3409
|
+
annotations: {
|
|
3410
|
+
audience: ["assistant"],
|
|
3411
|
+
priority: 0.55,
|
|
3412
|
+
lastModified: action.created_at
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
]
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
throw resourceNotFound(`Unknown resource URI: ${uri}`, uri);
|
|
3419
|
+
}
|
|
3420
|
+
function parseRepoUri(uri) {
|
|
3421
|
+
const prefix = "repository://";
|
|
3422
|
+
if (!uri.startsWith(prefix)) return null;
|
|
3423
|
+
const rest = uri.slice(prefix.length);
|
|
3424
|
+
const queryStart = rest.indexOf("?");
|
|
3425
|
+
const withoutQuery = queryStart === -1 ? rest : rest.slice(0, queryStart);
|
|
3426
|
+
const queryString = queryStart === -1 ? "" : rest.slice(queryStart + 1);
|
|
3427
|
+
const slashIdx = withoutQuery.indexOf("/");
|
|
3428
|
+
if (slashIdx === -1) return null;
|
|
3429
|
+
const name = withoutQuery.slice(0, slashIdx);
|
|
3430
|
+
const path5 = withoutQuery.slice(slashIdx + 1);
|
|
3431
|
+
if (!name || !path5) return null;
|
|
3432
|
+
return { name, path: path5, query: new URLSearchParams(queryString) };
|
|
3433
|
+
}
|
|
3434
|
+
function paginateEntries(key, entries, params) {
|
|
3435
|
+
const limit = normalizeLimit(params?.limit);
|
|
3436
|
+
const offset = decodeCursor(params?.cursor);
|
|
3437
|
+
const sliced = entries.slice(offset, offset + limit);
|
|
3438
|
+
const nextOffset = offset + sliced.length;
|
|
3439
|
+
return {
|
|
3440
|
+
[key]: sliced,
|
|
3441
|
+
nextCursor: nextOffset < entries.length ? encodeCursor(nextOffset) : void 0
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
function normalizeLimit(limit) {
|
|
3445
|
+
if (typeof limit !== "number" || !Number.isFinite(limit)) {
|
|
3446
|
+
return DEFAULT_PAGE_SIZE;
|
|
3447
|
+
}
|
|
3448
|
+
return Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)));
|
|
3449
|
+
}
|
|
3450
|
+
function deriveLastModifiedFromCollection(values) {
|
|
3451
|
+
const normalized = values.filter((value) => typeof value === "string" && value.length > 0);
|
|
3452
|
+
return normalized.sort().at(-1) ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3453
|
+
}
|
|
3454
|
+
function resourceNotFound(message, uri) {
|
|
3455
|
+
const error = new Error(message);
|
|
3456
|
+
error.code = -32002;
|
|
3457
|
+
error.data = { uri };
|
|
3458
|
+
return error;
|
|
3459
|
+
}
|
|
3460
|
+
function invalidCompletionParams(message) {
|
|
3461
|
+
const error = new Error(message);
|
|
3462
|
+
error.code = -32602;
|
|
3463
|
+
return error;
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
// src/mcp/prompts/loader.ts
|
|
3467
|
+
import fs3 from "fs";
|
|
3468
|
+
import path4 from "path";
|
|
3469
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
3470
|
+
import matter from "gray-matter";
|
|
3471
|
+
var __filename = fileURLToPath3(import.meta.url);
|
|
3472
|
+
var __dirname2 = path4.dirname(__filename);
|
|
3473
|
+
var PROMPT_DIR = path4.join(__dirname2, "definitions");
|
|
3474
|
+
function listPromptFiles() {
|
|
3475
|
+
if (!fs3.existsSync(PROMPT_DIR)) return [];
|
|
3476
|
+
return fs3.readdirSync(PROMPT_DIR).filter((file) => file.endsWith(".md")).map((file) => file.replace(/\.md$/, "")).sort();
|
|
3477
|
+
}
|
|
3478
|
+
function loadPromptFromMarkdown(name) {
|
|
3479
|
+
const filePath = path4.join(PROMPT_DIR, `${name}.md`);
|
|
3480
|
+
if (!fs3.existsSync(filePath)) {
|
|
3481
|
+
throw new Error(`Prompt file not found: ${filePath}`);
|
|
3482
|
+
}
|
|
3483
|
+
const fileContent = fs3.readFileSync(filePath, "utf-8");
|
|
3484
|
+
const { data, content } = matter(fileContent);
|
|
3485
|
+
return {
|
|
3486
|
+
name: data.name || name,
|
|
3487
|
+
description: data.description || "",
|
|
3488
|
+
arguments: data.arguments || [],
|
|
3489
|
+
agent: data.agent,
|
|
3490
|
+
content: content.trim()
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
// src/mcp/prompts/registry.ts
|
|
3495
|
+
function createPromptDefinition(loaded) {
|
|
3496
|
+
return {
|
|
3497
|
+
name: loaded.name,
|
|
3498
|
+
description: loaded.description,
|
|
3499
|
+
arguments: loaded.arguments,
|
|
3500
|
+
agent: loaded.agent,
|
|
3501
|
+
messages: [
|
|
3502
|
+
{
|
|
3503
|
+
role: "user",
|
|
3504
|
+
content: {
|
|
3505
|
+
type: "text",
|
|
3506
|
+
text: loaded.content
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
]
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
var PROMPTS = {};
|
|
3513
|
+
var promptFiles = listPromptFiles();
|
|
3514
|
+
for (const name of promptFiles) {
|
|
3515
|
+
try {
|
|
3516
|
+
PROMPTS[name] = createPromptDefinition(loadPromptFromMarkdown(name));
|
|
3517
|
+
} catch (e) {
|
|
3518
|
+
console.warn(`Failed to load prompt ${name}: ${e}`);
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
async function listPrompts(db, session, params) {
|
|
3522
|
+
const allPrompts = Object.values(PROMPTS).map((p) => ({
|
|
3523
|
+
name: p.name,
|
|
3524
|
+
description: p.description,
|
|
3525
|
+
arguments: p.arguments,
|
|
3526
|
+
metadata: p.agent ? { agent: p.agent } : void 0
|
|
3527
|
+
}));
|
|
3528
|
+
const rawLimit = typeof params?.limit === "number" && Number.isInteger(params?.limit) ? params.limit : 25;
|
|
3529
|
+
const limit = Math.max(1, Math.min(100, Math.trunc(rawLimit)));
|
|
3530
|
+
const offset = decodeCursor(params?.cursor);
|
|
3531
|
+
const sliced = allPrompts.slice(offset, offset + limit);
|
|
3532
|
+
const nextOffset = offset + sliced.length;
|
|
3533
|
+
return {
|
|
3534
|
+
prompts: sliced,
|
|
3535
|
+
nextCursor: nextOffset < allPrompts.length ? encodeCursor(nextOffset) : void 0
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
async function getPrompt(name, args = {}, db, session) {
|
|
3539
|
+
const prompt = PROMPTS[name];
|
|
3540
|
+
if (!prompt) {
|
|
3541
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
3542
|
+
}
|
|
3543
|
+
const inferredRepo = inferRepoFromSession(session);
|
|
3544
|
+
const messages = prompt.messages.map((m) => {
|
|
3545
|
+
let text = m.content.text;
|
|
3546
|
+
for (const [key, value] of Object.entries(args)) {
|
|
3547
|
+
text = text.replace(new RegExp(`\\{{${key}\\}}`, "g"), value);
|
|
3548
|
+
}
|
|
3549
|
+
text = text.replace(/{{current_repo}}/g, inferredRepo || "unknown-repo");
|
|
3550
|
+
return {
|
|
3551
|
+
...m,
|
|
3552
|
+
content: {
|
|
3553
|
+
...m.content,
|
|
3554
|
+
text
|
|
3555
|
+
}
|
|
3556
|
+
};
|
|
3557
|
+
});
|
|
3558
|
+
return {
|
|
3559
|
+
description: prompt.description,
|
|
3560
|
+
messages,
|
|
3561
|
+
metadata: prompt.agent ? { agent: prompt.agent } : void 0
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3564
|
+
async function completePromptArgument(name, argName, value, contextArguments, dataSources) {
|
|
3565
|
+
void name;
|
|
3566
|
+
void contextArguments;
|
|
3567
|
+
if (argName === "task_id") {
|
|
3568
|
+
const values = dataSources.tasks.map((t) => t.id);
|
|
3569
|
+
return rankCompletionValues(values, value);
|
|
3570
|
+
}
|
|
3571
|
+
return [];
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
export {
|
|
3575
|
+
MCP_PROTOCOL_VERSION,
|
|
3576
|
+
CAPABILITIES,
|
|
3577
|
+
logger,
|
|
3578
|
+
setLogLevel,
|
|
3579
|
+
getLogLevel,
|
|
3580
|
+
addLogSink,
|
|
3581
|
+
LOG_LEVEL_VALUES,
|
|
3582
|
+
normalizeRepo,
|
|
3583
|
+
SQLiteStore,
|
|
3584
|
+
MemoryStoreSchema,
|
|
3585
|
+
MemoryUpdateSchema,
|
|
3586
|
+
MemorySearchSchema,
|
|
3587
|
+
MemoryAcknowledgeSchema,
|
|
3588
|
+
MemoryRecapSchema,
|
|
3589
|
+
MemoryDeleteSchema,
|
|
3590
|
+
MemorySummarizeSchema,
|
|
3591
|
+
MemorySynthesizeSchema,
|
|
3592
|
+
TaskCreateSchema,
|
|
3593
|
+
TaskCreateInteractiveSchema,
|
|
3594
|
+
TaskUpdateSchema,
|
|
3595
|
+
TaskListSchema,
|
|
3596
|
+
TaskDeleteSchema,
|
|
3597
|
+
MemoryDetailSchema,
|
|
3598
|
+
TaskGetSchema,
|
|
3599
|
+
TOOL_DEFINITIONS,
|
|
3600
|
+
encodeCursor,
|
|
3601
|
+
decodeCursor,
|
|
3602
|
+
listResources,
|
|
3603
|
+
listResourceTemplates,
|
|
3604
|
+
completeResourceArgument,
|
|
3605
|
+
readResource,
|
|
3606
|
+
createSessionContext,
|
|
3607
|
+
updateSessionFromInitialize,
|
|
3608
|
+
updateSessionRoots,
|
|
3609
|
+
extractRootsFromResult,
|
|
3610
|
+
getFilesystemRoots,
|
|
3611
|
+
isPathWithinRoots,
|
|
3612
|
+
findContainingRoot,
|
|
3613
|
+
inferRepoFromSession,
|
|
3614
|
+
PROMPTS,
|
|
3615
|
+
listPrompts,
|
|
3616
|
+
getPrompt,
|
|
3617
|
+
completePromptArgument
|
|
3618
|
+
};
|