@stackmemoryai/stackmemory 0.3.17 → 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.
Files changed (212) hide show
  1. package/dist/cli/commands/skills.js +15 -2
  2. package/dist/cli/commands/skills.js.map +2 -2
  3. package/dist/cli/index.js +113 -834
  4. package/dist/cli/index.js.map +3 -3
  5. package/dist/core/context/dual-stack-manager.js +1 -1
  6. package/dist/core/context/dual-stack-manager.js.map +1 -1
  7. package/dist/core/context/frame-manager.js +3 -0
  8. package/dist/core/context/frame-manager.js.map +2 -2
  9. package/dist/integrations/claude-code/subagent-client.js +106 -3
  10. package/dist/integrations/claude-code/subagent-client.js.map +2 -2
  11. package/dist/servers/railway/config.js +51 -0
  12. package/dist/servers/railway/config.js.map +7 -0
  13. package/dist/servers/railway/index-enhanced.js +156 -0
  14. package/dist/servers/railway/index-enhanced.js.map +7 -0
  15. package/dist/servers/railway/minimal.js +48 -3
  16. package/dist/servers/railway/minimal.js.map +2 -2
  17. package/dist/servers/railway/storage-test.js +455 -0
  18. package/dist/servers/railway/storage-test.js.map +7 -0
  19. package/dist/skills/claude-skills.js +13 -12
  20. package/dist/skills/claude-skills.js.map +2 -2
  21. package/dist/skills/recursive-agent-orchestrator.js +27 -18
  22. package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
  23. package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
  24. package/package.json +6 -18
  25. package/scripts/README-TESTING.md +186 -0
  26. package/scripts/analyze-cli-security.js +288 -0
  27. package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
  28. package/scripts/archive/analyze-linear-duplicates.js +214 -0
  29. package/scripts/archive/analyze-remaining-duplicates.js +230 -0
  30. package/scripts/archive/analyze-sta-duplicates.js +292 -0
  31. package/scripts/archive/analyze-sta-graphql.js +399 -0
  32. package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
  33. package/scripts/archive/check-all-duplicates.ts +419 -0
  34. package/scripts/archive/clean-duplicate-tasks.js +114 -0
  35. package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
  36. package/scripts/archive/create-phase-tasks.js +387 -0
  37. package/scripts/archive/delete-linear-duplicates.js +182 -0
  38. package/scripts/archive/delete-remaining-duplicates.js +158 -0
  39. package/scripts/archive/delete-sta-duplicates.js +201 -0
  40. package/scripts/archive/delete-sta-oauth.js +201 -0
  41. package/scripts/archive/export-sta-tasks.js +62 -0
  42. package/scripts/archive/install-auto-sync.js +266 -0
  43. package/scripts/archive/install-chromadb-hooks.sh +133 -0
  44. package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
  45. package/scripts/archive/install-post-task-hooks.sh +289 -0
  46. package/scripts/archive/install-stackmemory-hooks.sh +420 -0
  47. package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
  48. package/scripts/archive/merge-linear-duplicates.ts +180 -0
  49. package/scripts/archive/remove-sta-tasks.js +70 -0
  50. package/scripts/archive/setup-background-sync.sh +168 -0
  51. package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
  52. package/scripts/archive/setup-claude-autostart.sh +305 -0
  53. package/scripts/archive/setup-git-hooks.sh +25 -0
  54. package/scripts/archive/setup-linear-oauth.sh +46 -0
  55. package/scripts/archive/setup-mcp.sh +113 -0
  56. package/scripts/archive/setup-railway-deployment.sh +81 -0
  57. package/scripts/auto-handoff.sh +262 -0
  58. package/scripts/background-sync-manager.js +416 -0
  59. package/scripts/benchmark-performance.ts +57 -0
  60. package/scripts/check-redis.ts +48 -0
  61. package/scripts/chromadb-auto-loader.sh +128 -0
  62. package/scripts/chromadb-context-loader.js +479 -0
  63. package/scripts/claude-chromadb-hook.js +460 -0
  64. package/scripts/claude-code-wrapper.sh +66 -0
  65. package/scripts/claude-linear-skill.js +455 -0
  66. package/scripts/claude-pre-commit.sh +302 -0
  67. package/scripts/claude-sm-autostart.js +532 -0
  68. package/scripts/claude-sm-setup.sh +367 -0
  69. package/scripts/claude-with-chromadb.sh +69 -0
  70. package/scripts/claude-worktree-manager.sh +323 -0
  71. package/scripts/claude-worktree-monitor.sh +371 -0
  72. package/scripts/claude-worktree-setup.sh +327 -0
  73. package/scripts/clean-linear-backlog.js +273 -0
  74. package/scripts/cleanup-old-sessions.sh +57 -0
  75. package/scripts/codex-wrapper.sh +88 -0
  76. package/scripts/create-sandbox.sh +269 -0
  77. package/scripts/debug-linear-update.js +174 -0
  78. package/scripts/delete-linear-tasks.js +167 -0
  79. package/scripts/deploy.sh +89 -0
  80. package/scripts/deployment/railway.sh +352 -0
  81. package/scripts/deployment/test-deployment.js +194 -0
  82. package/scripts/detect-and-rehydrate.js +162 -0
  83. package/scripts/detect-and-rehydrate.mjs +165 -0
  84. package/scripts/development/create-demo-tasks.js +143 -0
  85. package/scripts/development/debug-frame-test.js +16 -0
  86. package/scripts/development/demo-auto-sync.js +128 -0
  87. package/scripts/development/fix-all-imports.js +213 -0
  88. package/scripts/development/fix-imports.js +229 -0
  89. package/scripts/development/fix-lint-loop.cjs +103 -0
  90. package/scripts/development/fix-project-id.ts +161 -0
  91. package/scripts/development/fix-strict-mode-issues.ts +291 -0
  92. package/scripts/development/reorganize-structure.sh +228 -0
  93. package/scripts/development/test-persistence-direct.js +148 -0
  94. package/scripts/development/test-persistence.js +114 -0
  95. package/scripts/development/test-tasks.js +93 -0
  96. package/scripts/development/update-imports.js +212 -0
  97. package/scripts/fetch-linear-status.js +125 -0
  98. package/scripts/git-hooks/README.md +310 -0
  99. package/scripts/git-hooks/branch-context-manager.sh +342 -0
  100. package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
  101. package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
  102. package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
  103. package/scripts/hooks/cleanup-shell.sh +130 -0
  104. package/scripts/hooks/task-complete.sh +114 -0
  105. package/scripts/initialize.ts +129 -0
  106. package/scripts/install-claude-hooks-auto.js +104 -0
  107. package/scripts/install-claude-hooks.sh +133 -0
  108. package/scripts/install-global.sh +296 -0
  109. package/scripts/install.sh +235 -0
  110. package/scripts/linear-auto-sync.js +262 -0
  111. package/scripts/linear-auto-sync.sh +161 -0
  112. package/scripts/linear-sync-daemon.js +150 -0
  113. package/scripts/linear-task-review.js +237 -0
  114. package/scripts/list-linear-tasks.ts +178 -0
  115. package/scripts/mcp-proxy.js +66 -0
  116. package/scripts/opencode-wrapper.sh +85 -0
  117. package/scripts/publish-local.js +74 -0
  118. package/scripts/query-chromadb.ts +201 -0
  119. package/scripts/railway-env-setup.sh +39 -0
  120. package/scripts/reconcile-local-tasks.js +170 -0
  121. package/scripts/recreate-frames-db.js +89 -0
  122. package/scripts/setup/claude-integration.js +138 -0
  123. package/scripts/setup/configure-alias.js +125 -0
  124. package/scripts/setup/configure-codex-alias.js +161 -0
  125. package/scripts/setup/configure-opencode-alias.js +175 -0
  126. package/scripts/setup-claude-integration.js +204 -0
  127. package/scripts/setup-claude-integration.sh +183 -0
  128. package/scripts/setup.sh +31 -0
  129. package/scripts/show-linear-summary.ts +172 -0
  130. package/scripts/stackmemory-auto-handoff.sh +231 -0
  131. package/scripts/stackmemory-daemon.sh +40 -0
  132. package/scripts/start-linear-sync-daemon.sh +141 -0
  133. package/scripts/start-temporal-paradox.sh +214 -0
  134. package/scripts/status.ts +159 -0
  135. package/scripts/sync-and-clean-tasks.js +258 -0
  136. package/scripts/sync-frames-from-railway.js +228 -0
  137. package/scripts/sync-linear-graphql.js +303 -0
  138. package/scripts/sync-linear-tasks.js +186 -0
  139. package/scripts/test-auto-triggers.sh +57 -0
  140. package/scripts/test-browser-mcp.js +74 -0
  141. package/scripts/test-chromadb-full.js +115 -0
  142. package/scripts/test-chromadb-hooks.sh +28 -0
  143. package/scripts/test-chromadb-sync.ts +245 -0
  144. package/scripts/test-cli-security.js +293 -0
  145. package/scripts/test-hooks-persistence.sh +220 -0
  146. package/scripts/test-installation-scenarios.sh +359 -0
  147. package/scripts/test-installation.sh +224 -0
  148. package/scripts/test-mcp.js +163 -0
  149. package/scripts/test-pre-publish-quick.sh +75 -0
  150. package/scripts/test-quality-gates.sh +263 -0
  151. package/scripts/test-railway-db.js +222 -0
  152. package/scripts/test-redis-storage.ts +490 -0
  153. package/scripts/test-rlm-basic.sh +122 -0
  154. package/scripts/test-rlm-comprehensive.sh +260 -0
  155. package/scripts/test-rlm-e2e.sh +268 -0
  156. package/scripts/test-rlm-simple.js +90 -0
  157. package/scripts/test-rlm.js +110 -0
  158. package/scripts/test-session-handoff.sh +165 -0
  159. package/scripts/test-shell-integration.sh +275 -0
  160. package/scripts/testing/ab-test-runner.ts +508 -0
  161. package/scripts/testing/collect-metrics.ts +457 -0
  162. package/scripts/testing/quick-effectiveness-demo.js +187 -0
  163. package/scripts/testing/real-performance-test.js +422 -0
  164. package/scripts/testing/run-effectiveness-tests.sh +176 -0
  165. package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
  166. package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
  167. package/scripts/testing/simple-effectiveness-test.js +310 -0
  168. package/scripts/testing/src/core/context/context-bridge.js +253 -0
  169. package/scripts/testing/src/core/context/frame-manager.js +746 -0
  170. package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
  171. package/scripts/testing/src/core/database/database-adapter.js +54 -0
  172. package/scripts/testing/src/core/errors/index.js +291 -0
  173. package/scripts/testing/src/core/errors/recovery.js +268 -0
  174. package/scripts/testing/src/core/monitoring/logger.js +145 -0
  175. package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
  176. package/scripts/testing/src/core/session/index.js +1 -0
  177. package/scripts/testing/src/core/session/session-manager.js +323 -0
  178. package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
  179. package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
  180. package/scripts/testing/src/core/trace/debug-trace.js +398 -0
  181. package/scripts/testing/src/core/trace/index.js +120 -0
  182. package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
  183. package/scripts/update-linear-status.js +268 -0
  184. package/scripts/update-linear-tasks-fixed.js +284 -0
  185. package/templates/claude-hooks/hooks.json +5 -0
  186. package/templates/claude-hooks/on-clear.js +56 -0
  187. package/templates/claude-hooks/on-startup.js +56 -0
  188. package/templates/claude-hooks/tool-use-trace.js +67 -0
  189. package/dist/features/tui/components/analytics-panel.js +0 -157
  190. package/dist/features/tui/components/analytics-panel.js.map +0 -7
  191. package/dist/features/tui/components/frame-visualizer.js +0 -377
  192. package/dist/features/tui/components/frame-visualizer.js.map +0 -7
  193. package/dist/features/tui/components/pr-tracker.js +0 -135
  194. package/dist/features/tui/components/pr-tracker.js.map +0 -7
  195. package/dist/features/tui/components/session-monitor.js +0 -299
  196. package/dist/features/tui/components/session-monitor.js.map +0 -7
  197. package/dist/features/tui/components/subagent-fleet.js +0 -395
  198. package/dist/features/tui/components/subagent-fleet.js.map +0 -7
  199. package/dist/features/tui/components/task-board.js +0 -1139
  200. package/dist/features/tui/components/task-board.js.map +0 -7
  201. package/dist/features/tui/index.js +0 -408
  202. package/dist/features/tui/index.js.map +0 -7
  203. package/dist/features/tui/services/data-service.js +0 -641
  204. package/dist/features/tui/services/data-service.js.map +0 -7
  205. package/dist/features/tui/services/linear-task-reader.js +0 -102
  206. package/dist/features/tui/services/linear-task-reader.js.map +0 -7
  207. package/dist/features/tui/services/websocket-client.js +0 -162
  208. package/dist/features/tui/services/websocket-client.js.map +0 -7
  209. package/dist/features/tui/terminal-compat.js +0 -220
  210. package/dist/features/tui/terminal-compat.js.map +0 -7
  211. package/dist/features/tui/types.js +0 -1
  212. 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
+ }