@stackmemoryai/stackmemory 0.5.29 → 0.5.31
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 +53 -32
- package/dist/core/database/batch-operations.js +29 -4
- package/dist/core/database/batch-operations.js.map +2 -2
- package/dist/core/database/connection-pool.js +13 -2
- package/dist/core/database/connection-pool.js.map +2 -2
- package/dist/core/database/migration-manager.js +130 -34
- package/dist/core/database/migration-manager.js.map +2 -2
- package/dist/core/database/paradedb-adapter.js +23 -7
- package/dist/core/database/paradedb-adapter.js.map +2 -2
- package/dist/core/database/query-router.js +8 -3
- package/dist/core/database/query-router.js.map +2 -2
- package/dist/core/database/sqlite-adapter.js +152 -33
- package/dist/core/database/sqlite-adapter.js.map +2 -2
- package/dist/integrations/linear/auth.js +34 -20
- package/dist/integrations/linear/auth.js.map +2 -2
- package/dist/integrations/linear/auto-sync.js +18 -8
- package/dist/integrations/linear/auto-sync.js.map +2 -2
- package/dist/integrations/linear/client.js +42 -9
- package/dist/integrations/linear/client.js.map +2 -2
- package/dist/integrations/linear/migration.js +94 -36
- package/dist/integrations/linear/migration.js.map +2 -2
- package/dist/integrations/linear/oauth-server.js +77 -34
- package/dist/integrations/linear/oauth-server.js.map +2 -2
- package/dist/integrations/linear/rest-client.js +13 -3
- package/dist/integrations/linear/rest-client.js.map +2 -2
- package/dist/integrations/linear/sync-service.js +18 -15
- package/dist/integrations/linear/sync-service.js.map +2 -2
- package/dist/integrations/linear/sync.js +12 -4
- package/dist/integrations/linear/sync.js.map +2 -2
- package/dist/integrations/linear/unified-sync.js +33 -8
- package/dist/integrations/linear/unified-sync.js.map +2 -2
- package/dist/integrations/linear/webhook-handler.js +5 -1
- package/dist/integrations/linear/webhook-handler.js.map +2 -2
- package/dist/integrations/linear/webhook-server.js +7 -7
- package/dist/integrations/linear/webhook-server.js.map +2 -2
- package/dist/integrations/linear/webhook.js +9 -2
- package/dist/integrations/linear/webhook.js.map +2 -2
- package/dist/integrations/mcp/schemas.js +147 -0
- package/dist/integrations/mcp/schemas.js.map +7 -0
- package/dist/integrations/mcp/server.js +19 -3
- package/dist/integrations/mcp/server.js.map +2 -2
- package/package.json +1 -1
|
@@ -6,13 +6,17 @@ const __dirname = __pathDirname(__filename);
|
|
|
6
6
|
import express from "express";
|
|
7
7
|
import crypto from "crypto";
|
|
8
8
|
import { LinearSyncService } from "./sync-service.js";
|
|
9
|
+
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
|
|
9
10
|
import { logger } from "../../core/monitoring/logger.js";
|
|
10
11
|
import chalk from "chalk";
|
|
11
12
|
function getEnv(key, defaultValue) {
|
|
12
13
|
const value = process.env[key];
|
|
13
14
|
if (value === void 0) {
|
|
14
15
|
if (defaultValue !== void 0) return defaultValue;
|
|
15
|
-
throw new
|
|
16
|
+
throw new IntegrationError(
|
|
17
|
+
`Environment variable ${key} is required`,
|
|
18
|
+
ErrorCode.LINEAR_WEBHOOK_FAILED
|
|
19
|
+
);
|
|
16
20
|
}
|
|
17
21
|
return value;
|
|
18
22
|
}
|
|
@@ -72,9 +76,7 @@ class LinearWebhookServer {
|
|
|
72
76
|
return res.status(401).json({ error: "Unauthorized" });
|
|
73
77
|
}
|
|
74
78
|
const payload = JSON.parse(req.body.toString());
|
|
75
|
-
logger.info(
|
|
76
|
-
`Received webhook: ${payload.type} - ${payload.action}`
|
|
77
|
-
);
|
|
79
|
+
logger.info(`Received webhook: ${payload.type} - ${payload.action}`);
|
|
78
80
|
this.eventQueue.push(payload);
|
|
79
81
|
this.processQueue();
|
|
80
82
|
return res.status(200).json({
|
|
@@ -137,9 +139,7 @@ class LinearWebhookServer {
|
|
|
137
139
|
const issue = data;
|
|
138
140
|
switch (action) {
|
|
139
141
|
case "create":
|
|
140
|
-
logger.info(
|
|
141
|
-
`New issue created: ${issue.identifier} - ${issue.title}`
|
|
142
|
-
);
|
|
142
|
+
logger.info(`New issue created: ${issue.identifier} - ${issue.title}`);
|
|
143
143
|
await this.syncService.syncIssueToLocal(issue);
|
|
144
144
|
break;
|
|
145
145
|
case "update":
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/webhook-server.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n\nimport express from 'express';\nimport crypto from 'crypto';\nimport http from 'http';\nimport {\n LinearWebhookPayload,\n LinearIssue,\n LinearComment,\n LinearProject,\n} from './types.js';\nimport { LinearSyncService } from './sync-service.js';\nimport { LinearIssue as ClientLinearIssue } from './client.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport chalk from 'chalk';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new
|
|
5
|
-
"mappings": ";;;;;AAEA,OAAO,aAAa;AACpB,OAAO,YAAY;AAQnB,SAAS,yBAAyB;AAElC,SAAS,cAAc;AACvB,OAAO,WAAW;AAElB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI,
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\n\nimport express from 'express';\nimport crypto from 'crypto';\nimport http from 'http';\nimport {\n LinearWebhookPayload,\n LinearIssue,\n LinearComment,\n LinearProject,\n} from './types.js';\nimport { LinearSyncService } from './sync-service.js';\nimport { LinearIssue as ClientLinearIssue } from './client.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport chalk from 'chalk';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new IntegrationError(\n `Environment variable ${key} is required`,\n ErrorCode.LINEAR_WEBHOOK_FAILED\n );\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\nexport interface WebhookServerConfig {\n port?: number;\n host?: string;\n webhookSecret?: string;\n maxPayloadSize?: string;\n rateLimit?: {\n windowMs?: number;\n max?: number;\n };\n}\n\nexport class LinearWebhookServer {\n private app: express.Application;\n private server: http.Server | null = null;\n // Using singleton logger from monitoring\n private syncService: LinearSyncService;\n private config: WebhookServerConfig;\n private eventQueue: LinearWebhookPayload[] = [];\n private isProcessing = false;\n\n constructor(config?: WebhookServerConfig) {\n this.app = express();\n // Use singleton logger\n this.syncService = new LinearSyncService();\n\n this.config = {\n port: config?.port || parseInt(process.env['WEBHOOK_PORT'] || '3456'),\n host: config?.host || process.env['WEBHOOK_HOST'] || 'localhost',\n webhookSecret:\n config?.webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'],\n maxPayloadSize: config?.maxPayloadSize || '10mb',\n rateLimit: {\n windowMs: config?.rateLimit?.windowMs || 60000,\n max: config?.rateLimit?.max || 100,\n },\n };\n\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n this.app.use(\n express.raw({\n type: 'application/json',\n limit: this.config.maxPayloadSize,\n })\n );\n\n this.app.use((req, res, next) => {\n res.setHeader('X-Powered-By', 'StackMemory');\n next();\n });\n }\n\n private setupRoutes(): void {\n this.app.get('/health', (req, res) => {\n res.json({\n status: 'healthy',\n service: 'linear-webhook',\n timestamp: new Date().toISOString(),\n queue: this.eventQueue.length,\n processing: this.isProcessing,\n });\n });\n\n this.app.post('/webhook/linear', async (req, res) => {\n try {\n if (!this.verifyWebhookSignature(req)) {\n logger.warn('Invalid webhook signature');\n return res.status(401).json({ error: 'Unauthorized' });\n }\n\n const payload = JSON.parse(req.body.toString()) as LinearWebhookPayload;\n\n logger.info(`Received webhook: ${payload.type} - ${payload.action}`);\n\n this.eventQueue.push(payload);\n this.processQueue();\n\n return res.status(200).json({\n status: 'accepted',\n queued: true,\n });\n } catch (error: unknown) {\n logger.error('Webhook processing error:', error);\n return res.status(500).json({ error: 'Internal server error' });\n }\n });\n\n this.app.use((req, res) => {\n res.status(404).json({ error: 'Not found' });\n });\n }\n\n private verifyWebhookSignature(req: express.Request): boolean {\n if (!this.config.webhookSecret) {\n logger.warn('No webhook secret configured, accepting all webhooks');\n return true;\n }\n\n const signature = req.headers['linear-signature'] as string;\n if (!signature) {\n return false;\n }\n\n const hash = crypto\n .createHmac('sha256', this.config.webhookSecret)\n .update(req.body)\n .digest('hex');\n\n return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(hash));\n }\n\n private async processQueue(): Promise<void> {\n if (this.isProcessing || this.eventQueue.length === 0) {\n return;\n }\n\n this.isProcessing = true;\n\n while (this.eventQueue.length > 0) {\n const event = this.eventQueue.shift()!;\n\n try {\n await this.handleWebhookEvent(event);\n } catch (error: unknown) {\n logger.error(`Failed to process event: ${event.type}`, error);\n }\n }\n\n this.isProcessing = false;\n }\n\n private async handleWebhookEvent(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const { type, action, data } = payload;\n\n switch (type) {\n case 'Issue':\n await this.handleIssueEvent(action, data as LinearIssue);\n break;\n case 'Comment':\n await this.handleCommentEvent(action, data as LinearComment);\n break;\n case 'Project':\n await this.handleProjectEvent(action, data as LinearProject);\n break;\n default:\n logger.debug(`Unhandled event type: ${type}`);\n }\n }\n\n private async handleIssueEvent(\n action: string,\n data: LinearIssue\n ): Promise<void> {\n const issue = data as ClientLinearIssue;\n\n switch (action) {\n case 'create':\n logger.info(`New issue created: ${issue.identifier} - ${issue.title}`);\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'update':\n logger.info(`Issue updated: ${issue.identifier} - ${issue.title}`);\n await this.syncService.syncIssueToLocal(issue);\n break;\n case 'remove':\n logger.info(`Issue removed: ${issue.identifier}`);\n await this.syncService.removeLocalIssue(issue.identifier);\n break;\n default:\n logger.debug(`Unhandled issue action: ${action}`);\n }\n }\n\n private async handleCommentEvent(\n action: string,\n data: LinearComment\n ): Promise<void> {\n logger.debug(`Comment event: ${action}`, { issueId: data.issue?.id });\n }\n\n private async handleProjectEvent(\n action: string,\n data: LinearProject\n ): Promise<void> {\n logger.debug(`Project event: ${action}`, { projectId: data.id });\n }\n\n public async start(): Promise<void> {\n return new Promise((resolve) => {\n this.server = this.app.listen(\n this.config.port!,\n this.config.host!,\n () => {\n console.log(\n chalk.green('\u2713') + chalk.bold(' Linear Webhook Server Started')\n );\n console.log(\n chalk.cyan(' URL: ') +\n `http://${this.config.host}:${this.config.port}/webhook/linear`\n );\n console.log(\n chalk.cyan(' Health: ') +\n `http://${this.config.host}:${this.config.port}/health`\n );\n\n if (!this.config.webhookSecret) {\n console.log(\n chalk.yellow(\n ' \u26A0 Warning: No webhook secret configured (insecure)'\n )\n );\n }\n\n resolve();\n }\n );\n });\n }\n\n public async stop(): Promise<void> {\n return new Promise((resolve) => {\n if (this.server) {\n this.server.close(() => {\n logger.info('Webhook server stopped');\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n}\n\n// Standalone execution support\nif (process.argv[1] === new URL(import.meta.url).pathname) {\n const server = new LinearWebhookServer();\n\n server.start().catch((error) => {\n console.error(chalk.red('Failed to start webhook server:'), error);\n process.exit(1);\n });\n\n process.on('SIGINT', async () => {\n console.log(chalk.yellow('\\n\\nShutting down webhook server...'));\n await server.stop();\n process.exit(0);\n });\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAEA,OAAO,aAAa;AACpB,OAAO,YAAY;AAQnB,SAAS,yBAAyB;AAElC,SAAS,kBAAkB,iBAAiB;AAC5C,SAAS,cAAc;AACvB,OAAO,WAAW;AAElB,SAAS,OAAO,KAAa,cAA+B;AAC1D,QAAM,QAAQ,QAAQ,IAAI,GAAG;AAC7B,MAAI,UAAU,QAAW;AACvB,QAAI,iBAAiB,OAAW,QAAO;AACvC,UAAM,IAAI;AAAA,MACR,wBAAwB,GAAG;AAAA,MAC3B,UAAU;AAAA,IACZ;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAiC;AACvD,SAAO,QAAQ,IAAI,GAAG;AACxB;AAaO,MAAM,oBAAoB;AAAA,EACvB;AAAA,EACA,SAA6B;AAAA;AAAA,EAE7B;AAAA,EACA;AAAA,EACA,aAAqC,CAAC;AAAA,EACtC,eAAe;AAAA,EAEvB,YAAY,QAA8B;AACxC,SAAK,MAAM,QAAQ;AAEnB,SAAK,cAAc,IAAI,kBAAkB;AAEzC,SAAK,SAAS;AAAA,MACZ,MAAM,QAAQ,QAAQ,SAAS,QAAQ,IAAI,cAAc,KAAK,MAAM;AAAA,MACpE,MAAM,QAAQ,QAAQ,QAAQ,IAAI,cAAc,KAAK;AAAA,MACrD,eACE,QAAQ,iBAAiB,QAAQ,IAAI,uBAAuB;AAAA,MAC9D,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,WAAW;AAAA,QACT,UAAU,QAAQ,WAAW,YAAY;AAAA,QACzC,KAAK,QAAQ,WAAW,OAAO;AAAA,MACjC;AAAA,IACF;AAEA,SAAK,gBAAgB;AACrB,SAAK,YAAY;AAAA,EACnB;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,IAAI;AAAA,MACP,QAAQ,IAAI;AAAA,QACV,MAAM;AAAA,QACN,OAAO,KAAK,OAAO;AAAA,MACrB,CAAC;AAAA,IACH;AAEA,SAAK,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;AAC/B,UAAI,UAAU,gBAAgB,aAAa;AAC3C,WAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA,EAEQ,cAAoB;AAC1B,SAAK,IAAI,IAAI,WAAW,CAAC,KAAK,QAAQ;AACpC,UAAI,KAAK;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,OAAO,KAAK,WAAW;AAAA,QACvB,YAAY,KAAK;AAAA,MACnB,CAAC;AAAA,IACH,CAAC;AAED,SAAK,IAAI,KAAK,mBAAmB,OAAO,KAAK,QAAQ;AACnD,UAAI;AACF,YAAI,CAAC,KAAK,uBAAuB,GAAG,GAAG;AACrC,iBAAO,KAAK,2BAA2B;AACvC,iBAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,QACvD;AAEA,cAAM,UAAU,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAE9C,eAAO,KAAK,qBAAqB,QAAQ,IAAI,MAAM,QAAQ,MAAM,EAAE;AAEnE,aAAK,WAAW,KAAK,OAAO;AAC5B,aAAK,aAAa;AAElB,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UAC1B,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH,SAAS,OAAgB;AACvB,eAAO,MAAM,6BAA6B,KAAK;AAC/C,eAAO,IAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,MAChE;AAAA,IACF,CAAC;AAED,SAAK,IAAI,IAAI,CAAC,KAAK,QAAQ;AACzB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA,EAEQ,uBAAuB,KAA+B;AAC5D,QAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,aAAO,KAAK,sDAAsD;AAClE,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,IAAI,QAAQ,kBAAkB;AAChD,QAAI,CAAC,WAAW;AACd,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,OACV,WAAW,UAAU,KAAK,OAAO,aAAa,EAC9C,OAAO,IAAI,IAAI,EACf,OAAO,KAAK;AAEf,WAAO,OAAO,gBAAgB,OAAO,KAAK,SAAS,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,EACzE;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,gBAAgB,KAAK,WAAW,WAAW,GAAG;AACrD;AAAA,IACF;AAEA,SAAK,eAAe;AAEpB,WAAO,KAAK,WAAW,SAAS,GAAG;AACjC,YAAM,QAAQ,KAAK,WAAW,MAAM;AAEpC,UAAI;AACF,cAAM,KAAK,mBAAmB,KAAK;AAAA,MACrC,SAAS,OAAgB;AACvB,eAAO,MAAM,4BAA4B,MAAM,IAAI,IAAI,KAAK;AAAA,MAC9D;AAAA,IACF;AAEA,SAAK,eAAe;AAAA,EACtB;AAAA,EAEA,MAAc,mBACZ,SACe;AACf,UAAM,EAAE,MAAM,QAAQ,KAAK,IAAI;AAE/B,YAAQ,MAAM;AAAA,MACZ,KAAK;AACH,cAAM,KAAK,iBAAiB,QAAQ,IAAmB;AACvD;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF,KAAK;AACH,cAAM,KAAK,mBAAmB,QAAQ,IAAqB;AAC3D;AAAA,MACF;AACE,eAAO,MAAM,yBAAyB,IAAI,EAAE;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAc,iBACZ,QACA,MACe;AACf,UAAM,QAAQ;AAEd,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,KAAK,sBAAsB,MAAM,UAAU,MAAM,MAAM,KAAK,EAAE;AACrE,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,MAAM,MAAM,KAAK,EAAE;AACjE,cAAM,KAAK,YAAY,iBAAiB,KAAK;AAC7C;AAAA,MACF,KAAK;AACH,eAAO,KAAK,kBAAkB,MAAM,UAAU,EAAE;AAChD,cAAM,KAAK,YAAY,iBAAiB,MAAM,UAAU;AACxD;AAAA,MACF;AACE,eAAO,MAAM,2BAA2B,MAAM,EAAE;AAAA,IACpD;AAAA,EACF;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,SAAS,KAAK,OAAO,GAAG,CAAC;AAAA,EACtE;AAAA,EAEA,MAAc,mBACZ,QACA,MACe;AACf,WAAO,MAAM,kBAAkB,MAAM,IAAI,EAAE,WAAW,KAAK,GAAG,CAAC;AAAA,EACjE;AAAA,EAEA,MAAa,QAAuB;AAClC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,WAAK,SAAS,KAAK,IAAI;AAAA,QACrB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,MAAM;AACJ,kBAAQ;AAAA,YACN,MAAM,MAAM,QAAG,IAAI,MAAM,KAAK,gCAAgC;AAAA,UAChE;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,SAAS,IAClB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AACA,kBAAQ;AAAA,YACN,MAAM,KAAK,YAAY,IACrB,UAAU,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI;AAAA,UAClD;AAEA,cAAI,CAAC,KAAK,OAAO,eAAe;AAC9B,oBAAQ;AAAA,cACN,MAAM;AAAA,gBACJ;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAEA,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAa,OAAsB;AACjC,WAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,MAAM,MAAM;AACtB,iBAAO,KAAK,wBAAwB;AACpC,kBAAQ;AAAA,QACV,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,IAAI,QAAQ,KAAK,CAAC,MAAM,IAAI,IAAI,YAAY,GAAG,EAAE,UAAU;AACzD,QAAM,SAAS,IAAI,oBAAoB;AAEvC,SAAO,MAAM,EAAE,MAAM,CAAC,UAAU;AAC9B,YAAQ,MAAM,MAAM,IAAI,iCAAiC,GAAG,KAAK;AACjE,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,UAAQ,GAAG,UAAU,YAAY;AAC/B,YAAQ,IAAI,MAAM,OAAO,qCAAqC,CAAC;AAC/D,UAAM,OAAO,KAAK;AAClB,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,12 +3,16 @@ import { dirname as __pathDirname } from 'path';
|
|
|
3
3
|
const __filename = __fileURLToPath(import.meta.url);
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../../core/monitoring/logger.js";
|
|
6
|
+
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
|
|
6
7
|
import crypto from "crypto";
|
|
7
8
|
function getEnv(key, defaultValue) {
|
|
8
9
|
const value = process.env[key];
|
|
9
10
|
if (value === void 0) {
|
|
10
11
|
if (defaultValue !== void 0) return defaultValue;
|
|
11
|
-
throw new
|
|
12
|
+
throw new IntegrationError(
|
|
13
|
+
`Environment variable ${key} is required`,
|
|
14
|
+
ErrorCode.LINEAR_WEBHOOK_FAILED
|
|
15
|
+
);
|
|
12
16
|
}
|
|
13
17
|
return value;
|
|
14
18
|
}
|
|
@@ -72,7 +76,10 @@ class LinearWebhookHandler {
|
|
|
72
76
|
const validatedPayload = this.validateWebhookPayload(payload);
|
|
73
77
|
if (!validatedPayload) {
|
|
74
78
|
logger.error("Invalid webhook payload received");
|
|
75
|
-
throw new
|
|
79
|
+
throw new IntegrationError(
|
|
80
|
+
"Invalid webhook payload",
|
|
81
|
+
ErrorCode.LINEAR_WEBHOOK_FAILED
|
|
82
|
+
);
|
|
76
83
|
}
|
|
77
84
|
logger.info("Processing Linear webhook", {
|
|
78
85
|
action: validatedPayload.action,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/integrations/linear/webhook.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear for real-time sync\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport crypto from 'crypto';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new Error(`Environment variable ${key} is required`);\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\n\nexport interface LinearWebhookPayload {\n action: 'create' | 'update' | 'remove';\n createdAt: string;\n data: {\n id: string;\n identifier: string;\n title?: string;\n description?: string;\n state?: {\n id: string;\n name: string;\n type: string;\n };\n priority?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n team?: {\n id: string;\n key: string;\n name: string;\n };\n labels?: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n dueDate?: string;\n completedAt?: string;\n updatedAt: string;\n };\n type: 'Issue' | 'Comment' | 'Project' | 'Cycle';\n url: string;\n webhookId: string;\n webhookTimestamp: number;\n}\n\nexport class LinearWebhookHandler {\n private syncEngine?: LinearSyncEngine;\n private taskStore?: LinearTaskManager;\n private webhookSecret?: string;\n\n constructor(webhookSecret?: string) {\n this.webhookSecret = webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'];\n }\n\n /**\n * Set the sync engine for processing webhooks\n */\n setSyncEngine(syncEngine: LinearSyncEngine): void {\n this.syncEngine = syncEngine;\n }\n\n /**\n * Set the task store for direct updates\n */\n setTaskStore(taskStore: LinearTaskManager): void {\n this.taskStore = taskStore;\n }\n\n /**\n * Verify webhook signature\n */\n verifySignature(body: string, signature: string): boolean {\n if (!this.webhookSecret) {\n logger.warn('No webhook secret configured, skipping verification');\n return true; // Allow in development\n }\n\n const hmac = crypto.createHmac('sha256', this.webhookSecret);\n hmac.update(body);\n const expectedSignature = hmac.digest('hex');\n\n return signature === expectedSignature;\n }\n\n /**\n * Validate webhook payload structure\n */\n private validateWebhookPayload(\n payload: unknown\n ): LinearWebhookPayload | null {\n if (!payload || typeof payload !== 'object') return null;\n\n const p = payload as any;\n\n // Validate required fields\n if (!p.action || typeof p.action !== 'string') return null;\n if (!p.type || typeof p.type !== 'string') return null;\n if (!p.data || typeof p.data !== 'object') return null;\n if (!p.data.id || typeof p.data.id !== 'string') return null;\n\n // Sanitize string fields to prevent injection\n if (p.data.title && typeof p.data.title === 'string') {\n p.data.title = p.data.title.substring(0, 500); // Limit length\n }\n if (p.data.description && typeof p.data.description === 'string') {\n p.data.description = p.data.description.substring(0, 5000); // Limit length\n }\n\n return p as LinearWebhookPayload;\n }\n\n /**\n * Process incoming webhook\n */\n async processWebhook(payload: LinearWebhookPayload): Promise<void> {\n // Validate payload first\n const validatedPayload = this.validateWebhookPayload(payload);\n if (!validatedPayload) {\n logger.error('Invalid webhook payload received');\n throw new Error('Invalid webhook payload');\n }\n\n logger.info('Processing Linear webhook', {\n action: validatedPayload.action,\n type: validatedPayload.type,\n id: validatedPayload.data.id,\n });\n\n payload = validatedPayload;\n\n // Only process Issue webhooks for now\n if (payload.type !== 'Issue') {\n logger.info(`Ignoring webhook for type: ${payload.type}`);\n return;\n }\n\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreated(payload);\n break;\n case 'update':\n await this.handleIssueUpdated(payload);\n break;\n case 'remove':\n await this.handleIssueRemoved(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n }\n\n /**\n * Handle issue created in Linear\n */\n private async handleIssueCreated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue created', {\n identifier: payload.data.identifier,\n });\n\n // Check if we should sync this issue\n if (!this.shouldSyncIssue(payload.data)) {\n return;\n }\n\n // For now, just log it - full implementation would create a StackMemory task\n logger.info('Would create StackMemory task for Linear issue', {\n identifier: payload.data.identifier,\n title: payload.data.title,\n });\n\n // Create a StackMemory task from Linear issue\n if (this.taskStore) {\n try {\n const taskId = this.taskStore.createTask({\n frameId: 'linear-import', // Special frame for Linear imports\n title: payload.data.title || 'Untitled Linear Issue',\n description: payload.data.description || '',\n priority: this.mapLinearPriorityToStackMemory(payload.data.priority),\n assignee: payload.data.assignee?.email,\n tags: payload.data.labels?.map((l: any) => l.name) || [],\n });\n\n // Store mapping for future syncing\n this.storeMapping(taskId, payload.data.id);\n\n logger.info('Created StackMemory task from Linear issue', {\n stackmemoryId: taskId,\n linearId: payload.data.id,\n });\n } catch (error: unknown) {\n logger.error('Failed to create task from Linear issue', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n /**\n * Handle issue updated in Linear\n */\n private async handleIssueUpdated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue updated', {\n identifier: payload.data.identifier,\n });\n\n if (!this.syncEngine) {\n logger.warn('No sync engine configured, cannot process update');\n return;\n }\n\n // Find mapped StackMemory task\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for Linear issue', { id: payload.data.id });\n return;\n }\n\n // Check for conflicts\n const task = this.taskStore?.getTask(mapping.stackmemoryId);\n if (!task) {\n logger.warn('StackMemory task not found', { id: mapping.stackmemoryId });\n return;\n }\n\n // Update the task based on Linear changes\n let newStatus:\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled'\n | undefined;\n\n if (payload.data.state) {\n const mappedStatus = this.mapLinearStateToStatus(payload.data.state) as\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled';\n if (mappedStatus !== task.status) {\n newStatus = mappedStatus;\n }\n }\n\n if (payload.data.completedAt) {\n newStatus = 'completed';\n }\n\n // Update status if changed\n if (newStatus) {\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n newStatus,\n 'Linear webhook update'\n );\n logger.info('Updated StackMemory task status from webhook', {\n taskId: mapping.stackmemoryId,\n newStatus,\n });\n }\n\n // For other properties, we'd need to implement a more complete update method\n // For now, log what changed\n if (payload.data.title && payload.data.title !== task.title) {\n logger.info(\n 'Task title changed in Linear but not updated in StackMemory',\n {\n taskId: mapping.stackmemoryId,\n oldTitle: task.title,\n newTitle: payload.data.title,\n }\n );\n }\n }\n\n /**\n * Handle issue removed in Linear\n */\n private async handleIssueRemoved(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue removed', {\n identifier: payload.data.identifier,\n });\n\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for removed Linear issue');\n return;\n }\n\n // Mark the StackMemory task as cancelled\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n 'cancelled',\n 'Linear issue deleted'\n );\n\n logger.info('Marked StackMemory task as cancelled due to Linear deletion', {\n taskId: mapping.stackmemoryId,\n });\n }\n\n /**\n * Check if we should sync this issue\n */\n private shouldSyncIssue(issue: LinearWebhookPayload['data']): boolean {\n // Add your filtering logic here\n // For example, only sync issues from specific teams or with certain labels\n\n // Skip issues without a title\n if (!issue.title) {\n return false;\n }\n\n // Skip archived/cancelled issues\n if (issue.state?.type === 'canceled' || issue.state?.type === 'archived') {\n return false;\n }\n\n return true;\n }\n\n /**\n * Find mapping by Linear ID\n */\n private findMappingByLinearId(\n linearId: string\n ): { stackmemoryId: string; linearId: string } | null {\n // Use in-memory mapping for now\n // In production, this would query a database\n const mapping = this.taskMappings.get(linearId);\n if (mapping) {\n return { stackmemoryId: mapping, linearId };\n }\n return null;\n }\n\n // In-memory task mappings (Linear ID -> StackMemory ID)\n private taskMappings = new Map<string, string>();\n\n /**\n * Store mapping between Linear and StackMemory IDs\n */\n private storeMapping(stackmemoryId: string, linearId: string): void {\n this.taskMappings.set(linearId, stackmemoryId);\n // In production, persist to database\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToStackMemory(\n priority: number | undefined\n ): 'low' | 'medium' | 'high' | 'urgent' {\n if (!priority) return 'medium';\n if (priority <= 1) return 'urgent';\n if (priority === 2) return 'high';\n if (priority === 3) return 'medium';\n return 'low';\n }\n\n /**\n * Map Linear state to StackMemory status\n */\n private mapLinearStateToStatus(state: {\n type?: string;\n name?: string;\n }): string {\n const stateType = state.type?.toLowerCase() || state.name?.toLowerCase();\n\n switch (stateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n case 'in progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'completed';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToPriority(priority: number): number {\n // Linear uses 0-4, StackMemory uses 1-5\n return 5 - priority;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;AAKA,SAAS,cAAc;
|
|
4
|
+
"sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear for real-time sync\n */\n\nimport { logger } from '../../core/monitoring/logger.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport crypto from 'crypto';\n// Type-safe environment variable access\nfunction getEnv(key: string, defaultValue?: string): string {\n const value = process.env[key];\n if (value === undefined) {\n if (defaultValue !== undefined) return defaultValue;\n throw new IntegrationError(\n `Environment variable ${key} is required`,\n ErrorCode.LINEAR_WEBHOOK_FAILED\n );\n }\n return value;\n}\n\nfunction getOptionalEnv(key: string): string | undefined {\n return process.env[key];\n}\n\nexport interface LinearWebhookPayload {\n action: 'create' | 'update' | 'remove';\n createdAt: string;\n data: {\n id: string;\n identifier: string;\n title?: string;\n description?: string;\n state?: {\n id: string;\n name: string;\n type: string;\n };\n priority?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n team?: {\n id: string;\n key: string;\n name: string;\n };\n labels?: Array<{\n id: string;\n name: string;\n color: string;\n }>;\n dueDate?: string;\n completedAt?: string;\n updatedAt: string;\n };\n type: 'Issue' | 'Comment' | 'Project' | 'Cycle';\n url: string;\n webhookId: string;\n webhookTimestamp: number;\n}\n\nexport class LinearWebhookHandler {\n private syncEngine?: LinearSyncEngine;\n private taskStore?: LinearTaskManager;\n private webhookSecret?: string;\n\n constructor(webhookSecret?: string) {\n this.webhookSecret = webhookSecret || process.env['LINEAR_WEBHOOK_SECRET'];\n }\n\n /**\n * Set the sync engine for processing webhooks\n */\n setSyncEngine(syncEngine: LinearSyncEngine): void {\n this.syncEngine = syncEngine;\n }\n\n /**\n * Set the task store for direct updates\n */\n setTaskStore(taskStore: LinearTaskManager): void {\n this.taskStore = taskStore;\n }\n\n /**\n * Verify webhook signature\n */\n verifySignature(body: string, signature: string): boolean {\n if (!this.webhookSecret) {\n logger.warn('No webhook secret configured, skipping verification');\n return true; // Allow in development\n }\n\n const hmac = crypto.createHmac('sha256', this.webhookSecret);\n hmac.update(body);\n const expectedSignature = hmac.digest('hex');\n\n return signature === expectedSignature;\n }\n\n /**\n * Validate webhook payload structure\n */\n private validateWebhookPayload(\n payload: unknown\n ): LinearWebhookPayload | null {\n if (!payload || typeof payload !== 'object') return null;\n\n const p = payload as any;\n\n // Validate required fields\n if (!p.action || typeof p.action !== 'string') return null;\n if (!p.type || typeof p.type !== 'string') return null;\n if (!p.data || typeof p.data !== 'object') return null;\n if (!p.data.id || typeof p.data.id !== 'string') return null;\n\n // Sanitize string fields to prevent injection\n if (p.data.title && typeof p.data.title === 'string') {\n p.data.title = p.data.title.substring(0, 500); // Limit length\n }\n if (p.data.description && typeof p.data.description === 'string') {\n p.data.description = p.data.description.substring(0, 5000); // Limit length\n }\n\n return p as LinearWebhookPayload;\n }\n\n /**\n * Process incoming webhook\n */\n async processWebhook(payload: LinearWebhookPayload): Promise<void> {\n // Validate payload first\n const validatedPayload = this.validateWebhookPayload(payload);\n if (!validatedPayload) {\n logger.error('Invalid webhook payload received');\n throw new IntegrationError(\n 'Invalid webhook payload',\n ErrorCode.LINEAR_WEBHOOK_FAILED\n );\n }\n\n logger.info('Processing Linear webhook', {\n action: validatedPayload.action,\n type: validatedPayload.type,\n id: validatedPayload.data.id,\n });\n\n payload = validatedPayload;\n\n // Only process Issue webhooks for now\n if (payload.type !== 'Issue') {\n logger.info(`Ignoring webhook for type: ${payload.type}`);\n return;\n }\n\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreated(payload);\n break;\n case 'update':\n await this.handleIssueUpdated(payload);\n break;\n case 'remove':\n await this.handleIssueRemoved(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n }\n\n /**\n * Handle issue created in Linear\n */\n private async handleIssueCreated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue created', {\n identifier: payload.data.identifier,\n });\n\n // Check if we should sync this issue\n if (!this.shouldSyncIssue(payload.data)) {\n return;\n }\n\n // For now, just log it - full implementation would create a StackMemory task\n logger.info('Would create StackMemory task for Linear issue', {\n identifier: payload.data.identifier,\n title: payload.data.title,\n });\n\n // Create a StackMemory task from Linear issue\n if (this.taskStore) {\n try {\n const taskId = this.taskStore.createTask({\n frameId: 'linear-import', // Special frame for Linear imports\n title: payload.data.title || 'Untitled Linear Issue',\n description: payload.data.description || '',\n priority: this.mapLinearPriorityToStackMemory(payload.data.priority),\n assignee: payload.data.assignee?.email,\n tags: payload.data.labels?.map((l: any) => l.name) || [],\n });\n\n // Store mapping for future syncing\n this.storeMapping(taskId, payload.data.id);\n\n logger.info('Created StackMemory task from Linear issue', {\n stackmemoryId: taskId,\n linearId: payload.data.id,\n });\n } catch (error: unknown) {\n logger.error('Failed to create task from Linear issue', {\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n }\n\n /**\n * Handle issue updated in Linear\n */\n private async handleIssueUpdated(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue updated', {\n identifier: payload.data.identifier,\n });\n\n if (!this.syncEngine) {\n logger.warn('No sync engine configured, cannot process update');\n return;\n }\n\n // Find mapped StackMemory task\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for Linear issue', { id: payload.data.id });\n return;\n }\n\n // Check for conflicts\n const task = this.taskStore?.getTask(mapping.stackmemoryId);\n if (!task) {\n logger.warn('StackMemory task not found', { id: mapping.stackmemoryId });\n return;\n }\n\n // Update the task based on Linear changes\n let newStatus:\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled'\n | undefined;\n\n if (payload.data.state) {\n const mappedStatus = this.mapLinearStateToStatus(payload.data.state) as\n | 'pending'\n | 'in_progress'\n | 'completed'\n | 'cancelled';\n if (mappedStatus !== task.status) {\n newStatus = mappedStatus;\n }\n }\n\n if (payload.data.completedAt) {\n newStatus = 'completed';\n }\n\n // Update status if changed\n if (newStatus) {\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n newStatus,\n 'Linear webhook update'\n );\n logger.info('Updated StackMemory task status from webhook', {\n taskId: mapping.stackmemoryId,\n newStatus,\n });\n }\n\n // For other properties, we'd need to implement a more complete update method\n // For now, log what changed\n if (payload.data.title && payload.data.title !== task.title) {\n logger.info(\n 'Task title changed in Linear but not updated in StackMemory',\n {\n taskId: mapping.stackmemoryId,\n oldTitle: task.title,\n newTitle: payload.data.title,\n }\n );\n }\n }\n\n /**\n * Handle issue removed in Linear\n */\n private async handleIssueRemoved(\n payload: LinearWebhookPayload\n ): Promise<void> {\n logger.info('Linear issue removed', {\n identifier: payload.data.identifier,\n });\n\n const mapping = this.findMappingByLinearId(payload.data.id);\n if (!mapping) {\n logger.info('No mapping found for removed Linear issue');\n return;\n }\n\n // Mark the StackMemory task as cancelled\n this.taskStore?.updateTaskStatus(\n mapping.stackmemoryId,\n 'cancelled',\n 'Linear issue deleted'\n );\n\n logger.info('Marked StackMemory task as cancelled due to Linear deletion', {\n taskId: mapping.stackmemoryId,\n });\n }\n\n /**\n * Check if we should sync this issue\n */\n private shouldSyncIssue(issue: LinearWebhookPayload['data']): boolean {\n // Add your filtering logic here\n // For example, only sync issues from specific teams or with certain labels\n\n // Skip issues without a title\n if (!issue.title) {\n return false;\n }\n\n // Skip archived/cancelled issues\n if (issue.state?.type === 'canceled' || issue.state?.type === 'archived') {\n return false;\n }\n\n return true;\n }\n\n /**\n * Find mapping by Linear ID\n */\n private findMappingByLinearId(\n linearId: string\n ): { stackmemoryId: string; linearId: string } | null {\n // Use in-memory mapping for now\n // In production, this would query a database\n const mapping = this.taskMappings.get(linearId);\n if (mapping) {\n return { stackmemoryId: mapping, linearId };\n }\n return null;\n }\n\n // In-memory task mappings (Linear ID -> StackMemory ID)\n private taskMappings = new Map<string, string>();\n\n /**\n * Store mapping between Linear and StackMemory IDs\n */\n private storeMapping(stackmemoryId: string, linearId: string): void {\n this.taskMappings.set(linearId, stackmemoryId);\n // In production, persist to database\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToStackMemory(\n priority: number | undefined\n ): 'low' | 'medium' | 'high' | 'urgent' {\n if (!priority) return 'medium';\n if (priority <= 1) return 'urgent';\n if (priority === 2) return 'high';\n if (priority === 3) return 'medium';\n return 'low';\n }\n\n /**\n * Map Linear state to StackMemory status\n */\n private mapLinearStateToStatus(state: {\n type?: string;\n name?: string;\n }): string {\n const stateType = state.type?.toLowerCase() || state.name?.toLowerCase();\n\n switch (stateType) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n case 'in progress':\n return 'in_progress';\n case 'completed':\n case 'done':\n return 'completed';\n case 'canceled':\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n\n /**\n * Map Linear priority to StackMemory priority\n */\n private mapLinearPriorityToPriority(priority: number): number {\n // Linear uses 0-4, StackMemory uses 1-5\n return 5 - priority;\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
const GetContextSchema = z.object({
|
|
7
|
+
query: z.string().min(1).max(5e3).optional(),
|
|
8
|
+
limit: z.number().int().min(1).max(100).default(10)
|
|
9
|
+
});
|
|
10
|
+
const AddDecisionSchema = z.object({
|
|
11
|
+
content: z.string().min(1).max(1e4),
|
|
12
|
+
type: z.enum(["decision", "constraint", "learning"])
|
|
13
|
+
});
|
|
14
|
+
const StartFrameSchema = z.object({
|
|
15
|
+
name: z.string().min(1).max(500),
|
|
16
|
+
type: z.enum(["task", "subtask", "tool_scope", "review", "write", "debug"]),
|
|
17
|
+
constraints: z.array(z.string().max(1e3)).optional()
|
|
18
|
+
});
|
|
19
|
+
const CloseFrameSchema = z.object({
|
|
20
|
+
result: z.string().max(1e4).optional(),
|
|
21
|
+
outputs: z.record(z.unknown()).optional()
|
|
22
|
+
});
|
|
23
|
+
const AddAnchorSchema = z.object({
|
|
24
|
+
type: z.enum([
|
|
25
|
+
"FACT",
|
|
26
|
+
"DECISION",
|
|
27
|
+
"CONSTRAINT",
|
|
28
|
+
"INTERFACE_CONTRACT",
|
|
29
|
+
"TODO",
|
|
30
|
+
"RISK"
|
|
31
|
+
]),
|
|
32
|
+
text: z.string().min(1).max(1e4),
|
|
33
|
+
priority: z.number().int().min(0).max(10).default(5)
|
|
34
|
+
});
|
|
35
|
+
const GetHotStackSchema = z.object({
|
|
36
|
+
maxDepth: z.number().int().min(1).max(50).default(10)
|
|
37
|
+
});
|
|
38
|
+
const CreateTaskSchema = z.object({
|
|
39
|
+
title: z.string().min(1).max(500),
|
|
40
|
+
description: z.string().max(1e4).optional(),
|
|
41
|
+
priority: z.enum(["low", "medium", "high", "critical"]).default("medium"),
|
|
42
|
+
tags: z.array(z.string().max(100)).optional(),
|
|
43
|
+
parentId: z.string().uuid().optional()
|
|
44
|
+
});
|
|
45
|
+
const UpdateTaskStatusSchema = z.object({
|
|
46
|
+
taskId: z.string().min(1),
|
|
47
|
+
status: z.enum(["pending", "in_progress", "completed", "blocked"]),
|
|
48
|
+
note: z.string().max(5e3).optional()
|
|
49
|
+
});
|
|
50
|
+
const GetActiveTasksSchema = z.object({
|
|
51
|
+
status: z.enum(["pending", "in_progress", "blocked"]).optional(),
|
|
52
|
+
limit: z.number().int().min(1).max(100).default(50)
|
|
53
|
+
});
|
|
54
|
+
const AddTaskDependencySchema = z.object({
|
|
55
|
+
taskId: z.string().min(1),
|
|
56
|
+
dependsOn: z.string().min(1)
|
|
57
|
+
});
|
|
58
|
+
const LinearSyncSchema = z.object({
|
|
59
|
+
direction: z.enum(["to_linear", "from_linear", "bidirectional"]).optional(),
|
|
60
|
+
force: z.boolean().default(false)
|
|
61
|
+
});
|
|
62
|
+
const LinearUpdateTaskSchema = z.object({
|
|
63
|
+
taskId: z.string().min(1),
|
|
64
|
+
updates: z.object({
|
|
65
|
+
title: z.string().min(1).max(500).optional(),
|
|
66
|
+
description: z.string().max(1e4).optional(),
|
|
67
|
+
status: z.string().optional(),
|
|
68
|
+
priority: z.number().int().min(0).max(4).optional()
|
|
69
|
+
})
|
|
70
|
+
});
|
|
71
|
+
const LinearGetTasksSchema = z.object({
|
|
72
|
+
status: z.string().optional(),
|
|
73
|
+
limit: z.number().int().min(1).max(100).default(50)
|
|
74
|
+
});
|
|
75
|
+
const GetTracesSchema = z.object({
|
|
76
|
+
sessionId: z.string().optional(),
|
|
77
|
+
limit: z.number().int().min(1).max(1e3).default(100),
|
|
78
|
+
since: z.string().datetime().optional()
|
|
79
|
+
});
|
|
80
|
+
const CompressOldTracesSchema = z.object({
|
|
81
|
+
olderThanDays: z.number().int().min(1).max(365).default(7)
|
|
82
|
+
});
|
|
83
|
+
const SmartContextSchema = z.object({
|
|
84
|
+
query: z.string().min(1).max(5e3),
|
|
85
|
+
maxResults: z.number().int().min(1).max(50).default(10),
|
|
86
|
+
includeRelated: z.boolean().default(true)
|
|
87
|
+
});
|
|
88
|
+
const GetSummarySchema = z.object({
|
|
89
|
+
timeRange: z.object({
|
|
90
|
+
start: z.string().datetime(),
|
|
91
|
+
end: z.string().datetime()
|
|
92
|
+
}).optional(),
|
|
93
|
+
includeMetrics: z.boolean().default(true)
|
|
94
|
+
});
|
|
95
|
+
const DiscoverSchema = z.object({
|
|
96
|
+
pattern: z.string().max(500).optional(),
|
|
97
|
+
maxDepth: z.number().int().min(1).max(20).default(5)
|
|
98
|
+
});
|
|
99
|
+
const RelatedFilesSchema = z.object({
|
|
100
|
+
filePath: z.string().min(1).max(1e3),
|
|
101
|
+
limit: z.number().int().min(1).max(50).default(10)
|
|
102
|
+
});
|
|
103
|
+
const SearchSchema = z.object({
|
|
104
|
+
query: z.string().min(1).max(1e3),
|
|
105
|
+
type: z.enum(["code", "context", "task", "all"]).default("all"),
|
|
106
|
+
limit: z.number().int().min(1).max(100).default(20)
|
|
107
|
+
});
|
|
108
|
+
import { ValidationError, ErrorCode } from "../../core/errors/index.js";
|
|
109
|
+
function validateInput(schema, input, toolName) {
|
|
110
|
+
const result = schema.safeParse(input);
|
|
111
|
+
if (!result.success) {
|
|
112
|
+
const errors = result.error.errors.map((e) => ({
|
|
113
|
+
path: e.path.join("."),
|
|
114
|
+
message: e.message
|
|
115
|
+
}));
|
|
116
|
+
throw new ValidationError(
|
|
117
|
+
`Invalid input for tool '${toolName}': ${errors.map((e) => e.message).join(", ")}`,
|
|
118
|
+
ErrorCode.VALIDATION_FAILED,
|
|
119
|
+
{ toolName, errors }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return result.data;
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
AddAnchorSchema,
|
|
126
|
+
AddDecisionSchema,
|
|
127
|
+
AddTaskDependencySchema,
|
|
128
|
+
CloseFrameSchema,
|
|
129
|
+
CompressOldTracesSchema,
|
|
130
|
+
CreateTaskSchema,
|
|
131
|
+
DiscoverSchema,
|
|
132
|
+
GetActiveTasksSchema,
|
|
133
|
+
GetContextSchema,
|
|
134
|
+
GetHotStackSchema,
|
|
135
|
+
GetSummarySchema,
|
|
136
|
+
GetTracesSchema,
|
|
137
|
+
LinearGetTasksSchema,
|
|
138
|
+
LinearSyncSchema,
|
|
139
|
+
LinearUpdateTaskSchema,
|
|
140
|
+
RelatedFilesSchema,
|
|
141
|
+
SearchSchema,
|
|
142
|
+
SmartContextSchema,
|
|
143
|
+
StartFrameSchema,
|
|
144
|
+
UpdateTaskStatusSchema,
|
|
145
|
+
validateInput
|
|
146
|
+
};
|
|
147
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/integrations/mcp/schemas.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Zod schemas for MCP tool input validation\n * These schemas validate tool parameters before processing\n */\n\nimport { z } from 'zod';\n\n// ============================================\n// Context Tools\n// ============================================\n\nexport const GetContextSchema = z.object({\n query: z.string().min(1).max(5000).optional(),\n limit: z.number().int().min(1).max(100).default(10),\n});\nexport type GetContextInput = z.infer<typeof GetContextSchema>;\n\nexport const AddDecisionSchema = z.object({\n content: z.string().min(1).max(10000),\n type: z.enum(['decision', 'constraint', 'learning']),\n});\nexport type AddDecisionInput = z.infer<typeof AddDecisionSchema>;\n\n// ============================================\n// Frame Tools\n// ============================================\n\nexport const StartFrameSchema = z.object({\n name: z.string().min(1).max(500),\n type: z.enum(['task', 'subtask', 'tool_scope', 'review', 'write', 'debug']),\n constraints: z.array(z.string().max(1000)).optional(),\n});\nexport type StartFrameInput = z.infer<typeof StartFrameSchema>;\n\nexport const CloseFrameSchema = z.object({\n result: z.string().max(10000).optional(),\n outputs: z.record(z.unknown()).optional(),\n});\nexport type CloseFrameInput = z.infer<typeof CloseFrameSchema>;\n\nexport const AddAnchorSchema = z.object({\n type: z.enum([\n 'FACT',\n 'DECISION',\n 'CONSTRAINT',\n 'INTERFACE_CONTRACT',\n 'TODO',\n 'RISK',\n ]),\n text: z.string().min(1).max(10000),\n priority: z.number().int().min(0).max(10).default(5),\n});\nexport type AddAnchorInput = z.infer<typeof AddAnchorSchema>;\n\nexport const GetHotStackSchema = z.object({\n maxDepth: z.number().int().min(1).max(50).default(10),\n});\nexport type GetHotStackInput = z.infer<typeof GetHotStackSchema>;\n\n// ============================================\n// Task Tools\n// ============================================\n\nexport const CreateTaskSchema = z.object({\n title: z.string().min(1).max(500),\n description: z.string().max(10000).optional(),\n priority: z.enum(['low', 'medium', 'high', 'critical']).default('medium'),\n tags: z.array(z.string().max(100)).optional(),\n parentId: z.string().uuid().optional(),\n});\nexport type CreateTaskInput = z.infer<typeof CreateTaskSchema>;\n\nexport const UpdateTaskStatusSchema = z.object({\n taskId: z.string().min(1),\n status: z.enum(['pending', 'in_progress', 'completed', 'blocked']),\n note: z.string().max(5000).optional(),\n});\nexport type UpdateTaskStatusInput = z.infer<typeof UpdateTaskStatusSchema>;\n\nexport const GetActiveTasksSchema = z.object({\n status: z.enum(['pending', 'in_progress', 'blocked']).optional(),\n limit: z.number().int().min(1).max(100).default(50),\n});\nexport type GetActiveTasksInput = z.infer<typeof GetActiveTasksSchema>;\n\nexport const AddTaskDependencySchema = z.object({\n taskId: z.string().min(1),\n dependsOn: z.string().min(1),\n});\nexport type AddTaskDependencyInput = z.infer<typeof AddTaskDependencySchema>;\n\n// ============================================\n// Linear Integration Tools\n// ============================================\n\nexport const LinearSyncSchema = z.object({\n direction: z.enum(['to_linear', 'from_linear', 'bidirectional']).optional(),\n force: z.boolean().default(false),\n});\nexport type LinearSyncInput = z.infer<typeof LinearSyncSchema>;\n\nexport const LinearUpdateTaskSchema = z.object({\n taskId: z.string().min(1),\n updates: z.object({\n title: z.string().min(1).max(500).optional(),\n description: z.string().max(10000).optional(),\n status: z.string().optional(),\n priority: z.number().int().min(0).max(4).optional(),\n }),\n});\nexport type LinearUpdateTaskInput = z.infer<typeof LinearUpdateTaskSchema>;\n\nexport const LinearGetTasksSchema = z.object({\n status: z.string().optional(),\n limit: z.number().int().min(1).max(100).default(50),\n});\nexport type LinearGetTasksInput = z.infer<typeof LinearGetTasksSchema>;\n\n// ============================================\n// Trace Tools\n// ============================================\n\nexport const GetTracesSchema = z.object({\n sessionId: z.string().optional(),\n limit: z.number().int().min(1).max(1000).default(100),\n since: z.string().datetime().optional(),\n});\nexport type GetTracesInput = z.infer<typeof GetTracesSchema>;\n\nexport const CompressOldTracesSchema = z.object({\n olderThanDays: z.number().int().min(1).max(365).default(7),\n});\nexport type CompressOldTracesInput = z.infer<typeof CompressOldTracesSchema>;\n\n// ============================================\n// Smart Context Tools\n// ============================================\n\nexport const SmartContextSchema = z.object({\n query: z.string().min(1).max(5000),\n maxResults: z.number().int().min(1).max(50).default(10),\n includeRelated: z.boolean().default(true),\n});\nexport type SmartContextInput = z.infer<typeof SmartContextSchema>;\n\nexport const GetSummarySchema = z.object({\n timeRange: z\n .object({\n start: z.string().datetime(),\n end: z.string().datetime(),\n })\n .optional(),\n includeMetrics: z.boolean().default(true),\n});\nexport type GetSummaryInput = z.infer<typeof GetSummarySchema>;\n\n// ============================================\n// Discovery Tools\n// ============================================\n\nexport const DiscoverSchema = z.object({\n pattern: z.string().max(500).optional(),\n maxDepth: z.number().int().min(1).max(20).default(5),\n});\nexport type DiscoverInput = z.infer<typeof DiscoverSchema>;\n\nexport const RelatedFilesSchema = z.object({\n filePath: z.string().min(1).max(1000),\n limit: z.number().int().min(1).max(50).default(10),\n});\nexport type RelatedFilesInput = z.infer<typeof RelatedFilesSchema>;\n\nexport const SearchSchema = z.object({\n query: z.string().min(1).max(1000),\n type: z.enum(['code', 'context', 'task', 'all']).default('all'),\n limit: z.number().int().min(1).max(100).default(20),\n});\nexport type SearchInput = z.infer<typeof SearchSchema>;\n\n// ============================================\n// Validation Helper\n// ============================================\n\nimport { ValidationError, ErrorCode } from '../../core/errors/index.js';\n\n/**\n * Validate input using a Zod schema\n * Throws ValidationError with details if validation fails\n */\nexport function validateInput<T>(\n schema: z.ZodSchema<T>,\n input: unknown,\n toolName: string\n): T {\n const result = schema.safeParse(input);\n\n if (!result.success) {\n const errors = result.error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n }));\n\n throw new ValidationError(\n `Invalid input for tool '${toolName}': ${errors.map((e) => e.message).join(', ')}`,\n ErrorCode.VALIDATION_FAILED,\n { toolName, errors }\n );\n }\n\n return result.data;\n}\n"],
|
|
5
|
+
"mappings": ";;;;AAKA,SAAS,SAAS;AAMX,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI,EAAE,SAAS;AAAA,EAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AACpD,CAAC;AAGM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAK;AAAA,EACpC,MAAM,EAAE,KAAK,CAAC,YAAY,cAAc,UAAU,CAAC;AACrD,CAAC;AAOM,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,MAAM,EAAE,KAAK,CAAC,QAAQ,WAAW,cAAc,UAAU,SAAS,OAAO,CAAC;AAAA,EAC1E,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAI,CAAC,EAAE,SAAS;AACtD,CAAC;AAGM,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,QAAQ,EAAE,OAAO,EAAE,IAAI,GAAK,EAAE,SAAS;AAAA,EACvC,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS;AAC1C,CAAC;AAGM,MAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,MAAM,EAAE,KAAK;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAAA,EACD,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAK;AAAA,EACjC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC;AACrD,CAAC;AAGM,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACxC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE;AACtD,CAAC;AAOM,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAChC,aAAa,EAAE,OAAO,EAAE,IAAI,GAAK,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,KAAK,CAAC,OAAO,UAAU,QAAQ,UAAU,CAAC,EAAE,QAAQ,QAAQ;AAAA,EACxE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,CAAC,EAAE,SAAS;AAAA,EAC5C,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACvC,CAAC;AAGM,MAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,QAAQ,EAAE,KAAK,CAAC,WAAW,eAAe,aAAa,SAAS,CAAC;AAAA,EACjE,MAAM,EAAE,OAAO,EAAE,IAAI,GAAI,EAAE,SAAS;AACtC,CAAC;AAGM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,QAAQ,EAAE,KAAK,CAAC,WAAW,eAAe,SAAS,CAAC,EAAE,SAAS;AAAA,EAC/D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AACpD,CAAC;AAGM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC;AAC7B,CAAC;AAOM,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,WAAW,EAAE,KAAK,CAAC,aAAa,eAAe,eAAe,CAAC,EAAE,SAAS;AAAA,EAC1E,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAClC,CAAC;AAGM,MAAM,yBAAyB,EAAE,OAAO;AAAA,EAC7C,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,SAAS,EAAE,OAAO;AAAA,IAChB,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,IAC3C,aAAa,EAAE,OAAO,EAAE,IAAI,GAAK,EAAE,SAAS;AAAA,IAC5C,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,IAC5B,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACpD,CAAC;AACH,CAAC;AAGM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AACpD,CAAC;AAOM,MAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI,EAAE,QAAQ,GAAG;AAAA,EACpD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AACxC,CAAC;AAGM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,CAAC;AAC3D,CAAC;AAOM,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI;AAAA,EACjC,YAAY,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE;AAAA,EACtD,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAC1C,CAAC;AAGM,MAAM,mBAAmB,EAAE,OAAO;AAAA,EACvC,WAAW,EACR,OAAO;AAAA,IACN,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,CAAC,EACA,SAAS;AAAA,EACZ,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAC1C,CAAC;AAOM,MAAM,iBAAiB,EAAE,OAAO;AAAA,EACrC,SAAS,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACtC,UAAU,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC;AACrD,CAAC;AAGM,MAAM,qBAAqB,EAAE,OAAO;AAAA,EACzC,UAAU,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI;AAAA,EACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE;AACnD,CAAC;AAGM,MAAM,eAAe,EAAE,OAAO;AAAA,EACnC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAI;AAAA,EACjC,MAAM,EAAE,KAAK,CAAC,QAAQ,WAAW,QAAQ,KAAK,CAAC,EAAE,QAAQ,KAAK;AAAA,EAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AACpD,CAAC;AAOD,SAAS,iBAAiB,iBAAiB;AAMpC,SAAS,cACd,QACA,OACA,UACG;AACH,QAAM,SAAS,OAAO,UAAU,KAAK;AAErC,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAS,OAAO,MAAM,OAAO,IAAI,CAAC,OAAO;AAAA,MAC7C,MAAM,EAAE,KAAK,KAAK,GAAG;AAAA,MACrB,SAAS,EAAE;AAAA,IACb,EAAE;AAEF,UAAM,IAAI;AAAA,MACR,2BAA2B,QAAQ,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MAChF,UAAU;AAAA,MACV,EAAE,UAAU,OAAO;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,OAAO;AAChB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -7,6 +7,12 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import Database from "better-sqlite3";
|
|
10
|
+
import {
|
|
11
|
+
validateInput,
|
|
12
|
+
StartFrameSchema,
|
|
13
|
+
AddAnchorSchema,
|
|
14
|
+
CreateTaskSchema
|
|
15
|
+
} from "./schemas.js";
|
|
10
16
|
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
11
17
|
import { join, dirname } from "path";
|
|
12
18
|
import { execSync } from "child_process";
|
|
@@ -904,7 +910,11 @@ ID: ${id}`
|
|
|
904
910
|
};
|
|
905
911
|
}
|
|
906
912
|
async handleStartFrame(args) {
|
|
907
|
-
const { name, type, constraints } =
|
|
913
|
+
const { name, type, constraints } = validateInput(
|
|
914
|
+
StartFrameSchema,
|
|
915
|
+
args,
|
|
916
|
+
"start_frame"
|
|
917
|
+
);
|
|
908
918
|
const inputs = {};
|
|
909
919
|
if (constraints) {
|
|
910
920
|
inputs.constraints = constraints;
|
|
@@ -964,7 +974,11 @@ Stack depth: ${newStackDepth}`
|
|
|
964
974
|
};
|
|
965
975
|
}
|
|
966
976
|
async handleAddAnchor(args) {
|
|
967
|
-
const { type, text, priority
|
|
977
|
+
const { type, text, priority } = validateInput(
|
|
978
|
+
AddAnchorSchema,
|
|
979
|
+
args,
|
|
980
|
+
"add_anchor"
|
|
981
|
+
);
|
|
968
982
|
const anchorId = this.frameManager.addAnchor(type, text, priority);
|
|
969
983
|
this.frameManager.addEvent("decision", {
|
|
970
984
|
anchor_type: type,
|
|
@@ -1043,7 +1057,9 @@ Anchor ID: ${anchorId}`
|
|
|
1043
1057
|
).run(query, response);
|
|
1044
1058
|
}
|
|
1045
1059
|
async handleCreateTask(args) {
|
|
1046
|
-
const
|
|
1060
|
+
const validated = validateInput(CreateTaskSchema, args, "create_task");
|
|
1061
|
+
const { title, description, priority, tags } = validated;
|
|
1062
|
+
const { estimatedEffort, dependsOn } = args;
|
|
1047
1063
|
const currentFrameId = this.frameManager.getCurrentFrameId();
|
|
1048
1064
|
if (!currentFrameId) {
|
|
1049
1065
|
return {
|