@mcp-consultant-tools/azure-devops 30.0.0-beta.9 → 30.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 (129) hide show
  1. package/build/azure-devops-client.d.ts.map +1 -1
  2. package/build/azure-devops-client.js +23 -3
  3. package/build/azure-devops-client.js.map +1 -1
  4. package/build/cli/commands/index.d.ts +1 -0
  5. package/build/cli/commands/index.d.ts.map +1 -1
  6. package/build/cli/commands/index.js +3 -0
  7. package/build/cli/commands/index.js.map +1 -1
  8. package/build/cli/commands/test-commands.d.ts +7 -0
  9. package/build/cli/commands/test-commands.d.ts.map +1 -0
  10. package/build/cli/commands/test-commands.js +131 -0
  11. package/build/cli/commands/test-commands.js.map +1 -0
  12. package/build/cli/commands/work-item-commands.d.ts.map +1 -1
  13. package/build/cli/commands/work-item-commands.js +34 -0
  14. package/build/cli/commands/work-item-commands.js.map +1 -1
  15. package/build/context-factory.d.ts.map +1 -1
  16. package/build/context-factory.js +3 -1
  17. package/build/context-factory.js.map +1 -1
  18. package/build/schemas.d.ts +13 -0
  19. package/build/schemas.d.ts.map +1 -0
  20. package/build/schemas.js +47 -0
  21. package/build/schemas.js.map +1 -0
  22. package/build/services/checklist-service.d.ts.map +1 -1
  23. package/build/services/checklist-service.js +3 -0
  24. package/build/services/checklist-service.js.map +1 -1
  25. package/build/services/index.d.ts +1 -0
  26. package/build/services/index.d.ts.map +1 -1
  27. package/build/services/index.js +1 -0
  28. package/build/services/index.js.map +1 -1
  29. package/build/services/sync-service.d.ts.map +1 -1
  30. package/build/services/sync-service.js +75 -14
  31. package/build/services/sync-service.js.map +1 -1
  32. package/build/services/test-service.d.ts +106 -0
  33. package/build/services/test-service.d.ts.map +1 -0
  34. package/build/services/test-service.js +245 -0
  35. package/build/services/test-service.js.map +1 -0
  36. package/build/services/work-item-service.d.ts +25 -0
  37. package/build/services/work-item-service.d.ts.map +1 -1
  38. package/build/services/work-item-service.js +51 -3
  39. package/build/services/work-item-service.js.map +1 -1
  40. package/build/sync/annotation-parser.d.ts +52 -0
  41. package/build/sync/annotation-parser.d.ts.map +1 -0
  42. package/build/sync/annotation-parser.js +83 -0
  43. package/build/sync/annotation-parser.js.map +1 -0
  44. package/build/sync/field-aliases.d.ts +35 -0
  45. package/build/sync/field-aliases.d.ts.map +1 -0
  46. package/build/sync/field-aliases.js +76 -0
  47. package/build/sync/field-aliases.js.map +1 -0
  48. package/build/sync/html-converter.d.ts.map +1 -1
  49. package/build/sync/html-converter.js +12 -2
  50. package/build/sync/html-converter.js.map +1 -1
  51. package/build/sync/html-detection.d.ts +18 -65
  52. package/build/sync/html-detection.d.ts.map +1 -1
  53. package/build/sync/html-detection.js +72 -113
  54. package/build/sync/html-detection.js.map +1 -1
  55. package/build/sync/image-handler.d.ts +66 -0
  56. package/build/sync/image-handler.d.ts.map +1 -0
  57. package/build/sync/image-handler.js +135 -0
  58. package/build/sync/image-handler.js.map +1 -0
  59. package/build/sync/image-manifest.d.ts +66 -0
  60. package/build/sync/image-manifest.d.ts.map +1 -0
  61. package/build/sync/image-manifest.js +96 -0
  62. package/build/sync/image-manifest.js.map +1 -0
  63. package/build/sync/image-sync.d.ts +88 -0
  64. package/build/sync/image-sync.d.ts.map +1 -0
  65. package/build/sync/image-sync.js +274 -0
  66. package/build/sync/image-sync.js.map +1 -0
  67. package/build/sync/index.d.ts +7 -0
  68. package/build/sync/index.d.ts.map +1 -1
  69. package/build/sync/index.js +7 -0
  70. package/build/sync/index.js.map +1 -1
  71. package/build/sync/legacy-mappings.d.ts +37 -0
  72. package/build/sync/legacy-mappings.d.ts.map +1 -0
  73. package/build/sync/legacy-mappings.js +75 -0
  74. package/build/sync/legacy-mappings.js.map +1 -0
  75. package/build/sync/markdown-serializer.d.ts +54 -60
  76. package/build/sync/markdown-serializer.d.ts.map +1 -1
  77. package/build/sync/markdown-serializer.js +607 -545
  78. package/build/sync/markdown-serializer.js.map +1 -1
  79. package/build/sync/task-serializer.d.ts.map +1 -1
  80. package/build/sync/task-serializer.js +46 -8
  81. package/build/sync/task-serializer.js.map +1 -1
  82. package/build/sync/template-loader.d.ts +56 -0
  83. package/build/sync/template-loader.d.ts.map +1 -0
  84. package/build/sync/template-loader.js +138 -0
  85. package/build/sync/template-loader.js.map +1 -0
  86. package/build/sync/templates/bug.md +25 -0
  87. package/build/sync/templates/epic.md +23 -0
  88. package/build/sync/templates/feature.md +23 -0
  89. package/build/sync/templates/task.md +14 -0
  90. package/build/sync/templates/user-story.md +26 -0
  91. package/build/tool-examples.d.ts +20 -0
  92. package/build/tool-examples.d.ts.map +1 -1
  93. package/build/tool-examples.js +41 -0
  94. package/build/tool-examples.js.map +1 -1
  95. package/build/tools/build-tools.d.ts.map +1 -1
  96. package/build/tools/build-tools.js +7 -6
  97. package/build/tools/build-tools.js.map +1 -1
  98. package/build/tools/checklist-tools.d.ts.map +1 -1
  99. package/build/tools/checklist-tools.js +6 -5
  100. package/build/tools/checklist-tools.js.map +1 -1
  101. package/build/tools/index.d.ts +1 -0
  102. package/build/tools/index.d.ts.map +1 -1
  103. package/build/tools/index.js +5 -2
  104. package/build/tools/index.js.map +1 -1
  105. package/build/tools/pull-request-tools.d.ts.map +1 -1
  106. package/build/tools/pull-request-tools.js +15 -14
  107. package/build/tools/pull-request-tools.js.map +1 -1
  108. package/build/tools/sync-tools.d.ts.map +1 -1
  109. package/build/tools/sync-tools.js +9 -8
  110. package/build/tools/sync-tools.js.map +1 -1
  111. package/build/tools/test-tools.d.ts +3 -0
  112. package/build/tools/test-tools.d.ts.map +1 -0
  113. package/build/tools/test-tools.js +184 -0
  114. package/build/tools/test-tools.js.map +1 -0
  115. package/build/tools/variable-group-tools.d.ts.map +1 -1
  116. package/build/tools/variable-group-tools.js +2 -1
  117. package/build/tools/variable-group-tools.js.map +1 -1
  118. package/build/tools/visualize-tools.d.ts.map +1 -1
  119. package/build/tools/visualize-tools.js +2 -1
  120. package/build/tools/visualize-tools.js.map +1 -1
  121. package/build/tools/wiki-tools.d.ts.map +1 -1
  122. package/build/tools/wiki-tools.js +4 -3
  123. package/build/tools/wiki-tools.js.map +1 -1
  124. package/build/tools/work-item-tools.d.ts.map +1 -1
  125. package/build/tools/work-item-tools.js +42 -11
  126. package/build/tools/work-item-tools.js.map +1 -1
  127. package/build/types.d.ts +2 -0
  128. package/build/types.d.ts.map +1 -1
  129. package/package.json +3 -3
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Image Sync Orchestration
3
+ *
4
+ * Hooks `image-handler` + `image-manifest` + `WorkItemService.downloadAttachment`
5
+ * into the work-item pull/push pipelines.
6
+ *
7
+ * - On pull: scan all string fields + comment bodies for ADO attachment <img>
8
+ * refs, download each binary into `{folder}/{id}/attachments/`, rewrite the
9
+ * src to the local relative path, and write the manifest.
10
+ *
11
+ * - On push: read the manifest, scan content for image refs, rewrite local
12
+ * paths back to original ADO URLs, upload any new local files, append to
13
+ * the manifest.
14
+ */
15
+ import * as path from 'node:path';
16
+ import { promises as fs } from 'node:fs';
17
+ import { extractImageRefs, rewriteImageSrcs, parseAdoAttachmentUrl, } from './image-handler.js';
18
+ import { readManifest, writeManifest, upsertEntry, findEntryByLocalPath, buildLocalAttachmentPath, getAttachmentsBinDir, getWorkItemAttachmentDir, } from './image-manifest.js';
19
+ /**
20
+ * Append `?fileName=...` (or `&fileName=...`) to an attachment URL only when
21
+ * one isn't already present. ADO's upload response sometimes already includes
22
+ * the param; appending unconditionally produces duplicate `fileName=` entries.
23
+ */
24
+ function appendFileNameParam(url, fileName) {
25
+ if (/[?&]fileName=/i.test(url))
26
+ return url;
27
+ const sep = url.includes('?') ? '&' : '?';
28
+ return `${url}${sep}fileName=${encodeURIComponent(fileName)}`;
29
+ }
30
+ /**
31
+ * Scan an arbitrary HTML/markdown string for ADO attachment refs, download
32
+ * each missing binary, and rewrite the srcs to local relative paths.
33
+ *
34
+ * Returns the rewritten content, plus updates the manifest in-place.
35
+ */
36
+ async function processFieldImages(content, fieldName, project, workItemId, syncFolder, service, manifest, result) {
37
+ if (!content)
38
+ return content;
39
+ const refs = extractImageRefs(content);
40
+ const adoRefs = refs.filter(r => r.adoAttachment);
41
+ if (adoRefs.length === 0)
42
+ return content;
43
+ const binDir = getAttachmentsBinDir(syncFolder, workItemId);
44
+ // Build a URL → localPath map by ensuring each ADO attachment is on disk.
45
+ const urlToLocalPath = new Map();
46
+ for (const ref of adoRefs) {
47
+ const ado = ref.adoAttachment;
48
+ const existing = manifest.attachments.find(a => a.guid === ado.guid);
49
+ const localRelPath = buildLocalAttachmentPath(ado.guid, ado.fileName);
50
+ const localAbsPath = path.join(getWorkItemAttachmentDir(syncFolder, workItemId), localRelPath);
51
+ let onDisk = false;
52
+ try {
53
+ await fs.access(localAbsPath);
54
+ onDisk = true;
55
+ }
56
+ catch { /* not on disk */ }
57
+ if (existing && onDisk) {
58
+ result.reused++;
59
+ urlToLocalPath.set(ref.originalSrc, `./${localRelPath}`);
60
+ continue;
61
+ }
62
+ try {
63
+ await service.downloadAttachment(project, ado.guid, ado.fileName, binDir, `${ado.guid}-${ado.fileName}`);
64
+ result.downloaded++;
65
+ const entry = {
66
+ guid: ado.guid,
67
+ fileName: ado.fileName,
68
+ localPath: localRelPath,
69
+ originalUrl: ado.originalUrl,
70
+ source: 'ado-pull',
71
+ field: fieldName,
72
+ };
73
+ upsertEntry(manifest, entry);
74
+ urlToLocalPath.set(ref.originalSrc, `./${localRelPath}`);
75
+ }
76
+ catch (err) {
77
+ result.failed.push({ guid: ado.guid, fileName: ado.fileName, error: err.message });
78
+ }
79
+ }
80
+ // Rewrite srcs in one pass using the map we built.
81
+ return rewriteImageSrcs(content, (src, _ado) => urlToLocalPath.get(src) ?? null);
82
+ }
83
+ /**
84
+ * Process all string fields on a work item, downloading attachments and
85
+ * rewriting srcs. Mutates `workItem.fields` in place. Persists the manifest.
86
+ */
87
+ export async function pullWorkItemImages(workItem, project, syncFolder, service) {
88
+ const workItemId = workItem.id;
89
+ const manifest = await readManifest(syncFolder, workItemId);
90
+ const result = { downloaded: 0, reused: 0, failed: [], manifest };
91
+ const fields = workItem.fields || {};
92
+ for (const [fieldName, value] of Object.entries(fields)) {
93
+ if (typeof value !== 'string')
94
+ continue;
95
+ const newContent = await processFieldImages(value, fieldName, project, workItemId, syncFolder, service, manifest, result);
96
+ if (newContent !== value) {
97
+ fields[fieldName] = newContent;
98
+ }
99
+ }
100
+ if (result.downloaded > 0 || result.reused > 0 || manifest.attachments.length > 0) {
101
+ await writeManifest(syncFolder, manifest);
102
+ }
103
+ return result;
104
+ }
105
+ /**
106
+ * Process comment bodies for image refs. Mutates `comments[i].text` in place.
107
+ * Uses the same manifest as the parent work item so attachments are tracked
108
+ * together. Returns aggregated counts.
109
+ */
110
+ export async function pullCommentImages(workItemId, comments, project, syncFolder, service) {
111
+ const manifest = await readManifest(syncFolder, workItemId);
112
+ const result = { downloaded: 0, reused: 0, failed: [], manifest };
113
+ for (let i = 0; i < comments.length; i++) {
114
+ const c = comments[i];
115
+ const body = c.text ?? c.content ?? '';
116
+ if (typeof body !== 'string' || !body)
117
+ continue;
118
+ const newBody = await processFieldImages(body, `Comment#${i + 1}`, project, workItemId, syncFolder, service, manifest, result);
119
+ if (newBody !== body) {
120
+ if (c.text !== undefined)
121
+ c.text = newBody;
122
+ else
123
+ c.content = newBody;
124
+ }
125
+ }
126
+ if (result.downloaded > 0 || result.reused > 0) {
127
+ await writeManifest(syncFolder, manifest);
128
+ }
129
+ return result;
130
+ }
131
+ /**
132
+ * Resolve a markdown image src against the work-item attachment folder.
133
+ * Returns an absolute path that may or may not exist.
134
+ *
135
+ * Accepted forms:
136
+ * - "./attachments/foo.png"
137
+ * - "attachments/foo.png"
138
+ * - "{workItemId}/attachments/foo.png" (relative to the sync folder)
139
+ * - any absolute path
140
+ */
141
+ function resolveLocalImagePath(src, syncFolder, workItemId) {
142
+ // Skip remote URLs and data URIs
143
+ if (/^(https?:|data:|file:)/i.test(src))
144
+ return null;
145
+ const workItemDir = getWorkItemAttachmentDir(syncFolder, workItemId);
146
+ if (path.isAbsolute(src)) {
147
+ return {
148
+ absolute: src,
149
+ relativeToWorkItem: path.relative(workItemDir, src).replace(/\\/g, '/'),
150
+ };
151
+ }
152
+ // Strip leading ./
153
+ const stripped = src.replace(/^\.\//, '');
154
+ // If src starts with the work item ID, treat as syncFolder-relative
155
+ const idPrefix = `${workItemId}/`;
156
+ const candidate = stripped.startsWith(idPrefix)
157
+ ? path.join(syncFolder, stripped)
158
+ : path.join(workItemDir, stripped);
159
+ return {
160
+ absolute: candidate,
161
+ relativeToWorkItem: path.relative(workItemDir, candidate).replace(/\\/g, '/'),
162
+ };
163
+ }
164
+ /**
165
+ * Rewrite a content string for push:
166
+ * - ADO URL → leave alone
167
+ * - local path matching manifest → swap to original ADO URL
168
+ * - local path NOT in manifest → upload, add to manifest, swap to new URL
169
+ * - anything else (relative URL we can't find) → leave alone, log
170
+ */
171
+ async function processFieldImagesForPush(content, fieldName, project, workItemId, syncFolder, service, manifest, result) {
172
+ if (!content)
173
+ return content;
174
+ const refs = extractImageRefs(content);
175
+ if (refs.length === 0)
176
+ return content;
177
+ // Resolve each ref upfront (uploads need to happen before we rewrite).
178
+ const srcToFinalUrl = new Map();
179
+ for (const ref of refs) {
180
+ const src = ref.originalSrc;
181
+ // Already an ADO URL — keep as-is
182
+ if (parseAdoAttachmentUrl(src)) {
183
+ continue;
184
+ }
185
+ const resolved = resolveLocalImagePath(src, syncFolder, workItemId);
186
+ if (!resolved)
187
+ continue; // remote/non-local URL we don't manage
188
+ // Manifest hit?
189
+ const entry = findEntryByLocalPath(manifest, resolved.relativeToWorkItem);
190
+ if (entry) {
191
+ srcToFinalUrl.set(src, entry.originalUrl);
192
+ result.reused++;
193
+ continue;
194
+ }
195
+ // Not in manifest — try to upload as new attachment.
196
+ try {
197
+ await fs.access(resolved.absolute);
198
+ }
199
+ catch {
200
+ result.failed.push({ src, error: `Local file not found: ${resolved.absolute}` });
201
+ continue;
202
+ }
203
+ try {
204
+ const uploaded = await service.uploadAttachment(project, resolved.absolute);
205
+ const finalUrl = appendFileNameParam(uploaded.url, uploaded.fileName);
206
+ const entryFromUpload = {
207
+ guid: uploaded.id,
208
+ fileName: uploaded.fileName,
209
+ localPath: resolved.relativeToWorkItem,
210
+ originalUrl: finalUrl,
211
+ source: 'local-uploaded',
212
+ field: fieldName,
213
+ uploadedAt: new Date().toISOString(),
214
+ };
215
+ upsertEntry(manifest, entryFromUpload);
216
+ srcToFinalUrl.set(src, finalUrl);
217
+ result.uploaded++;
218
+ }
219
+ catch (err) {
220
+ result.failed.push({ src, error: err.message });
221
+ }
222
+ }
223
+ if (srcToFinalUrl.size === 0)
224
+ return content;
225
+ return rewriteImageSrcs(content, (src, _ado) => srcToFinalUrl.get(src) ?? null);
226
+ }
227
+ /**
228
+ * Apply image push processing to a parsed work item file's body fields.
229
+ * Iterates every refname in `bodyFieldMap`, so custom body fields (not just
230
+ * the historical Description/ReproSteps/AC/custom-four) get image handling.
231
+ * Mutates the parsed object in-place and persists the manifest.
232
+ */
233
+ export async function pushWorkItemImages(parsed, workItemId, project, syncFolder, service) {
234
+ const manifest = await readManifest(syncFolder, workItemId);
235
+ const result = { uploaded: 0, reused: 0, failed: [], manifest };
236
+ for (const [refname, value] of Object.entries(parsed.bodyFieldMap)) {
237
+ if (typeof value !== 'string' || !value)
238
+ continue;
239
+ parsed.bodyFieldMap[refname] = await processFieldImagesForPush(value, refname, project, workItemId, syncFolder, service, manifest, result);
240
+ }
241
+ // Keep the legacy convenience slots in sync with bodyFieldMap so downstream
242
+ // consumers that still read them (logs, diffs) see the updated content.
243
+ if (parsed.description !== undefined) {
244
+ parsed.description = parsed.bodyFieldMap['System.Description'] ?? '';
245
+ }
246
+ if (parsed.reproSteps !== undefined) {
247
+ parsed.reproSteps = parsed.bodyFieldMap['Microsoft.VSTS.TCM.ReproSteps'] ?? '';
248
+ }
249
+ if (parsed.acceptanceCriteria !== undefined) {
250
+ parsed.acceptanceCriteria = parsed.bodyFieldMap['Microsoft.VSTS.Common.AcceptanceCriteria'] ?? '';
251
+ }
252
+ if (result.uploaded > 0 || result.reused > 0 || manifest.attachments.length > 0) {
253
+ await writeManifest(syncFolder, manifest);
254
+ }
255
+ return result;
256
+ }
257
+ /**
258
+ * Append a manually-uploaded attachment to a work item's manifest.
259
+ * Used by the standalone `upload-work-item-attachment` tool.
260
+ */
261
+ export async function recordExternalUpload(syncFolder, workItemId, uploaded, localPath) {
262
+ const manifest = await readManifest(syncFolder, workItemId);
263
+ const finalUrl = appendFileNameParam(uploaded.url, uploaded.fileName);
264
+ upsertEntry(manifest, {
265
+ guid: uploaded.id,
266
+ fileName: uploaded.fileName,
267
+ localPath,
268
+ originalUrl: finalUrl,
269
+ source: 'local-uploaded',
270
+ uploadedAt: new Date().toISOString(),
271
+ });
272
+ await writeManifest(syncFolder, manifest);
273
+ }
274
+ //# sourceMappingURL=image-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-sync.js","sourceRoot":"","sources":["../../src/sync/image-sync.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,15 @@
5
5
  */
6
6
  export * from './html-detection.js';
7
7
  export * from './html-converter.js';
8
+ export * from './field-aliases.js';
9
+ export * from './annotation-parser.js';
10
+ export * from './legacy-mappings.js';
11
+ export * from './template-loader.js';
8
12
  export * from './markdown-serializer.js';
9
13
  export * from './file-utils.js';
10
14
  export * from './git-utils.js';
11
15
  export * from './task-serializer.js';
16
+ export * from './image-handler.js';
17
+ export * from './image-manifest.js';
18
+ export * from './image-sync.js';
12
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
@@ -5,8 +5,15 @@
5
5
  */
6
6
  export * from './html-detection.js';
7
7
  export * from './html-converter.js';
8
+ export * from './field-aliases.js';
9
+ export * from './annotation-parser.js';
10
+ export * from './legacy-mappings.js';
11
+ export * from './template-loader.js';
8
12
  export * from './markdown-serializer.js';
9
13
  export * from './file-utils.js';
10
14
  export * from './git-utils.js';
11
15
  export * from './task-serializer.js';
16
+ export * from './image-handler.js';
17
+ export * from './image-manifest.js';
18
+ export * from './image-sync.js';
12
19
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Backwards-compatibility mapping for pre-annotation synced files.
3
+ *
4
+ * Files generated before the annotation-driven sync format have `#`-level
5
+ * section headings with no `<!-- ado-field: ... -->` comment. We can still
6
+ * parse them by matching known headings against a fixed refname table.
7
+ *
8
+ * Type-aware: Bug work items historically used `# Description` for what is
9
+ * semantically Repro Steps. After upgrade, a Bug's `# Description` section
10
+ * maps to `Microsoft.VSTS.TCM.ReproSteps`, not `System.Description`.
11
+ */
12
+ export interface LegacyHeadingRule {
13
+ heading: string;
14
+ refname: string;
15
+ /** Optional type filter — if set, only applies when workItemType matches. */
16
+ typeFilter?: string;
17
+ }
18
+ /**
19
+ * Ordered list of legacy heading → refname rules.
20
+ * First matching rule wins.
21
+ *
22
+ * Evaluated in order — bug-specific rules come before generic rules for
23
+ * the same heading.
24
+ */
25
+ export declare const LEGACY_HEADING_RULES: LegacyHeadingRule[];
26
+ /**
27
+ * Resolve a legacy heading to an ADO refname given the work item type.
28
+ * Returns null if the heading has no known mapping (section stays local-only).
29
+ */
30
+ export declare function resolveLegacyHeading(heading: string, workItemType: string): string | null;
31
+ /**
32
+ * Override the default custom-field refnames from env vars. Preserved so that
33
+ * existing installs relying on `AZUREDEVOPS_SYNC_FIELD_*` env vars still parse
34
+ * their legacy files correctly. New installs should use templates instead.
35
+ */
36
+ export declare function applyLegacyEnvOverrides(): void;
37
+ //# sourceMappingURL=legacy-mappings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-mappings.d.ts","sourceRoot":"","sources":["../../src/sync/legacy-mappings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,EAkBnD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQzF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAY9C"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Backwards-compatibility mapping for pre-annotation synced files.
3
+ *
4
+ * Files generated before the annotation-driven sync format have `#`-level
5
+ * section headings with no `<!-- ado-field: ... -->` comment. We can still
6
+ * parse them by matching known headings against a fixed refname table.
7
+ *
8
+ * Type-aware: Bug work items historically used `# Description` for what is
9
+ * semantically Repro Steps. After upgrade, a Bug's `# Description` section
10
+ * maps to `Microsoft.VSTS.TCM.ReproSteps`, not `System.Description`.
11
+ */
12
+ /**
13
+ * Ordered list of legacy heading → refname rules.
14
+ * First matching rule wins.
15
+ *
16
+ * Evaluated in order — bug-specific rules come before generic rules for
17
+ * the same heading.
18
+ */
19
+ export const LEGACY_HEADING_RULES = [
20
+ // Bug-specific: legacy bug files wrote repro steps under `# Description`.
21
+ { heading: 'Description', refname: 'Microsoft.VSTS.TCM.ReproSteps', typeFilter: 'Bug' },
22
+ // Generic
23
+ { heading: 'Description', refname: 'System.Description' },
24
+ { heading: 'Repro Steps', refname: 'Microsoft.VSTS.TCM.ReproSteps' },
25
+ { heading: 'Acceptance Criteria', refname: 'Microsoft.VSTS.Common.AcceptanceCriteria' },
26
+ // Deprecated custom fields — historically configurable via env vars
27
+ { heading: 'How to Test', refname: 'Custom.Howtotest' },
28
+ { heading: 'Deployment Information', refname: 'Custom.Deploymentinformation' },
29
+ {
30
+ heading: 'Predeployment Steps',
31
+ refname: 'Custom.7519d1bc-5305-4905-822b-2b380e61b154',
32
+ },
33
+ {
34
+ heading: 'Postdeployment Steps',
35
+ refname: 'Custom.abd6763f-a242-4938-85ed-bda419e34e7e',
36
+ },
37
+ ];
38
+ /**
39
+ * Resolve a legacy heading to an ADO refname given the work item type.
40
+ * Returns null if the heading has no known mapping (section stays local-only).
41
+ */
42
+ export function resolveLegacyHeading(heading, workItemType) {
43
+ const normalized = heading.trim();
44
+ for (const rule of LEGACY_HEADING_RULES) {
45
+ if (rule.heading !== normalized)
46
+ continue;
47
+ if (rule.typeFilter && rule.typeFilter !== workItemType)
48
+ continue;
49
+ return rule.refname;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Override the default custom-field refnames from env vars. Preserved so that
55
+ * existing installs relying on `AZUREDEVOPS_SYNC_FIELD_*` env vars still parse
56
+ * their legacy files correctly. New installs should use templates instead.
57
+ */
58
+ export function applyLegacyEnvOverrides() {
59
+ const overrides = [
60
+ ['How to Test', process.env.AZUREDEVOPS_SYNC_FIELD_HOW_TO_TEST],
61
+ ['Deployment Information', process.env.AZUREDEVOPS_SYNC_FIELD_DEPLOYMENT_INFO],
62
+ ['Predeployment Steps', process.env.AZUREDEVOPS_SYNC_FIELD_PREDEPLOY],
63
+ ['Postdeployment Steps', process.env.AZUREDEVOPS_SYNC_FIELD_POSTDEPLOY],
64
+ ];
65
+ for (const [heading, envValue] of overrides) {
66
+ if (!envValue)
67
+ continue;
68
+ const rule = LEGACY_HEADING_RULES.find((r) => r.heading === heading && !r.typeFilter);
69
+ if (rule)
70
+ rule.refname = envValue;
71
+ }
72
+ }
73
+ // Apply env overrides once at module load.
74
+ applyLegacyEnvOverrides();
75
+ //# sourceMappingURL=legacy-mappings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legacy-mappings.js","sourceRoot":"","sources":["../../src/sync/legacy-mappings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAwB;IACvD,0EAA0E;IAC1E,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,+BAA+B,EAAE,UAAU,EAAE,KAAK,EAAE;IACvF,UAAU;IACV,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,EAAE;IACzD,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,+BAA+B,EAAE;IACpE,EAAE,OAAO,EAAE,qBAAqB,EAAE,OAAO,EAAE,0CAA0C,EAAE;IACvF,oEAAoE;IACpE,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,kBAAkB,EAAE;IACvD,EAAE,OAAO,EAAE,wBAAwB,EAAE,OAAO,EAAE,8BAA8B,EAAE;IAC9E;QACE,OAAO,EAAE,qBAAqB;QAC9B,OAAO,EAAE,6CAA6C;KACvD;IACD;QACE,OAAO,EAAE,sBAAsB;QAC/B,OAAO,EAAE,6CAA6C;KACvD;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe,EAAE,YAAoB;IACxE,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAClC,KAAK,MAAM,IAAI,IAAI,oBAAoB,EAAE,CAAC;QACxC,IAAI,IAAI,CAAC,OAAO,KAAK,UAAU;YAAE,SAAS;QAC1C,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,KAAK,YAAY;YAAE,SAAS;QAClE,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,SAAS,GAAwC;QACrD,CAAC,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC;QAC/D,CAAC,wBAAwB,EAAE,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC;QAC9E,CAAC,qBAAqB,EAAE,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC;QACrE,CAAC,sBAAsB,EAAE,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;KACxE,CAAC;IACF,KAAK,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,SAAS,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ;YAAE,SAAS;QACxB,MAAM,IAAI,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACtF,IAAI,IAAI;YAAE,IAAI,CAAC,OAAO,GAAG,QAAQ,CAAC;IACpC,CAAC;AACH,CAAC;AAED,2CAA2C;AAC3C,uBAAuB,EAAE,CAAC"}
@@ -2,7 +2,22 @@
2
2
  * Markdown Serialization Utilities
3
3
  *
4
4
  * Convert between ADO work items and local markdown files.
5
- * Uses YAML frontmatter for metadata and markdown sections for content.
5
+ *
6
+ * Files use YAML frontmatter (scalar fields — ADO refnames or friendly
7
+ * aliases) + body sections (long text fields tagged with
8
+ * `<!-- ado-field: REFNAME -->` comments). The sync engine is generic:
9
+ * any field named in the file is pushed to ADO, any field not named is
10
+ * left alone.
11
+ *
12
+ * Legacy files (pre-annotation) are detected and parsed via a
13
+ * legacy-mappings fallback table. They auto-upgrade to the annotated
14
+ * format on the next pull.
15
+ */
16
+ import { type LocalOnlySection } from './annotation-parser.js';
17
+ /**
18
+ * Subset of frontmatter values used by downstream services (sync-service,
19
+ * file-utils) via stable friendly names. Always populated on parse by
20
+ * pulling the corresponding refname out of `fieldMap`.
6
21
  */
7
22
  export interface WorkItemFrontmatter {
8
23
  id: number;
@@ -20,6 +35,11 @@ export interface WorkItemFrontmatter {
20
35
  lastSyncedRevision: number;
21
36
  lastSyncedAt: string;
22
37
  }
38
+ /**
39
+ * Legacy slot for the four historically-supported custom fields. Retained
40
+ * so that pre-annotation callers (and reports) keep working. New-style
41
+ * consumers should read `bodyFieldMap` instead.
42
+ */
23
43
  export interface AdditionalFields {
24
44
  howToTest?: string;
25
45
  deploymentInformation?: string;
@@ -27,12 +47,23 @@ export interface AdditionalFields {
27
47
  postdeploymentSteps?: string;
28
48
  }
29
49
  export interface ParsedWorkItemFile {
50
+ /** Friendly-name frontmatter view for back-compat. */
30
51
  frontmatter: WorkItemFrontmatter;
52
+ /** All scalar ADO fields from frontmatter, keyed by refname. */
53
+ fieldMap: Record<string, FieldValue>;
54
+ /** All body text fields (from annotated sections OR legacy headings), keyed by refname. */
55
+ bodyFieldMap: Record<string, string>;
56
+ /** Sections with no ADO-field annotation — preserved in the file but not pushed. */
57
+ localOnlySections: LocalOnlySection[];
58
+ /** Work item type (mirrors frontmatter.type). */
59
+ workItemType: string;
31
60
  description: string;
61
+ reproSteps: string;
32
62
  acceptanceCriteria: string;
33
63
  additionalFields: AdditionalFields;
34
64
  rawContent: string;
35
65
  }
66
+ export type FieldValue = string | number | boolean | string[];
36
67
  export interface CommentsFrontmatter {
37
68
  id: number;
38
69
  title: string;
@@ -44,50 +75,25 @@ export interface ParsedComment {
44
75
  date: string;
45
76
  content: string;
46
77
  }
47
- /**
48
- * Result of converting work item to markdown
49
- */
50
78
  export interface WorkItemToMarkdownResult {
51
79
  content: string;
52
80
  skippedFields: string[];
53
81
  }
54
82
  /**
55
- * Convert an ADO work item to markdown file content
83
+ * Serialize an ADO work item to an annotated markdown file.
56
84
  *
57
- * @param workItem - The work item from ADO API
58
- * @param revision - The revision number
59
- * @returns Object with markdown content and list of skipped fields (if any were HTML)
85
+ * Follows the template for `fields['System.WorkItemType']`. Frontmatter
86
+ * order and body-section order come from the template. Fields the ADO
87
+ * response carries but the template doesn't mention are appended to the
88
+ * frontmatter with a discovery comment (see D4 in the design plan).
60
89
  */
61
90
  export declare function workItemToMarkdown(workItem: any, revision: number): WorkItemToMarkdownResult;
62
- /**
63
- * Parse a markdown file to extract frontmatter and content sections
64
- */
65
91
  export declare function parseWorkItemMarkdown(content: string): ParsedWorkItemFile;
66
- /**
67
- * Convert ADO comments to a read-only markdown file
68
- */
69
- export declare function commentsToMarkdown(workItem: any, comments: any[]): string;
70
- /**
71
- * Build ADO patch operations from parsed markdown changes
72
- * Only updates fields that have actually changed.
73
- * Auto-converts HTML fields to markdown format unless skipAutoConvert is true.
74
- *
75
- * @param parsed - Parsed work item file
76
- * @param currentWorkItem - Current work item from ADO
77
- * @param skipAutoConvert - Skip automatic HTML-to-markdown conversion (default: false)
78
- */
79
92
  export declare function buildPatchOperations(parsed: ParsedWorkItemFile, currentWorkItem: any, skipAutoConvert?: boolean): {
80
93
  operations: any[];
81
94
  skippedFields: string[];
82
95
  convertedFields: string[];
83
96
  };
84
- /**
85
- * Update the lastSyncedRevision in a markdown file content
86
- */
87
- export declare function updateSyncRevision(content: string, newRevision: number): string;
88
- /**
89
- * Frontmatter for new work items (before they have an ID)
90
- */
91
97
  export interface NewWorkItemFrontmatter {
92
98
  title: string;
93
99
  type: string;
@@ -100,52 +106,40 @@ export interface NewWorkItemFrontmatter {
100
106
  areaPath?: string;
101
107
  iterationPath?: string;
102
108
  }
103
- /**
104
- * Parsed new work item file structure
105
- */
106
109
  export interface ParsedNewWorkItemFile {
107
110
  frontmatter: NewWorkItemFrontmatter;
111
+ fieldMap: Record<string, FieldValue>;
112
+ bodyFieldMap: Record<string, string>;
113
+ localOnlySections: LocalOnlySection[];
114
+ workItemType: string;
108
115
  description: string;
116
+ reproSteps: string;
109
117
  acceptanceCriteria: string;
110
118
  additionalFields: AdditionalFields;
111
119
  rawContent: string;
112
120
  }
113
- /**
114
- * Check if a work item frontmatter indicates a new (not yet created) work item
115
- * New work items don't have an 'id' field
116
- */
117
121
  export declare function isNewWorkItem(frontmatter: Record<string, any>): boolean;
118
- /**
119
- * Parse a markdown file for a NEW work item (no id required)
120
- */
121
122
  export declare function parseNewWorkItemMarkdown(content: string): ParsedNewWorkItemFile;
122
123
  /**
123
- * Result of building new work item fields, split into standard and custom.
124
- * Custom fields (MoSCoW, HowToTest, Deployment fields) cannot be set during
125
- * ADO work item creation — they must be set via a follow-up update.
124
+ * Split new-work-item fields into standard (safe for creation) and custom
125
+ * (must be set via a follow-up update because ADO rejects custom fields
126
+ * during creation).
127
+ *
128
+ * Standard = refname starts with `System.*` or `Microsoft.VSTS.*`.
129
+ * Custom = everything else (conventionally `Custom.*`).
126
130
  */
127
131
  export interface NewWorkItemFieldSplit {
128
- /** Standard ADO fields safe for creation */
129
132
  standardFields: Record<string, any>;
130
- /** Custom fields that must be set via update after creation */
131
133
  customFields: Record<string, any>;
132
134
  }
133
- /**
134
- * Build ADO fields object for creating a new work item
135
- * Inherits areaPath and iterationPath from parent work item when available
136
- *
137
- * Returns fields split into standard (safe for creation) and custom (require
138
- * a follow-up update) because ADO rejects custom fields during work item creation.
139
- */
140
135
  export declare function buildNewWorkItemFields(parsed: ParsedNewWorkItemFile, parentWorkItem?: any): NewWorkItemFieldSplit;
141
- /**
142
- * Generate a new work item template markdown file
143
- * When parentId is undefined, creates a standalone work item template
144
- */
145
136
  export declare function generateNewWorkItemTemplate(parentId: number | undefined, parentTitle: string, project: string, workItemType?: string): string;
137
+ export declare function commentsToMarkdown(workItem: any, comments: any[]): string;
138
+ export declare function updateSyncRevision(content: string, newRevision: number): string;
139
+ export declare function convertNewFileToSynced(content: string, workItemId: number, revision: number, url: string): string;
146
140
  /**
147
- * Update a new work item file after creation with the assigned ID
148
- * Converts it from a "new" file to a synced file with proper frontmatter
141
+ * Return the list of large-text (body) refnames that should be HTML-checked
142
+ * for a given work-item type. Driven by the loaded template.
149
143
  */
150
- export declare function convertNewFileToSynced(content: string, workItemId: number, revision: number, url: string): string;
144
+ export declare function templateBodyRefnamesForType(workItemType: string): string[];
151
145
  //# sourceMappingURL=markdown-serializer.d.ts.map