@stackmemoryai/stackmemory 0.3.16 → 0.3.18
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 +48 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +113 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-manager.js +3 -0
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +6 -18
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- package/dist/features/tui/types.js.map +0 -7
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Claude Linear Update Skill
|
|
5
|
+
* Automatically updates Linear tasks based on Claude's work
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import dotenv from 'dotenv';
|
|
12
|
+
import chalk from 'chalk';
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
// Load environment variables
|
|
17
|
+
dotenv.config({
|
|
18
|
+
path: path.join(__dirname, '..', '.env'),
|
|
19
|
+
override: true,
|
|
20
|
+
silent: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
class LinearUpdateSkill {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.apiKey = process.env.LINEAR_API_KEY;
|
|
26
|
+
this.graphqlUrl = 'https://api.linear.app/graphql';
|
|
27
|
+
this.logFile = path.join(process.env.HOME, '.stackmemory', 'logs', 'linear-skill.log');
|
|
28
|
+
|
|
29
|
+
// State mappings
|
|
30
|
+
this.stateMap = {
|
|
31
|
+
'todo': 'backlog',
|
|
32
|
+
'backlog': 'backlog',
|
|
33
|
+
'in_progress': 'started',
|
|
34
|
+
'in progress': 'started',
|
|
35
|
+
'started': 'started',
|
|
36
|
+
'completed': 'completed',
|
|
37
|
+
'done': 'completed',
|
|
38
|
+
'finished': 'completed',
|
|
39
|
+
'implemented': 'completed',
|
|
40
|
+
'blocked': 'blocked',
|
|
41
|
+
'cancelled': 'cancelled',
|
|
42
|
+
'canceled': 'cancelled',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse task identifier from text
|
|
48
|
+
*/
|
|
49
|
+
parseTaskId(text) {
|
|
50
|
+
// Match STA-XXX pattern
|
|
51
|
+
const staMatch = text.match(/STA-(\d+)/i);
|
|
52
|
+
if (staMatch) {
|
|
53
|
+
return { identifier: staMatch[0].toUpperCase(), type: 'identifier' };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Match Linear URL
|
|
57
|
+
const urlMatch = text.match(/linear\.app\/[^\/]+\/issue\/([^\/\s]+)/);
|
|
58
|
+
if (urlMatch) {
|
|
59
|
+
return { identifier: urlMatch[1], type: 'identifier' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Match UUID pattern
|
|
63
|
+
const uuidMatch = text.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/);
|
|
64
|
+
if (uuidMatch) {
|
|
65
|
+
return { identifier: uuidMatch[0], type: 'id' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Detect status from text
|
|
73
|
+
*/
|
|
74
|
+
detectStatus(text) {
|
|
75
|
+
const lowerText = text.toLowerCase();
|
|
76
|
+
|
|
77
|
+
// Check for explicit status keywords
|
|
78
|
+
for (const [keyword, status] of Object.entries(this.stateMap)) {
|
|
79
|
+
if (lowerText.includes(keyword)) {
|
|
80
|
+
return status;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for action keywords
|
|
85
|
+
if (lowerText.includes('implement') || lowerText.includes('complet') ||
|
|
86
|
+
lowerText.includes('done') || lowerText.includes('finish')) {
|
|
87
|
+
return 'completed';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (lowerText.includes('start') || lowerText.includes('working on') ||
|
|
91
|
+
lowerText.includes('in progress')) {
|
|
92
|
+
return 'started';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (lowerText.includes('block')) {
|
|
96
|
+
return 'blocked';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract implementation details from text
|
|
104
|
+
*/
|
|
105
|
+
extractImplementationDetails(text) {
|
|
106
|
+
const details = [];
|
|
107
|
+
|
|
108
|
+
// Extract features
|
|
109
|
+
const featuresMatch = text.match(/(?:features?|implemented?|added?)[::\s]*([\s\S]*?)(?=\n\n|\n[A-Z]|$)/i);
|
|
110
|
+
if (featuresMatch) {
|
|
111
|
+
details.push('## Implementation Details\n' + featuresMatch[1].trim());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Extract technical details
|
|
115
|
+
const techMatch = text.match(/(?:technical|implementation)[::\s]*([\s\S]*?)(?=\n\n|\n[A-Z]|$)/i);
|
|
116
|
+
if (techMatch) {
|
|
117
|
+
details.push('## Technical Implementation\n' + techMatch[1].trim());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Extract file changes
|
|
121
|
+
const filesMatch = text.match(/(?:files?|created?|modified?)[::\s]*([\s\S]*?)(?=\n\n|\n[A-Z]|$)/i);
|
|
122
|
+
if (filesMatch) {
|
|
123
|
+
details.push('## Files Changed\n' + filesMatch[1].trim());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Add timestamp
|
|
127
|
+
details.push(`\n---\n_Updated by Claude: ${new Date().toISOString()}_`);
|
|
128
|
+
|
|
129
|
+
return details.join('\n\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get Linear issue by identifier
|
|
134
|
+
*/
|
|
135
|
+
async getIssue(identifier) {
|
|
136
|
+
const query = `
|
|
137
|
+
query GetIssue($identifier: String!) {
|
|
138
|
+
issue(id: $identifier) {
|
|
139
|
+
id
|
|
140
|
+
identifier
|
|
141
|
+
title
|
|
142
|
+
description
|
|
143
|
+
state {
|
|
144
|
+
id
|
|
145
|
+
name
|
|
146
|
+
type
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const response = await fetch(this.graphqlUrl, {
|
|
154
|
+
method: 'POST',
|
|
155
|
+
headers: {
|
|
156
|
+
'Content-Type': 'application/json',
|
|
157
|
+
'Authorization': this.apiKey,
|
|
158
|
+
},
|
|
159
|
+
body: JSON.stringify({
|
|
160
|
+
query,
|
|
161
|
+
variables: { identifier },
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const data = await response.json();
|
|
166
|
+
|
|
167
|
+
if (data.errors) {
|
|
168
|
+
this.log(`Error fetching issue: ${data.errors[0].message}`, 'ERROR');
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return data.data.issue;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this.log(`Failed to fetch issue: ${error.message}`, 'ERROR');
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get Linear state ID by name
|
|
181
|
+
*/
|
|
182
|
+
async getStateId(stateName) {
|
|
183
|
+
const query = `
|
|
184
|
+
query GetStates {
|
|
185
|
+
workflowStates {
|
|
186
|
+
nodes {
|
|
187
|
+
id
|
|
188
|
+
name
|
|
189
|
+
type
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch(this.graphqlUrl, {
|
|
197
|
+
method: 'POST',
|
|
198
|
+
headers: {
|
|
199
|
+
'Content-Type': 'application/json',
|
|
200
|
+
'Authorization': this.apiKey,
|
|
201
|
+
},
|
|
202
|
+
body: JSON.stringify({ query }),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const data = await response.json();
|
|
206
|
+
|
|
207
|
+
if (data.errors) {
|
|
208
|
+
this.log(`Error fetching states: ${data.errors[0].message}`, 'ERROR');
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const states = data.data.workflowStates.nodes;
|
|
213
|
+
const state = states.find(s => s.type === stateName || s.name.toLowerCase() === stateName);
|
|
214
|
+
|
|
215
|
+
return state ? state.id : null;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.log(`Failed to fetch states: ${error.message}`, 'ERROR');
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Update Linear issue
|
|
224
|
+
*/
|
|
225
|
+
async updateIssue(issueId, updates) {
|
|
226
|
+
const mutation = `
|
|
227
|
+
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
|
|
228
|
+
issueUpdate(id: $id, input: $input) {
|
|
229
|
+
success
|
|
230
|
+
issue {
|
|
231
|
+
id
|
|
232
|
+
identifier
|
|
233
|
+
title
|
|
234
|
+
state {
|
|
235
|
+
name
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
`;
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const response = await fetch(this.graphqlUrl, {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
headers: {
|
|
246
|
+
'Content-Type': 'application/json',
|
|
247
|
+
'Authorization': this.apiKey,
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
query: mutation,
|
|
251
|
+
variables: {
|
|
252
|
+
id: issueId,
|
|
253
|
+
input: updates,
|
|
254
|
+
},
|
|
255
|
+
}),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const data = await response.json();
|
|
259
|
+
|
|
260
|
+
if (data.errors) {
|
|
261
|
+
this.log(`Error updating issue: ${data.errors[0].message}`, 'ERROR');
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (data.data.issueUpdate.success) {
|
|
266
|
+
const issue = data.data.issueUpdate.issue;
|
|
267
|
+
this.log(`Updated ${issue.identifier}: ${issue.state.name}`);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return false;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
this.log(`Failed to update issue: ${error.message}`, 'ERROR');
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Process update request
|
|
280
|
+
*/
|
|
281
|
+
async processUpdate(text, options = {}) {
|
|
282
|
+
// Parse task ID
|
|
283
|
+
const taskInfo = this.parseTaskId(text);
|
|
284
|
+
if (!taskInfo) {
|
|
285
|
+
this.log('No task identifier found in text');
|
|
286
|
+
return { success: false, reason: 'No task identifier found' };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.log(`Processing update for ${taskInfo.identifier}`);
|
|
290
|
+
|
|
291
|
+
// Get issue details
|
|
292
|
+
const issue = await this.getIssue(taskInfo.identifier);
|
|
293
|
+
if (!issue) {
|
|
294
|
+
return { success: false, reason: 'Issue not found' };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Prepare updates
|
|
298
|
+
const updates = {};
|
|
299
|
+
|
|
300
|
+
// Detect and set new status
|
|
301
|
+
const newStatus = options.status || this.detectStatus(text);
|
|
302
|
+
if (newStatus) {
|
|
303
|
+
const stateId = await this.getStateId(newStatus);
|
|
304
|
+
if (stateId) {
|
|
305
|
+
updates.stateId = stateId;
|
|
306
|
+
this.log(`Setting status to ${newStatus}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Add implementation details if completing
|
|
311
|
+
if (newStatus === 'completed' && options.addDetails !== false) {
|
|
312
|
+
const details = this.extractImplementationDetails(text);
|
|
313
|
+
if (details) {
|
|
314
|
+
updates.description = issue.description + '\n\n' + details;
|
|
315
|
+
this.log('Adding implementation details');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Add comment if provided
|
|
320
|
+
if (options.comment) {
|
|
321
|
+
updates.comment = {
|
|
322
|
+
body: options.comment,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Update the issue
|
|
327
|
+
if (Object.keys(updates).length > 0) {
|
|
328
|
+
const success = await this.updateIssue(issue.id, updates);
|
|
329
|
+
return {
|
|
330
|
+
success,
|
|
331
|
+
issue: issue.identifier,
|
|
332
|
+
updates: Object.keys(updates),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { success: false, reason: 'No updates to apply' };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Log message
|
|
341
|
+
*/
|
|
342
|
+
log(message, level = 'INFO') {
|
|
343
|
+
const timestamp = new Date().toISOString();
|
|
344
|
+
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
|
|
345
|
+
|
|
346
|
+
console.log(level === 'ERROR' ? chalk.red(message) : chalk.green(message));
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
fs.appendFileSync(this.logFile, logMessage);
|
|
350
|
+
} catch {
|
|
351
|
+
// Silent fail
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Batch update multiple tasks
|
|
357
|
+
*/
|
|
358
|
+
async batchUpdate(updates) {
|
|
359
|
+
const results = [];
|
|
360
|
+
|
|
361
|
+
for (const update of updates) {
|
|
362
|
+
const result = await this.processUpdate(update.text, update.options);
|
|
363
|
+
results.push({
|
|
364
|
+
...result,
|
|
365
|
+
original: update,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Rate limiting
|
|
369
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return results;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// CLI interface
|
|
377
|
+
async function main() {
|
|
378
|
+
const skill = new LinearUpdateSkill();
|
|
379
|
+
|
|
380
|
+
if (!skill.apiKey) {
|
|
381
|
+
console.error(chalk.red('LINEAR_API_KEY not found in environment'));
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const command = process.argv[2];
|
|
386
|
+
const args = process.argv.slice(3).join(' ');
|
|
387
|
+
|
|
388
|
+
if (!command) {
|
|
389
|
+
console.log(chalk.yellow('Linear Update Skill'));
|
|
390
|
+
console.log('Usage:');
|
|
391
|
+
console.log(' claude-linear-skill update <text> - Update task from text');
|
|
392
|
+
console.log(' claude-linear-skill detect <text> - Detect task and status');
|
|
393
|
+
console.log(' claude-linear-skill test - Test connection');
|
|
394
|
+
console.log();
|
|
395
|
+
console.log('Examples:');
|
|
396
|
+
console.log(' claude-linear-skill update "STA-287 is completed with infinite storage"');
|
|
397
|
+
console.log(' claude-linear-skill update "Starting work on STA-288"');
|
|
398
|
+
process.exit(0);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
switch (command) {
|
|
402
|
+
case 'update':
|
|
403
|
+
if (!args) {
|
|
404
|
+
console.error(chalk.red('Please provide update text'));
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const result = await skill.processUpdate(args);
|
|
409
|
+
if (result.success) {
|
|
410
|
+
console.log(chalk.green(`✅ Updated ${result.issue}`));
|
|
411
|
+
console.log('Updates:', result.updates.join(', '));
|
|
412
|
+
} else {
|
|
413
|
+
console.log(chalk.red(`❌ Update failed: ${result.reason}`));
|
|
414
|
+
}
|
|
415
|
+
break;
|
|
416
|
+
|
|
417
|
+
case 'detect':
|
|
418
|
+
if (!args) {
|
|
419
|
+
console.error(chalk.red('Please provide text to analyze'));
|
|
420
|
+
process.exit(1);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const taskId = skill.parseTaskId(args);
|
|
424
|
+
const status = skill.detectStatus(args);
|
|
425
|
+
|
|
426
|
+
console.log('Detected:');
|
|
427
|
+
console.log(` Task: ${taskId ? taskId.identifier : 'Not found'}`);
|
|
428
|
+
console.log(` Status: ${status || 'Not detected'}`);
|
|
429
|
+
break;
|
|
430
|
+
|
|
431
|
+
case 'test':
|
|
432
|
+
const testResult = await skill.getStateId('backlog');
|
|
433
|
+
if (testResult) {
|
|
434
|
+
console.log(chalk.green('✅ Linear API connection successful'));
|
|
435
|
+
} else {
|
|
436
|
+
console.log(chalk.red('❌ Linear API connection failed'));
|
|
437
|
+
}
|
|
438
|
+
break;
|
|
439
|
+
|
|
440
|
+
default:
|
|
441
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Export for use in other scripts
|
|
447
|
+
export { LinearUpdateSkill };
|
|
448
|
+
|
|
449
|
+
// Run if called directly
|
|
450
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
451
|
+
main().catch(error => {
|
|
452
|
+
console.error(chalk.red('Error:'), error);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
});
|
|
455
|
+
}
|