@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.
- package/README.md +139 -45
- package/bin/codex-sm +6 -0
- package/bin/opencode-sm +1 -1
- package/dist/src/cli/claude-sm.js +162 -25
- package/dist/src/cli/claude-sm.js.map +2 -2
- package/dist/src/cli/commands/ping.js +14 -0
- package/dist/src/cli/commands/ping.js.map +7 -0
- package/dist/src/cli/commands/ralph.js +103 -1
- package/dist/src/cli/commands/ralph.js.map +2 -2
- package/dist/src/cli/commands/retrieval.js +1 -1
- package/dist/src/cli/commands/retrieval.js.map +2 -2
- package/dist/src/cli/commands/skills.js +201 -6
- package/dist/src/cli/commands/skills.js.map +2 -2
- package/dist/src/cli/index.js +66 -27
- package/dist/src/cli/index.js.map +2 -2
- package/dist/src/core/digest/types.js +1 -1
- package/dist/src/core/digest/types.js.map +1 -1
- package/dist/src/core/extensions/provider-adapter.js +2 -5
- package/dist/src/core/extensions/provider-adapter.js.map +2 -2
- package/dist/src/core/retrieval/llm-provider.js +2 -2
- package/dist/src/core/retrieval/llm-provider.js.map +1 -1
- package/dist/src/core/retrieval/types.js +1 -1
- package/dist/src/core/retrieval/types.js.map +1 -1
- package/dist/src/features/sweep/pty-wrapper.js +15 -5
- package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
- package/dist/src/features/workers/tmux-manager.js +71 -0
- package/dist/src/features/workers/tmux-manager.js.map +7 -0
- package/dist/src/features/workers/worker-registry.js +52 -0
- package/dist/src/features/workers/worker-registry.js.map +7 -0
- package/dist/src/integrations/linear/webhook-handler.js +82 -0
- package/dist/src/integrations/linear/webhook-handler.js.map +2 -2
- package/dist/src/integrations/mcp/server.js +16 -10
- package/dist/src/integrations/mcp/server.js.map +2 -2
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +2 -2
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js.map +2 -2
- package/dist/src/orchestrators/multimodal/constants.js +1 -1
- package/dist/src/orchestrators/multimodal/constants.js.map +1 -1
- package/dist/src/orchestrators/multimodal/harness.js +28 -29
- package/dist/src/orchestrators/multimodal/harness.js.map +2 -2
- package/dist/src/orchestrators/multimodal/providers.js +35 -22
- package/dist/src/orchestrators/multimodal/providers.js.map +2 -2
- package/dist/src/skills/claude-skills.js +116 -1
- package/dist/src/skills/claude-skills.js.map +2 -2
- package/dist/src/skills/linear-task-runner.js +262 -0
- package/dist/src/skills/linear-task-runner.js.map +7 -0
- package/dist/src/skills/recursive-agent-orchestrator.js +114 -85
- package/dist/src/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/src/skills/spec-generator-skill.js +441 -0
- package/dist/src/skills/spec-generator-skill.js.map +7 -0
- package/package.json +12 -5
- package/scripts/install-claude-hooks-auto.js +23 -9
- package/scripts/install-claude-hooks.sh +2 -2
- package/templates/claude-hooks/hooks.json +4 -2
- package/templates/claude-hooks/on-task-complete.js +91 -0
- package/templates/claude-hooks/post-edit-sweep.js +7 -10
- package/templates/claude-hooks/skill-eval.cjs +411 -0
- package/templates/claude-hooks/skill-eval.sh +31 -0
- 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": ";;;;AAKA,SAAS,kBAAkB;AAE3B,SAAS,wBAAwB;AACjC,SAAS,yBAAyB;AAElC,SAAS,kBAAkB,iBAAiB;AAC5C,SAAS,cAAc;
|
|
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
|
|
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"] ||
|
|
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"] ??
|
|
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"] ||
|
|
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"] ||
|
|
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"] ||
|
|
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"] ||
|
|
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"] ??
|
|
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"] ||
|
|
1422
|
-
reviewerModel: process.env["STACKMEMORY_MM_REVIEWER_MODEL"] || process.env["STACKMEMORY_MM_PLANNER_MODEL"] ||
|
|
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,
|