@stackmemoryai/stackmemory 0.5.66 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +139 -45
  2. package/bin/codex-sm +6 -0
  3. package/bin/opencode-sm +1 -1
  4. package/dist/src/cli/claude-sm.js +162 -25
  5. package/dist/src/cli/claude-sm.js.map +2 -2
  6. package/dist/src/cli/commands/ping.js +14 -0
  7. package/dist/src/cli/commands/ping.js.map +7 -0
  8. package/dist/src/cli/commands/ralph.js +103 -1
  9. package/dist/src/cli/commands/ralph.js.map +2 -2
  10. package/dist/src/cli/commands/retrieval.js +1 -1
  11. package/dist/src/cli/commands/retrieval.js.map +2 -2
  12. package/dist/src/cli/commands/skills.js +201 -6
  13. package/dist/src/cli/commands/skills.js.map +2 -2
  14. package/dist/src/cli/index.js +66 -27
  15. package/dist/src/cli/index.js.map +2 -2
  16. package/dist/src/core/digest/types.js +1 -1
  17. package/dist/src/core/digest/types.js.map +1 -1
  18. package/dist/src/core/extensions/provider-adapter.js +2 -5
  19. package/dist/src/core/extensions/provider-adapter.js.map +2 -2
  20. package/dist/src/core/retrieval/llm-provider.js +2 -2
  21. package/dist/src/core/retrieval/llm-provider.js.map +1 -1
  22. package/dist/src/core/retrieval/types.js +1 -1
  23. package/dist/src/core/retrieval/types.js.map +1 -1
  24. package/dist/src/features/sweep/pty-wrapper.js +15 -5
  25. package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
  26. package/dist/src/features/workers/tmux-manager.js +71 -0
  27. package/dist/src/features/workers/tmux-manager.js.map +7 -0
  28. package/dist/src/features/workers/worker-registry.js +52 -0
  29. package/dist/src/features/workers/worker-registry.js.map +7 -0
  30. package/dist/src/integrations/linear/webhook-handler.js +82 -0
  31. package/dist/src/integrations/linear/webhook-handler.js.map +2 -2
  32. package/dist/src/integrations/mcp/server.js +16 -10
  33. package/dist/src/integrations/mcp/server.js.map +2 -2
  34. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +2 -2
  35. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js.map +2 -2
  36. package/dist/src/orchestrators/multimodal/constants.js +1 -1
  37. package/dist/src/orchestrators/multimodal/constants.js.map +1 -1
  38. package/dist/src/orchestrators/multimodal/harness.js +28 -29
  39. package/dist/src/orchestrators/multimodal/harness.js.map +2 -2
  40. package/dist/src/orchestrators/multimodal/providers.js +35 -22
  41. package/dist/src/orchestrators/multimodal/providers.js.map +2 -2
  42. package/dist/src/skills/claude-skills.js +116 -1
  43. package/dist/src/skills/claude-skills.js.map +2 -2
  44. package/dist/src/skills/linear-task-runner.js +262 -0
  45. package/dist/src/skills/linear-task-runner.js.map +7 -0
  46. package/dist/src/skills/recursive-agent-orchestrator.js +114 -85
  47. package/dist/src/skills/recursive-agent-orchestrator.js.map +2 -2
  48. package/dist/src/skills/spec-generator-skill.js +441 -0
  49. package/dist/src/skills/spec-generator-skill.js.map +7 -0
  50. package/package.json +12 -5
  51. package/scripts/install-claude-hooks-auto.js +23 -9
  52. package/scripts/install-claude-hooks.sh +2 -2
  53. package/templates/claude-hooks/hooks.json +4 -2
  54. package/templates/claude-hooks/on-task-complete.js +91 -0
  55. package/templates/claude-hooks/post-edit-sweep.js +7 -10
  56. package/templates/claude-hooks/skill-eval.cjs +411 -0
  57. package/templates/claude-hooks/skill-eval.sh +31 -0
  58. package/templates/claude-hooks/skill-rules.json +274 -0
@@ -7,6 +7,8 @@ import { LinearSyncEngine } from "./sync.js";
7
7
  import { LinearAuthManager } from "./auth.js";
8
8
  import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
9
9
  import { logger } from "../../core/monitoring/logger.js";
10
+ import { ClaudeCodeSubagentClient } from "../claude-code/subagent-client.js";
11
+ const AUTOMATION_LABELS = ["automated", "claude-code", "stackmemory"];
10
12
  function getEnv(key, defaultValue) {
11
13
  const value = process.env[key];
12
14
  if (value === void 0) {
@@ -115,6 +117,86 @@ class LinearWebhookHandler {
115
117
  }
116
118
  await this.storeLinearMapping(taskId, issue.id, issue.identifier);
117
119
  logger.info(`Created task ${taskId} from Linear issue ${issue.identifier}`);
120
+ if (this.shouldSpawnSubagent(issue)) {
121
+ await this.spawnSubagentForIssue(issue);
122
+ }
123
+ }
124
+ /**
125
+ * Check if issue should trigger subagent spawn
126
+ */
127
+ shouldSpawnSubagent(issue) {
128
+ if (!issue.labels) return false;
129
+ const labelNames = issue.labels.map((l) => l.name.toLowerCase());
130
+ return AUTOMATION_LABELS.some((label) => labelNames.includes(label));
131
+ }
132
+ /**
133
+ * Spawn a Claude Code subagent for the issue
134
+ */
135
+ async spawnSubagentForIssue(issue) {
136
+ logger.info(`Spawning subagent for ${issue.identifier}`);
137
+ try {
138
+ const client = new ClaudeCodeSubagentClient();
139
+ const agentType = this.determineAgentType(issue.labels || []);
140
+ const task = this.buildTaskPrompt(issue);
141
+ const sourceUrl = this.extractSourceUrl(issue.description);
142
+ const result = await client.executeSubagent({
143
+ type: agentType,
144
+ task,
145
+ context: {
146
+ linearIssueId: issue.id,
147
+ linearIdentifier: issue.identifier,
148
+ linearUrl: issue.url,
149
+ sourceUrl: sourceUrl || issue.url
150
+ },
151
+ timeout: 5 * 60 * 1e3
152
+ // 5 min
153
+ });
154
+ logger.info(`Subagent completed for ${issue.identifier}`, {
155
+ sessionId: result.sessionId,
156
+ status: result.status
157
+ });
158
+ } catch (error) {
159
+ logger.error(`Failed to spawn subagent for ${issue.identifier}`, {
160
+ error: error instanceof Error ? error.message : "Unknown error"
161
+ });
162
+ }
163
+ }
164
+ /**
165
+ * Determine agent type from issue labels
166
+ */
167
+ determineAgentType(labels) {
168
+ const lowerLabels = labels.map((l) => l.name.toLowerCase());
169
+ if (lowerLabels.some((l) => l.includes("review") || l.includes("pr"))) {
170
+ return "review";
171
+ }
172
+ if (lowerLabels.some((l) => l.includes("explore") || l.includes("research"))) {
173
+ return "context";
174
+ }
175
+ return "code";
176
+ }
177
+ /**
178
+ * Build task prompt from Linear issue
179
+ */
180
+ buildTaskPrompt(issue) {
181
+ const parts = [`Linear Issue: ${issue.identifier} - ${issue.title}`];
182
+ if (issue.description) {
183
+ const quoteMatch = issue.description.match(/^>\s*(.+?)(?:\n\n|$)/s);
184
+ if (quoteMatch) {
185
+ parts.push("", "Context:", quoteMatch[1].replace(/^>\s*/gm, ""));
186
+ } else {
187
+ parts.push("", "Description:", issue.description);
188
+ }
189
+ }
190
+ parts.push("", `URL: ${issue.url}`);
191
+ return parts.join("\n");
192
+ }
193
+ /**
194
+ * Extract source URL from description
195
+ */
196
+ extractSourceUrl(description) {
197
+ if (!description) return void 0;
198
+ const urlMatch = description.match(/\*\*Source:\*\*\s*\[.+?\]\((.+?)\)/);
199
+ return urlMatch?.[1];
118
200
  }
119
201
  /**
120
202
  * Handle issue update
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/integrations/linear/webhook-handler.ts"],
4
- "sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear to update local task store\n */\n\nimport { createHmac } from 'crypto';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearAuthManager } from './auth.js';\nimport { LinearClient } from './client.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport type { Request, Response } from 'express';\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: 'backlog' | 'unstarted' | 'started' | 'completed' | 'cancelled';\n };\n priority?: number;\n estimate?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n labels?: Array<{ id: string; name: string }>;\n updatedAt: string;\n url: string;\n };\n type: 'Issue';\n organizationId: string;\n webhookId: string;\n}\n\nexport class LinearWebhookHandler {\n private taskStore: LinearTaskManager;\n private syncEngine: LinearSyncEngine | null = null;\n private webhookSecret: string;\n\n constructor(taskStore: LinearTaskManager, webhookSecret: string) {\n this.taskStore = taskStore;\n this.webhookSecret = webhookSecret;\n\n // Initialize sync engine if API key is available\n if (process.env['LINEAR_API_KEY']) {\n const authManager = new LinearAuthManager();\n this.syncEngine = new LinearSyncEngine(taskStore, authManager, {\n enabled: true,\n direction: 'from_linear',\n autoSync: false,\n conflictResolution: 'linear_wins',\n });\n }\n }\n\n /**\n * Verify webhook signature\n */\n private verifySignature(payload: string, signature: string): boolean {\n const hmac = createHmac('sha256', this.webhookSecret);\n hmac.update(payload);\n const expectedSignature = hmac.digest('hex');\n return signature === expectedSignature;\n }\n\n /**\n * Handle incoming webhook from Linear\n */\n async handleWebhook(req: Request, res: Response): Promise<void> {\n try {\n // Get raw body for signature verification\n const rawBody = JSON.stringify(req.body);\n const signature = req.headers['linear-signature'] as string;\n\n // Verify signature\n if (!this.verifySignature(rawBody, signature)) {\n logger.error('Invalid webhook signature');\n res.status(401).json({ error: 'Invalid signature' });\n return;\n }\n\n const payload = req.body as LinearWebhookPayload;\n\n // Only process Issue webhooks\n if (payload.type !== 'Issue') {\n res.status(200).json({ message: 'Ignored non-issue webhook' });\n return;\n }\n\n // Process based on action\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreate(payload);\n break;\n case 'update':\n await this.handleIssueUpdate(payload);\n break;\n case 'remove':\n await this.handleIssueRemove(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n\n res.status(200).json({ message: 'Webhook processed successfully' });\n } catch (error: unknown) {\n logger.error('Failed to process webhook:', error as Error);\n res.status(500).json({ error: 'Failed to process webhook' });\n }\n }\n\n /**\n * Handle issue creation\n */\n private async handleIssueCreate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Check if task already exists locally\n const existingTasks = this.taskStore.getActiveTasks();\n const exists = existingTasks.some(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (exists) {\n logger.info(`Task ${issue.identifier} already exists locally`);\n return;\n }\n\n // Create local task\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description: issue.description || '',\n priority: this.mapLinearPriorityToLocal(issue.priority),\n frameId: 'linear-webhook',\n tags: issue.labels?.map((l) => l.name) || ['linear'],\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n assignee: issue.assignee?.name,\n });\n\n // Update task status if not pending\n const status = this.mapLinearStateToLocalStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Synced from Linear (${issue.state.name})`\n );\n }\n\n // Store Linear mapping\n await this.storeLinearMapping(taskId, issue.id, issue.identifier);\n\n logger.info(`Created task ${taskId} from Linear issue ${issue.identifier}`);\n }\n\n /**\n * Handle issue update\n */\n private async handleIssueUpdate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find local task by Linear ID or identifier\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (!localTask) {\n // Task doesn't exist locally, create it\n await this.handleIssueCreate(payload);\n return;\n }\n\n // Update task status\n const newStatus = this.mapLinearStateToLocalStatus(issue.state.type);\n if (newStatus !== localTask.status) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n newStatus,\n `Updated from Linear (${issue.state.name})`\n );\n }\n\n // Update priority if changed\n const newPriority = this.mapLinearPriorityToLocal(issue.priority);\n if (newPriority !== localTask.priority) {\n // Note: Would need to add updateTaskPriority method to taskStore\n logger.info(\n `Priority changed for ${issue.identifier}: ${localTask.priority} -> ${newPriority}`\n );\n }\n\n logger.info(\n `Updated task ${localTask.id} from Linear issue ${issue.identifier}`\n );\n }\n\n /**\n * Handle issue removal\n */\n private async handleIssueRemove(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find and cancel local task\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (localTask) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n 'cancelled',\n `Removed in Linear`\n );\n logger.info(\n `Cancelled task ${localTask.id} (Linear issue ${issue.identifier} was removed)`\n );\n }\n }\n\n /**\n * Store Linear mapping for a task\n */\n private async storeLinearMapping(\n taskId: string,\n linearId: string,\n linearIdentifier: string\n ): Promise<void> {\n // This would update the linear-mappings.json file\n // For now, just log it\n logger.info(\n `Mapped task ${taskId} to Linear ${linearIdentifier} (${linearId})`\n );\n }\n\n /**\n * Map Linear priority to local priority\n */\n private mapLinearPriorityToLocal(\n priority?: number\n ): 'urgent' | 'high' | 'medium' | 'low' {\n if (!priority) return 'medium';\n switch (priority) {\n case 0:\n return 'urgent';\n case 1:\n return 'high';\n case 2:\n return 'medium';\n case 3:\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n\n /**\n * Map Linear state to local status\n */\n private mapLinearStateToLocalStatus(\n state: string\n ): 'pending' | 'in_progress' | 'completed' | 'cancelled' | 'blocked' {\n switch (state) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * Linear Webhook Handler\n * Processes incoming webhooks from Linear to update local task store\n */\n\nimport { createHmac } from 'crypto';\nimport { LinearTaskManager } from '../../features/tasks/linear-task-manager.js';\nimport { LinearSyncEngine } from './sync.js';\nimport { LinearAuthManager } from './auth.js';\nimport { LinearClient } from './client.js';\nimport { IntegrationError, ErrorCode } from '../../core/errors/index.js';\nimport { logger } from '../../core/monitoring/logger.js';\nimport { ClaudeCodeSubagentClient } from '../claude-code/subagent-client.js';\nimport type { Request, Response } from 'express';\n\n/** Labels that trigger automated Claude Code subagent */\nconst AUTOMATION_LABELS = ['automated', 'claude-code', 'stackmemory'];\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: 'backlog' | 'unstarted' | 'started' | 'completed' | 'cancelled';\n };\n priority?: number;\n estimate?: number;\n assignee?: {\n id: string;\n name: string;\n email: string;\n };\n labels?: Array<{ id: string; name: string }>;\n updatedAt: string;\n url: string;\n };\n type: 'Issue';\n organizationId: string;\n webhookId: string;\n}\n\nexport class LinearWebhookHandler {\n private taskStore: LinearTaskManager;\n private syncEngine: LinearSyncEngine | null = null;\n private webhookSecret: string;\n\n constructor(taskStore: LinearTaskManager, webhookSecret: string) {\n this.taskStore = taskStore;\n this.webhookSecret = webhookSecret;\n\n // Initialize sync engine if API key is available\n if (process.env['LINEAR_API_KEY']) {\n const authManager = new LinearAuthManager();\n this.syncEngine = new LinearSyncEngine(taskStore, authManager, {\n enabled: true,\n direction: 'from_linear',\n autoSync: false,\n conflictResolution: 'linear_wins',\n });\n }\n }\n\n /**\n * Verify webhook signature\n */\n private verifySignature(payload: string, signature: string): boolean {\n const hmac = createHmac('sha256', this.webhookSecret);\n hmac.update(payload);\n const expectedSignature = hmac.digest('hex');\n return signature === expectedSignature;\n }\n\n /**\n * Handle incoming webhook from Linear\n */\n async handleWebhook(req: Request, res: Response): Promise<void> {\n try {\n // Get raw body for signature verification\n const rawBody = JSON.stringify(req.body);\n const signature = req.headers['linear-signature'] as string;\n\n // Verify signature\n if (!this.verifySignature(rawBody, signature)) {\n logger.error('Invalid webhook signature');\n res.status(401).json({ error: 'Invalid signature' });\n return;\n }\n\n const payload = req.body as LinearWebhookPayload;\n\n // Only process Issue webhooks\n if (payload.type !== 'Issue') {\n res.status(200).json({ message: 'Ignored non-issue webhook' });\n return;\n }\n\n // Process based on action\n switch (payload.action) {\n case 'create':\n await this.handleIssueCreate(payload);\n break;\n case 'update':\n await this.handleIssueUpdate(payload);\n break;\n case 'remove':\n await this.handleIssueRemove(payload);\n break;\n default:\n logger.warn(`Unknown webhook action: ${payload.action}`);\n }\n\n res.status(200).json({ message: 'Webhook processed successfully' });\n } catch (error: unknown) {\n logger.error('Failed to process webhook:', error as Error);\n res.status(500).json({ error: 'Failed to process webhook' });\n }\n }\n\n /**\n * Handle issue creation\n */\n private async handleIssueCreate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Check if task already exists locally\n const existingTasks = this.taskStore.getActiveTasks();\n const exists = existingTasks.some(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (exists) {\n logger.info(`Task ${issue.identifier} already exists locally`);\n return;\n }\n\n // Create local task\n const taskId = this.taskStore.createTask({\n title: `[${issue.identifier}] ${issue.title}`,\n description: issue.description || '',\n priority: this.mapLinearPriorityToLocal(issue.priority),\n frameId: 'linear-webhook',\n tags: issue.labels?.map((l) => l.name) || ['linear'],\n estimatedEffort: issue.estimate ? issue.estimate * 60 : undefined,\n assignee: issue.assignee?.name,\n });\n\n // Update task status if not pending\n const status = this.mapLinearStateToLocalStatus(issue.state.type);\n if (status !== 'pending') {\n this.taskStore.updateTaskStatus(\n taskId,\n status,\n `Synced from Linear (${issue.state.name})`\n );\n }\n\n // Store Linear mapping\n await this.storeLinearMapping(taskId, issue.id, issue.identifier);\n\n logger.info(`Created task ${taskId} from Linear issue ${issue.identifier}`);\n\n // Check if we should spawn a Claude Code subagent\n if (this.shouldSpawnSubagent(issue)) {\n await this.spawnSubagentForIssue(issue);\n }\n }\n\n /**\n * Check if issue should trigger subagent spawn\n */\n private shouldSpawnSubagent(issue: LinearWebhookPayload['data']): boolean {\n if (!issue.labels) return false;\n const labelNames = issue.labels.map((l) => l.name.toLowerCase());\n return AUTOMATION_LABELS.some((label) => labelNames.includes(label));\n }\n\n /**\n * Spawn a Claude Code subagent for the issue\n */\n private async spawnSubagentForIssue(\n issue: LinearWebhookPayload['data']\n ): Promise<void> {\n logger.info(`Spawning subagent for ${issue.identifier}`);\n\n try {\n const client = new ClaudeCodeSubagentClient();\n const agentType = this.determineAgentType(issue.labels || []);\n const task = this.buildTaskPrompt(issue);\n const sourceUrl = this.extractSourceUrl(issue.description);\n\n const result = await client.executeSubagent({\n type: agentType,\n task,\n context: {\n linearIssueId: issue.id,\n linearIdentifier: issue.identifier,\n linearUrl: issue.url,\n sourceUrl: sourceUrl || issue.url,\n },\n timeout: 5 * 60 * 1000, // 5 min\n });\n\n logger.info(`Subagent completed for ${issue.identifier}`, {\n sessionId: result.sessionId,\n status: result.status,\n });\n } catch (error) {\n logger.error(`Failed to spawn subagent for ${issue.identifier}`, {\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n\n /**\n * Determine agent type from issue labels\n */\n private determineAgentType(\n labels: Array<{ name: string }>\n ): 'code' | 'review' | 'context' {\n const lowerLabels = labels.map((l) => l.name.toLowerCase());\n\n if (lowerLabels.some((l) => l.includes('review') || l.includes('pr'))) {\n return 'review';\n }\n\n if (\n lowerLabels.some((l) => l.includes('explore') || l.includes('research'))\n ) {\n return 'context';\n }\n\n return 'code';\n }\n\n /**\n * Build task prompt from Linear issue\n */\n private buildTaskPrompt(issue: LinearWebhookPayload['data']): string {\n const parts = [`Linear Issue: ${issue.identifier} - ${issue.title}`];\n\n if (issue.description) {\n const quoteMatch = issue.description.match(/^>\\s*(.+?)(?:\\n\\n|$)/s);\n if (quoteMatch) {\n parts.push('', 'Context:', quoteMatch[1].replace(/^>\\s*/gm, ''));\n } else {\n parts.push('', 'Description:', issue.description);\n }\n }\n\n parts.push('', `URL: ${issue.url}`);\n return parts.join('\\n');\n }\n\n /**\n * Extract source URL from description\n */\n private extractSourceUrl(description?: string): string | undefined {\n if (!description) return undefined;\n const urlMatch = description.match(/\\*\\*Source:\\*\\*\\s*\\[.+?\\]\\((.+?)\\)/);\n return urlMatch?.[1];\n }\n\n /**\n * Handle issue update\n */\n private async handleIssueUpdate(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find local task by Linear ID or identifier\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (!localTask) {\n // Task doesn't exist locally, create it\n await this.handleIssueCreate(payload);\n return;\n }\n\n // Update task status\n const newStatus = this.mapLinearStateToLocalStatus(issue.state.type);\n if (newStatus !== localTask.status) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n newStatus,\n `Updated from Linear (${issue.state.name})`\n );\n }\n\n // Update priority if changed\n const newPriority = this.mapLinearPriorityToLocal(issue.priority);\n if (newPriority !== localTask.priority) {\n // Note: Would need to add updateTaskPriority method to taskStore\n logger.info(\n `Priority changed for ${issue.identifier}: ${localTask.priority} -> ${newPriority}`\n );\n }\n\n logger.info(\n `Updated task ${localTask.id} from Linear issue ${issue.identifier}`\n );\n }\n\n /**\n * Handle issue removal\n */\n private async handleIssueRemove(\n payload: LinearWebhookPayload\n ): Promise<void> {\n const issue = payload.data;\n\n // Find and cancel local task\n const tasks = this.taskStore.getActiveTasks();\n const localTask = tasks.find(\n (t) =>\n t.title.includes(issue.identifier) ||\n t.external_refs?.linear === issue.id\n );\n\n if (localTask) {\n this.taskStore.updateTaskStatus(\n localTask.id,\n 'cancelled',\n `Removed in Linear`\n );\n logger.info(\n `Cancelled task ${localTask.id} (Linear issue ${issue.identifier} was removed)`\n );\n }\n }\n\n /**\n * Store Linear mapping for a task\n */\n private async storeLinearMapping(\n taskId: string,\n linearId: string,\n linearIdentifier: string\n ): Promise<void> {\n // This would update the linear-mappings.json file\n // For now, just log it\n logger.info(\n `Mapped task ${taskId} to Linear ${linearIdentifier} (${linearId})`\n );\n }\n\n /**\n * Map Linear priority to local priority\n */\n private mapLinearPriorityToLocal(\n priority?: number\n ): 'urgent' | 'high' | 'medium' | 'low' {\n if (!priority) return 'medium';\n switch (priority) {\n case 0:\n return 'urgent';\n case 1:\n return 'high';\n case 2:\n return 'medium';\n case 3:\n case 4:\n return 'low';\n default:\n return 'medium';\n }\n }\n\n /**\n * Map Linear state to local status\n */\n private mapLinearStateToLocalStatus(\n state: string\n ): 'pending' | 'in_progress' | 'completed' | 'cancelled' | 'blocked' {\n switch (state) {\n case 'backlog':\n case 'unstarted':\n return 'pending';\n case 'started':\n return 'in_progress';\n case 'completed':\n return 'completed';\n case 'cancelled':\n return 'cancelled';\n default:\n return 'pending';\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -31,6 +31,11 @@ import { LLMContextRetrieval } from "../../core/retrieval/index.js";
31
31
  import { DiscoveryHandlers } from "./handlers/discovery-handlers.js";
32
32
  import { DiffMemHandlers } from "./handlers/diffmem-handlers.js";
33
33
  import { v4 as uuidv4 } from "uuid";
34
+ import {
35
+ DEFAULT_PLANNER_MODEL,
36
+ DEFAULT_IMPLEMENTER,
37
+ DEFAULT_MAX_ITERS
38
+ } from "../../orchestrators/multimodal/constants.js";
34
39
  function getEnv(key, defaultValue) {
35
40
  const value = process.env[key];
36
41
  if (value === void 0) {
@@ -1232,11 +1237,12 @@ ${summary}...`, 0.8);
1232
1237
  // Handle plan_and_code tool by invoking the mm harness
1233
1238
  async handlePlanAndCode(args) {
1234
1239
  const { runSpike } = await import("../../orchestrators/multimodal/harness.js");
1235
- const plannerModel = process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
1240
+ const envPlanner = process.env["STACKMEMORY_MM_PLANNER_MODEL"];
1241
+ const plannerModel = envPlanner || DEFAULT_PLANNER_MODEL;
1236
1242
  const reviewerModel = process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || plannerModel;
1237
- const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || "codex";
1243
+ const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || DEFAULT_IMPLEMENTER;
1238
1244
  const maxIters = Number(
1239
- args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? 2
1245
+ args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? DEFAULT_MAX_ITERS
1240
1246
  );
1241
1247
  const execute = Boolean(args.execute);
1242
1248
  const record = Boolean(args.record);
@@ -1281,7 +1287,7 @@ ${summary}...`, 0.8);
1281
1287
  async handlePlanOnly(args) {
1282
1288
  const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
1283
1289
  const task = String(args.task || "Plan change");
1284
- const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
1290
+ const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1285
1291
  const plan = await runPlanOnly(
1286
1292
  { task, repoPath: this.projectRoot },
1287
1293
  { plannerModel }
@@ -1319,7 +1325,7 @@ ${summary}...`, 0.8);
1319
1325
  async handleCallClaude(args) {
1320
1326
  const { callClaude } = await import("../../orchestrators/multimodal/providers.js");
1321
1327
  const prompt = String(args.prompt || "");
1322
- const model = args.model || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
1328
+ const model = args.model || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1323
1329
  const system = args.system || "You are a precise assistant. Return plain text unless asked for JSON.";
1324
1330
  const text = await callClaude(prompt, { model, system });
1325
1331
  return {
@@ -1374,7 +1380,7 @@ ${summary}...`, 0.8);
1374
1380
  async handlePlanGate(args) {
1375
1381
  const { runPlanOnly } = await import("../../orchestrators/multimodal/harness.js");
1376
1382
  const task = String(args.task || "Plan change");
1377
- const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest";
1383
+ const plannerModel = args.plannerModel || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL;
1378
1384
  const plan = await runPlanOnly(
1379
1385
  { task, repoPath: this.projectRoot },
1380
1386
  { plannerModel }
@@ -1409,17 +1415,17 @@ ${summary}...`, 0.8);
1409
1415
  isError: false
1410
1416
  };
1411
1417
  }
1412
- const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || "codex";
1418
+ const implementer = args.implementer || process.env["STACKMEMORY_MM_IMPLEMENTER"] || DEFAULT_IMPLEMENTER;
1413
1419
  const maxIters = Number(
1414
- args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? 2
1420
+ args.maxIters ?? process.env["STACKMEMORY_MM_MAX_ITERS"] ?? DEFAULT_MAX_ITERS
1415
1421
  );
1416
1422
  const recordFrame = args.recordFrame !== false;
1417
1423
  const execute = args.execute !== false;
1418
1424
  const result = await runSpike(
1419
1425
  { task: pending.task, repoPath: this.projectRoot },
1420
1426
  {
1421
- plannerModel: process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest",
1422
- reviewerModel: process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || "claude-3-5-sonnet-latest",
1427
+ plannerModel: process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL,
1428
+ reviewerModel: process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || process.env["STACKMEMORY_MM_PLANNER_MODEL"] || DEFAULT_PLANNER_MODEL,
1423
1429
  implementer: implementer === "claude" ? "claude" : "codex",
1424
1430
  maxIters: isFinite(maxIters) ? Math.max(1, maxIters) : 2,
1425
1431
  dryRun: !execute,