wogiflow 1.0.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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Linear Integration
5
+ *
6
+ * Sync tasks between Wogi Flow and Linear. Supports:
7
+ * - Listing assigned Linear issues
8
+ * - Importing issues to ready.json
9
+ * - Syncing completed tasks back to Linear
10
+ *
11
+ * Part of Phase 6: Team & Integrations
12
+ *
13
+ * Usage:
14
+ * flow linear list List assigned issues
15
+ * flow linear sync Import issues to ready.json
16
+ * flow linear push Push completed tasks to Linear
17
+ * flow linear config Show/set Linear configuration
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const { HttpClient } = require('./flow-http-client');
23
+ const { TIMEOUTS } = require('./flow-constants');
24
+ const {
25
+ PROJECT_ROOT,
26
+ STATE_DIR,
27
+ parseFlags,
28
+ color,
29
+ info,
30
+ warn,
31
+ error,
32
+ success,
33
+ fileExists,
34
+ safeJsonParse,
35
+ getConfig,
36
+ setConfigValue,
37
+ resolveConfigValue,
38
+ printHeader,
39
+ generateTaskId,
40
+ writeJson
41
+ } = require('./flow-utils');
42
+
43
+ // ============================================================
44
+ // Constants
45
+ // ============================================================
46
+
47
+ const READY_PATH = path.join(STATE_DIR, 'ready.json');
48
+ const LINEAR_CACHE_PATH = path.join(STATE_DIR, 'linear-cache.json');
49
+ const CACHE_TTL_MS = TIMEOUTS.CACHE_TTL;
50
+ const LINEAR_API_URL = 'https://api.linear.app/graphql';
51
+
52
+ // ============================================================
53
+ // Configuration
54
+ // ============================================================
55
+
56
+ /**
57
+ * Get Linear configuration from config.json
58
+ */
59
+ function getLinearConfig() {
60
+ const config = getConfig();
61
+ const linearConfig = config?.integrations?.linear || {};
62
+
63
+ return {
64
+ enabled: linearConfig.enabled || false,
65
+ apiKey: resolveConfigValue(linearConfig.apiKey),
66
+ teamId: linearConfig.teamId || null,
67
+ teamKey: linearConfig.teamKey || null,
68
+ syncStatuses: linearConfig.syncStatuses || {
69
+ ready: ['Backlog', 'Todo', 'Triage'],
70
+ inProgress: ['In Progress', 'In Review'],
71
+ completed: ['Done', 'Canceled']
72
+ }
73
+ };
74
+ }
75
+
76
+ // ============================================================
77
+ // Linear GraphQL Client
78
+ // ============================================================
79
+
80
+ /**
81
+ * Make authenticated Linear GraphQL request using shared HttpClient
82
+ */
83
+ async function linearRequest(query, variables = {}) {
84
+ const config = getLinearConfig();
85
+
86
+ if (!config.apiKey) {
87
+ throw new Error('Linear API key not configured. Run: flow linear config');
88
+ }
89
+
90
+ const client = new HttpClient(LINEAR_API_URL, {
91
+ headers: {
92
+ 'Authorization': config.apiKey
93
+ },
94
+ timeout: 30000
95
+ });
96
+
97
+ const response = await client.post('/', { query, variables });
98
+
99
+ if (response.data?.errors) {
100
+ throw new Error(response.data.errors[0].message);
101
+ }
102
+
103
+ return response.data?.data;
104
+ }
105
+
106
+ // ============================================================
107
+ // Issue Operations
108
+ // ============================================================
109
+
110
+ /**
111
+ * Fetch assigned issues from Linear
112
+ */
113
+ async function fetchIssues() {
114
+ const config = getLinearConfig();
115
+
116
+ const query = `
117
+ query AssignedIssues($teamId: String) {
118
+ viewer {
119
+ assignedIssues(
120
+ filter: {
121
+ state: { type: { nin: ["completed", "canceled"] } }
122
+ ${config.teamId ? 'team: { id: { eq: $teamId } }' : ''}
123
+ }
124
+ orderBy: priority
125
+ first: 50
126
+ ) {
127
+ nodes {
128
+ id
129
+ identifier
130
+ title
131
+ description
132
+ priority
133
+ priorityLabel
134
+ state {
135
+ name
136
+ type
137
+ }
138
+ team {
139
+ key
140
+ name
141
+ }
142
+ createdAt
143
+ updatedAt
144
+ url
145
+ }
146
+ }
147
+ }
148
+ }
149
+ `;
150
+
151
+ const variables = config.teamId ? { teamId: config.teamId } : {};
152
+ const result = await linearRequest(query, variables);
153
+
154
+ return result.viewer.assignedIssues.nodes.map(issue => ({
155
+ id: issue.id,
156
+ identifier: issue.identifier,
157
+ title: issue.title,
158
+ description: issue.description || '',
159
+ priority: issue.priority,
160
+ priorityLabel: issue.priorityLabel,
161
+ status: issue.state.name,
162
+ statusType: issue.state.type,
163
+ team: issue.team.key,
164
+ teamName: issue.team.name,
165
+ createdAt: issue.createdAt,
166
+ updatedAt: issue.updatedAt,
167
+ url: issue.url
168
+ }));
169
+ }
170
+
171
+ /**
172
+ * Get cached issues or fetch fresh
173
+ */
174
+ async function getIssues(forceRefresh = false) {
175
+ if (!forceRefresh && fileExists(LINEAR_CACHE_PATH)) {
176
+ const cache = safeJsonParse(LINEAR_CACHE_PATH);
177
+ // Validate cache has issues array and valid timestamp
178
+ if (cache?.issues && cache?.fetchedAt) {
179
+ const fetchTime = new Date(cache.fetchedAt).getTime();
180
+ // Check for valid date and within TTL
181
+ if (!isNaN(fetchTime) && Date.now() - fetchTime < CACHE_TTL_MS) {
182
+ return cache.issues;
183
+ }
184
+ }
185
+ }
186
+
187
+ const issues = await fetchIssues();
188
+
189
+ // Save to cache using writeJson for atomic writes
190
+ writeJson(LINEAR_CACHE_PATH, {
191
+ fetchedAt: new Date().toISOString(),
192
+ issues
193
+ });
194
+
195
+ return issues;
196
+ }
197
+
198
+ /**
199
+ * Update issue state in Linear
200
+ */
201
+ async function updateIssueState(issueId, stateName) {
202
+ // First, get available states for the issue's team
203
+ const stateQuery = `
204
+ query GetIssueStates($issueId: String!) {
205
+ issue(id: $issueId) {
206
+ team {
207
+ states {
208
+ nodes {
209
+ id
210
+ name
211
+ type
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ `;
218
+
219
+ const stateResult = await linearRequest(stateQuery, { issueId });
220
+ const states = stateResult.issue.team.states.nodes;
221
+
222
+ const targetState = states.find(s =>
223
+ s.name.toLowerCase() === stateName.toLowerCase() ||
224
+ s.type.toLowerCase() === stateName.toLowerCase()
225
+ );
226
+
227
+ if (!targetState) {
228
+ throw new Error(`State "${stateName}" not found`);
229
+ }
230
+
231
+ // Update the issue
232
+ const updateQuery = `
233
+ mutation UpdateIssue($issueId: String!, $stateId: String!) {
234
+ issueUpdate(id: $issueId, input: { stateId: $stateId }) {
235
+ success
236
+ issue {
237
+ identifier
238
+ state { name }
239
+ }
240
+ }
241
+ }
242
+ `;
243
+
244
+ const updateResult = await linearRequest(updateQuery, {
245
+ issueId,
246
+ stateId: targetState.id
247
+ });
248
+
249
+ return {
250
+ success: updateResult.issueUpdate.success,
251
+ identifier: updateResult.issueUpdate.issue.identifier,
252
+ newState: updateResult.issueUpdate.issue.state.name
253
+ };
254
+ }
255
+
256
+ /**
257
+ * Add comment to issue
258
+ */
259
+ async function addComment(issueId, body) {
260
+ const query = `
261
+ mutation AddComment($issueId: String!, $body: String!) {
262
+ commentCreate(input: { issueId: $issueId, body: $body }) {
263
+ success
264
+ comment {
265
+ id
266
+ }
267
+ }
268
+ }
269
+ `;
270
+
271
+ return linearRequest(query, { issueId, body });
272
+ }
273
+
274
+ // ============================================================
275
+ // Sync Operations
276
+ // ============================================================
277
+
278
+ /**
279
+ * Import Linear issues to ready.json
280
+ */
281
+ async function syncToReady() {
282
+ const config = getLinearConfig();
283
+ const issues = await getIssues(true);
284
+
285
+ // Load current ready.json
286
+ const ready = safeJsonParse(READY_PATH) || {
287
+ ready: [],
288
+ inProgress: [],
289
+ blocked: [],
290
+ recentlyCompleted: []
291
+ };
292
+
293
+ // Map existing tasks by external ID
294
+ const existingByExternal = new Map();
295
+ for (const task of [...ready.ready, ...ready.inProgress, ...ready.blocked]) {
296
+ if (task.externalId) {
297
+ existingByExternal.set(task.externalId, task);
298
+ }
299
+ }
300
+
301
+ const imported = [];
302
+ const updated = [];
303
+
304
+ for (const issue of issues) {
305
+ const externalId = `linear:${issue.identifier}`;
306
+ const existing = existingByExternal.get(externalId);
307
+
308
+ // Determine status category
309
+ let targetList = 'ready';
310
+ if (config.syncStatuses.inProgress.includes(issue.status)) {
311
+ targetList = 'inProgress';
312
+ } else if (issue.statusType === 'completed' || issue.statusType === 'canceled') {
313
+ continue; // Skip completed
314
+ }
315
+
316
+ if (existing) {
317
+ // Update existing task
318
+ existing.title = issue.title;
319
+ existing.priority = mapPriority(issue.priority);
320
+ existing.updatedAt = new Date().toISOString();
321
+ updated.push(issue.identifier);
322
+ } else {
323
+ // Create new task using standard task ID generator
324
+ const taskId = generateTaskId();
325
+ const task = {
326
+ id: taskId,
327
+ externalId,
328
+ externalUrl: issue.url,
329
+ title: issue.title,
330
+ type: 'story',
331
+ feature: 'general',
332
+ status: targetList === 'ready' ? 'ready' : 'in_progress',
333
+ priority: mapPriority(issue.priority),
334
+ source: 'linear',
335
+ importedAt: new Date().toISOString()
336
+ };
337
+
338
+ ready[targetList].push(task);
339
+ imported.push(issue.identifier);
340
+ }
341
+ }
342
+
343
+ // Save ready.json using atomic write
344
+ ready.lastUpdated = new Date().toISOString();
345
+ writeJson(READY_PATH, ready);
346
+
347
+ return { imported, updated };
348
+ }
349
+
350
+ /**
351
+ * Push completed tasks back to Linear
352
+ */
353
+ async function pushCompleted() {
354
+ const ready = safeJsonParse(READY_PATH);
355
+
356
+ if (!ready || !ready.recentlyCompleted) {
357
+ return { pushed: [] };
358
+ }
359
+
360
+ const pushed = [];
361
+
362
+ for (const task of ready.recentlyCompleted) {
363
+ if (!task.externalId || !task.externalId.startsWith('linear:')) {
364
+ continue;
365
+ }
366
+
367
+ // Need to get the issue ID from the identifier
368
+ const identifier = task.externalId.replace('linear:', '');
369
+
370
+ try {
371
+ // Use proper GraphQL variables to prevent injection
372
+ const searchQuery = `
373
+ query SearchIssue($identifier: String!) {
374
+ issues(filter: { identifier: { eq: $identifier } }) {
375
+ nodes { id }
376
+ }
377
+ }
378
+ `;
379
+
380
+ const searchResult = await linearRequest(searchQuery, { identifier });
381
+ const issueId = searchResult.issues?.nodes?.[0]?.id;
382
+
383
+ if (!issueId) {
384
+ warn(`Could not find issue ${identifier}`);
385
+ continue;
386
+ }
387
+
388
+ await updateIssueState(issueId, 'Done');
389
+ await addComment(issueId, `Completed via Wogi Flow at ${task.completedAt}`);
390
+ pushed.push(identifier);
391
+ } catch (err) {
392
+ warn(`Failed to update ${identifier}: ${err.message}`);
393
+ }
394
+ }
395
+
396
+ return { pushed };
397
+ }
398
+
399
+ /**
400
+ * Map Linear priority (0-4) to Wogi Flow priority
401
+ */
402
+ function mapPriority(linearPriority) {
403
+ // Linear: 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
404
+ const mapping = {
405
+ 0: 'P2',
406
+ 1: 'P0',
407
+ 2: 'P1',
408
+ 3: 'P2',
409
+ 4: 'P3'
410
+ };
411
+ return mapping[linearPriority] || 'P2';
412
+ }
413
+
414
+ // ============================================================
415
+ // CLI Output
416
+ // ============================================================
417
+
418
+ function printIssues(issues) {
419
+ if (issues.length === 0) {
420
+ info('No issues found');
421
+ return;
422
+ }
423
+
424
+ printHeader('LINEAR ISSUES');
425
+
426
+ for (const issue of issues) {
427
+ const statusColor = getStatusColor(issue.statusType);
428
+ const priorityIcon = getPriorityIcon(issue.priority);
429
+
430
+ console.log(`\n ${color('purple', issue.identifier)} ${priorityIcon}`);
431
+ console.log(` ${issue.title}`);
432
+ console.log(` ${color(statusColor, issue.status)} • ${color('dim', issue.teamName)}`);
433
+ }
434
+
435
+ console.log(`\n ${color('dim', `Total: ${issues.length} issues`)}\n`);
436
+ }
437
+
438
+ function getStatusColor(statusType) {
439
+ const colors = {
440
+ 'backlog': 'dim',
441
+ 'unstarted': 'blue',
442
+ 'started': 'yellow',
443
+ 'completed': 'green',
444
+ 'canceled': 'dim'
445
+ };
446
+ return colors[statusType] || 'blue';
447
+ }
448
+
449
+ function getPriorityIcon(priority) {
450
+ const icons = {
451
+ 0: '',
452
+ 1: color('red', '!!!'),
453
+ 2: color('red', '!!'),
454
+ 3: color('yellow', '!'),
455
+ 4: color('dim', '-')
456
+ };
457
+ return icons[priority] || '';
458
+ }
459
+
460
+ function printConfig(config) {
461
+ printHeader('LINEAR CONFIGURATION');
462
+
463
+ console.log(` ${color('dim', 'Enabled:')} ${config.enabled ? color('green', 'Yes') : color('red', 'No')}`);
464
+ console.log(` ${color('dim', 'API Key:')} ${config.apiKey ? color('green', 'Set') : color('yellow', 'Not set')}`);
465
+ console.log(` ${color('dim', 'Team ID:')} ${config.teamId || color('dim', 'All teams')}`);
466
+ console.log(` ${color('dim', 'Team Key:')} ${config.teamKey || color('dim', 'Not set')}`);
467
+
468
+ console.log(`\nTo configure, add to .workflow/config.json:`);
469
+ console.log(` "integrations": {`);
470
+ console.log(` "linear": {`);
471
+ console.log(` "enabled": true,`);
472
+ console.log(` "apiKey": "{env:LINEAR_API_KEY}",`);
473
+ console.log(` "teamId": "your-team-id"`);
474
+ console.log(` }`);
475
+ console.log(` }`);
476
+ console.log('');
477
+ console.log('Get your API key at: https://linear.app/settings/api');
478
+ console.log('');
479
+ }
480
+
481
+ // ============================================================
482
+ // CLI Entry Point
483
+ // ============================================================
484
+
485
+ function showHelp() {
486
+ console.log(`
487
+ Wogi Flow - Linear Integration
488
+
489
+ Sync tasks between Wogi Flow and Linear.
490
+
491
+ Usage:
492
+ flow linear list List assigned issues
493
+ flow linear list --refresh Force refresh from Linear
494
+ flow linear sync Import issues to ready.json
495
+ flow linear push Push completed tasks to Linear
496
+ flow linear config Show Linear configuration
497
+
498
+ Options:
499
+ --refresh Force refresh from API (bypass cache)
500
+ --json Output as JSON
501
+ --help, -h Show this help
502
+
503
+ Configuration:
504
+ Add to .workflow/config.json:
505
+ {
506
+ "integrations": {
507
+ "linear": {
508
+ "enabled": true,
509
+ "apiKey": "{env:LINEAR_API_KEY}",
510
+ "teamId": "optional-team-id"
511
+ }
512
+ }
513
+ }
514
+
515
+ Get your API key at: https://linear.app/settings/api
516
+ `);
517
+ }
518
+
519
+ async function main() {
520
+ const args = process.argv.slice(2);
521
+ const { flags, positional } = parseFlags(args);
522
+ const command = positional[0] || 'list';
523
+
524
+ if (flags.help || flags.h) {
525
+ showHelp();
526
+ process.exit(0);
527
+ }
528
+
529
+ const config = getLinearConfig();
530
+
531
+ switch (command) {
532
+ case 'list': {
533
+ if (!config.enabled) {
534
+ warn('Linear integration is not enabled');
535
+ printConfig(config);
536
+ process.exit(1);
537
+ }
538
+
539
+ info('Fetching Linear issues...');
540
+ const issues = await getIssues(flags.refresh);
541
+
542
+ if (flags.json) {
543
+ console.log(JSON.stringify(issues, null, 2));
544
+ } else {
545
+ printIssues(issues);
546
+ }
547
+ break;
548
+ }
549
+
550
+ case 'sync': {
551
+ if (!config.enabled) {
552
+ error('Linear integration is not enabled');
553
+ process.exit(1);
554
+ }
555
+
556
+ info('Syncing Linear issues to ready.json...');
557
+ const result = await syncToReady();
558
+
559
+ if (flags.json) {
560
+ console.log(JSON.stringify(result, null, 2));
561
+ } else {
562
+ if (result.imported.length > 0) {
563
+ success(`Imported ${result.imported.length} issues: ${result.imported.join(', ')}`);
564
+ }
565
+ if (result.updated.length > 0) {
566
+ info(`Updated ${result.updated.length} issues: ${result.updated.join(', ')}`);
567
+ }
568
+ if (result.imported.length === 0 && result.updated.length === 0) {
569
+ info('No changes needed');
570
+ }
571
+ }
572
+ break;
573
+ }
574
+
575
+ case 'push': {
576
+ if (!config.enabled) {
577
+ error('Linear integration is not enabled');
578
+ process.exit(1);
579
+ }
580
+
581
+ info('Pushing completed tasks to Linear...');
582
+ const result = await pushCompleted();
583
+
584
+ if (flags.json) {
585
+ console.log(JSON.stringify(result, null, 2));
586
+ } else {
587
+ if (result.pushed.length > 0) {
588
+ success(`Pushed ${result.pushed.length} tasks: ${result.pushed.join(', ')}`);
589
+ } else {
590
+ info('No completed Linear tasks to push');
591
+ }
592
+ }
593
+ break;
594
+ }
595
+
596
+ case 'config': {
597
+ if (flags.json) {
598
+ console.log(JSON.stringify(config, null, 2));
599
+ } else {
600
+ printConfig(config);
601
+ }
602
+ break;
603
+ }
604
+
605
+ default:
606
+ error(`Unknown command: ${command}`);
607
+ showHelp();
608
+ process.exit(1);
609
+ }
610
+ }
611
+
612
+ // ============================================================
613
+ // Exports
614
+ // ============================================================
615
+
616
+ module.exports = {
617
+ getLinearConfig,
618
+ fetchIssues,
619
+ getIssues,
620
+ syncToReady,
621
+ pushCompleted,
622
+ updateIssueState,
623
+ addComment
624
+ };
625
+
626
+ if (require.main === module) {
627
+ main().catch(err => {
628
+ error(err.message);
629
+ process.exit(1);
630
+ });
631
+ }