@covibes/zeroshot 5.3.0 → 5.4.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 (82) hide show
  1. package/README.md +94 -14
  2. package/cli/commands/providers.js +8 -9
  3. package/cli/index.js +3032 -2409
  4. package/cli/message-formatters-normal.js +28 -6
  5. package/cluster-templates/base-templates/debug-workflow.json +1 -1
  6. package/cluster-templates/base-templates/full-workflow.json +72 -188
  7. package/cluster-templates/base-templates/worker-validator.json +59 -3
  8. package/cluster-templates/conductor-bootstrap.json +4 -4
  9. package/lib/docker-config.js +8 -0
  10. package/lib/git-remote-utils.js +165 -0
  11. package/lib/id-detector.js +10 -7
  12. package/lib/provider-defaults.js +62 -0
  13. package/lib/provider-names.js +2 -1
  14. package/lib/settings/claude-auth.js +78 -0
  15. package/lib/settings.js +161 -63
  16. package/package.json +7 -2
  17. package/scripts/setup-merge-queue.sh +170 -0
  18. package/src/agent/agent-config.js +135 -82
  19. package/src/agent/agent-context-builder.js +297 -188
  20. package/src/agent/agent-hook-executor.js +310 -113
  21. package/src/agent/agent-lifecycle.js +385 -325
  22. package/src/agent/agent-stuck-detector.js +7 -7
  23. package/src/agent/agent-task-executor.js +824 -565
  24. package/src/agent/output-extraction.js +41 -24
  25. package/src/agent/output-reformatter.js +1 -1
  26. package/src/agent/schema-utils.js +108 -73
  27. package/src/agent-wrapper.js +10 -1
  28. package/src/agents/git-pusher-template.js +285 -0
  29. package/src/claude-task-runner.js +85 -34
  30. package/src/config-validator.js +922 -657
  31. package/src/input-helpers.js +65 -0
  32. package/src/isolation-manager.js +289 -199
  33. package/src/issue-providers/README.md +305 -0
  34. package/src/issue-providers/azure-devops-provider.js +273 -0
  35. package/src/issue-providers/base-provider.js +232 -0
  36. package/src/issue-providers/github-provider.js +179 -0
  37. package/src/issue-providers/gitlab-provider.js +241 -0
  38. package/src/issue-providers/index.js +196 -0
  39. package/src/issue-providers/jira-provider.js +239 -0
  40. package/src/ledger.js +22 -5
  41. package/src/lib/safe-exec.js +88 -0
  42. package/src/orchestrator.js +1107 -811
  43. package/src/preflight.js +313 -159
  44. package/src/process-metrics.js +98 -56
  45. package/src/providers/anthropic/cli-builder.js +53 -25
  46. package/src/providers/anthropic/index.js +72 -2
  47. package/src/providers/anthropic/output-parser.js +32 -14
  48. package/src/providers/base-provider.js +70 -0
  49. package/src/providers/capabilities.js +9 -0
  50. package/src/providers/google/output-parser.js +47 -38
  51. package/src/providers/index.js +19 -3
  52. package/src/providers/openai/cli-builder.js +14 -3
  53. package/src/providers/openai/index.js +1 -0
  54. package/src/providers/openai/output-parser.js +44 -30
  55. package/src/providers/opencode/cli-builder.js +42 -0
  56. package/src/providers/opencode/index.js +103 -0
  57. package/src/providers/opencode/models.js +55 -0
  58. package/src/providers/opencode/output-parser.js +122 -0
  59. package/src/schemas/sub-cluster.js +68 -39
  60. package/src/status-footer.js +94 -48
  61. package/src/sub-cluster-wrapper.js +76 -35
  62. package/src/template-resolver.js +12 -9
  63. package/src/tui/data-poller.js +123 -99
  64. package/src/tui/formatters.js +4 -3
  65. package/src/tui/keybindings.js +259 -318
  66. package/src/tui/renderer.js +11 -21
  67. package/task-lib/attachable-watcher.js +118 -81
  68. package/task-lib/claude-recovery.js +61 -24
  69. package/task-lib/commands/episodes.js +105 -0
  70. package/task-lib/commands/list.js +2 -2
  71. package/task-lib/commands/logs.js +231 -189
  72. package/task-lib/commands/schedules.js +111 -61
  73. package/task-lib/config.js +0 -2
  74. package/task-lib/runner.js +84 -27
  75. package/task-lib/scheduler.js +1 -1
  76. package/task-lib/store.js +467 -168
  77. package/task-lib/tui/formatters.js +94 -45
  78. package/task-lib/tui/renderer.js +13 -3
  79. package/task-lib/tui.js +73 -32
  80. package/task-lib/watcher.js +128 -90
  81. package/src/agents/git-pusher-agent.json +0 -20
  82. package/src/github.js +0 -139
@@ -0,0 +1,305 @@
1
+ # Issue Providers
2
+
3
+ Multi-platform issue support for Zeroshot. Fetch issues from GitHub, GitLab, Jira, and Azure DevOps.
4
+
5
+ ## Supported Providers
6
+
7
+ | Provider | CLI Tool | URL Pattern | Issue Key Format |
8
+ | ---------------- | -------- | -------------------------------------------- | --------------------- |
9
+ | **GitHub** | `gh` | `github.com/org/repo/issues/123` | `123`, `org/repo#123` |
10
+ | **GitLab** | `glab` | `gitlab.com/org/repo/-/issues/123` | `123`, `org/repo#123` |
11
+ | **Jira** | `jira` | `*.atlassian.net/browse/KEY-123` | `KEY-123` |
12
+ | **Azure DevOps** | `az` | `dev.azure.com/org/proj/_workitems/edit/123` | `123` |
13
+
14
+ ## Quick Start
15
+
16
+ ```bash
17
+ # GitHub
18
+ zeroshot run 123
19
+ zeroshot run https://github.com/org/repo/issues/123
20
+ zeroshot run org/repo#123
21
+
22
+ # GitLab
23
+ zeroshot run https://gitlab.com/org/repo/-/issues/123
24
+ zeroshot run 123 --gitlab
25
+
26
+ # Jira
27
+ zeroshot run PROJ-123
28
+ zeroshot run https://company.atlassian.net/browse/PROJ-123
29
+
30
+ # Azure DevOps
31
+ zeroshot run https://dev.azure.com/org/project/_workitems/edit/123
32
+ zeroshot run 123 --devops
33
+ ```
34
+
35
+ ## Automatic Git Remote Detection
36
+
37
+ When working in a git repository, zeroshot automatically detects the issue provider from your git remote URL:
38
+
39
+ ```bash
40
+ # In a GitHub repository
41
+ git remote get-url origin # → https://github.com/org/repo.git
42
+ zeroshot run 123 # Automatically uses GitHub
43
+
44
+ # In a GitLab repository
45
+ git remote get-url origin # → https://gitlab.com/org/repo.git
46
+ zeroshot run 456 # Automatically uses GitLab
47
+
48
+ # In an Azure DevOps repository
49
+ git remote get-url origin # → https://dev.azure.com/org/project/_git/repo
50
+ zeroshot run 789 # Automatically uses Azure DevOps
51
+ ```
52
+
53
+ ## Force Flags
54
+
55
+ Override auto-detection with explicit provider flags:
56
+
57
+ ```bash
58
+ -G, --github # Force GitHub as issue source
59
+ -L, --gitlab # Force GitLab as issue source
60
+ -J, --jira # Force Jira as issue source
61
+ -D, --devops # Force Azure DevOps as issue source
62
+ ```
63
+
64
+ **Example:**
65
+
66
+ ```bash
67
+ # Force GitLab even when in a GitHub repo
68
+ zeroshot run 123 --gitlab
69
+
70
+ # Force Jira for bare number
71
+ zeroshot run 456 --jira
72
+ ```
73
+
74
+ ## Detection Priority
75
+
76
+ For bare issue numbers (e.g., `123`), zeroshot uses this priority:
77
+
78
+ 1. **Force flag** (`--github`, `--gitlab`, etc.) - Explicit CLI override
79
+ 2. **Git remote URL** - Auto-detected from your repository
80
+ 3. **Settings** (`defaultIssueSource`) - User preference
81
+ 4. **Legacy fallback** - GitHub (when no git context and no settings)
82
+
83
+ For URLs and issue keys (like `PROJ-123`), the provider is detected from the format.
84
+
85
+ ## Settings
86
+
87
+ Configure default provider and platform-specific settings:
88
+
89
+ ```bash
90
+ # Set default provider for bare numbers
91
+ zeroshot settings set defaultIssueSource gitlab
92
+
93
+ # GitLab self-hosted instance
94
+ zeroshot settings set gitlabInstance gitlab.company.com
95
+
96
+ # Jira configuration
97
+ zeroshot settings set jiraInstance jira.company.com
98
+ zeroshot settings set jiraProject MYPROJECT # Default project for bare numbers
99
+
100
+ # Azure DevOps configuration
101
+ zeroshot settings set azureOrg mycompany
102
+ zeroshot settings set azureProject myproject # Default project for bare numbers
103
+ ```
104
+
105
+ ### Settings Reference
106
+
107
+ | Setting | Type | Default | Description |
108
+ | -------------------- | ------ | ------- | ------------------------------------------ |
109
+ | `defaultIssueSource` | string | github | Provider for bare numbers (123) |
110
+ | `gitlabInstance` | string | null | Self-hosted GitLab URL |
111
+ | `jiraInstance` | string | null | Self-hosted Jira URL |
112
+ | `jiraProject` | string | null | Default Jira project key for bare numbers |
113
+ | `azureOrg` | string | null | Azure DevOps organization name |
114
+ | `azureProject` | string | null | Azure DevOps project name for bare numbers |
115
+
116
+ ## CLI Tool Setup
117
+
118
+ Each provider requires its corresponding CLI tool:
119
+
120
+ ### GitHub
121
+
122
+ ```bash
123
+ # Install
124
+ brew install gh # macOS
125
+ apt install gh # Linux
126
+ # Or: https://cli.github.com/
127
+
128
+ # Authenticate
129
+ gh auth login
130
+ gh auth status
131
+ ```
132
+
133
+ ### GitLab
134
+
135
+ ```bash
136
+ # Install
137
+ brew install glab # macOS
138
+ # Or: https://gitlab.com/gitlab-org/cli
139
+
140
+ # Authenticate
141
+ glab auth login
142
+ glab auth status
143
+ ```
144
+
145
+ ### Jira
146
+
147
+ ```bash
148
+ # Install go-jira
149
+ brew install go-jira # macOS
150
+ # Or: https://github.com/go-jira/jira
151
+
152
+ # Configure
153
+ jira login
154
+ jira version
155
+ ```
156
+
157
+ ### Azure DevOps
158
+
159
+ ```bash
160
+ # Install Azure CLI
161
+ brew install azure-cli # macOS
162
+ # Or: https://docs.microsoft.com/cli/azure/
163
+
164
+ # Configure
165
+ az login
166
+ az devops configure --defaults organization=https://dev.azure.com/yourorg
167
+ ```
168
+
169
+ ## Self-Hosted Instances
170
+
171
+ ### GitLab Self-Hosted
172
+
173
+ ```bash
174
+ # Configure self-hosted instance
175
+ zeroshot settings set gitlabInstance gitlab.company.com
176
+
177
+ # Now these work
178
+ zeroshot run https://gitlab.company.com/org/repo/-/issues/123
179
+ zeroshot run 123 --gitlab # Uses self-hosted instance
180
+ ```
181
+
182
+ ### Jira Self-Hosted (Server/Data Center)
183
+
184
+ ```bash
185
+ # Configure self-hosted instance and default project
186
+ zeroshot settings set jiraInstance jira.company.com
187
+ zeroshot settings set jiraProject MYPROJ
188
+
189
+ # Now these work
190
+ zeroshot run https://jira.company.com/browse/MYPROJ-123
191
+ zeroshot run MYPROJ-123
192
+ zeroshot run 123 --jira # Becomes MYPROJ-123
193
+ ```
194
+
195
+ ## Examples
196
+
197
+ ### Team Using GitLab
198
+
199
+ ```bash
200
+ # Set once per machine
201
+ zeroshot settings set defaultIssueSource gitlab
202
+
203
+ # Then just use bare numbers
204
+ zeroshot run 456
205
+ ```
206
+
207
+ ### Team Using Jira
208
+
209
+ ```bash
210
+ # Set once per machine
211
+ zeroshot settings set defaultIssueSource jira
212
+ zeroshot settings set jiraProject MYTEAM
213
+
214
+ # Then just use bare numbers (becomes MYTEAM-789)
215
+ zeroshot run 789
216
+ ```
217
+
218
+ ### Mixed Team
219
+
220
+ ```bash
221
+ # Use explicit URLs or flags when working across platforms
222
+ zeroshot run https://github.com/org/repo/issues/100
223
+ zeroshot run https://gitlab.com/org/repo/-/issues/200
224
+ zeroshot run PROJ-300
225
+ ```
226
+
227
+ ### CI/CD Integration
228
+
229
+ ```yaml
230
+ # .github/workflows/zeroshot.yml
231
+ - name: Run Zeroshot on Issue
232
+ run: zeroshot run ${{ github.event.issue.html_url }} --ship
233
+
234
+ # .gitlab-ci.yml
235
+ - script: zeroshot run $CI_MERGE_REQUEST_IID --gitlab --ship
236
+
237
+ # Azure Pipelines
238
+ - script: zeroshot run $(System.WorkItemId) --devops --ship
239
+ ```
240
+
241
+ ## Troubleshooting
242
+
243
+ ### Provider Not Detected
244
+
245
+ **Problem:** Bare number not detected as expected provider
246
+
247
+ ```bash
248
+ $ zeroshot run 123
249
+ # Fetches from GitHub, but I wanted GitLab
250
+ ```
251
+
252
+ **Solution:** Set `defaultIssueSource` or use force flag
253
+
254
+ ```bash
255
+ zeroshot settings set defaultIssueSource gitlab
256
+ # OR
257
+ zeroshot run 123 --gitlab
258
+ ```
259
+
260
+ ### CLI Tool Not Installed
261
+
262
+ **Problem:** Preflight check fails
263
+
264
+ ```bash
265
+ PREFLIGHT CHECK FAILED
266
+ GitLab CLI (glab) not installed
267
+ ```
268
+
269
+ **Solution:** Install the required CLI tool
270
+
271
+ ```bash
272
+ brew install glab
273
+ glab auth login
274
+ ```
275
+
276
+ ### Self-Hosted Instance Not Recognized
277
+
278
+ **Problem:** Self-hosted URL not detected
279
+
280
+ ```bash
281
+ $ zeroshot run https://gitlab.mycompany.com/org/repo/-/issues/123
282
+ Error: No issue provider matched input
283
+ ```
284
+
285
+ **Solution:** Configure the instance setting
286
+
287
+ ```bash
288
+ zeroshot settings set gitlabInstance gitlab.mycompany.com
289
+ ```
290
+
291
+ ### Jira Bare Numbers Don't Work
292
+
293
+ **Problem:** Bare numbers don't convert to Jira keys
294
+
295
+ ```bash
296
+ $ zeroshot run 123 --jira
297
+ Error: Failed to fetch Jira issue
298
+ ```
299
+
300
+ **Solution:** Configure `jiraProject` setting
301
+
302
+ ```bash
303
+ zeroshot settings set jiraProject MYPROJECT
304
+ # Now: 123 becomes MYPROJECT-123
305
+ ```
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Azure DevOps Provider - Fetch work items via az CLI
3
+ * Supports Azure DevOps cloud (dev.azure.com)
4
+ */
5
+
6
+ const IssueProvider = require('./base-provider');
7
+ const { execSync } = require('../lib/safe-exec');
8
+ const { detectGitContext } = require('../../lib/git-remote-utils');
9
+
10
+ class AzureDevOpsProvider extends IssueProvider {
11
+ static id = 'azure-devops';
12
+ static displayName = 'Azure DevOps';
13
+
14
+ /**
15
+ * Azure DevOps supports PR creation via az CLI
16
+ */
17
+ static supportsPR() {
18
+ return true;
19
+ }
20
+
21
+ /**
22
+ * Get PR CLI tool info for preflight checks
23
+ */
24
+ static getPRTool() {
25
+ return {
26
+ name: 'az',
27
+ checkCmd: 'az --version',
28
+ installHint: 'https://docs.microsoft.com/cli/azure/',
29
+ displayName: 'Azure DevOps',
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Detect Azure DevOps work item URLs
35
+ * Matches:
36
+ * - dev.azure.com URLs
37
+ * - *.visualstudio.com URLs
38
+ * - Bare numbers when git remote is Azure DevOps (auto-detected)
39
+ * - Bare numbers when defaultIssueSource=azure-devops (requires azureOrg setting)
40
+ *
41
+ * @param {string} input - Issue identifier (URL or number)
42
+ * @param {Object} settings - User settings
43
+ * @param {Object|null} gitContext - Auto-detected git remote context
44
+ * @returns {boolean} True if this provider should handle the input
45
+ */
46
+ static detectIdentifier(input, settings, gitContext = null) {
47
+ // dev.azure.com URLs
48
+ if (/dev\.azure\.com\/.*\/_workitems\/edit\/\d+/.test(input)) {
49
+ return true;
50
+ }
51
+
52
+ // Legacy visualstudio.com URLs (e.g., https://org.visualstudio.com/project/_workitems/edit/123)
53
+ if (/https?:\/\/[^.]+\.visualstudio\.com\/[^/]+\/_workitems\/edit\/\d+/.test(input)) {
54
+ return true;
55
+ }
56
+
57
+ // Bare numbers - use shared priority cascade logic
58
+ // Azure DevOps requires azureOrg setting when using defaultIssueSource
59
+ return IssueProvider.detectBareNumber(input, settings, gitContext, 'azure-devops', {
60
+ requiredSettings: ['azureOrg'],
61
+ });
62
+ }
63
+
64
+ static getRequiredTool() {
65
+ return {
66
+ name: 'az',
67
+ checkCmd: 'az --version',
68
+ installHint:
69
+ 'Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli\nThen: az extension add --name azure-devops',
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Check az CLI authentication for Azure DevOps
75
+ */
76
+ static checkAuth() {
77
+ try {
78
+ // First check Azure login
79
+ execSync('az account show', { encoding: 'utf8', stdio: 'pipe' });
80
+ } catch (err) {
81
+ const stderr = err.stderr || err.message || '';
82
+
83
+ if (stderr.includes('az login') || stderr.includes('not logged in')) {
84
+ return {
85
+ authenticated: false,
86
+ error: 'Azure CLI not authenticated',
87
+ recovery: [
88
+ 'Run: az login',
89
+ 'Then verify: az account show',
90
+ 'Ensure azure-devops extension: az extension add --name azure-devops',
91
+ ],
92
+ };
93
+ }
94
+
95
+ return {
96
+ authenticated: false,
97
+ error: stderr.trim() || 'Unknown Azure CLI error',
98
+ recovery: ['Run: az login', 'Then verify: az account show'],
99
+ };
100
+ }
101
+
102
+ // Check azure-devops extension is installed
103
+ try {
104
+ const output = execSync('az extension list --query "[?name==\'azure-devops\']" -o json', {
105
+ encoding: 'utf8',
106
+ stdio: 'pipe',
107
+ });
108
+ const extensions = JSON.parse(output);
109
+ if (extensions.length === 0) {
110
+ return {
111
+ authenticated: false,
112
+ error: 'Azure DevOps extension not installed',
113
+ recovery: [
114
+ 'Install the extension: az extension add --name azure-devops',
115
+ 'Then verify: az extension list | grep azure-devops',
116
+ ],
117
+ };
118
+ }
119
+ } catch {
120
+ return {
121
+ authenticated: false,
122
+ error: 'Could not verify Azure DevOps extension',
123
+ recovery: ['Install the extension: az extension add --name azure-devops'],
124
+ };
125
+ }
126
+
127
+ return { authenticated: true, error: null, recovery: [] };
128
+ }
129
+
130
+ /**
131
+ * Azure DevOps-specific settings schema
132
+ */
133
+ static getSettingsSchema() {
134
+ return {
135
+ azureOrg: {
136
+ type: 'string',
137
+ nullable: true,
138
+ default: null,
139
+ description: "Azure DevOps org URL (e.g., 'https://dev.azure.com/myorg')",
140
+ },
141
+ azureProject: {
142
+ type: 'string',
143
+ nullable: true,
144
+ default: null,
145
+ description: "Azure DevOps project name (e.g., 'MyProject')",
146
+ },
147
+ };
148
+ }
149
+
150
+ fetchIssue(identifier, settings) {
151
+ try {
152
+ const { workItemId, org, project: _project } = this._parseIdentifier(identifier, settings);
153
+
154
+ if (!org) {
155
+ throw new Error(
156
+ 'Azure DevOps requires azureOrg setting. Set via: zeroshot settings set azureOrg <url>'
157
+ );
158
+ }
159
+
160
+ // Fetch work item using az CLI
161
+ // Note: Work item IDs are unique per organization, no --project flag needed
162
+ const cmd = `az boards work-item show --id ${workItemId} --org "${org}" -o json`;
163
+ const output = execSync(cmd, { encoding: 'utf8' });
164
+ const workItem = JSON.parse(output);
165
+
166
+ return this._parseWorkItem(workItem);
167
+ } catch (error) {
168
+ throw new Error(`Failed to fetch Azure DevOps work item: ${error.message}`);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Parse identifier to extract work item ID, org, and project
174
+ * Uses git context for auto-detection when settings not provided
175
+ * @private
176
+ */
177
+ _parseIdentifier(identifier, settings) {
178
+ // URL format: https://dev.azure.com/org/project/_workitems/edit/123
179
+ // Use [^/]+ to match any characters except /, supporting spaces and special chars
180
+ const urlMatch = identifier.match(/dev\.azure\.com\/([^/]+)\/([^/]+)\/_workitems\/edit\/(\d+)/);
181
+ if (urlMatch) {
182
+ return {
183
+ org: `https://dev.azure.com/${urlMatch[1]}`,
184
+ project: decodeURIComponent(urlMatch[2]), // Decode URL-encoded project names
185
+ workItemId: urlMatch[3],
186
+ };
187
+ }
188
+
189
+ // Legacy URL format: https://org.visualstudio.com/project/_workitems/edit/123
190
+ const legacyMatch = identifier.match(
191
+ /https?:\/\/([^.]+)\.visualstudio\.com\/([^/]+)\/_workitems\/edit\/(\d+)/
192
+ );
193
+ if (legacyMatch) {
194
+ return {
195
+ org: `https://${legacyMatch[1]}.visualstudio.com`,
196
+ project: decodeURIComponent(legacyMatch[2]), // Decode URL-encoded project names
197
+ workItemId: legacyMatch[3],
198
+ };
199
+ }
200
+
201
+ // Bare number: try settings first, then git context
202
+ if (/^\d+$/.test(identifier)) {
203
+ let org = settings.azureOrg;
204
+ let project = settings.azureProject;
205
+
206
+ // If settings don't have org, try git context
207
+ if (!org) {
208
+ const gitContext = detectGitContext();
209
+ if (gitContext?.provider === 'azure-devops') {
210
+ org = gitContext.azureOrg;
211
+ project = gitContext.azureProject;
212
+ }
213
+ }
214
+
215
+ return {
216
+ org,
217
+ project,
218
+ workItemId: identifier,
219
+ };
220
+ }
221
+
222
+ throw new Error(`Could not parse Azure DevOps work item identifier: ${identifier}`);
223
+ }
224
+
225
+ /**
226
+ * Parse Azure DevOps work item into standardized InputData format
227
+ * @private
228
+ */
229
+ _parseWorkItem(workItem) {
230
+ const fields = workItem.fields || {};
231
+ const id = workItem.id;
232
+ const title = fields['System.Title'] || '';
233
+ const description = fields['System.Description'] || '';
234
+ const tags = fields['System.Tags'] ? fields['System.Tags'].split(';').map((t) => t.trim()) : [];
235
+
236
+ let context = `# Azure DevOps Work Item #${id}\n\n`;
237
+ context += `## Title\n${title}\n\n`;
238
+
239
+ if (description) {
240
+ // Strip HTML tags from description (Azure stores as HTML)
241
+ const plainDescription = description.replace(/<[^>]*>/g, '');
242
+ context += `## Description\n${plainDescription}\n\n`;
243
+ }
244
+
245
+ if (tags.length > 0) {
246
+ context += `## Tags\n`;
247
+ context += tags.map((t) => `- ${t}`).join('\n');
248
+ context += '\n\n';
249
+ }
250
+
251
+ // Map tags to labels format
252
+ const labels = tags.map((name) => ({ name }));
253
+
254
+ // Azure work items don't have comments in the same API call
255
+ // Would need separate call to fetch comments, omitting for now
256
+ const comments = [];
257
+
258
+ // Strip HTML from description for body field
259
+ const plainBody = description.replace(/<[^>]*>/g, '');
260
+
261
+ return {
262
+ number: id,
263
+ title,
264
+ body: plainBody,
265
+ labels,
266
+ comments,
267
+ url: workItem.url || null,
268
+ context,
269
+ };
270
+ }
271
+ }
272
+
273
+ module.exports = AzureDevOpsProvider;