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,579 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Jira Integration
5
+ *
6
+ * Sync tasks between Wogi Flow and Jira. Supports:
7
+ * - Listing assigned Jira issues
8
+ * - Importing issues to ready.json
9
+ * - Syncing completed tasks back to Jira
10
+ *
11
+ * Part of Phase 6: Team & Integrations
12
+ *
13
+ * Usage:
14
+ * flow jira list List assigned issues
15
+ * flow jira sync Import issues to ready.json
16
+ * flow jira push Push completed tasks to Jira
17
+ * flow jira config Show/set Jira configuration
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const crypto = require('crypto');
23
+ const { HttpClient } = require('./flow-http-client');
24
+ const { TIMEOUTS } = require('./flow-constants');
25
+ const {
26
+ PROJECT_ROOT,
27
+ STATE_DIR,
28
+ parseFlags,
29
+ color,
30
+ info,
31
+ warn,
32
+ error,
33
+ success,
34
+ fileExists,
35
+ safeJsonParse,
36
+ getConfig,
37
+ setConfigValue,
38
+ resolveConfigValue,
39
+ printHeader,
40
+ generateTaskId,
41
+ writeJson,
42
+ getReadyData
43
+ } = require('./flow-utils');
44
+
45
+ // ============================================================
46
+ // Constants
47
+ // ============================================================
48
+
49
+ const READY_PATH = path.join(STATE_DIR, 'ready.json');
50
+ const JIRA_CACHE_PATH = path.join(STATE_DIR, 'jira-cache.json');
51
+ const CACHE_TTL_MS = TIMEOUTS.CACHE_TTL;
52
+
53
+ // ============================================================
54
+ // Configuration
55
+ // ============================================================
56
+
57
+ /**
58
+ * Get Jira configuration from config.json
59
+ */
60
+ function getJiraConfig() {
61
+ const config = getConfig();
62
+ const jiraConfig = config?.integrations?.jira || {};
63
+
64
+ return {
65
+ enabled: jiraConfig.enabled || false,
66
+ baseUrl: jiraConfig.baseUrl || null,
67
+ projectKey: jiraConfig.projectKey || null,
68
+ email: jiraConfig.email || null,
69
+ apiToken: resolveConfigValue(jiraConfig.apiToken),
70
+ jqlFilter: jiraConfig.jqlFilter || 'assignee = currentUser() AND status != Done ORDER BY priority DESC',
71
+ syncStatuses: jiraConfig.syncStatuses || {
72
+ ready: ['To Do', 'Open', 'Backlog'],
73
+ inProgress: ['In Progress', 'In Review'],
74
+ completed: ['Done', 'Closed', 'Resolved']
75
+ }
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Save Jira configuration (async with locking)
81
+ */
82
+ async function saveJiraConfig(jiraConfig) {
83
+ const config = getConfig();
84
+ const currentJira = config?.integrations?.jira || {};
85
+ await setConfigValue('integrations.jira', {
86
+ ...currentJira,
87
+ ...jiraConfig
88
+ });
89
+ }
90
+
91
+ // ============================================================
92
+ // Jira API Client
93
+ // ============================================================
94
+
95
+ /**
96
+ * Make authenticated Jira API request using shared HttpClient
97
+ */
98
+ async function jiraRequest(method, endpoint, body = null) {
99
+ const config = getJiraConfig();
100
+
101
+ if (!config.baseUrl || !config.email || !config.apiToken) {
102
+ throw new Error('Jira not configured. Run: flow jira config');
103
+ }
104
+
105
+ const auth = Buffer.from(`${config.email}:${config.apiToken}`).toString('base64');
106
+ const client = new HttpClient(config.baseUrl, {
107
+ headers: {
108
+ 'Authorization': `Basic ${auth}`,
109
+ 'Accept': 'application/json'
110
+ },
111
+ timeout: 30000
112
+ });
113
+
114
+ const response = await client.request(method, endpoint, body);
115
+
116
+ if (response.status >= 400) {
117
+ throw new Error(`Jira API error ${response.status}: ${JSON.stringify(response.data)}`);
118
+ }
119
+
120
+ return response.data;
121
+ }
122
+
123
+ // ============================================================
124
+ // Issue Operations
125
+ // ============================================================
126
+
127
+ /**
128
+ * Fetch issues from Jira using JQL
129
+ */
130
+ async function fetchIssues(jql = null) {
131
+ const config = getJiraConfig();
132
+ const query = jql || config.jqlFilter;
133
+
134
+ const searchUrl = `/rest/api/3/search?jql=${encodeURIComponent(query)}&fields=summary,status,priority,assignee,created,updated,description`;
135
+
136
+ const result = await jiraRequest('GET', searchUrl);
137
+
138
+ return result.issues.map(issue => ({
139
+ key: issue.key,
140
+ id: issue.id,
141
+ summary: issue.fields.summary,
142
+ description: issue.fields.description?.content?.[0]?.content?.[0]?.text || '',
143
+ status: issue.fields.status?.name,
144
+ priority: issue.fields.priority?.name,
145
+ assignee: issue.fields.assignee?.displayName,
146
+ created: issue.fields.created,
147
+ updated: issue.fields.updated,
148
+ url: `${config.baseUrl}/browse/${issue.key}`
149
+ }));
150
+ }
151
+
152
+ /**
153
+ * Get cached issues or fetch fresh
154
+ */
155
+ async function getIssues(forceRefresh = false) {
156
+ if (!forceRefresh && fileExists(JIRA_CACHE_PATH)) {
157
+ const cache = safeJsonParse(JIRA_CACHE_PATH);
158
+ // Validate cache has issues array and valid timestamp
159
+ if (cache?.issues && cache?.fetchedAt) {
160
+ const fetchTime = new Date(cache.fetchedAt).getTime();
161
+ // Check for valid date and within TTL
162
+ if (!isNaN(fetchTime) && Date.now() - fetchTime < CACHE_TTL_MS) {
163
+ return cache.issues;
164
+ }
165
+ }
166
+ }
167
+
168
+ const issues = await fetchIssues();
169
+
170
+ // Save to cache using writeJson for atomic writes
171
+ writeJson(JIRA_CACHE_PATH, {
172
+ fetchedAt: new Date().toISOString(),
173
+ issues
174
+ });
175
+
176
+ return issues;
177
+ }
178
+
179
+ /**
180
+ * Update issue status in Jira
181
+ */
182
+ async function updateIssueStatus(issueKey, statusName) {
183
+ // Get available transitions
184
+ const transitions = await jiraRequest('GET', `/rest/api/3/issue/${issueKey}/transitions`);
185
+
186
+ const transition = transitions.transitions.find(t =>
187
+ t.name.toLowerCase() === statusName.toLowerCase() ||
188
+ t.to.name.toLowerCase() === statusName.toLowerCase()
189
+ );
190
+
191
+ if (!transition) {
192
+ throw new Error(`Transition to "${statusName}" not available for ${issueKey}`);
193
+ }
194
+
195
+ await jiraRequest('POST', `/rest/api/3/issue/${issueKey}/transitions`, {
196
+ transition: { id: transition.id }
197
+ });
198
+
199
+ return { success: true, issueKey, newStatus: statusName };
200
+ }
201
+
202
+ /**
203
+ * Add comment to issue
204
+ */
205
+ async function addComment(issueKey, comment) {
206
+ await jiraRequest('POST', `/rest/api/3/issue/${issueKey}/comment`, {
207
+ body: {
208
+ type: 'doc',
209
+ version: 1,
210
+ content: [{
211
+ type: 'paragraph',
212
+ content: [{ type: 'text', text: comment }]
213
+ }]
214
+ }
215
+ });
216
+ }
217
+
218
+ // ============================================================
219
+ // Sync Operations
220
+ // ============================================================
221
+
222
+ /**
223
+ * Import Jira issues to ready.json
224
+ */
225
+ async function syncToReady() {
226
+ const config = getJiraConfig();
227
+ const issues = await getIssues(true);
228
+
229
+ // Load current ready.json
230
+ const ready = safeJsonParse(READY_PATH) || {
231
+ ready: [],
232
+ inProgress: [],
233
+ blocked: [],
234
+ recentlyCompleted: []
235
+ };
236
+
237
+ // Map existing tasks by external ID
238
+ const existingByExternal = new Map();
239
+ for (const task of [...ready.ready, ...ready.inProgress, ...ready.blocked]) {
240
+ if (task.externalId) {
241
+ existingByExternal.set(task.externalId, task);
242
+ }
243
+ }
244
+
245
+ const imported = [];
246
+ const updated = [];
247
+
248
+ for (const issue of issues) {
249
+ const externalId = `jira:${issue.key}`;
250
+ const existing = existingByExternal.get(externalId);
251
+
252
+ // Determine status category
253
+ let targetList = 'ready';
254
+ if (config.syncStatuses.inProgress.includes(issue.status)) {
255
+ targetList = 'inProgress';
256
+ } else if (config.syncStatuses.completed.includes(issue.status)) {
257
+ continue; // Skip completed
258
+ }
259
+
260
+ if (existing) {
261
+ // Update existing task
262
+ existing.title = issue.summary;
263
+ existing.priority = mapPriority(issue.priority);
264
+ existing.updatedAt = new Date().toISOString();
265
+ updated.push(issue.key);
266
+ } else {
267
+ // Create new task using standard task ID generator
268
+ const taskId = generateTaskId();
269
+ const task = {
270
+ id: taskId,
271
+ externalId,
272
+ externalUrl: issue.url,
273
+ title: issue.summary,
274
+ type: 'story',
275
+ feature: 'general',
276
+ status: targetList === 'ready' ? 'ready' : 'in_progress',
277
+ priority: mapPriority(issue.priority),
278
+ source: 'jira',
279
+ importedAt: new Date().toISOString()
280
+ };
281
+
282
+ ready[targetList].push(task);
283
+ imported.push(issue.key);
284
+ }
285
+ }
286
+
287
+ // Save ready.json using atomic write
288
+ ready.lastUpdated = new Date().toISOString();
289
+ writeJson(READY_PATH, ready);
290
+
291
+ return { imported, updated };
292
+ }
293
+
294
+ /**
295
+ * Push completed tasks back to Jira
296
+ */
297
+ async function pushCompleted() {
298
+ const config = getJiraConfig();
299
+ const ready = safeJsonParse(READY_PATH);
300
+
301
+ if (!ready || !ready.recentlyCompleted) {
302
+ return { pushed: [] };
303
+ }
304
+
305
+ // Validate completed statuses array exists
306
+ const completedStatuses = config.syncStatuses?.completed || [];
307
+ if (completedStatuses.length === 0) {
308
+ warn('No completed statuses configured, using default "Done"');
309
+ }
310
+ const targetStatus = completedStatuses[0] || 'Done';
311
+
312
+ const pushed = [];
313
+
314
+ for (const task of ready.recentlyCompleted) {
315
+ // Validate external ID format
316
+ if (!task.externalId || !task.externalId.startsWith('jira:')) {
317
+ continue;
318
+ }
319
+
320
+ const issueKey = task.externalId.replace('jira:', '');
321
+ // Validate issue key format (PROJECT-123)
322
+ if (!/^[A-Z][A-Z0-9]+-\d+$/i.test(issueKey)) {
323
+ warn(`Invalid Jira issue key format: ${issueKey}`);
324
+ continue;
325
+ }
326
+
327
+ try {
328
+ await updateIssueStatus(issueKey, targetStatus);
329
+ } catch (err) {
330
+ warn(`Failed to update status for ${issueKey}: ${err.message}`);
331
+ continue;
332
+ }
333
+
334
+ try {
335
+ await addComment(issueKey, `Completed via Wogi Flow at ${task.completedAt}`);
336
+ } catch (err) {
337
+ warn(`Failed to add comment to ${issueKey}: ${err.message}`);
338
+ // Status was updated, so still count as pushed
339
+ }
340
+
341
+ pushed.push(issueKey);
342
+ }
343
+
344
+ return { pushed };
345
+ }
346
+
347
+ /**
348
+ * Map Jira priority to Wogi Flow priority
349
+ */
350
+ function mapPriority(jiraPriority) {
351
+ const mapping = {
352
+ 'Highest': 'P0',
353
+ 'High': 'P1',
354
+ 'Medium': 'P2',
355
+ 'Low': 'P3',
356
+ 'Lowest': 'P4'
357
+ };
358
+ return mapping[jiraPriority] || 'P2';
359
+ }
360
+
361
+ // ============================================================
362
+ // CLI Output
363
+ // ============================================================
364
+
365
+ function printIssues(issues) {
366
+ if (issues.length === 0) {
367
+ info('No issues found');
368
+ return;
369
+ }
370
+
371
+ printHeader('JIRA ISSUES');
372
+
373
+ for (const issue of issues) {
374
+ const statusColor = getStatusColor(issue.status);
375
+ const priorityIcon = getPriorityIcon(issue.priority);
376
+
377
+ console.log(`\n ${color('cyan', issue.key)} ${priorityIcon}`);
378
+ console.log(` ${issue.summary}`);
379
+ console.log(` ${color(statusColor, issue.status)} • ${color('dim', issue.assignee || 'Unassigned')}`);
380
+ }
381
+
382
+ console.log(`\n ${color('dim', `Total: ${issues.length} issues`)}\n`);
383
+ }
384
+
385
+ function getStatusColor(status) {
386
+ const lower = status?.toLowerCase() || '';
387
+ if (lower.includes('done') || lower.includes('closed')) return 'green';
388
+ if (lower.includes('progress')) return 'yellow';
389
+ if (lower.includes('blocked')) return 'red';
390
+ return 'blue';
391
+ }
392
+
393
+ function getPriorityIcon(priority) {
394
+ const icons = {
395
+ 'Highest': color('red', '!!!'),
396
+ 'High': color('red', '!!'),
397
+ 'Medium': color('yellow', '!'),
398
+ 'Low': color('dim', '-'),
399
+ 'Lowest': color('dim', '--')
400
+ };
401
+ return icons[priority] || '';
402
+ }
403
+
404
+ function printConfig(config) {
405
+ printHeader('JIRA CONFIGURATION');
406
+
407
+ console.log(` ${color('dim', 'Enabled:')} ${config.enabled ? color('green', 'Yes') : color('red', 'No')}`);
408
+ console.log(` ${color('dim', 'Base URL:')} ${config.baseUrl || color('yellow', 'Not set')}`);
409
+ console.log(` ${color('dim', 'Project:')} ${config.projectKey || color('yellow', 'Not set')}`);
410
+ console.log(` ${color('dim', 'Email:')} ${config.email || color('yellow', 'Not set')}`);
411
+ console.log(` ${color('dim', 'API Token:')} ${config.apiToken ? color('green', 'Set') : color('yellow', 'Not set')}`);
412
+
413
+ console.log(`\n ${color('dim', 'JQL Filter:')}`);
414
+ console.log(` ${config.jqlFilter}`);
415
+
416
+ console.log(`\nTo configure, add to .workflow/config.json:`);
417
+ console.log(` "integrations": {`);
418
+ console.log(` "jira": {`);
419
+ console.log(` "enabled": true,`);
420
+ console.log(` "baseUrl": "https://your-company.atlassian.net",`);
421
+ console.log(` "projectKey": "PROJ",`);
422
+ console.log(` "email": "you@company.com",`);
423
+ console.log(` "apiToken": "{env:JIRA_API_TOKEN}"`);
424
+ console.log(` }`);
425
+ console.log(` }`);
426
+ console.log('');
427
+ }
428
+
429
+ // ============================================================
430
+ // CLI Entry Point
431
+ // ============================================================
432
+
433
+ function showHelp() {
434
+ console.log(`
435
+ Wogi Flow - Jira Integration
436
+
437
+ Sync tasks between Wogi Flow and Jira.
438
+
439
+ Usage:
440
+ flow jira list List assigned issues
441
+ flow jira list --refresh Force refresh from Jira
442
+ flow jira sync Import issues to ready.json
443
+ flow jira push Push completed tasks to Jira
444
+ flow jira config Show Jira configuration
445
+
446
+ Options:
447
+ --refresh Force refresh from API (bypass cache)
448
+ --json Output as JSON
449
+ --help, -h Show this help
450
+
451
+ Configuration:
452
+ Add to .workflow/config.json:
453
+ {
454
+ "integrations": {
455
+ "jira": {
456
+ "enabled": true,
457
+ "baseUrl": "https://company.atlassian.net",
458
+ "projectKey": "PROJ",
459
+ "email": "user@company.com",
460
+ "apiToken": "{env:JIRA_API_TOKEN}"
461
+ }
462
+ }
463
+ }
464
+ `);
465
+ }
466
+
467
+ async function main() {
468
+ const args = process.argv.slice(2);
469
+ const { flags, positional } = parseFlags(args);
470
+ const command = positional[0] || 'list';
471
+
472
+ if (flags.help || flags.h) {
473
+ showHelp();
474
+ process.exit(0);
475
+ }
476
+
477
+ const config = getJiraConfig();
478
+
479
+ switch (command) {
480
+ case 'list': {
481
+ if (!config.enabled) {
482
+ warn('Jira integration is not enabled');
483
+ printConfig(config);
484
+ process.exit(1);
485
+ }
486
+
487
+ info('Fetching Jira issues...');
488
+ const issues = await getIssues(flags.refresh);
489
+
490
+ if (flags.json) {
491
+ console.log(JSON.stringify(issues, null, 2));
492
+ } else {
493
+ printIssues(issues);
494
+ }
495
+ break;
496
+ }
497
+
498
+ case 'sync': {
499
+ if (!config.enabled) {
500
+ error('Jira integration is not enabled');
501
+ process.exit(1);
502
+ }
503
+
504
+ info('Syncing Jira issues to ready.json...');
505
+ const result = await syncToReady();
506
+
507
+ if (flags.json) {
508
+ console.log(JSON.stringify(result, null, 2));
509
+ } else {
510
+ if (result.imported.length > 0) {
511
+ success(`Imported ${result.imported.length} issues: ${result.imported.join(', ')}`);
512
+ }
513
+ if (result.updated.length > 0) {
514
+ info(`Updated ${result.updated.length} issues: ${result.updated.join(', ')}`);
515
+ }
516
+ if (result.imported.length === 0 && result.updated.length === 0) {
517
+ info('No changes needed');
518
+ }
519
+ }
520
+ break;
521
+ }
522
+
523
+ case 'push': {
524
+ if (!config.enabled) {
525
+ error('Jira integration is not enabled');
526
+ process.exit(1);
527
+ }
528
+
529
+ info('Pushing completed tasks to Jira...');
530
+ const result = await pushCompleted();
531
+
532
+ if (flags.json) {
533
+ console.log(JSON.stringify(result, null, 2));
534
+ } else {
535
+ if (result.pushed.length > 0) {
536
+ success(`Pushed ${result.pushed.length} tasks: ${result.pushed.join(', ')}`);
537
+ } else {
538
+ info('No completed Jira tasks to push');
539
+ }
540
+ }
541
+ break;
542
+ }
543
+
544
+ case 'config': {
545
+ if (flags.json) {
546
+ console.log(JSON.stringify(config, null, 2));
547
+ } else {
548
+ printConfig(config);
549
+ }
550
+ break;
551
+ }
552
+
553
+ default:
554
+ error(`Unknown command: ${command}`);
555
+ showHelp();
556
+ process.exit(1);
557
+ }
558
+ }
559
+
560
+ // ============================================================
561
+ // Exports
562
+ // ============================================================
563
+
564
+ module.exports = {
565
+ getJiraConfig,
566
+ fetchIssues,
567
+ getIssues,
568
+ syncToReady,
569
+ pushCompleted,
570
+ updateIssueStatus,
571
+ addComment
572
+ };
573
+
574
+ if (require.main === module) {
575
+ main().catch(err => {
576
+ error(err.message);
577
+ process.exit(1);
578
+ });
579
+ }