@stackmemoryai/stackmemory 0.2.6 → 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.
Files changed (203) hide show
  1. package/README.md +245 -83
  2. package/dist/scripts/cancel-duplicate-tasks.d.ts +7 -0
  3. package/dist/scripts/cancel-duplicate-tasks.d.ts.map +1 -0
  4. package/dist/scripts/cancel-duplicate-tasks.js +171 -0
  5. package/dist/scripts/cancel-duplicate-tasks.js.map +1 -0
  6. package/dist/scripts/list-linear-tasks.d.ts +6 -0
  7. package/dist/scripts/list-linear-tasks.d.ts.map +1 -0
  8. package/dist/scripts/list-linear-tasks.js +122 -0
  9. package/dist/scripts/list-linear-tasks.js.map +1 -0
  10. package/dist/scripts/merge-linear-duplicates-safe.d.ts +7 -0
  11. package/dist/scripts/merge-linear-duplicates-safe.d.ts.map +1 -0
  12. package/dist/scripts/merge-linear-duplicates-safe.js +265 -0
  13. package/dist/scripts/merge-linear-duplicates-safe.js.map +1 -0
  14. package/dist/scripts/merge-linear-duplicates.d.ts +7 -0
  15. package/dist/scripts/merge-linear-duplicates.d.ts.map +1 -0
  16. package/dist/scripts/merge-linear-duplicates.js +126 -0
  17. package/dist/scripts/merge-linear-duplicates.js.map +1 -0
  18. package/dist/scripts/show-linear-summary.d.ts +6 -0
  19. package/dist/scripts/show-linear-summary.d.ts.map +1 -0
  20. package/dist/scripts/show-linear-summary.js +117 -0
  21. package/dist/scripts/show-linear-summary.js.map +1 -0
  22. package/dist/src/cli/__tests__/index.test.d.ts +5 -0
  23. package/dist/src/cli/__tests__/index.test.d.ts.map +1 -0
  24. package/dist/src/cli/__tests__/index.test.js +726 -0
  25. package/dist/src/cli/__tests__/index.test.js.map +1 -0
  26. package/dist/src/cli/auto-detect.d.ts +61 -0
  27. package/dist/src/cli/auto-detect.d.ts.map +1 -0
  28. package/dist/src/cli/auto-detect.js +350 -0
  29. package/dist/src/cli/auto-detect.js.map +1 -0
  30. package/dist/src/cli/claude-sm.d.ts +7 -0
  31. package/dist/src/cli/claude-sm.d.ts.map +1 -0
  32. package/dist/src/cli/claude-sm.js +357 -0
  33. package/dist/src/cli/claude-sm.js.map +1 -0
  34. package/dist/src/cli/commands/context.d.ts +7 -0
  35. package/dist/src/cli/commands/context.d.ts.map +1 -0
  36. package/dist/src/cli/commands/context.js +365 -0
  37. package/dist/src/cli/commands/context.js.map +1 -0
  38. package/dist/src/cli/commands/linear-test.d.ts +6 -0
  39. package/dist/src/cli/commands/linear-test.d.ts.map +1 -0
  40. package/dist/src/cli/commands/linear-test.js +123 -0
  41. package/dist/src/cli/commands/linear-test.js.map +1 -0
  42. package/dist/src/cli/commands/linear.d.ts +6 -0
  43. package/dist/src/cli/commands/linear.d.ts.map +1 -0
  44. package/dist/src/cli/commands/linear.js +317 -0
  45. package/dist/src/cli/commands/linear.js.map +1 -0
  46. package/dist/src/cli/commands/log.d.ts +7 -0
  47. package/dist/src/cli/commands/log.d.ts.map +1 -0
  48. package/dist/src/cli/commands/log.js +168 -0
  49. package/dist/src/cli/commands/log.js.map +1 -0
  50. package/dist/src/cli/commands/onboard.d.ts +8 -0
  51. package/dist/src/cli/commands/onboard.d.ts.map +1 -0
  52. package/dist/src/cli/commands/onboard.js +363 -0
  53. package/dist/src/cli/commands/onboard.js.map +1 -0
  54. package/dist/src/cli/commands/projects.js +1 -1
  55. package/dist/src/cli/commands/projects.js.map +1 -1
  56. package/dist/src/cli/commands/search.d.ts +7 -0
  57. package/dist/src/cli/commands/search.d.ts.map +1 -0
  58. package/dist/src/cli/commands/search.js +162 -0
  59. package/dist/src/cli/commands/search.js.map +1 -0
  60. package/dist/src/cli/commands/session.d.ts +7 -0
  61. package/dist/src/cli/commands/session.d.ts.map +1 -0
  62. package/dist/src/cli/commands/session.js +222 -0
  63. package/dist/src/cli/commands/session.js.map +1 -0
  64. package/dist/src/cli/commands/tasks.d.ts +7 -0
  65. package/dist/src/cli/commands/tasks.d.ts.map +1 -0
  66. package/dist/src/cli/commands/tasks.js +229 -0
  67. package/dist/src/cli/commands/tasks.js.map +1 -0
  68. package/dist/src/cli/commands/webhook.d.ts +3 -0
  69. package/dist/src/cli/commands/webhook.d.ts.map +1 -0
  70. package/dist/src/cli/commands/webhook.js +157 -0
  71. package/dist/src/cli/commands/webhook.js.map +1 -0
  72. package/dist/src/cli/commands/worktree.d.ts +8 -0
  73. package/dist/src/cli/commands/worktree.d.ts.map +1 -0
  74. package/dist/src/cli/commands/worktree.js +339 -0
  75. package/dist/src/cli/commands/worktree.js.map +1 -0
  76. package/dist/src/cli/index.d.ts +2 -1
  77. package/dist/src/cli/index.d.ts.map +1 -1
  78. package/dist/src/cli/index.js +290 -50
  79. package/dist/src/cli/index.js.map +1 -1
  80. package/dist/src/cli/utils/viewer.d.ts.map +1 -1
  81. package/dist/src/cli/utils/viewer.js +3 -1
  82. package/dist/src/cli/utils/viewer.js.map +1 -1
  83. package/dist/src/core/context/__tests__/frame-manager.test.d.ts +5 -0
  84. package/dist/src/core/context/__tests__/frame-manager.test.d.ts.map +1 -0
  85. package/dist/src/core/context/__tests__/frame-manager.test.js +892 -0
  86. package/dist/src/core/context/__tests__/frame-manager.test.js.map +1 -0
  87. package/dist/src/core/context/auto-context.d.ts +22 -0
  88. package/dist/src/core/context/auto-context.d.ts.map +1 -0
  89. package/dist/src/core/context/auto-context.js +77 -0
  90. package/dist/src/core/context/auto-context.js.map +1 -0
  91. package/dist/src/core/context/frame-manager.d.ts +4 -0
  92. package/dist/src/core/context/frame-manager.d.ts.map +1 -1
  93. package/dist/src/core/context/frame-manager.js +350 -144
  94. package/dist/src/core/context/frame-manager.js.map +1 -1
  95. package/dist/src/core/errors/__tests__/error-handling.test.d.ts +5 -0
  96. package/dist/src/core/errors/__tests__/error-handling.test.d.ts.map +1 -0
  97. package/dist/src/core/errors/__tests__/error-handling.test.js +239 -0
  98. package/dist/src/core/errors/__tests__/error-handling.test.js.map +1 -0
  99. package/dist/src/core/errors/index.d.ts +135 -0
  100. package/dist/src/core/errors/index.d.ts.map +1 -0
  101. package/dist/src/core/errors/index.js +274 -0
  102. package/dist/src/core/errors/index.js.map +1 -0
  103. package/dist/src/core/errors/recovery.d.ts +86 -0
  104. package/dist/src/core/errors/recovery.d.ts.map +1 -0
  105. package/dist/src/core/errors/recovery.js +274 -0
  106. package/dist/src/core/errors/recovery.js.map +1 -0
  107. package/dist/src/core/projects/project-manager.d.ts.map +1 -1
  108. package/dist/src/core/projects/project-manager.js +240 -122
  109. package/dist/src/core/projects/project-manager.js.map +1 -1
  110. package/dist/src/core/session/index.d.ts +2 -0
  111. package/dist/src/core/session/index.d.ts.map +1 -0
  112. package/dist/src/core/session/index.js +2 -0
  113. package/dist/src/core/session/index.js.map +1 -0
  114. package/dist/src/core/session/session-manager.d.ts +69 -0
  115. package/dist/src/core/session/session-manager.d.ts.map +1 -0
  116. package/dist/src/core/session/session-manager.js +311 -0
  117. package/dist/src/core/session/session-manager.js.map +1 -0
  118. package/dist/src/core/utils/update-checker.d.ts.map +1 -1
  119. package/dist/src/core/utils/update-checker.js +82 -25
  120. package/dist/src/core/utils/update-checker.js.map +1 -1
  121. package/dist/src/core/worktree/worktree-manager.d.ts +110 -0
  122. package/dist/src/core/worktree/worktree-manager.d.ts.map +1 -0
  123. package/dist/src/core/worktree/worktree-manager.js +456 -0
  124. package/dist/src/core/worktree/worktree-manager.js.map +1 -0
  125. package/dist/src/features/analytics/core/analytics-service.d.ts +6 -0
  126. package/dist/src/features/analytics/core/analytics-service.d.ts.map +1 -1
  127. package/dist/src/features/analytics/core/analytics-service.js +125 -10
  128. package/dist/src/features/analytics/core/analytics-service.js.map +1 -1
  129. package/dist/src/features/analytics/queries/metrics-queries.d.ts.map +1 -1
  130. package/dist/src/features/analytics/queries/metrics-queries.js +220 -163
  131. package/dist/src/features/analytics/queries/metrics-queries.js.map +1 -1
  132. package/dist/src/features/browser/browser-mcp.d.ts.map +1 -1
  133. package/dist/src/features/browser/browser-mcp.js +3 -0
  134. package/dist/src/features/browser/browser-mcp.js.map +1 -1
  135. package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts +5 -0
  136. package/dist/src/features/tasks/__tests__/pebbles-task-store.test.d.ts.map +1 -0
  137. package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js +712 -0
  138. package/dist/src/features/tasks/__tests__/pebbles-task-store.test.js.map +1 -0
  139. package/dist/src/features/tasks/pebbles-task-store.d.ts +4 -0
  140. package/dist/src/features/tasks/pebbles-task-store.d.ts.map +1 -1
  141. package/dist/src/features/tasks/pebbles-task-store.js +299 -141
  142. package/dist/src/features/tasks/pebbles-task-store.js.map +1 -1
  143. package/dist/src/integrations/linear/__tests__/auth.test.d.ts +5 -0
  144. package/dist/src/integrations/linear/__tests__/auth.test.d.ts.map +1 -0
  145. package/dist/src/integrations/linear/__tests__/auth.test.js +517 -0
  146. package/dist/src/integrations/linear/__tests__/auth.test.js.map +1 -0
  147. package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts +5 -0
  148. package/dist/src/integrations/linear/__tests__/sync-service.test.d.ts.map +1 -0
  149. package/dist/src/integrations/linear/__tests__/sync-service.test.js +700 -0
  150. package/dist/src/integrations/linear/__tests__/sync-service.test.js.map +1 -0
  151. package/dist/src/integrations/linear/client.d.ts +28 -1
  152. package/dist/src/integrations/linear/client.d.ts.map +1 -1
  153. package/dist/src/integrations/linear/client.js +87 -0
  154. package/dist/src/integrations/linear/client.js.map +1 -1
  155. package/dist/src/integrations/linear/sync-service.d.ts +25 -0
  156. package/dist/src/integrations/linear/sync-service.d.ts.map +1 -0
  157. package/dist/src/integrations/linear/sync-service.js +198 -0
  158. package/dist/src/integrations/linear/sync-service.js.map +1 -0
  159. package/dist/src/integrations/linear/sync.d.ts +23 -1
  160. package/dist/src/integrations/linear/sync.d.ts.map +1 -1
  161. package/dist/src/integrations/linear/sync.js +156 -9
  162. package/dist/src/integrations/linear/sync.js.map +1 -1
  163. package/dist/src/integrations/linear/types.d.ts +75 -0
  164. package/dist/src/integrations/linear/types.d.ts.map +1 -0
  165. package/dist/src/integrations/linear/types.js +2 -0
  166. package/dist/src/integrations/linear/types.js.map +1 -0
  167. package/dist/src/integrations/linear/webhook-server.d.ts +32 -0
  168. package/dist/src/integrations/linear/webhook-server.d.ts.map +1 -0
  169. package/dist/src/integrations/linear/webhook-server.js +188 -0
  170. package/dist/src/integrations/linear/webhook-server.js.map +1 -0
  171. package/dist/src/integrations/linear/webhook.d.ts +95 -0
  172. package/dist/src/integrations/linear/webhook.d.ts.map +1 -0
  173. package/dist/src/integrations/linear/webhook.js +204 -0
  174. package/dist/src/integrations/linear/webhook.js.map +1 -0
  175. package/dist/src/integrations/mcp/__tests__/server.test.d.ts +5 -0
  176. package/dist/src/integrations/mcp/__tests__/server.test.d.ts.map +1 -0
  177. package/dist/src/integrations/mcp/__tests__/server.test.js +790 -0
  178. package/dist/src/integrations/mcp/__tests__/server.test.js.map +1 -0
  179. package/dist/src/integrations/mcp/server.d.ts +6 -0
  180. package/dist/src/integrations/mcp/server.d.ts.map +1 -1
  181. package/dist/src/integrations/mcp/server.js +490 -54
  182. package/dist/src/integrations/mcp/server.js.map +1 -1
  183. package/dist/src/servers/production/auth-middleware.d.ts +2 -2
  184. package/dist/src/servers/production/auth-middleware.d.ts.map +1 -1
  185. package/dist/src/servers/production/auth-middleware.js +1 -1
  186. package/dist/src/servers/production/auth-middleware.js.map +1 -1
  187. package/dist/src/services/config-service.d.ts +44 -0
  188. package/dist/src/services/config-service.d.ts.map +1 -0
  189. package/dist/src/services/config-service.js +61 -0
  190. package/dist/src/services/config-service.js.map +1 -0
  191. package/dist/src/services/context-service.d.ts +17 -0
  192. package/dist/src/services/context-service.d.ts.map +1 -0
  193. package/dist/src/services/context-service.js +88 -0
  194. package/dist/src/services/context-service.js.map +1 -0
  195. package/dist/src/types/task.d.ts +17 -0
  196. package/dist/src/types/task.d.ts.map +1 -0
  197. package/dist/src/types/task.js +2 -0
  198. package/dist/src/types/task.js.map +1 -0
  199. package/dist/src/utils/logger.d.ts +13 -0
  200. package/dist/src/utils/logger.d.ts.map +1 -0
  201. package/dist/src/utils/logger.js +52 -0
  202. package/dist/src/utils/logger.js.map +1 -0
  203. package/package.json +16 -3
@@ -12,9 +12,8 @@ import { join, dirname } from 'path';
12
12
  import { execSync } from 'child_process';
13
13
  import { FrameManager } from '../../core/context/frame-manager.js';
14
14
  import { PebblesTaskStore, } from '../../features/tasks/pebbles-task-store.js';
15
- // TODO: Temporarily disabled due to TypeScript errors
16
- // import { LinearAuthManager, LinearOAuthSetup } from '../integrations/linear/auth.js';
17
- // import { LinearSyncEngine, DEFAULT_SYNC_CONFIG } from '../integrations/linear/sync.js';
15
+ import { LinearAuthManager } from '../linear/auth.js';
16
+ import { LinearSyncEngine, DEFAULT_SYNC_CONFIG } from '../linear/sync.js';
18
17
  import { logger } from '../../core/monitoring/logger.js';
19
18
  import { BrowserMCPIntegration } from '../../features/browser/browser-mcp.js';
20
19
  // ============================================
@@ -26,9 +25,8 @@ class LocalStackMemoryMCP {
26
25
  projectRoot;
27
26
  frameManager;
28
27
  taskStore;
29
- // TODO: Temporarily disabled
30
- // private linearAuthManager: LinearAuthManager;
31
- // private linearSync: LinearSyncEngine;
28
+ linearAuthManager;
29
+ linearSync;
32
30
  projectId;
33
31
  contexts = new Map();
34
32
  browserMCP;
@@ -49,13 +47,9 @@ class LocalStackMemoryMCP {
49
47
  this.frameManager = new FrameManager(this.db, this.projectId);
50
48
  // Initialize task store
51
49
  this.taskStore = new PebblesTaskStore(this.projectRoot, this.db);
52
- // TODO: Initialize Linear integration (temporarily disabled)
53
- // this.linearAuthManager = new LinearAuthManager(this.projectRoot);
54
- // this.linearSync = new LinearSyncEngine(
55
- // this.taskStore,
56
- // this.linearAuthManager,
57
- // DEFAULT_SYNC_CONFIG
58
- // );
50
+ // Initialize Linear integration
51
+ this.linearAuthManager = new LinearAuthManager(this.projectRoot);
52
+ this.linearSync = new LinearSyncEngine(this.taskStore, this.linearAuthManager, DEFAULT_SYNC_CONFIG);
59
53
  // Initialize MCP server
60
54
  this.server = new Server({
61
55
  name: 'stackmemory-local',
@@ -388,7 +382,7 @@ class LocalStackMemoryMCP {
388
382
  },
389
383
  {
390
384
  name: 'get_active_tasks',
391
- description: 'Get currently active tasks',
385
+ description: 'Get currently active tasks synced from Linear',
392
386
  inputSchema: {
393
387
  type: 'object',
394
388
  properties: {
@@ -396,6 +390,30 @@ class LocalStackMemoryMCP {
396
390
  type: 'string',
397
391
  description: 'Filter by specific frame ID',
398
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
+ },
399
417
  },
400
418
  },
401
419
  },
@@ -425,6 +443,78 @@ class LocalStackMemoryMCP {
425
443
  required: ['taskId', 'dependsOnId'],
426
444
  },
427
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
+ },
428
518
  ],
429
519
  };
430
520
  });
@@ -437,32 +527,84 @@ class LocalStackMemoryMCP {
437
527
  }),
438
528
  }), async (request) => {
439
529
  const { name, arguments: args } = request.params;
440
- switch (name) {
441
- case 'get_context':
442
- return this.handleGetContext(args);
443
- case 'add_decision':
444
- return this.handleAddDecision(args);
445
- case 'start_frame':
446
- return this.handleStartFrame(args);
447
- case 'close_frame':
448
- return this.handleCloseFrame(args);
449
- case 'add_anchor':
450
- return this.handleAddAnchor(args);
451
- case 'get_hot_stack':
452
- return this.handleGetHotStack(args);
453
- case 'create_task':
454
- return this.handleCreateTask(args);
455
- case 'update_task_status':
456
- return this.handleUpdateTaskStatus(args);
457
- case 'get_active_tasks':
458
- return this.handleGetActiveTasks(args);
459
- case 'get_task_metrics':
460
- return this.handleGetTaskMetrics(args);
461
- case 'add_task_dependency':
462
- return this.handleAddTaskDependency(args);
463
- default:
464
- throw new Error(`Unknown tool: ${name}`);
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
+ }
465
606
  }
607
+ return result;
466
608
  });
467
609
  }
468
610
  async handleGetContext(args) {
@@ -712,31 +854,70 @@ class LocalStackMemoryMCP {
712
854
  }
713
855
  }
714
856
  async handleGetActiveTasks(args) {
715
- const { frameId } = args;
716
- const activeTasks = this.taskStore.getActiveTasks(frameId);
717
- if (activeTasks.length === 0) {
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) {
718
883
  return {
719
884
  content: [
720
885
  {
721
886
  type: 'text',
722
- text: frameId
723
- ? `šŸ“ No active tasks in frame ${frameId}`
724
- : 'šŸ“ No active tasks in project',
887
+ text: search
888
+ ? `šŸ“ No tasks matching "${search}"`
889
+ : 'šŸ“ No active tasks found',
725
890
  },
726
891
  ],
727
892
  };
728
893
  }
729
- let response = 'šŸ“ **Active Tasks**\n\n';
730
- activeTasks.forEach((task) => {
731
- const priority = task.priority.toUpperCase();
732
- const status = task.status.replace('_', ' ').toUpperCase();
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] || '⚪';
733
905
  const effort = task.estimated_effort
734
- ? ` (${task.estimated_effort}m)`
906
+ ? ` (~${task.estimated_effort}m)`
735
907
  : '';
736
- response += `- **[${status}]** ${task.title}${effort}\n`;
737
- response += ` Priority: ${priority} | ID: ${task.id}\n`;
738
- if (task.depends_on.length > 0) {
739
- response += ` Depends on: ${task.depends_on.join(', ')}\n`;
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`;
740
921
  }
741
922
  response += '\n';
742
923
  });
@@ -807,6 +988,261 @@ class LocalStackMemoryMCP {
807
988
  };
808
989
  }
809
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
+ }
810
1246
  async start() {
811
1247
  const transport = new StdioServerTransport();
812
1248
  await this.server.connect(transport);