@stackmemoryai/stackmemory 0.2.4 → 0.2.7
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 +353 -83
- package/dist/index.js +382 -0
- package/dist/scripts/cancel-duplicate-tasks.d.ts +7 -0
- package/dist/scripts/cancel-duplicate-tasks.d.ts.map +1 -0
- package/dist/scripts/cancel-duplicate-tasks.js +171 -0
- package/dist/scripts/cancel-duplicate-tasks.js.map +1 -0
- package/dist/scripts/list-linear-tasks.d.ts +6 -0
- package/dist/scripts/list-linear-tasks.d.ts.map +1 -0
- package/dist/scripts/list-linear-tasks.js +122 -0
- package/dist/scripts/list-linear-tasks.js.map +1 -0
- package/dist/scripts/merge-linear-duplicates-safe.d.ts +7 -0
- package/dist/scripts/merge-linear-duplicates-safe.d.ts.map +1 -0
- package/dist/scripts/merge-linear-duplicates-safe.js +265 -0
- package/dist/scripts/merge-linear-duplicates-safe.js.map +1 -0
- package/dist/scripts/merge-linear-duplicates.d.ts +7 -0
- package/dist/scripts/merge-linear-duplicates.d.ts.map +1 -0
- package/dist/scripts/merge-linear-duplicates.js +126 -0
- package/dist/scripts/merge-linear-duplicates.js.map +1 -0
- package/dist/scripts/show-linear-summary.d.ts +6 -0
- package/dist/scripts/show-linear-summary.d.ts.map +1 -0
- package/dist/scripts/show-linear-summary.js +117 -0
- package/dist/scripts/show-linear-summary.js.map +1 -0
- package/dist/src/analytics/api/analytics-api.d.ts +24 -0
- package/dist/src/analytics/api/analytics-api.d.ts.map +1 -0
- package/dist/src/analytics/api/analytics-api.js +279 -0
- package/dist/src/analytics/api/analytics-api.js.map +1 -0
- package/dist/src/analytics/core/analytics-service.d.ts +23 -0
- package/dist/src/analytics/core/analytics-service.d.ts.map +1 -0
- package/dist/src/analytics/core/analytics-service.js +160 -0
- package/dist/src/analytics/core/analytics-service.js.map +1 -0
- package/dist/src/analytics/index.d.ts +12 -0
- package/dist/src/analytics/index.d.ts.map +1 -0
- package/dist/src/analytics/index.js +11 -0
- package/dist/src/analytics/index.js.map +1 -0
- package/dist/src/analytics/queries/metrics-queries.d.ts +11 -0
- package/dist/src/analytics/queries/metrics-queries.d.ts.map +1 -0
- package/dist/src/analytics/queries/metrics-queries.js +179 -0
- package/dist/src/analytics/queries/metrics-queries.js.map +1 -0
- package/dist/src/analytics/types/metrics.d.ts +60 -0
- package/dist/src/analytics/types/metrics.d.ts.map +1 -0
- package/dist/src/analytics/types/metrics.js +2 -0
- package/dist/src/analytics/types/metrics.js.map +1 -0
- package/dist/src/cli/__tests__/index.test.d.ts +5 -0
- package/dist/src/cli/__tests__/index.test.d.ts.map +1 -0
- package/dist/src/cli/__tests__/index.test.js +726 -0
- package/dist/src/cli/__tests__/index.test.js.map +1 -0
- package/dist/src/cli/analytics-viewer.d.ts +3 -0
- package/dist/src/cli/analytics-viewer.d.ts.map +1 -0
- package/dist/src/cli/analytics-viewer.js +89 -0
- package/dist/src/cli/analytics-viewer.js.map +1 -0
- package/dist/src/cli/auto-detect.d.ts +61 -0
- package/dist/src/cli/auto-detect.d.ts.map +1 -0
- package/dist/src/cli/auto-detect.js +350 -0
- package/dist/src/cli/auto-detect.js.map +1 -0
- package/dist/src/cli/browser-test.d.ts +6 -0
- package/dist/src/cli/browser-test.d.ts.map +1 -0
- package/dist/src/cli/browser-test.js +32 -0
- package/dist/src/cli/browser-test.js.map +1 -0
- package/dist/src/cli/claude-sm.d.ts +7 -0
- package/dist/src/cli/claude-sm.d.ts.map +1 -0
- package/dist/src/cli/claude-sm.js +357 -0
- package/dist/src/cli/claude-sm.js.map +1 -0
- package/dist/src/cli/cli.js +157 -0
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/cli/commands/context.d.ts +7 -0
- package/dist/src/cli/commands/context.d.ts.map +1 -0
- package/dist/src/cli/commands/context.js +365 -0
- package/dist/src/cli/commands/context.js.map +1 -0
- package/dist/src/cli/commands/linear-test.d.ts +6 -0
- package/dist/src/cli/commands/linear-test.d.ts.map +1 -0
- package/dist/src/cli/commands/linear-test.js +123 -0
- package/dist/src/cli/commands/linear-test.js.map +1 -0
- package/dist/src/cli/commands/linear.d.ts +6 -0
- package/dist/src/cli/commands/linear.d.ts.map +1 -0
- package/dist/src/cli/commands/linear.js +317 -0
- package/dist/src/cli/commands/linear.js.map +1 -0
- package/dist/src/cli/commands/log.d.ts +7 -0
- package/dist/src/cli/commands/log.d.ts.map +1 -0
- package/dist/src/cli/commands/log.js +168 -0
- package/dist/src/cli/commands/log.js.map +1 -0
- package/dist/src/cli/commands/onboard.d.ts +8 -0
- package/dist/src/cli/commands/onboard.d.ts.map +1 -0
- package/dist/src/cli/commands/onboard.js +363 -0
- package/dist/src/cli/commands/onboard.js.map +1 -0
- package/dist/src/cli/commands/projects.d.ts +8 -0
- package/dist/src/cli/commands/projects.d.ts.map +1 -0
- package/dist/src/cli/commands/projects.js +220 -0
- package/dist/src/cli/commands/projects.js.map +1 -0
- package/dist/src/cli/commands/search.d.ts +7 -0
- package/dist/src/cli/commands/search.d.ts.map +1 -0
- package/dist/src/cli/commands/search.js +162 -0
- package/dist/src/cli/commands/search.js.map +1 -0
- package/dist/src/cli/commands/session.d.ts +7 -0
- package/dist/src/cli/commands/session.d.ts.map +1 -0
- package/dist/src/cli/commands/session.js +222 -0
- package/dist/src/cli/commands/session.js.map +1 -0
- package/dist/src/cli/commands/tasks.d.ts +7 -0
- package/dist/src/cli/commands/tasks.d.ts.map +1 -0
- package/dist/src/cli/commands/tasks.js +229 -0
- package/dist/src/cli/commands/tasks.js.map +1 -0
- package/dist/src/cli/commands/webhook.d.ts +3 -0
- package/dist/src/cli/commands/webhook.d.ts.map +1 -0
- package/dist/src/cli/commands/webhook.js +157 -0
- package/dist/src/cli/commands/webhook.js.map +1 -0
- package/dist/src/cli/commands/worktree.d.ts +8 -0
- package/dist/src/cli/commands/worktree.d.ts.map +1 -0
- package/dist/src/cli/commands/worktree.js +339 -0
- package/dist/src/cli/commands/worktree.js.map +1 -0
- package/dist/src/cli/index.d.ts +8 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/cli/index.js +944 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/project-commands.d.ts +8 -0
- package/dist/src/cli/project-commands.d.ts.map +1 -0
- package/dist/src/cli/project-commands.js +212 -0
- package/dist/src/cli/project-commands.js.map +1 -0
- package/dist/src/cli/utils/viewer.d.ts +3 -0
- package/dist/src/cli/utils/viewer.d.ts.map +1 -0
- package/dist/src/cli/utils/viewer.js +91 -0
- package/dist/src/cli/utils/viewer.js.map +1 -0
- package/dist/src/core/context/__tests__/frame-manager.test.d.ts +5 -0
- package/dist/src/core/context/__tests__/frame-manager.test.d.ts.map +1 -0
- package/dist/src/core/context/__tests__/frame-manager.test.js +892 -0
- package/dist/src/core/context/__tests__/frame-manager.test.js.map +1 -0
- package/dist/src/core/context/auto-context.d.ts +22 -0
- package/dist/src/core/context/auto-context.d.ts.map +1 -0
- package/dist/src/core/context/auto-context.js +77 -0
- package/dist/src/core/context/auto-context.js.map +1 -0
- package/dist/src/core/context/frame-manager.d.ts +110 -0
- package/dist/src/core/context/frame-manager.d.ts.map +1 -0
- package/dist/src/core/context/frame-manager.js +593 -0
- package/dist/src/core/context/frame-manager.js.map +1 -0
- package/dist/src/core/errors/__tests__/error-handling.test.d.ts +5 -0
- package/dist/src/core/errors/__tests__/error-handling.test.d.ts.map +1 -0
- package/dist/src/core/errors/__tests__/error-handling.test.js +239 -0
- package/dist/src/core/errors/__tests__/error-handling.test.js.map +1 -0
- package/dist/src/core/errors/index.d.ts +135 -0
- package/dist/src/core/errors/index.d.ts.map +1 -0
- package/dist/src/core/errors/index.js +274 -0
- package/dist/src/core/errors/index.js.map +1 -0
- package/dist/src/core/errors/recovery.d.ts +86 -0
- package/dist/src/core/errors/recovery.d.ts.map +1 -0
- package/dist/src/core/errors/recovery.js +274 -0
- package/dist/src/core/errors/recovery.js.map +1 -0
- package/dist/src/core/logger.test.js +1 -1
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/monitoring/error-handler.d.ts +46 -0
- package/dist/src/core/monitoring/error-handler.d.ts.map +1 -0
- package/dist/src/core/monitoring/error-handler.js +212 -0
- package/dist/src/core/monitoring/error-handler.js.map +1 -0
- package/dist/src/core/monitoring/logger.d.ts +24 -0
- package/dist/src/core/monitoring/logger.d.ts.map +1 -0
- package/dist/src/core/monitoring/logger.js +121 -0
- package/dist/src/core/monitoring/logger.js.map +1 -0
- package/dist/src/core/monitoring/metrics.d.ts +7 -0
- package/dist/src/core/monitoring/metrics.d.ts.map +1 -0
- package/dist/src/core/monitoring/metrics.js +13 -0
- package/dist/src/core/monitoring/metrics.js.map +1 -0
- package/dist/src/core/monitoring/progress-tracker.d.ts +95 -0
- package/dist/src/core/monitoring/progress-tracker.d.ts.map +1 -0
- package/dist/src/core/monitoring/progress-tracker.js +178 -0
- package/dist/src/core/monitoring/progress-tracker.js.map +1 -0
- package/dist/src/core/project-manager.d.ts +130 -0
- package/dist/src/core/project-manager.d.ts.map +1 -0
- package/dist/src/core/project-manager.js +582 -0
- package/dist/src/core/project-manager.js.map +1 -0
- package/dist/src/core/projects/project-manager.d.ts +130 -0
- package/dist/src/core/projects/project-manager.d.ts.map +1 -0
- package/dist/src/core/projects/project-manager.js +709 -0
- package/dist/src/core/projects/project-manager.js.map +1 -0
- package/dist/src/core/session/index.d.ts +2 -0
- package/dist/src/core/session/index.d.ts.map +1 -0
- package/dist/src/core/session/index.js +2 -0
- package/dist/src/core/session/index.js.map +1 -0
- package/dist/src/core/session/session-manager.d.ts +69 -0
- package/dist/src/core/session/session-manager.d.ts.map +1 -0
- package/dist/src/core/session/session-manager.js +311 -0
- package/dist/src/core/session/session-manager.js.map +1 -0
- package/dist/src/core/utils/update-checker.d.ts +38 -0
- package/dist/src/core/utils/update-checker.d.ts.map +1 -0
- package/dist/src/core/utils/update-checker.js +213 -0
- package/dist/src/core/utils/update-checker.js.map +1 -0
- package/dist/src/core/worktree/worktree-manager.d.ts +110 -0
- package/dist/src/core/worktree/worktree-manager.d.ts.map +1 -0
- package/dist/src/core/worktree/worktree-manager.js +456 -0
- package/dist/src/core/worktree/worktree-manager.js.map +1 -0
- package/dist/src/features/analytics/api/analytics-api.d.ts +24 -0
- package/dist/src/features/analytics/api/analytics-api.d.ts.map +1 -0
- package/dist/src/features/analytics/api/analytics-api.js +289 -0
- package/dist/src/features/analytics/api/analytics-api.js.map +1 -0
- package/dist/src/features/analytics/core/analytics-service.d.ts +29 -0
- package/dist/src/features/analytics/core/analytics-service.d.ts.map +1 -0
- package/dist/src/features/analytics/core/analytics-service.js +275 -0
- package/dist/src/features/analytics/core/analytics-service.js.map +1 -0
- package/dist/src/features/analytics/index.d.ts +12 -0
- package/dist/src/features/analytics/index.d.ts.map +1 -0
- package/dist/src/features/analytics/index.js +11 -0
- package/dist/src/features/analytics/index.js.map +1 -0
- package/dist/src/features/analytics/queries/metrics-queries.d.ts +11 -0
- package/dist/src/features/analytics/queries/metrics-queries.d.ts.map +1 -0
- package/dist/src/features/analytics/queries/metrics-queries.js +240 -0
- package/dist/src/features/analytics/queries/metrics-queries.js.map +1 -0
- package/dist/src/features/analytics/types/metrics.d.ts +60 -0
- package/dist/src/features/analytics/types/metrics.d.ts.map +1 -0
- package/dist/src/features/analytics/types/metrics.js +2 -0
- package/dist/src/features/analytics/types/metrics.js.map +1 -0
- package/dist/src/features/browser/browser-mcp.d.ts +94 -0
- package/dist/src/features/browser/browser-mcp.d.ts.map +1 -0
- package/dist/src/features/browser/browser-mcp.js +459 -0
- package/dist/src/features/browser/browser-mcp.js.map +1 -0
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts +5 -0
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts.map +1 -0
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js +712 -0
- package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js.map +1 -0
- package/dist/src/features/tasks/pebbles-task-store.d.ts +121 -0
- package/dist/src/features/tasks/pebbles-task-store.d.ts.map +1 -0
- package/dist/src/features/tasks/pebbles-task-store.js +493 -0
- package/dist/src/features/tasks/pebbles-task-store.js.map +1 -0
- package/dist/src/features/tasks/task-aware-context.d.ts +103 -0
- package/dist/src/features/tasks/task-aware-context.d.ts.map +1 -0
- package/dist/src/features/tasks/task-aware-context.js +412 -0
- package/dist/src/features/tasks/task-aware-context.js.map +1 -0
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/integrations/browser-mcp.d.ts +94 -0
- package/dist/src/integrations/browser-mcp.d.ts.map +1 -0
- package/dist/src/integrations/browser-mcp.js +431 -0
- package/dist/src/integrations/browser-mcp.js.map +1 -0
- package/dist/src/integrations/linear/__tests__/auth.test.d.ts +5 -0
- package/dist/src/integrations/linear/__tests__/auth.test.d.ts.map +1 -0
- package/dist/src/integrations/linear/__tests__/auth.test.js +517 -0
- package/dist/src/integrations/linear/__tests__/auth.test.js.map +1 -0
- package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts +5 -0
- package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts.map +1 -0
- package/dist/src/integrations/linear/__tests__/sync-service.test.js +700 -0
- package/dist/src/integrations/linear/__tests__/sync-service.test.js.map +1 -0
- package/dist/src/integrations/linear/auth.d.ts +99 -0
- package/dist/src/integrations/linear/auth.d.ts.map +1 -0
- package/dist/src/integrations/linear/auth.js +319 -0
- package/dist/src/integrations/linear/auth.js.map +1 -0
- package/dist/src/integrations/linear/auto-sync.d.ts +77 -0
- package/dist/src/integrations/linear/auto-sync.d.ts.map +1 -0
- package/dist/src/integrations/linear/auto-sync.js +268 -0
- package/dist/src/integrations/linear/auto-sync.js.map +1 -0
- package/dist/src/integrations/linear/client.d.ts +113 -0
- package/dist/src/integrations/linear/client.d.ts.map +1 -0
- package/dist/src/integrations/linear/client.js +364 -0
- package/dist/src/integrations/linear/client.js.map +1 -0
- package/dist/src/integrations/linear/config.d.ts +51 -0
- package/dist/src/integrations/linear/config.d.ts.map +1 -0
- package/dist/src/integrations/linear/config.js +103 -0
- package/dist/src/integrations/linear/config.js.map +1 -0
- package/dist/src/integrations/linear/sync-service.d.ts +25 -0
- package/dist/src/integrations/linear/sync-service.d.ts.map +1 -0
- package/dist/src/integrations/linear/sync-service.js +198 -0
- package/dist/src/integrations/linear/sync-service.js.map +1 -0
- package/dist/src/integrations/linear/sync.d.ts +119 -0
- package/dist/src/integrations/linear/sync.d.ts.map +1 -0
- package/dist/src/integrations/linear/sync.js +538 -0
- package/dist/src/integrations/linear/sync.js.map +1 -0
- package/dist/src/integrations/linear/types.d.ts +75 -0
- package/dist/src/integrations/linear/types.d.ts.map +1 -0
- package/dist/src/integrations/linear/types.js +2 -0
- package/dist/src/integrations/linear/types.js.map +1 -0
- package/dist/src/integrations/linear/webhook-server.d.ts +32 -0
- package/dist/src/integrations/linear/webhook-server.d.ts.map +1 -0
- package/dist/src/integrations/linear/webhook-server.js +188 -0
- package/dist/src/integrations/linear/webhook-server.js.map +1 -0
- package/dist/src/integrations/linear/webhook.d.ts +95 -0
- package/dist/src/integrations/linear/webhook.d.ts.map +1 -0
- package/dist/src/integrations/linear/webhook.js +204 -0
- package/dist/src/integrations/linear/webhook.js.map +1 -0
- package/dist/src/integrations/mcp/__tests__/server.test.d.ts +5 -0
- package/dist/src/integrations/mcp/__tests__/server.test.d.ts.map +1 -0
- package/dist/src/integrations/mcp/__tests__/server.test.js +790 -0
- package/dist/src/integrations/mcp/__tests__/server.test.js.map +1 -0
- package/dist/src/integrations/mcp/server.d.ts +46 -0
- package/dist/src/integrations/mcp/server.d.ts.map +1 -0
- package/dist/src/integrations/mcp/server.js +1264 -0
- package/dist/src/integrations/mcp/server.js.map +1 -0
- package/dist/src/mcp/mcp-server.d.ts +1 -0
- package/dist/src/mcp/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp/mcp-server.js +11 -0
- package/dist/src/mcp/mcp-server.js.map +1 -1
- package/dist/src/railway/index.d.ts +7 -0
- package/dist/src/railway/index.d.ts.map +1 -0
- package/dist/src/railway/index.js +401 -0
- package/dist/src/railway/index.js.map +1 -0
- package/dist/src/runway/auth/auth-middleware.d.ts +66 -0
- package/dist/src/runway/auth/auth-middleware.d.ts.map +1 -0
- package/dist/src/runway/auth/auth-middleware.js +337 -0
- package/dist/src/runway/auth/auth-middleware.js.map +1 -0
- package/dist/src/runway/server/runway-mcp-server.d.ts +46 -0
- package/dist/src/runway/server/runway-mcp-server.d.ts.map +1 -0
- package/dist/src/runway/server/runway-mcp-server.js +601 -0
- package/dist/src/runway/server/runway-mcp-server.js.map +1 -0
- package/dist/src/runway.bak/auth/auth-middleware.d.ts +66 -0
- package/dist/src/runway.bak/auth/auth-middleware.d.ts.map +1 -0
- package/dist/src/runway.bak/auth/auth-middleware.js +337 -0
- package/dist/src/runway.bak/auth/auth-middleware.js.map +1 -0
- package/dist/src/runway.bak/server/runway-mcp-server.d.ts +46 -0
- package/dist/src/runway.bak/server/runway-mcp-server.d.ts.map +1 -0
- package/dist/src/runway.bak/server/runway-mcp-server.js +601 -0
- package/dist/src/runway.bak/server/runway-mcp-server.js.map +1 -0
- package/dist/src/servers/production/auth-middleware.d.ts +66 -0
- package/dist/src/servers/production/auth-middleware.d.ts.map +1 -0
- package/dist/src/servers/production/auth-middleware.js +346 -0
- package/dist/src/servers/production/auth-middleware.js.map +1 -0
- package/dist/src/servers/railway/index.d.ts +7 -0
- package/dist/src/servers/railway/index.d.ts.map +1 -0
- package/dist/src/servers/railway/index.js +401 -0
- package/dist/src/servers/railway/index.js.map +1 -0
- package/dist/src/services/config-service.d.ts +44 -0
- package/dist/src/services/config-service.d.ts.map +1 -0
- package/dist/src/services/config-service.js +61 -0
- package/dist/src/services/config-service.js.map +1 -0
- package/dist/src/services/context-service.d.ts +17 -0
- package/dist/src/services/context-service.d.ts.map +1 -0
- package/dist/src/services/context-service.js +88 -0
- package/dist/src/services/context-service.js.map +1 -0
- package/dist/src/types/task.d.ts +17 -0
- package/dist/src/types/task.d.ts.map +1 -0
- package/dist/src/types/task.js +2 -0
- package/dist/src/types/task.js.map +1 -0
- package/dist/src/utils/logger.d.ts +13 -0
- package/dist/src/utils/logger.d.ts.map +1 -0
- package/dist/src/utils/logger.js +52 -0
- package/dist/src/utils/logger.js.map +1 -0
- package/package.json +40 -5
|
@@ -0,0 +1,1264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* StackMemory MCP Server - Local Instance
|
|
4
|
+
* This runs locally and provides context to Claude Code
|
|
5
|
+
*/
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import Database from 'better-sqlite3';
|
|
10
|
+
import { readFileSync, existsSync, mkdirSync } from 'fs';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import { FrameManager } from '../../core/context/frame-manager.js';
|
|
14
|
+
import { PebblesTaskStore, } from '../../features/tasks/pebbles-task-store.js';
|
|
15
|
+
import { LinearAuthManager } from '../linear/auth.js';
|
|
16
|
+
import { LinearSyncEngine, DEFAULT_SYNC_CONFIG } from '../linear/sync.js';
|
|
17
|
+
import { logger } from '../../core/monitoring/logger.js';
|
|
18
|
+
import { BrowserMCPIntegration } from '../../features/browser/browser-mcp.js';
|
|
19
|
+
// ============================================
|
|
20
|
+
// Simple Local MCP Server
|
|
21
|
+
// ============================================
|
|
22
|
+
class LocalStackMemoryMCP {
|
|
23
|
+
server;
|
|
24
|
+
db;
|
|
25
|
+
projectRoot;
|
|
26
|
+
frameManager;
|
|
27
|
+
taskStore;
|
|
28
|
+
linearAuthManager;
|
|
29
|
+
linearSync;
|
|
30
|
+
projectId;
|
|
31
|
+
contexts = new Map();
|
|
32
|
+
browserMCP;
|
|
33
|
+
constructor() {
|
|
34
|
+
// Find project root (where .git is)
|
|
35
|
+
this.projectRoot = this.findProjectRoot();
|
|
36
|
+
this.projectId = this.getProjectId();
|
|
37
|
+
// Ensure .stackmemory directory exists
|
|
38
|
+
const dbDir = join(this.projectRoot, '.stackmemory');
|
|
39
|
+
if (!existsSync(dbDir)) {
|
|
40
|
+
mkdirSync(dbDir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
// Initialize database
|
|
43
|
+
const dbPath = join(dbDir, 'context.db');
|
|
44
|
+
this.db = new Database(dbPath);
|
|
45
|
+
this.initDB();
|
|
46
|
+
// Initialize frame manager
|
|
47
|
+
this.frameManager = new FrameManager(this.db, this.projectId);
|
|
48
|
+
// Initialize task store
|
|
49
|
+
this.taskStore = new PebblesTaskStore(this.projectRoot, this.db);
|
|
50
|
+
// Initialize Linear integration
|
|
51
|
+
this.linearAuthManager = new LinearAuthManager(this.projectRoot);
|
|
52
|
+
this.linearSync = new LinearSyncEngine(this.taskStore, this.linearAuthManager, DEFAULT_SYNC_CONFIG);
|
|
53
|
+
// Initialize MCP server
|
|
54
|
+
this.server = new Server({
|
|
55
|
+
name: 'stackmemory-local',
|
|
56
|
+
version: '0.1.0',
|
|
57
|
+
}, {
|
|
58
|
+
capabilities: {
|
|
59
|
+
tools: {},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
// Initialize Browser MCP integration
|
|
63
|
+
this.browserMCP = new BrowserMCPIntegration({
|
|
64
|
+
headless: process.env.BROWSER_HEADLESS !== 'false',
|
|
65
|
+
defaultViewport: { width: 1280, height: 720 },
|
|
66
|
+
});
|
|
67
|
+
this.setupHandlers();
|
|
68
|
+
this.loadInitialContext();
|
|
69
|
+
// Initialize Browser MCP with this server
|
|
70
|
+
this.browserMCP.initialize(this.server).catch((error) => {
|
|
71
|
+
logger.error('Failed to initialize Browser MCP', error);
|
|
72
|
+
});
|
|
73
|
+
logger.info('StackMemory MCP Server initialized', {
|
|
74
|
+
projectRoot: this.projectRoot,
|
|
75
|
+
projectId: this.projectId,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
findProjectRoot() {
|
|
79
|
+
let dir = process.cwd();
|
|
80
|
+
while (dir !== '/') {
|
|
81
|
+
if (existsSync(join(dir, '.git'))) {
|
|
82
|
+
return dir;
|
|
83
|
+
}
|
|
84
|
+
dir = dirname(dir);
|
|
85
|
+
}
|
|
86
|
+
return process.cwd();
|
|
87
|
+
}
|
|
88
|
+
initDB() {
|
|
89
|
+
this.db.exec(`
|
|
90
|
+
CREATE TABLE IF NOT EXISTS contexts (
|
|
91
|
+
id TEXT PRIMARY KEY,
|
|
92
|
+
type TEXT NOT NULL,
|
|
93
|
+
content TEXT NOT NULL,
|
|
94
|
+
importance REAL DEFAULT 0.5,
|
|
95
|
+
created_at INTEGER DEFAULT (unixepoch()),
|
|
96
|
+
last_accessed INTEGER DEFAULT (unixepoch()),
|
|
97
|
+
access_count INTEGER DEFAULT 1
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
CREATE TABLE IF NOT EXISTS frames (
|
|
101
|
+
frame_id TEXT PRIMARY KEY,
|
|
102
|
+
task TEXT NOT NULL,
|
|
103
|
+
status TEXT DEFAULT 'active',
|
|
104
|
+
created_at INTEGER DEFAULT (unixepoch())
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS attention_log (
|
|
108
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
109
|
+
context_id TEXT,
|
|
110
|
+
query TEXT,
|
|
111
|
+
response TEXT,
|
|
112
|
+
influence_score REAL,
|
|
113
|
+
timestamp INTEGER DEFAULT (unixepoch())
|
|
114
|
+
);
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
loadInitialContext() {
|
|
118
|
+
// Load project information
|
|
119
|
+
const projectInfo = this.getProjectInfo();
|
|
120
|
+
this.addContext('project', `Project: ${projectInfo.name}\nPath: ${projectInfo.path}`, 0.9);
|
|
121
|
+
// Load recent git commits
|
|
122
|
+
try {
|
|
123
|
+
const recentCommits = execSync('git log --oneline -10', {
|
|
124
|
+
cwd: this.projectRoot,
|
|
125
|
+
}).toString();
|
|
126
|
+
this.addContext('git_history', `Recent commits:\n${recentCommits}`, 0.6);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Not a git repo or git not available
|
|
130
|
+
}
|
|
131
|
+
// Load README if exists
|
|
132
|
+
const readmePath = join(this.projectRoot, 'README.md');
|
|
133
|
+
if (existsSync(readmePath)) {
|
|
134
|
+
const readme = readFileSync(readmePath, 'utf-8');
|
|
135
|
+
const summary = readme.substring(0, 500);
|
|
136
|
+
this.addContext('readme', `Project README:\n${summary}...`, 0.8);
|
|
137
|
+
}
|
|
138
|
+
// Load any existing decisions from previous sessions
|
|
139
|
+
this.loadStoredContexts();
|
|
140
|
+
}
|
|
141
|
+
getProjectId() {
|
|
142
|
+
// Use git remote or directory name as project ID
|
|
143
|
+
try {
|
|
144
|
+
const remoteUrl = execSync('git config --get remote.origin.url', {
|
|
145
|
+
cwd: this.projectRoot,
|
|
146
|
+
stdio: 'pipe',
|
|
147
|
+
})
|
|
148
|
+
.toString()
|
|
149
|
+
.trim();
|
|
150
|
+
return remoteUrl || this.projectRoot.split('/').pop() || 'unknown';
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return this.projectRoot.split('/').pop() || 'unknown';
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
getProjectInfo() {
|
|
157
|
+
const packageJsonPath = join(this.projectRoot, 'package.json');
|
|
158
|
+
if (existsSync(packageJsonPath)) {
|
|
159
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
160
|
+
return {
|
|
161
|
+
name: pkg.name || 'unknown',
|
|
162
|
+
path: this.projectRoot,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
name: this.projectRoot.split('/').pop() || 'unknown',
|
|
167
|
+
path: this.projectRoot,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
addContext(type, content, importance = 0.5) {
|
|
171
|
+
const id = `${type}_${Date.now()}`;
|
|
172
|
+
this.db
|
|
173
|
+
.prepare(`
|
|
174
|
+
INSERT OR REPLACE INTO contexts (id, type, content, importance)
|
|
175
|
+
VALUES (?, ?, ?, ?)
|
|
176
|
+
`)
|
|
177
|
+
.run(id, type, content, importance);
|
|
178
|
+
this.contexts.set(id, { type, content, importance });
|
|
179
|
+
return id;
|
|
180
|
+
}
|
|
181
|
+
loadStoredContexts() {
|
|
182
|
+
const stored = this.db
|
|
183
|
+
.prepare(`
|
|
184
|
+
SELECT * FROM contexts
|
|
185
|
+
ORDER BY importance DESC, last_accessed DESC
|
|
186
|
+
LIMIT 50
|
|
187
|
+
`)
|
|
188
|
+
.all();
|
|
189
|
+
stored.forEach((ctx) => {
|
|
190
|
+
this.contexts.set(ctx.id, ctx);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
setupHandlers() {
|
|
194
|
+
// Tool listing
|
|
195
|
+
this.server.setRequestHandler(z.object({
|
|
196
|
+
method: z.literal('tools/list'),
|
|
197
|
+
}), async () => {
|
|
198
|
+
return {
|
|
199
|
+
tools: [
|
|
200
|
+
{
|
|
201
|
+
name: 'get_context',
|
|
202
|
+
description: 'Get current project context',
|
|
203
|
+
inputSchema: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
query: {
|
|
207
|
+
type: 'string',
|
|
208
|
+
description: 'What you want to know',
|
|
209
|
+
},
|
|
210
|
+
limit: {
|
|
211
|
+
type: 'number',
|
|
212
|
+
description: 'Max contexts to return',
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'add_decision',
|
|
219
|
+
description: 'Record a decision or important information',
|
|
220
|
+
inputSchema: {
|
|
221
|
+
type: 'object',
|
|
222
|
+
properties: {
|
|
223
|
+
content: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
description: 'The decision or information',
|
|
226
|
+
},
|
|
227
|
+
type: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
enum: ['decision', 'constraint', 'learning'],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
required: ['content', 'type'],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'start_frame',
|
|
237
|
+
description: 'Start a new frame (task/subtask) on the call stack',
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
properties: {
|
|
241
|
+
name: { type: 'string', description: 'Frame name/goal' },
|
|
242
|
+
type: {
|
|
243
|
+
type: 'string',
|
|
244
|
+
enum: [
|
|
245
|
+
'task',
|
|
246
|
+
'subtask',
|
|
247
|
+
'tool_scope',
|
|
248
|
+
'review',
|
|
249
|
+
'write',
|
|
250
|
+
'debug',
|
|
251
|
+
],
|
|
252
|
+
description: 'Frame type',
|
|
253
|
+
},
|
|
254
|
+
constraints: {
|
|
255
|
+
type: 'array',
|
|
256
|
+
items: { type: 'string' },
|
|
257
|
+
description: 'Constraints for this frame',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
required: ['name', 'type'],
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'close_frame',
|
|
265
|
+
description: 'Close current frame and generate digest',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
result: {
|
|
270
|
+
type: 'string',
|
|
271
|
+
description: 'Frame completion result',
|
|
272
|
+
},
|
|
273
|
+
outputs: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
description: 'Final outputs from frame',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'add_anchor',
|
|
282
|
+
description: 'Add anchored fact/decision/constraint to current frame',
|
|
283
|
+
inputSchema: {
|
|
284
|
+
type: 'object',
|
|
285
|
+
properties: {
|
|
286
|
+
type: {
|
|
287
|
+
type: 'string',
|
|
288
|
+
enum: [
|
|
289
|
+
'FACT',
|
|
290
|
+
'DECISION',
|
|
291
|
+
'CONSTRAINT',
|
|
292
|
+
'INTERFACE_CONTRACT',
|
|
293
|
+
'TODO',
|
|
294
|
+
'RISK',
|
|
295
|
+
],
|
|
296
|
+
description: 'Anchor type',
|
|
297
|
+
},
|
|
298
|
+
text: { type: 'string', description: 'Anchor content' },
|
|
299
|
+
priority: {
|
|
300
|
+
type: 'number',
|
|
301
|
+
description: 'Priority (0-10)',
|
|
302
|
+
minimum: 0,
|
|
303
|
+
maximum: 10,
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
required: ['type', 'text'],
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
name: 'get_hot_stack',
|
|
311
|
+
description: 'Get current active frames and context',
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: 'object',
|
|
314
|
+
properties: {
|
|
315
|
+
maxEvents: {
|
|
316
|
+
type: 'number',
|
|
317
|
+
description: 'Max recent events per frame',
|
|
318
|
+
default: 20,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: 'create_task',
|
|
325
|
+
description: 'Create a new task in git-tracked JSONL storage',
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: 'object',
|
|
328
|
+
properties: {
|
|
329
|
+
title: { type: 'string', description: 'Task title' },
|
|
330
|
+
description: {
|
|
331
|
+
type: 'string',
|
|
332
|
+
description: 'Task description',
|
|
333
|
+
},
|
|
334
|
+
priority: {
|
|
335
|
+
type: 'string',
|
|
336
|
+
enum: ['low', 'medium', 'high', 'urgent'],
|
|
337
|
+
description: 'Task priority',
|
|
338
|
+
},
|
|
339
|
+
estimatedEffort: {
|
|
340
|
+
type: 'number',
|
|
341
|
+
description: 'Estimated effort in minutes',
|
|
342
|
+
},
|
|
343
|
+
dependsOn: {
|
|
344
|
+
type: 'array',
|
|
345
|
+
items: { type: 'string' },
|
|
346
|
+
description: 'Task IDs this depends on',
|
|
347
|
+
},
|
|
348
|
+
tags: {
|
|
349
|
+
type: 'array',
|
|
350
|
+
items: { type: 'string' },
|
|
351
|
+
description: 'Tags for categorization',
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
required: ['title'],
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'update_task_status',
|
|
359
|
+
description: 'Update task status with automatic time tracking',
|
|
360
|
+
inputSchema: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
properties: {
|
|
363
|
+
taskId: { type: 'string', description: 'Task ID to update' },
|
|
364
|
+
status: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
enum: [
|
|
367
|
+
'pending',
|
|
368
|
+
'in_progress',
|
|
369
|
+
'completed',
|
|
370
|
+
'blocked',
|
|
371
|
+
'cancelled',
|
|
372
|
+
],
|
|
373
|
+
description: 'New status',
|
|
374
|
+
},
|
|
375
|
+
reason: {
|
|
376
|
+
type: 'string',
|
|
377
|
+
description: 'Reason for status change (especially for blocked)',
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
required: ['taskId', 'status'],
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
name: 'get_active_tasks',
|
|
385
|
+
description: 'Get currently active tasks synced from Linear',
|
|
386
|
+
inputSchema: {
|
|
387
|
+
type: 'object',
|
|
388
|
+
properties: {
|
|
389
|
+
frameId: {
|
|
390
|
+
type: 'string',
|
|
391
|
+
description: 'Filter by specific frame ID',
|
|
392
|
+
},
|
|
393
|
+
status: {
|
|
394
|
+
type: 'string',
|
|
395
|
+
enum: [
|
|
396
|
+
'pending',
|
|
397
|
+
'in_progress',
|
|
398
|
+
'completed',
|
|
399
|
+
'blocked',
|
|
400
|
+
'cancelled',
|
|
401
|
+
],
|
|
402
|
+
description: 'Filter by status',
|
|
403
|
+
},
|
|
404
|
+
priority: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
enum: ['low', 'medium', 'high', 'urgent'],
|
|
407
|
+
description: 'Filter by priority',
|
|
408
|
+
},
|
|
409
|
+
search: {
|
|
410
|
+
type: 'string',
|
|
411
|
+
description: 'Search in task title or description',
|
|
412
|
+
},
|
|
413
|
+
limit: {
|
|
414
|
+
type: 'number',
|
|
415
|
+
description: 'Max number of tasks to return (default: 20)',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: 'get_task_metrics',
|
|
422
|
+
description: 'Get project task metrics and analytics',
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {},
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: 'add_task_dependency',
|
|
430
|
+
description: 'Add dependency relationship between tasks',
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: 'object',
|
|
433
|
+
properties: {
|
|
434
|
+
taskId: {
|
|
435
|
+
type: 'string',
|
|
436
|
+
description: 'Task that depends on another',
|
|
437
|
+
},
|
|
438
|
+
dependsOnId: {
|
|
439
|
+
type: 'string',
|
|
440
|
+
description: 'Task ID that this depends on',
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
required: ['taskId', 'dependsOnId'],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: 'linear_sync',
|
|
448
|
+
description: 'Sync tasks with Linear',
|
|
449
|
+
inputSchema: {
|
|
450
|
+
type: 'object',
|
|
451
|
+
properties: {
|
|
452
|
+
direction: {
|
|
453
|
+
type: 'string',
|
|
454
|
+
enum: ['bidirectional', 'to_linear', 'from_linear'],
|
|
455
|
+
description: 'Sync direction',
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'linear_update_task',
|
|
462
|
+
description: 'Update a Linear task status',
|
|
463
|
+
inputSchema: {
|
|
464
|
+
type: 'object',
|
|
465
|
+
properties: {
|
|
466
|
+
issueId: {
|
|
467
|
+
type: 'string',
|
|
468
|
+
description: 'Linear issue ID or identifier (e.g., STA-34)',
|
|
469
|
+
},
|
|
470
|
+
status: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
enum: ['todo', 'in-progress', 'done', 'canceled'],
|
|
473
|
+
description: 'New status for the task',
|
|
474
|
+
},
|
|
475
|
+
title: {
|
|
476
|
+
type: 'string',
|
|
477
|
+
description: 'Update task title (optional)',
|
|
478
|
+
},
|
|
479
|
+
description: {
|
|
480
|
+
type: 'string',
|
|
481
|
+
description: 'Update task description (optional)',
|
|
482
|
+
},
|
|
483
|
+
priority: {
|
|
484
|
+
type: 'number',
|
|
485
|
+
enum: [1, 2, 3, 4],
|
|
486
|
+
description: 'Priority (1=urgent, 2=high, 3=medium, 4=low)',
|
|
487
|
+
},
|
|
488
|
+
},
|
|
489
|
+
required: ['issueId'],
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: 'linear_get_tasks',
|
|
494
|
+
description: 'Get Linear tasks',
|
|
495
|
+
inputSchema: {
|
|
496
|
+
type: 'object',
|
|
497
|
+
properties: {
|
|
498
|
+
status: {
|
|
499
|
+
type: 'string',
|
|
500
|
+
enum: ['todo', 'in-progress', 'done', 'all'],
|
|
501
|
+
description: 'Filter by status',
|
|
502
|
+
},
|
|
503
|
+
limit: {
|
|
504
|
+
type: 'number',
|
|
505
|
+
description: 'Maximum number of tasks to return',
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
name: 'linear_status',
|
|
512
|
+
description: 'Get Linear integration status',
|
|
513
|
+
inputSchema: {
|
|
514
|
+
type: 'object',
|
|
515
|
+
properties: {},
|
|
516
|
+
},
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
};
|
|
520
|
+
});
|
|
521
|
+
// Tool execution
|
|
522
|
+
this.server.setRequestHandler(z.object({
|
|
523
|
+
method: z.literal('tools/call'),
|
|
524
|
+
params: z.object({
|
|
525
|
+
name: z.string(),
|
|
526
|
+
arguments: z.record(z.unknown()),
|
|
527
|
+
}),
|
|
528
|
+
}), async (request) => {
|
|
529
|
+
const { name, arguments: args } = request.params;
|
|
530
|
+
// Log tool call event before execution
|
|
531
|
+
const currentFrameId = this.frameManager.getCurrentFrameId();
|
|
532
|
+
if (currentFrameId) {
|
|
533
|
+
this.frameManager.addEvent('tool_call', {
|
|
534
|
+
tool_name: name,
|
|
535
|
+
arguments: args,
|
|
536
|
+
timestamp: Date.now(),
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
let result;
|
|
540
|
+
let error;
|
|
541
|
+
try {
|
|
542
|
+
switch (name) {
|
|
543
|
+
case 'get_context':
|
|
544
|
+
result = await this.handleGetContext(args);
|
|
545
|
+
break;
|
|
546
|
+
case 'add_decision':
|
|
547
|
+
result = await this.handleAddDecision(args);
|
|
548
|
+
break;
|
|
549
|
+
case 'start_frame':
|
|
550
|
+
result = await this.handleStartFrame(args);
|
|
551
|
+
break;
|
|
552
|
+
case 'close_frame':
|
|
553
|
+
result = await this.handleCloseFrame(args);
|
|
554
|
+
break;
|
|
555
|
+
case 'add_anchor':
|
|
556
|
+
result = await this.handleAddAnchor(args);
|
|
557
|
+
break;
|
|
558
|
+
case 'get_hot_stack':
|
|
559
|
+
result = await this.handleGetHotStack(args);
|
|
560
|
+
break;
|
|
561
|
+
case 'create_task':
|
|
562
|
+
result = await this.handleCreateTask(args);
|
|
563
|
+
break;
|
|
564
|
+
case 'update_task_status':
|
|
565
|
+
result = await this.handleUpdateTaskStatus(args);
|
|
566
|
+
break;
|
|
567
|
+
case 'get_active_tasks':
|
|
568
|
+
result = await this.handleGetActiveTasks(args);
|
|
569
|
+
break;
|
|
570
|
+
case 'get_task_metrics':
|
|
571
|
+
result = await this.handleGetTaskMetrics(args);
|
|
572
|
+
break;
|
|
573
|
+
case 'add_task_dependency':
|
|
574
|
+
result = await this.handleAddTaskDependency(args);
|
|
575
|
+
break;
|
|
576
|
+
case 'linear_sync':
|
|
577
|
+
result = await this.handleLinearSync(args);
|
|
578
|
+
break;
|
|
579
|
+
case 'linear_update_task':
|
|
580
|
+
result = await this.handleLinearUpdateTask(args);
|
|
581
|
+
break;
|
|
582
|
+
case 'linear_get_tasks':
|
|
583
|
+
result = await this.handleLinearGetTasks(args);
|
|
584
|
+
break;
|
|
585
|
+
case 'linear_status':
|
|
586
|
+
result = await this.handleLinearStatus(args);
|
|
587
|
+
break;
|
|
588
|
+
default:
|
|
589
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
error = err;
|
|
594
|
+
throw err;
|
|
595
|
+
}
|
|
596
|
+
finally {
|
|
597
|
+
// Log tool result event after execution (success or failure)
|
|
598
|
+
if (currentFrameId) {
|
|
599
|
+
this.frameManager.addEvent('tool_result', {
|
|
600
|
+
tool_name: name,
|
|
601
|
+
success: !error,
|
|
602
|
+
result: error ? { error: error.message } : result,
|
|
603
|
+
timestamp: Date.now(),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
return result;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
async handleGetContext(args) {
|
|
611
|
+
const { query = '', limit = 10 } = args;
|
|
612
|
+
// Get relevant contexts
|
|
613
|
+
const contexts = Array.from(this.contexts.values())
|
|
614
|
+
.sort((a, b) => b.importance - a.importance)
|
|
615
|
+
.slice(0, limit);
|
|
616
|
+
// Update access counts
|
|
617
|
+
contexts.forEach((ctx) => {
|
|
618
|
+
this.db
|
|
619
|
+
.prepare(`
|
|
620
|
+
UPDATE contexts
|
|
621
|
+
SET last_accessed = unixepoch(),
|
|
622
|
+
access_count = access_count + 1
|
|
623
|
+
WHERE id = ?
|
|
624
|
+
`)
|
|
625
|
+
.run(ctx.id);
|
|
626
|
+
});
|
|
627
|
+
// Format response
|
|
628
|
+
const response = contexts
|
|
629
|
+
.map((ctx) => `[${ctx.type.toUpperCase()}] (importance: ${ctx.importance.toFixed(2)})\n${ctx.content}`)
|
|
630
|
+
.join('\n\n---\n\n');
|
|
631
|
+
// Log for attention tracking
|
|
632
|
+
this.logAttention(query, response);
|
|
633
|
+
return {
|
|
634
|
+
content: [
|
|
635
|
+
{
|
|
636
|
+
type: 'text',
|
|
637
|
+
text: response ||
|
|
638
|
+
'No context available yet. Start adding decisions and information!',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
async handleAddDecision(args) {
|
|
644
|
+
const { content, type = 'decision' } = args;
|
|
645
|
+
const id = this.addContext(type, content, 0.8);
|
|
646
|
+
return {
|
|
647
|
+
content: [
|
|
648
|
+
{
|
|
649
|
+
type: 'text',
|
|
650
|
+
text: `✓ Added ${type}: ${content}\nID: ${id}`,
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async handleStartFrame(args) {
|
|
656
|
+
const { name, type, constraints } = args;
|
|
657
|
+
const inputs = {};
|
|
658
|
+
if (constraints) {
|
|
659
|
+
inputs.constraints = constraints;
|
|
660
|
+
}
|
|
661
|
+
const frameId = this.frameManager.createFrame({
|
|
662
|
+
type: type,
|
|
663
|
+
name,
|
|
664
|
+
inputs,
|
|
665
|
+
});
|
|
666
|
+
// Log event
|
|
667
|
+
this.frameManager.addEvent('user_message', {
|
|
668
|
+
action: 'start_frame',
|
|
669
|
+
name,
|
|
670
|
+
type,
|
|
671
|
+
constraints,
|
|
672
|
+
});
|
|
673
|
+
// Add as context
|
|
674
|
+
this.addContext('active_frame', `Active frame: ${name} (${type})`, 0.9);
|
|
675
|
+
const stackDepth = this.frameManager.getStackDepth();
|
|
676
|
+
return {
|
|
677
|
+
content: [
|
|
678
|
+
{
|
|
679
|
+
type: 'text',
|
|
680
|
+
text: `🚀 Started ${type}: ${name}\nFrame ID: ${frameId}\nStack depth: ${stackDepth}`,
|
|
681
|
+
},
|
|
682
|
+
],
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
async handleCloseFrame(args) {
|
|
686
|
+
const { result, outputs } = args;
|
|
687
|
+
const currentFrameId = this.frameManager.getCurrentFrameId();
|
|
688
|
+
if (!currentFrameId) {
|
|
689
|
+
return {
|
|
690
|
+
content: [
|
|
691
|
+
{
|
|
692
|
+
type: 'text',
|
|
693
|
+
text: '⚠️ No active frame to close',
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
// Log completion event
|
|
699
|
+
this.frameManager.addEvent('assistant_message', {
|
|
700
|
+
action: 'close_frame',
|
|
701
|
+
result,
|
|
702
|
+
outputs,
|
|
703
|
+
});
|
|
704
|
+
this.frameManager.closeFrame(currentFrameId, outputs);
|
|
705
|
+
const newStackDepth = this.frameManager.getStackDepth();
|
|
706
|
+
return {
|
|
707
|
+
content: [
|
|
708
|
+
{
|
|
709
|
+
type: 'text',
|
|
710
|
+
text: `✅ Closed frame: ${result || 'completed'}\nStack depth: ${newStackDepth}`,
|
|
711
|
+
},
|
|
712
|
+
],
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async handleAddAnchor(args) {
|
|
716
|
+
const { type, text, priority = 5 } = args;
|
|
717
|
+
const anchorId = this.frameManager.addAnchor(type, text, priority);
|
|
718
|
+
// Log anchor creation
|
|
719
|
+
this.frameManager.addEvent('decision', {
|
|
720
|
+
anchor_type: type,
|
|
721
|
+
text,
|
|
722
|
+
priority,
|
|
723
|
+
anchor_id: anchorId,
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
content: [
|
|
727
|
+
{
|
|
728
|
+
type: 'text',
|
|
729
|
+
text: `📌 Added ${type}: ${text}\nAnchor ID: ${anchorId}`,
|
|
730
|
+
},
|
|
731
|
+
],
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
async handleGetHotStack(args) {
|
|
735
|
+
const { maxEvents = 20 } = args;
|
|
736
|
+
const hotStack = this.frameManager.getHotStackContext(maxEvents);
|
|
737
|
+
const activePath = this.frameManager.getActiveFramePath();
|
|
738
|
+
if (hotStack.length === 0) {
|
|
739
|
+
return {
|
|
740
|
+
content: [
|
|
741
|
+
{
|
|
742
|
+
type: 'text',
|
|
743
|
+
text: '📚 No active frames. Start a frame with start_frame tool.',
|
|
744
|
+
},
|
|
745
|
+
],
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
let response = '📚 **Active Call Stack:**\n\n';
|
|
749
|
+
activePath.forEach((frame, index) => {
|
|
750
|
+
const indent = ' '.repeat(index);
|
|
751
|
+
const context = hotStack[index];
|
|
752
|
+
response += `${indent}${index + 1}. **${frame.name}** (${frame.type})\n`;
|
|
753
|
+
if (context && context.anchors && context.anchors.length > 0) {
|
|
754
|
+
response += `${indent} 📌 ${context.anchors.length} anchors\n`;
|
|
755
|
+
}
|
|
756
|
+
if (context && context.recentEvents && context.recentEvents.length > 0) {
|
|
757
|
+
response += `${indent} 📝 ${context.recentEvents.length} recent events\n`;
|
|
758
|
+
}
|
|
759
|
+
response += '\n';
|
|
760
|
+
});
|
|
761
|
+
response += `**Total stack depth:** ${hotStack.length}`;
|
|
762
|
+
// Log stack access
|
|
763
|
+
this.frameManager.addEvent('observation', {
|
|
764
|
+
action: 'get_hot_stack',
|
|
765
|
+
stack_depth: hotStack.length,
|
|
766
|
+
total_anchors: hotStack.reduce((sum, frame) => sum + frame.anchors.length, 0),
|
|
767
|
+
total_events: hotStack.reduce((sum, frame) => sum + frame.recentEvents.length, 0),
|
|
768
|
+
});
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: 'text',
|
|
773
|
+
text: response,
|
|
774
|
+
},
|
|
775
|
+
],
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
logAttention(query, response) {
|
|
779
|
+
// Simple attention logging for analysis
|
|
780
|
+
this.db
|
|
781
|
+
.prepare(`
|
|
782
|
+
INSERT INTO attention_log (query, response)
|
|
783
|
+
VALUES (?, ?)
|
|
784
|
+
`)
|
|
785
|
+
.run(query, response);
|
|
786
|
+
}
|
|
787
|
+
async handleCreateTask(args) {
|
|
788
|
+
const { title, description, priority, estimatedEffort, dependsOn, tags } = args;
|
|
789
|
+
const currentFrameId = this.frameManager.getCurrentFrameId();
|
|
790
|
+
if (!currentFrameId) {
|
|
791
|
+
return {
|
|
792
|
+
content: [
|
|
793
|
+
{
|
|
794
|
+
type: 'text',
|
|
795
|
+
text: '⚠️ No active frame. Start a frame first with start_frame tool.',
|
|
796
|
+
},
|
|
797
|
+
],
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const taskId = this.taskStore.createTask({
|
|
801
|
+
title,
|
|
802
|
+
description,
|
|
803
|
+
priority: priority,
|
|
804
|
+
frameId: currentFrameId,
|
|
805
|
+
dependsOn,
|
|
806
|
+
tags,
|
|
807
|
+
estimatedEffort,
|
|
808
|
+
});
|
|
809
|
+
// Log task creation event
|
|
810
|
+
this.frameManager.addEvent('decision', {
|
|
811
|
+
action: 'create_task',
|
|
812
|
+
task_id: taskId,
|
|
813
|
+
title,
|
|
814
|
+
priority: priority || 'medium',
|
|
815
|
+
});
|
|
816
|
+
return {
|
|
817
|
+
content: [
|
|
818
|
+
{
|
|
819
|
+
type: 'text',
|
|
820
|
+
text: `✅ Created task: ${title}\nID: ${taskId}\nFrame: ${currentFrameId}\nStored in: .stackmemory/tasks.jsonl`,
|
|
821
|
+
},
|
|
822
|
+
],
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
async handleUpdateTaskStatus(args) {
|
|
826
|
+
const { taskId, status, reason } = args;
|
|
827
|
+
try {
|
|
828
|
+
this.taskStore.updateTaskStatus(taskId, status, reason);
|
|
829
|
+
// Log status change event
|
|
830
|
+
this.frameManager.addEvent('observation', {
|
|
831
|
+
action: 'update_task_status',
|
|
832
|
+
task_id: taskId,
|
|
833
|
+
new_status: status,
|
|
834
|
+
reason,
|
|
835
|
+
});
|
|
836
|
+
return {
|
|
837
|
+
content: [
|
|
838
|
+
{
|
|
839
|
+
type: 'text',
|
|
840
|
+
text: `✅ Updated task ${taskId} to ${status}${reason ? `\nReason: ${reason}` : ''}`,
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
return {
|
|
847
|
+
content: [
|
|
848
|
+
{
|
|
849
|
+
type: 'text',
|
|
850
|
+
text: `❌ Failed to update task: ${error}`,
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async handleGetActiveTasks(args) {
|
|
857
|
+
const { frameId, status, priority, search, limit = 20 } = args;
|
|
858
|
+
let tasks = this.taskStore.getActiveTasks(frameId);
|
|
859
|
+
// Apply filters
|
|
860
|
+
if (status) {
|
|
861
|
+
tasks = tasks.filter((t) => t.status === status);
|
|
862
|
+
}
|
|
863
|
+
if (priority) {
|
|
864
|
+
tasks = tasks.filter((t) => t.priority === priority);
|
|
865
|
+
}
|
|
866
|
+
if (search) {
|
|
867
|
+
const searchLower = search.toLowerCase();
|
|
868
|
+
tasks = tasks.filter((t) => t.title.toLowerCase().includes(searchLower) ||
|
|
869
|
+
(t.description && t.description.toLowerCase().includes(searchLower)));
|
|
870
|
+
}
|
|
871
|
+
// Sort by priority (urgent first) then by created_at
|
|
872
|
+
const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 };
|
|
873
|
+
tasks.sort((a, b) => {
|
|
874
|
+
const pa = priorityOrder[a.priority] ?? 2;
|
|
875
|
+
const pb = priorityOrder[b.priority] ?? 2;
|
|
876
|
+
if (pa !== pb)
|
|
877
|
+
return pa - pb;
|
|
878
|
+
return b.created_at - a.created_at;
|
|
879
|
+
});
|
|
880
|
+
// Limit results
|
|
881
|
+
tasks = tasks.slice(0, limit);
|
|
882
|
+
if (tasks.length === 0) {
|
|
883
|
+
return {
|
|
884
|
+
content: [
|
|
885
|
+
{
|
|
886
|
+
type: 'text',
|
|
887
|
+
text: search
|
|
888
|
+
? `📝 No tasks matching "${search}"`
|
|
889
|
+
: '📝 No active tasks found',
|
|
890
|
+
},
|
|
891
|
+
],
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
let response = `📝 **Tasks** (${tasks.length} found)\n\n`;
|
|
895
|
+
tasks.forEach((task) => {
|
|
896
|
+
const priorityIcon = { urgent: '🔴', high: '🟠', medium: '🟡', low: '🟢' }[task.priority] ||
|
|
897
|
+
'⚪';
|
|
898
|
+
const statusIcon = {
|
|
899
|
+
pending: '⏳',
|
|
900
|
+
in_progress: '🔄',
|
|
901
|
+
completed: '✅',
|
|
902
|
+
blocked: '🚫',
|
|
903
|
+
cancelled: '❌',
|
|
904
|
+
}[task.status] || '⚪';
|
|
905
|
+
const effort = task.estimated_effort
|
|
906
|
+
? ` (~${task.estimated_effort}m)`
|
|
907
|
+
: '';
|
|
908
|
+
// Extract Linear ID from title if present
|
|
909
|
+
const linearMatch = task.title.match(/\[ENG-\d+\]/);
|
|
910
|
+
const linearId = linearMatch ? linearMatch[0] : '';
|
|
911
|
+
const title = linearId
|
|
912
|
+
? task.title.replace(linearId, '').trim()
|
|
913
|
+
: task.title;
|
|
914
|
+
response += `${statusIcon} ${priorityIcon} **${linearId || task.id}** ${title}${effort}\n`;
|
|
915
|
+
if (task.description) {
|
|
916
|
+
const desc = task.description.split('\n')[0].slice(0, 100);
|
|
917
|
+
response += ` ${desc}${task.description.length > 100 ? '...' : ''}\n`;
|
|
918
|
+
}
|
|
919
|
+
if (task.tags && task.tags.length > 0) {
|
|
920
|
+
response += ` 🏷️ ${task.tags.join(', ')}\n`;
|
|
921
|
+
}
|
|
922
|
+
response += '\n';
|
|
923
|
+
});
|
|
924
|
+
return {
|
|
925
|
+
content: [
|
|
926
|
+
{
|
|
927
|
+
type: 'text',
|
|
928
|
+
text: response,
|
|
929
|
+
},
|
|
930
|
+
],
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
async handleGetTaskMetrics(_args) {
|
|
934
|
+
const metrics = this.taskStore.getMetrics();
|
|
935
|
+
let response = '📊 **Task Metrics**\n\n';
|
|
936
|
+
response += `**Total Tasks:** ${metrics.total_tasks}\n`;
|
|
937
|
+
response += `**Completion Rate:** ${(metrics.completion_rate * 100).toFixed(1)}%\n\n`;
|
|
938
|
+
response += '**By Status:**\n';
|
|
939
|
+
Object.entries(metrics.by_status).forEach(([status, count]) => {
|
|
940
|
+
response += `- ${status}: ${count}\n`;
|
|
941
|
+
});
|
|
942
|
+
response += '\n**By Priority:**\n';
|
|
943
|
+
Object.entries(metrics.by_priority).forEach(([priority, count]) => {
|
|
944
|
+
response += `- ${priority}: ${count}\n`;
|
|
945
|
+
});
|
|
946
|
+
if (metrics.blocked_tasks > 0) {
|
|
947
|
+
response += `\n⚠️ **${metrics.blocked_tasks} blocked tasks**`;
|
|
948
|
+
}
|
|
949
|
+
if (metrics.avg_effort_accuracy > 0) {
|
|
950
|
+
response += `\n🎯 **Effort Accuracy:** ${(metrics.avg_effort_accuracy * 100).toFixed(1)}%`;
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
content: [
|
|
954
|
+
{
|
|
955
|
+
type: 'text',
|
|
956
|
+
text: response,
|
|
957
|
+
},
|
|
958
|
+
],
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
async handleAddTaskDependency(args) {
|
|
962
|
+
const { taskId, dependsOnId } = args;
|
|
963
|
+
try {
|
|
964
|
+
this.taskStore.addDependency(taskId, dependsOnId);
|
|
965
|
+
// Log dependency creation
|
|
966
|
+
this.frameManager.addEvent('decision', {
|
|
967
|
+
action: 'add_task_dependency',
|
|
968
|
+
task_id: taskId,
|
|
969
|
+
depends_on_id: dependsOnId,
|
|
970
|
+
});
|
|
971
|
+
return {
|
|
972
|
+
content: [
|
|
973
|
+
{
|
|
974
|
+
type: 'text',
|
|
975
|
+
text: `🔗 Added dependency: ${taskId} depends on ${dependsOnId}`,
|
|
976
|
+
},
|
|
977
|
+
],
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
catch (error) {
|
|
981
|
+
return {
|
|
982
|
+
content: [
|
|
983
|
+
{
|
|
984
|
+
type: 'text',
|
|
985
|
+
text: `❌ Failed to add dependency: ${error}`,
|
|
986
|
+
},
|
|
987
|
+
],
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
// Linear Integration Handlers
|
|
992
|
+
async handleLinearSync(args) {
|
|
993
|
+
try {
|
|
994
|
+
const tokens = this.linearAuthManager.loadTokens();
|
|
995
|
+
if (!tokens) {
|
|
996
|
+
return {
|
|
997
|
+
content: [
|
|
998
|
+
{
|
|
999
|
+
type: 'text',
|
|
1000
|
+
text: '❌ Linear not authenticated. Run: stackmemory linear setup',
|
|
1001
|
+
},
|
|
1002
|
+
],
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
const syncConfig = { ...DEFAULT_SYNC_CONFIG, enabled: true };
|
|
1006
|
+
if (args.direction) {
|
|
1007
|
+
syncConfig.direction = args.direction;
|
|
1008
|
+
}
|
|
1009
|
+
// Update sync engine configuration for this sync
|
|
1010
|
+
this.linearSync.updateConfig(syncConfig);
|
|
1011
|
+
const result = await this.linearSync.sync();
|
|
1012
|
+
return {
|
|
1013
|
+
content: [
|
|
1014
|
+
{
|
|
1015
|
+
type: 'text',
|
|
1016
|
+
text: `✅ Linear sync completed\n- To Linear: ${result.synced.toLinear} tasks\n- From Linear: ${result.synced.fromLinear} tasks\n- Updated: ${result.synced.updated} tasks`,
|
|
1017
|
+
},
|
|
1018
|
+
],
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
return {
|
|
1023
|
+
content: [
|
|
1024
|
+
{
|
|
1025
|
+
type: 'text',
|
|
1026
|
+
text: `❌ Linear sync failed: ${error.message}`,
|
|
1027
|
+
},
|
|
1028
|
+
],
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async handleLinearUpdateTask(args) {
|
|
1033
|
+
try {
|
|
1034
|
+
const { LinearClient } = await import('../linear/client.js');
|
|
1035
|
+
const tokens = this.linearAuthManager.loadTokens();
|
|
1036
|
+
if (!tokens) {
|
|
1037
|
+
return {
|
|
1038
|
+
content: [
|
|
1039
|
+
{
|
|
1040
|
+
type: 'text',
|
|
1041
|
+
text: '❌ Linear not authenticated. Run: stackmemory linear setup',
|
|
1042
|
+
},
|
|
1043
|
+
],
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
const client = new LinearClient({
|
|
1047
|
+
apiKey: tokens.accessToken,
|
|
1048
|
+
});
|
|
1049
|
+
// Find the issue
|
|
1050
|
+
let issue = await client.getIssue(args.issueId);
|
|
1051
|
+
if (!issue) {
|
|
1052
|
+
issue = await client.findIssueByIdentifier(args.issueId);
|
|
1053
|
+
}
|
|
1054
|
+
if (!issue) {
|
|
1055
|
+
return {
|
|
1056
|
+
content: [
|
|
1057
|
+
{
|
|
1058
|
+
type: 'text',
|
|
1059
|
+
text: `❌ Linear issue ${args.issueId} not found`,
|
|
1060
|
+
},
|
|
1061
|
+
],
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
const updates = {};
|
|
1065
|
+
// Handle status update
|
|
1066
|
+
if (args.status) {
|
|
1067
|
+
const team = await client.getTeam();
|
|
1068
|
+
const states = await client.getWorkflowStates(team.id);
|
|
1069
|
+
const statusMap = {
|
|
1070
|
+
todo: 'unstarted',
|
|
1071
|
+
'in-progress': 'started',
|
|
1072
|
+
done: 'completed',
|
|
1073
|
+
canceled: 'cancelled',
|
|
1074
|
+
};
|
|
1075
|
+
const targetType = statusMap[args.status] || args.status;
|
|
1076
|
+
const targetState = states.find((s) => s.type === targetType);
|
|
1077
|
+
if (!targetState) {
|
|
1078
|
+
return {
|
|
1079
|
+
content: [
|
|
1080
|
+
{
|
|
1081
|
+
type: 'text',
|
|
1082
|
+
text: `❌ Invalid status: ${args.status}`,
|
|
1083
|
+
},
|
|
1084
|
+
],
|
|
1085
|
+
};
|
|
1086
|
+
}
|
|
1087
|
+
updates.stateId = targetState.id;
|
|
1088
|
+
}
|
|
1089
|
+
if (args.title)
|
|
1090
|
+
updates.title = args.title;
|
|
1091
|
+
if (args.description)
|
|
1092
|
+
updates.description = args.description;
|
|
1093
|
+
if (args.priority)
|
|
1094
|
+
updates.priority = args.priority;
|
|
1095
|
+
const updatedIssue = await client.updateIssue(issue.id, updates);
|
|
1096
|
+
// Auto-sync to local tasks after update
|
|
1097
|
+
this.linearSync.updateConfig({ ...DEFAULT_SYNC_CONFIG, enabled: true, direction: 'from_linear' });
|
|
1098
|
+
const syncResult = await this.linearSync.sync();
|
|
1099
|
+
let response = `✅ Updated ${updatedIssue.identifier}: ${updatedIssue.title}\n`;
|
|
1100
|
+
if (args.status) {
|
|
1101
|
+
response += `Status: ${updatedIssue.state.name}\n`;
|
|
1102
|
+
}
|
|
1103
|
+
response += `URL: ${updatedIssue.url}\n`;
|
|
1104
|
+
response += `\n🔄 Local sync: ${syncResult.synced.fromLinear} new, ${syncResult.synced.updated} updated`;
|
|
1105
|
+
return {
|
|
1106
|
+
content: [
|
|
1107
|
+
{
|
|
1108
|
+
type: 'text',
|
|
1109
|
+
text: response,
|
|
1110
|
+
},
|
|
1111
|
+
],
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
catch (error) {
|
|
1115
|
+
return {
|
|
1116
|
+
content: [
|
|
1117
|
+
{
|
|
1118
|
+
type: 'text',
|
|
1119
|
+
text: `❌ Failed to update Linear task: ${error.message}`,
|
|
1120
|
+
},
|
|
1121
|
+
],
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
async handleLinearGetTasks(args) {
|
|
1126
|
+
try {
|
|
1127
|
+
const { LinearClient } = await import('../linear/client.js');
|
|
1128
|
+
const tokens = this.linearAuthManager.loadTokens();
|
|
1129
|
+
if (!tokens) {
|
|
1130
|
+
return {
|
|
1131
|
+
content: [
|
|
1132
|
+
{
|
|
1133
|
+
type: 'text',
|
|
1134
|
+
text: '❌ Linear not authenticated. Run: stackmemory linear setup',
|
|
1135
|
+
},
|
|
1136
|
+
],
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
const client = new LinearClient({
|
|
1140
|
+
apiKey: tokens.accessToken,
|
|
1141
|
+
});
|
|
1142
|
+
let stateType = undefined;
|
|
1143
|
+
if (args.status && args.status !== 'all') {
|
|
1144
|
+
const statusMap = {
|
|
1145
|
+
todo: 'unstarted',
|
|
1146
|
+
'in-progress': 'started',
|
|
1147
|
+
done: 'completed',
|
|
1148
|
+
};
|
|
1149
|
+
stateType = statusMap[args.status] || args.status;
|
|
1150
|
+
}
|
|
1151
|
+
const issues = await client.getIssues({
|
|
1152
|
+
stateType,
|
|
1153
|
+
limit: args.limit || 20,
|
|
1154
|
+
});
|
|
1155
|
+
if (!issues || issues.length === 0) {
|
|
1156
|
+
return {
|
|
1157
|
+
content: [
|
|
1158
|
+
{
|
|
1159
|
+
type: 'text',
|
|
1160
|
+
text: 'No Linear tasks found',
|
|
1161
|
+
},
|
|
1162
|
+
],
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
let response = `📋 **Linear Tasks** (${issues.length} items)\n\n`;
|
|
1166
|
+
issues.forEach((issue) => {
|
|
1167
|
+
const priority = issue.priority ? `P${issue.priority}` : '-';
|
|
1168
|
+
response += `- **${issue.identifier}**: ${issue.title}\n`;
|
|
1169
|
+
response += ` Status: ${issue.state.name} | Priority: ${priority}\n`;
|
|
1170
|
+
if (issue.assignee) {
|
|
1171
|
+
response += ` Assignee: ${issue.assignee.name}\n`;
|
|
1172
|
+
}
|
|
1173
|
+
response += ` ${issue.url}\n\n`;
|
|
1174
|
+
});
|
|
1175
|
+
return {
|
|
1176
|
+
content: [
|
|
1177
|
+
{
|
|
1178
|
+
type: 'text',
|
|
1179
|
+
text: response,
|
|
1180
|
+
},
|
|
1181
|
+
],
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
catch (error) {
|
|
1185
|
+
return {
|
|
1186
|
+
content: [
|
|
1187
|
+
{
|
|
1188
|
+
type: 'text',
|
|
1189
|
+
text: `❌ Failed to get Linear tasks: ${error.message}`,
|
|
1190
|
+
},
|
|
1191
|
+
],
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
async handleLinearStatus(_args) {
|
|
1196
|
+
try {
|
|
1197
|
+
const { LinearClient } = await import('../linear/client.js');
|
|
1198
|
+
const tokens = this.linearAuthManager.loadTokens();
|
|
1199
|
+
if (!tokens) {
|
|
1200
|
+
return {
|
|
1201
|
+
content: [
|
|
1202
|
+
{
|
|
1203
|
+
type: 'text',
|
|
1204
|
+
text: '❌ Linear integration not configured\nRun: stackmemory linear setup',
|
|
1205
|
+
},
|
|
1206
|
+
],
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
const client = new LinearClient({
|
|
1211
|
+
apiKey: tokens.accessToken,
|
|
1212
|
+
});
|
|
1213
|
+
const viewer = await client.getViewer();
|
|
1214
|
+
const team = await client.getTeam();
|
|
1215
|
+
return {
|
|
1216
|
+
content: [
|
|
1217
|
+
{
|
|
1218
|
+
type: 'text',
|
|
1219
|
+
text: `✅ **Linear Integration Status**\n\nConnected as: ${viewer.name} (${viewer.email})\nTeam: ${team.name} (${team.key})\nTokens: Valid`,
|
|
1220
|
+
},
|
|
1221
|
+
],
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
return {
|
|
1226
|
+
content: [
|
|
1227
|
+
{
|
|
1228
|
+
type: 'text',
|
|
1229
|
+
text: `⚠️ Linear configured but connection failed: ${error.message}`,
|
|
1230
|
+
},
|
|
1231
|
+
],
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
catch (error) {
|
|
1236
|
+
return {
|
|
1237
|
+
content: [
|
|
1238
|
+
{
|
|
1239
|
+
type: 'text',
|
|
1240
|
+
text: `❌ Linear status check failed: ${error.message}`,
|
|
1241
|
+
},
|
|
1242
|
+
],
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
async start() {
|
|
1247
|
+
const transport = new StdioServerTransport();
|
|
1248
|
+
await this.server.connect(transport);
|
|
1249
|
+
console.error('StackMemory MCP Server started');
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
// Export the class
|
|
1253
|
+
export default LocalStackMemoryMCP;
|
|
1254
|
+
// Export function to run the server
|
|
1255
|
+
export async function runMCPServer() {
|
|
1256
|
+
const server = new LocalStackMemoryMCP();
|
|
1257
|
+
await server.start();
|
|
1258
|
+
}
|
|
1259
|
+
// Start the server
|
|
1260
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1261
|
+
const server = new LocalStackMemoryMCP();
|
|
1262
|
+
server.start().catch(console.error);
|
|
1263
|
+
}
|
|
1264
|
+
//# sourceMappingURL=server.js.map
|