specweave 1.0.350 → 1.0.352

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 (282) hide show
  1. package/bin/specweave.js +9 -0
  2. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts +5 -0
  3. package/dist/plugins/specweave-ado/lib/ado-client-v2.d.ts.map +1 -1
  4. package/dist/plugins/specweave-ado/lib/ado-client-v2.js +61 -23
  5. package/dist/plugins/specweave-ado/lib/ado-client-v2.js.map +1 -1
  6. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.d.ts.map +1 -1
  7. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js +3 -2
  8. package/dist/plugins/specweave-ado/lib/ado-duplicate-detector.js.map +1 -1
  9. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.d.ts.map +1 -1
  10. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js +2 -1
  11. package/dist/plugins/specweave-ado/lib/ado-profile-resolver.js.map +1 -1
  12. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts +1 -1
  13. package/dist/plugins/specweave-ado/lib/ado-spec-sync.d.ts.map +1 -1
  14. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js +25 -9
  15. package/dist/plugins/specweave-ado/lib/ado-spec-sync.js.map +1 -1
  16. package/dist/plugins/specweave-ado/lib/conflict-resolver.d.ts.map +1 -1
  17. package/dist/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  18. package/dist/plugins/specweave-ado/lib/conflict-resolver.js.map +1 -1
  19. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts +3 -0
  20. package/dist/plugins/specweave-ado/lib/per-us-sync.d.ts.map +1 -1
  21. package/dist/plugins/specweave-ado/lib/per-us-sync.js +14 -1
  22. package/dist/plugins/specweave-ado/lib/per-us-sync.js.map +1 -1
  23. package/dist/plugins/specweave-github/lib/github-client-v2.d.ts.map +1 -1
  24. package/dist/plugins/specweave-github/lib/github-client-v2.js +10 -7
  25. package/dist/plugins/specweave-github/lib/github-client-v2.js.map +1 -1
  26. package/dist/plugins/specweave-github/lib/github-client.d.ts +1 -1
  27. package/dist/plugins/specweave-github/lib/github-client.d.ts.map +1 -1
  28. package/dist/plugins/specweave-github/lib/github-client.js +7 -5
  29. package/dist/plugins/specweave-github/lib/github-client.js.map +1 -1
  30. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js +13 -3
  31. package/dist/plugins/specweave-github/lib/github-cross-repo-sync.js.map +1 -1
  32. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts +24 -1
  33. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.d.ts.map +1 -1
  34. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js +36 -20
  35. package/dist/plugins/specweave-github/lib/github-feature-sync-cli.js.map +1 -1
  36. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +4 -2
  37. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  38. package/dist/plugins/specweave-github/lib/github-feature-sync.js +38 -9
  39. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  40. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts +1 -0
  41. package/dist/plugins/specweave-github/lib/github-graphql-client.d.ts.map +1 -1
  42. package/dist/plugins/specweave-github/lib/github-graphql-client.js +32 -22
  43. package/dist/plugins/specweave-github/lib/github-graphql-client.js.map +1 -1
  44. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +144 -8
  45. package/dist/plugins/specweave-github/lib/github-spec-frontmatter-updater.js.map +1 -1
  46. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts +8 -1
  47. package/dist/plugins/specweave-github/lib/github-spec-sync.d.ts.map +1 -1
  48. package/dist/plugins/specweave-github/lib/github-spec-sync.js +94 -24
  49. package/dist/plugins/specweave-github/lib/github-spec-sync.js.map +1 -1
  50. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts +1 -0
  51. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.d.ts.map +1 -1
  52. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  53. package/dist/plugins/specweave-github/lib/github-sync-orchestrator.js.map +1 -1
  54. package/dist/plugins/specweave-github/lib/github-us-auto-closer.d.ts.map +1 -1
  55. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  56. package/dist/plugins/specweave-github/lib/github-us-auto-closer.js.map +1 -1
  57. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts +3 -0
  58. package/dist/plugins/specweave-github/lib/per-us-sync.d.ts.map +1 -1
  59. package/dist/plugins/specweave-github/lib/per-us-sync.js +29 -9
  60. package/dist/plugins/specweave-github/lib/per-us-sync.js.map +1 -1
  61. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts +59 -0
  62. package/dist/plugins/specweave-jira/lib/content-format-adapter.d.ts.map +1 -0
  63. package/dist/plugins/specweave-jira/lib/content-format-adapter.js +159 -0
  64. package/dist/plugins/specweave-jira/lib/content-format-adapter.js.map +1 -0
  65. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts +45 -0
  66. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.d.ts.map +1 -0
  67. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js +92 -0
  68. package/dist/plugins/specweave-jira/lib/jira-deployment-detector.js.map +1 -0
  69. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.d.ts.map +1 -1
  70. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js +13 -28
  71. package/dist/plugins/specweave-jira/lib/jira-duplicate-detector.js.map +1 -1
  72. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts +2 -1
  73. package/dist/plugins/specweave-jira/lib/jira-epic-sync.d.ts.map +1 -1
  74. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js +19 -7
  75. package/dist/plugins/specweave-jira/lib/jira-epic-sync.js.map +1 -1
  76. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts +47 -0
  77. package/dist/plugins/specweave-jira/lib/jira-field-discovery.d.ts.map +1 -0
  78. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js +110 -0
  79. package/dist/plugins/specweave-jira/lib/jira-field-discovery.js.map +1 -0
  80. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts +26 -0
  81. package/dist/plugins/specweave-jira/lib/jira-paginated-search.d.ts.map +1 -0
  82. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js +77 -0
  83. package/dist/plugins/specweave-jira/lib/jira-paginated-search.js.map +1 -0
  84. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.d.ts.map +1 -1
  85. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  86. package/dist/plugins/specweave-jira/lib/jira-spec-commit-sync.js.map +1 -1
  87. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts +17 -2
  88. package/dist/plugins/specweave-jira/lib/jira-spec-sync.d.ts.map +1 -1
  89. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js +103 -33
  90. package/dist/plugins/specweave-jira/lib/jira-spec-sync.js.map +1 -1
  91. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts +4 -0
  92. package/dist/plugins/specweave-jira/lib/jira-status-sync.d.ts.map +1 -1
  93. package/dist/plugins/specweave-jira/lib/jira-status-sync.js +19 -6
  94. package/dist/plugins/specweave-jira/lib/jira-status-sync.js.map +1 -1
  95. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts +29 -0
  96. package/dist/plugins/specweave-jira/lib/metadata-paths.d.ts.map +1 -0
  97. package/dist/plugins/specweave-jira/lib/metadata-paths.js +73 -0
  98. package/dist/plugins/specweave-jira/lib/metadata-paths.js.map +1 -0
  99. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts +15 -2
  100. package/dist/plugins/specweave-jira/lib/reorganization-detector.d.ts.map +1 -1
  101. package/dist/plugins/specweave-jira/lib/reorganization-detector.js +121 -33
  102. package/dist/plugins/specweave-jira/lib/reorganization-detector.js.map +1 -1
  103. package/dist/src/cli/commands/init.d.ts.map +1 -1
  104. package/dist/src/cli/commands/init.js +23 -18
  105. package/dist/src/cli/commands/init.js.map +1 -1
  106. package/dist/src/cli/commands/sync-progress.d.ts +6 -0
  107. package/dist/src/cli/commands/sync-progress.d.ts.map +1 -1
  108. package/dist/src/cli/commands/sync-progress.js +37 -0
  109. package/dist/src/cli/commands/sync-progress.js.map +1 -1
  110. package/dist/src/cli/commands/sync-task.d.ts +16 -0
  111. package/dist/src/cli/commands/sync-task.d.ts.map +1 -0
  112. package/dist/src/cli/commands/sync-task.js +42 -0
  113. package/dist/src/cli/commands/sync-task.js.map +1 -0
  114. package/dist/src/cli/helpers/init/instruction-file-merger.js +3 -3
  115. package/dist/src/cli/helpers/init/instruction-file-merger.js.map +1 -1
  116. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts +9 -1
  117. package/dist/src/core/hooks/LifecycleHookDispatcher.d.ts.map +1 -1
  118. package/dist/src/core/hooks/LifecycleHookDispatcher.js +26 -8
  119. package/dist/src/core/hooks/LifecycleHookDispatcher.js.map +1 -1
  120. package/dist/src/core/increment/metadata-manager.d.ts +13 -0
  121. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  122. package/dist/src/core/increment/metadata-manager.js +144 -17
  123. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  124. package/dist/src/core/increment/status-change-sync-trigger.d.ts +1 -1
  125. package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
  126. package/dist/src/core/increment/status-change-sync-trigger.js +2 -1
  127. package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
  128. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  129. package/dist/src/core/increment/status-commands.js +33 -11
  130. package/dist/src/core/increment/status-commands.js.map +1 -1
  131. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  132. package/dist/src/core/repo-structure/repo-structure-manager.js +2 -1
  133. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  134. package/dist/src/locales/de/cli.json +252 -77
  135. package/dist/src/locales/en/cli.json +7 -0
  136. package/dist/src/locales/es/cli.json +245 -3
  137. package/dist/src/locales/fr/cli.json +259 -84
  138. package/dist/src/locales/ja/cli.json +253 -78
  139. package/dist/src/locales/ko/cli.json +253 -78
  140. package/dist/src/locales/pt/cli.json +252 -77
  141. package/dist/src/locales/ru/cli.json +17 -3
  142. package/dist/src/locales/zh/cli.json +258 -83
  143. package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
  144. package/dist/src/sync/ado-reconciler.js +5 -1
  145. package/dist/src/sync/ado-reconciler.js.map +1 -1
  146. package/dist/src/sync/base-reconciler.d.ts.map +1 -1
  147. package/dist/src/sync/base-reconciler.js +6 -1
  148. package/dist/src/sync/base-reconciler.js.map +1 -1
  149. package/dist/src/sync/config.d.ts +4 -0
  150. package/dist/src/sync/config.d.ts.map +1 -1
  151. package/dist/src/sync/config.js +6 -4
  152. package/dist/src/sync/config.js.map +1 -1
  153. package/dist/src/sync/external-issue-auto-creator.d.ts +3 -0
  154. package/dist/src/sync/external-issue-auto-creator.d.ts.map +1 -1
  155. package/dist/src/sync/external-issue-auto-creator.js +53 -17
  156. package/dist/src/sync/external-issue-auto-creator.js.map +1 -1
  157. package/dist/src/sync/external-item-sync-service.d.ts +9 -0
  158. package/dist/src/sync/external-item-sync-service.d.ts.map +1 -1
  159. package/dist/src/sync/external-item-sync-service.js +210 -9
  160. package/dist/src/sync/external-item-sync-service.js.map +1 -1
  161. package/dist/src/sync/github-reconciler.d.ts +30 -0
  162. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  163. package/dist/src/sync/github-reconciler.js +242 -3
  164. package/dist/src/sync/github-reconciler.js.map +1 -1
  165. package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
  166. package/dist/src/sync/jira-reconciler.js +5 -1
  167. package/dist/src/sync/jira-reconciler.js.map +1 -1
  168. package/dist/src/sync/provider-router.d.ts.map +1 -1
  169. package/dist/src/sync/provider-router.js +2 -1
  170. package/dist/src/sync/provider-router.js.map +1 -1
  171. package/dist/src/sync/providers/ado.d.ts +4 -0
  172. package/dist/src/sync/providers/ado.d.ts.map +1 -1
  173. package/dist/src/sync/providers/ado.js +36 -11
  174. package/dist/src/sync/providers/ado.js.map +1 -1
  175. package/dist/src/sync/providers/github.d.ts.map +1 -1
  176. package/dist/src/sync/providers/github.js +48 -35
  177. package/dist/src/sync/providers/github.js.map +1 -1
  178. package/dist/src/sync/providers/jira.d.ts.map +1 -1
  179. package/dist/src/sync/providers/jira.js +42 -26
  180. package/dist/src/sync/providers/jira.js.map +1 -1
  181. package/dist/src/sync/status-mapper.d.ts +3 -1
  182. package/dist/src/sync/status-mapper.d.ts.map +1 -1
  183. package/dist/src/sync/status-mapper.js +10 -2
  184. package/dist/src/sync/status-mapper.js.map +1 -1
  185. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  186. package/dist/src/sync/sync-coordinator.js +29 -19
  187. package/dist/src/sync/sync-coordinator.js.map +1 -1
  188. package/package.json +1 -1
  189. package/plugins/specweave/hooks/v2/guards/task-ac-sync-guard.sh +31 -0
  190. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.d.ts +13 -0
  191. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +144 -17
  192. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  193. package/plugins/specweave/lib/vendor/sync/github-reconciler.d.ts +30 -0
  194. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +242 -3
  195. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  196. package/plugins/specweave/skills/architect/SKILL.md +2 -0
  197. package/plugins/specweave/skills/grill/SKILL.md +2 -0
  198. package/plugins/specweave/skills/team-lead/SKILL.md +43 -320
  199. package/plugins/specweave/skills/team-lead/agents/backend.md +60 -0
  200. package/plugins/specweave/skills/team-lead/agents/database.md +51 -0
  201. package/plugins/specweave/skills/team-lead/agents/frontend.md +61 -0
  202. package/plugins/specweave/skills/team-lead/agents/security.md +52 -0
  203. package/plugins/specweave/skills/team-lead/agents/testing.md +57 -0
  204. package/plugins/specweave/skills/test-aware-planner/SKILL.md +2 -0
  205. package/plugins/specweave-ado/hooks/post-task-completion.sh +2 -2
  206. package/plugins/specweave-ado/lib/ado-client-v2.js +51 -21
  207. package/plugins/specweave-ado/lib/ado-client-v2.ts +62 -23
  208. package/plugins/specweave-ado/lib/ado-duplicate-detector.js +4 -4
  209. package/plugins/specweave-ado/lib/ado-duplicate-detector.ts +4 -3
  210. package/plugins/specweave-ado/lib/ado-hierarchical-sync.js +54 -12
  211. package/plugins/specweave-ado/lib/ado-hierarchical-sync.ts +88 -18
  212. package/plugins/specweave-ado/lib/ado-profile-resolver.js +1 -1
  213. package/plugins/specweave-ado/lib/ado-profile-resolver.ts +3 -1
  214. package/plugins/specweave-ado/lib/ado-spec-sync.js +22 -9
  215. package/plugins/specweave-ado/lib/ado-spec-sync.ts +27 -9
  216. package/plugins/specweave-ado/lib/conflict-resolver.js +17 -1
  217. package/plugins/specweave-ado/lib/conflict-resolver.ts +17 -1
  218. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +11 -1
  219. package/plugins/specweave-ado/lib/per-us-sync.js +8 -1
  220. package/plugins/specweave-ado/lib/per-us-sync.ts +17 -2
  221. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +28 -2
  222. package/plugins/specweave-github/hooks/post-task-completion.sh +6 -3
  223. package/plugins/specweave-github/lib/enhanced-github-sync.js +35 -6
  224. package/plugins/specweave-github/lib/github-board-resolver.js +4 -4
  225. package/plugins/specweave-github/lib/github-board-resolver.ts +4 -4
  226. package/plugins/specweave-github/lib/github-client-v2.js +6 -6
  227. package/plugins/specweave-github/lib/github-client-v2.ts +11 -7
  228. package/plugins/specweave-github/lib/github-client.js +5 -4
  229. package/plugins/specweave-github/lib/github-client.ts +7 -5
  230. package/plugins/specweave-github/lib/github-cross-repo-sync.js +17 -3
  231. package/plugins/specweave-github/lib/github-cross-repo-sync.ts +16 -3
  232. package/plugins/specweave-github/lib/github-feature-sync-cli.js +20 -11
  233. package/plugins/specweave-github/lib/github-feature-sync-cli.ts +42 -20
  234. package/plugins/specweave-github/lib/github-feature-sync.js +32 -8
  235. package/plugins/specweave-github/lib/github-feature-sync.ts +41 -9
  236. package/plugins/specweave-github/lib/github-graphql-client.js +29 -20
  237. package/plugins/specweave-github/lib/github-graphql-client.ts +34 -22
  238. package/plugins/specweave-github/lib/github-hierarchical-sync.js +2 -2
  239. package/plugins/specweave-github/lib/github-hierarchical-sync.ts +2 -2
  240. package/plugins/specweave-github/lib/github-multi-project-sync.js +23 -7
  241. package/plugins/specweave-github/lib/github-multi-project-sync.ts +26 -8
  242. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.js +110 -5
  243. package/plugins/specweave-github/lib/github-spec-frontmatter-updater.ts +135 -9
  244. package/plugins/specweave-github/lib/github-spec-sync.js +85 -24
  245. package/plugins/specweave-github/lib/github-spec-sync.ts +100 -26
  246. package/plugins/specweave-github/lib/github-sync-orchestrator.js +2 -1
  247. package/plugins/specweave-github/lib/github-sync-orchestrator.ts +3 -1
  248. package/plugins/specweave-github/lib/github-us-auto-closer.js +25 -0
  249. package/plugins/specweave-github/lib/github-us-auto-closer.ts +43 -0
  250. package/plugins/specweave-github/lib/per-us-sync.js +26 -11
  251. package/plugins/specweave-github/lib/per-us-sync.ts +29 -11
  252. package/plugins/specweave-jira/hooks/post-task-completion.sh +2 -1
  253. package/plugins/specweave-jira/lib/content-format-adapter.js +116 -0
  254. package/plugins/specweave-jira/lib/content-format-adapter.ts +189 -0
  255. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +21 -5
  256. package/plugins/specweave-jira/lib/jira-deployment-detector.js +63 -0
  257. package/plugins/specweave-jira/lib/jira-deployment-detector.ts +113 -0
  258. package/plugins/specweave-jira/lib/jira-duplicate-detector.js +12 -29
  259. package/plugins/specweave-jira/lib/jira-duplicate-detector.ts +13 -27
  260. package/plugins/specweave-jira/lib/jira-epic-sync.js +15 -5
  261. package/plugins/specweave-jira/lib/jira-epic-sync.ts +22 -7
  262. package/plugins/specweave-jira/lib/jira-field-discovery.js +76 -0
  263. package/plugins/specweave-jira/lib/jira-field-discovery.ts +139 -0
  264. package/plugins/specweave-jira/lib/jira-hierarchical-sync.js +10 -0
  265. package/plugins/specweave-jira/lib/jira-hierarchical-sync.ts +11 -0
  266. package/plugins/specweave-jira/lib/jira-multi-project-sync.js +19 -9
  267. package/plugins/specweave-jira/lib/jira-multi-project-sync.ts +25 -14
  268. package/plugins/specweave-jira/lib/jira-paginated-search.js +55 -0
  269. package/plugins/specweave-jira/lib/jira-paginated-search.ts +108 -0
  270. package/plugins/specweave-jira/lib/jira-spec-commit-sync.js +5 -3
  271. package/plugins/specweave-jira/lib/jira-spec-commit-sync.ts +5 -3
  272. package/plugins/specweave-jira/lib/jira-spec-sync.js +102 -31
  273. package/plugins/specweave-jira/lib/jira-spec-sync.ts +123 -45
  274. package/plugins/specweave-jira/lib/jira-status-sync.js +18 -5
  275. package/plugins/specweave-jira/lib/jira-status-sync.ts +21 -6
  276. package/plugins/specweave-jira/lib/metadata-paths.js +38 -0
  277. package/plugins/specweave-jira/lib/metadata-paths.ts +73 -0
  278. package/plugins/specweave-jira/lib/reorganization-detector.js +101 -23
  279. package/plugins/specweave-jira/lib/reorganization-detector.ts +125 -35
  280. package/plugins/specweave-jira/scripts/refresh-cache.js +1 -1
  281. package/plugins/specweave-jira/scripts/refresh-cache.ts +2 -2
  282. package/plugins/specweave-jira/skills/jira-resource-validator/SKILL.md +3 -5
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Content Format Adapter
3
+ *
4
+ * Converts content between formats based on JIRA API version:
5
+ * - API v3 (Cloud): Atlassian Document Format (ADF)
6
+ * - API v2 (Server/DC): Wiki markup
7
+ *
8
+ * @module content-format-adapter
9
+ */
10
+
11
+ import { getApiVersion } from './jira-deployment-detector.js';
12
+
13
+ /** ADF document node */
14
+ export interface AdfDocument {
15
+ type: 'doc';
16
+ version: 1;
17
+ content: AdfNode[];
18
+ }
19
+
20
+ export interface AdfNode {
21
+ type: string;
22
+ content?: AdfNode[];
23
+ text?: string;
24
+ attrs?: Record<string, any>;
25
+ marks?: Array<{ type: string; attrs?: Record<string, any> }>;
26
+ }
27
+
28
+ /**
29
+ * Convert text/wiki markup to ADF document format.
30
+ */
31
+ export function toADF(text: string): AdfDocument {
32
+ const lines = text.split('\n');
33
+ const content: AdfNode[] = [];
34
+ let currentParagraph: AdfNode[] = [];
35
+
36
+ const flushParagraph = () => {
37
+ if (currentParagraph.length > 0) {
38
+ content.push({ type: 'paragraph', content: [...currentParagraph] });
39
+ currentParagraph = [];
40
+ }
41
+ };
42
+
43
+ for (const line of lines) {
44
+ // Headings: h1. h2. h3. or # ## ###
45
+ const wikiHeadingMatch = line.match(/^h(\d)\.\s+(.+)$/);
46
+ const mdHeadingMatch = line.match(/^(#{1,6})\s+(.+)$/);
47
+
48
+ if (wikiHeadingMatch) {
49
+ flushParagraph();
50
+ const level = parseInt(wikiHeadingMatch[1]);
51
+ content.push({
52
+ type: 'heading',
53
+ attrs: { level },
54
+ content: [{ type: 'text', text: wikiHeadingMatch[2] }],
55
+ });
56
+ continue;
57
+ }
58
+
59
+ if (mdHeadingMatch) {
60
+ flushParagraph();
61
+ const level = mdHeadingMatch[1].length;
62
+ content.push({
63
+ type: 'heading',
64
+ attrs: { level },
65
+ content: [{ type: 'text', text: mdHeadingMatch[2] }],
66
+ });
67
+ continue;
68
+ }
69
+
70
+ // Horizontal rule: ---- or ---
71
+ if (/^-{3,}$/.test(line.trim())) {
72
+ flushParagraph();
73
+ content.push({ type: 'rule' });
74
+ continue;
75
+ }
76
+
77
+ // Bullet list item: * item or - item
78
+ const bulletMatch = line.match(/^\*\s+(.+)$/) || line.match(/^-\s+(.+)$/);
79
+ if (bulletMatch) {
80
+ flushParagraph();
81
+ const listItem: AdfNode = {
82
+ type: 'listItem',
83
+ content: [{ type: 'paragraph', content: [{ type: 'text', text: bulletMatch[1] }] }],
84
+ };
85
+ // Append to existing bulletList if the last node is one (consecutive items)
86
+ const lastNode = content[content.length - 1];
87
+ if (lastNode && lastNode.type === 'bulletList' && lastNode.content) {
88
+ lastNode.content.push(listItem);
89
+ } else {
90
+ content.push({ type: 'bulletList', content: [listItem] });
91
+ }
92
+ continue;
93
+ }
94
+
95
+ // Empty line = paragraph break
96
+ if (line.trim() === '') {
97
+ flushParagraph();
98
+ continue;
99
+ }
100
+
101
+ // Bold: *text* → strong mark
102
+ let textContent = line;
103
+ const textNodes: AdfNode[] = [];
104
+
105
+ // Simple inline formatting: *bold* and _italic_
106
+ const parts = textContent.split(/(\*[^*]+\*|_[^_]+_)/);
107
+ for (const part of parts) {
108
+ if (part.startsWith('*') && part.endsWith('*') && part.length > 2) {
109
+ textNodes.push({
110
+ type: 'text',
111
+ text: part.slice(1, -1),
112
+ marks: [{ type: 'strong' }],
113
+ });
114
+ } else if (part.startsWith('_') && part.endsWith('_') && part.length > 2) {
115
+ textNodes.push({
116
+ type: 'text',
117
+ text: part.slice(1, -1),
118
+ marks: [{ type: 'em' }],
119
+ });
120
+ } else if (part) {
121
+ textNodes.push({ type: 'text', text: part });
122
+ }
123
+ }
124
+
125
+ currentParagraph.push(...textNodes);
126
+ }
127
+
128
+ flushParagraph();
129
+
130
+ // Ensure at least one paragraph
131
+ if (content.length === 0) {
132
+ content.push({
133
+ type: 'paragraph',
134
+ content: [{ type: 'text', text: text || '' }],
135
+ });
136
+ }
137
+
138
+ return {
139
+ type: 'doc',
140
+ version: 1,
141
+ content,
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Convert content to wiki markup format (for JIRA Server/DC API v2).
147
+ * If already in wiki markup, returns as-is.
148
+ */
149
+ export function toWikiMarkup(text: string): string {
150
+ // Already wiki markup — return as-is
151
+ return text;
152
+ }
153
+
154
+ /**
155
+ * Format content based on JIRA API version.
156
+ *
157
+ * @param text - Raw text/wiki markup content
158
+ * @param domain - JIRA domain (used to look up cached API version)
159
+ * @returns ADF document for v3/Cloud, wiki markup string for v2/Server
160
+ */
161
+ export function formatContent(text: string, domain: string): AdfDocument | string {
162
+ const version = getApiVersion(domain);
163
+ if (version === '3') {
164
+ return toADF(text);
165
+ }
166
+ return toWikiMarkup(text);
167
+ }
168
+
169
+ /**
170
+ * Format a description field for JIRA issue create/update payload.
171
+ *
172
+ * @param text - Raw description text
173
+ * @param domain - JIRA domain
174
+ * @returns Properly formatted description for the API version
175
+ */
176
+ export function toDescription(text: string, domain: string): AdfDocument | string {
177
+ return formatContent(text, domain);
178
+ }
179
+
180
+ /**
181
+ * Format a comment body for JIRA issue comment payload.
182
+ *
183
+ * @param text - Raw comment text
184
+ * @param domain - JIRA domain
185
+ * @returns Properly formatted comment body for the API version
186
+ */
187
+ export function toCommentBody(text: string, domain: string): AdfDocument | string {
188
+ return formatContent(text, domain);
189
+ }
@@ -1,6 +1,7 @@
1
1
  import { EnhancedContentBuilder } from "../../../dist/src/core/sync/enhanced-content-builder.js";
2
2
  import { SpecIncrementMapper } from "../../../dist/src/core/sync/spec-increment-mapper.js";
3
3
  import { parseSpecContent } from "../../../dist/src/core/spec-content-sync.js";
4
+ import { readIssueKey } from "./metadata-paths.js";
4
5
  import * as path from "path";
5
6
  import * as fs from "fs/promises";
6
7
  async function syncSpecToJiraWithEnhancedContent(options) {
@@ -61,11 +62,26 @@ async function syncSpecToJiraWithEnhancedContent(options) {
61
62
  tasksLinked: taskMapping?.tasks.length || 0
62
63
  };
63
64
  if (domain && project && !dryRun) {
64
- result.epicKey = `SPEC-001`;
65
- result.epicUrl = `https://${domain}/browse/SPEC-001`;
66
- if (verbose) {
67
- console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
68
- console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
65
+ // Read real JIRA key from metadata if available
66
+ const rootDir = await findSpecWeaveRoot(specPath);
67
+ const metadataPath = path.join(rootDir, '.specweave', 'increments', specId, 'metadata.json');
68
+ let realKey = null;
69
+ try {
70
+ const meta = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
71
+ realKey = readIssueKey(meta);
72
+ } catch { /* no metadata */ }
73
+
74
+ if (realKey) {
75
+ result.epicKey = realKey;
76
+ result.epicUrl = `https://${domain}/browse/${realKey}`;
77
+ } else {
78
+ // No real JIRA key — return null instead of placeholder
79
+ result.epicKey = null;
80
+ result.epicUrl = null;
81
+ if (verbose) {
82
+ console.log(`\u26A0\uFE0F JIRA API integration not implemented in this file`);
83
+ console.log(` Use jira-spec-sync.ts for actual JIRA synchronization`);
84
+ }
69
85
  }
70
86
  }
71
87
  return result;
@@ -0,0 +1,63 @@
1
+ import axios from "axios";
2
+ const deploymentCache = /* @__PURE__ */ new Map();
3
+ async function detectDeploymentType(domain, auth) {
4
+ const cached = deploymentCache.get(domain);
5
+ if (cached) return cached;
6
+ try {
7
+ const response = await axios.get(
8
+ `https://${domain}/rest/api/2/serverInfo`,
9
+ {
10
+ auth: {
11
+ username: auth.email,
12
+ password: auth.apiToken
13
+ },
14
+ headers: {
15
+ Accept: "application/json"
16
+ },
17
+ timeout: 1e4
18
+ }
19
+ );
20
+ const data = response.data;
21
+ const deploymentType = (data.deploymentType || "").toLowerCase();
22
+ const isCloud2 = deploymentType === "cloud" || domain.endsWith(".atlassian.net");
23
+ const info = {
24
+ type: isCloud2 ? "cloud" : "server",
25
+ apiVersion: isCloud2 ? "3" : "2",
26
+ baseUrl: `https://${domain}/rest/api/${isCloud2 ? "3" : "2"}`
27
+ };
28
+ deploymentCache.set(domain, info);
29
+ return info;
30
+ } catch {
31
+ const isCloud2 = domain.endsWith(".atlassian.net");
32
+ const info = {
33
+ type: isCloud2 ? "cloud" : "server",
34
+ apiVersion: isCloud2 ? "3" : "2",
35
+ baseUrl: `https://${domain}/rest/api/${isCloud2 ? "3" : "2"}`
36
+ };
37
+ deploymentCache.set(domain, info);
38
+ return info;
39
+ }
40
+ }
41
+ function getApiBaseUrl(domain) {
42
+ const cached = deploymentCache.get(domain);
43
+ if (cached) return cached.baseUrl;
44
+ return `https://${domain}/rest/api/3`;
45
+ }
46
+ function getApiVersion(domain) {
47
+ const cached = deploymentCache.get(domain);
48
+ return cached?.apiVersion ?? "3";
49
+ }
50
+ function isCloud(domain) {
51
+ const cached = deploymentCache.get(domain);
52
+ return cached ? cached.type === "cloud" : domain.endsWith(".atlassian.net");
53
+ }
54
+ function clearDeploymentCache() {
55
+ deploymentCache.clear();
56
+ }
57
+ export {
58
+ clearDeploymentCache,
59
+ detectDeploymentType,
60
+ getApiBaseUrl,
61
+ getApiVersion,
62
+ isCloud
63
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * JIRA Deployment Type Detector
3
+ *
4
+ * Detects whether a JIRA instance is Cloud or Server/Data Center
5
+ * and returns the correct API version to use.
6
+ *
7
+ * - Cloud: /rest/api/3 (ADF content format)
8
+ * - Server/DC: /rest/api/2 (wiki markup content format)
9
+ *
10
+ * Results are cached per domain to avoid repeated lookups.
11
+ *
12
+ * @module jira-deployment-detector
13
+ */
14
+
15
+ import axios from 'axios';
16
+
17
+ export interface DeploymentInfo {
18
+ type: 'cloud' | 'server';
19
+ apiVersion: '2' | '3';
20
+ baseUrl: string;
21
+ }
22
+
23
+ /** Cache: domain → deployment info */
24
+ const deploymentCache = new Map<string, DeploymentInfo>();
25
+
26
+ /**
27
+ * Detect JIRA deployment type by calling /rest/api/2/serverInfo.
28
+ * This endpoint exists on both Cloud and Server/DC.
29
+ *
30
+ * Cloud instances return deploymentType: "Cloud".
31
+ * Server/DC instances return deploymentType: "Server" or "DataCenter".
32
+ */
33
+ export async function detectDeploymentType(
34
+ domain: string,
35
+ auth: { email: string; apiToken: string }
36
+ ): Promise<DeploymentInfo> {
37
+ // Check cache first
38
+ const cached = deploymentCache.get(domain);
39
+ if (cached) return cached;
40
+
41
+ try {
42
+ const response = await axios.get(
43
+ `https://${domain}/rest/api/2/serverInfo`,
44
+ {
45
+ auth: {
46
+ username: auth.email,
47
+ password: auth.apiToken,
48
+ },
49
+ headers: {
50
+ Accept: 'application/json',
51
+ },
52
+ timeout: 10000,
53
+ }
54
+ );
55
+
56
+ const data = response.data;
57
+ const deploymentType = (data.deploymentType || '').toLowerCase();
58
+
59
+ const isCloud = deploymentType === 'cloud' || domain.endsWith('.atlassian.net');
60
+
61
+ const info: DeploymentInfo = {
62
+ type: isCloud ? 'cloud' : 'server',
63
+ apiVersion: isCloud ? '3' : '2',
64
+ baseUrl: `https://${domain}/rest/api/${isCloud ? '3' : '2'}`,
65
+ };
66
+
67
+ deploymentCache.set(domain, info);
68
+ return info;
69
+ } catch {
70
+ // Fallback: if .atlassian.net assume Cloud, otherwise Server
71
+ const isCloud = domain.endsWith('.atlassian.net');
72
+ const info: DeploymentInfo = {
73
+ type: isCloud ? 'cloud' : 'server',
74
+ apiVersion: isCloud ? '3' : '2',
75
+ baseUrl: `https://${domain}/rest/api/${isCloud ? '3' : '2'}`,
76
+ };
77
+
78
+ deploymentCache.set(domain, info);
79
+ return info;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Build the full API base URL for a given JIRA domain.
85
+ * Uses cached deployment info if available.
86
+ */
87
+ export function getApiBaseUrl(domain: string): string {
88
+ const cached = deploymentCache.get(domain);
89
+ if (cached) return cached.baseUrl;
90
+ // Default to v3 (Cloud) if not yet detected
91
+ return `https://${domain}/rest/api/3`;
92
+ }
93
+
94
+ /**
95
+ * Get the API version string for a domain.
96
+ */
97
+ export function getApiVersion(domain: string): '2' | '3' {
98
+ const cached = deploymentCache.get(domain);
99
+ return cached?.apiVersion ?? '3';
100
+ }
101
+
102
+ /**
103
+ * Check if a domain is Cloud deployment.
104
+ */
105
+ export function isCloud(domain: string): boolean {
106
+ const cached = deploymentCache.get(domain);
107
+ return cached ? cached.type === 'cloud' : domain.endsWith('.atlassian.net');
108
+ }
109
+
110
+ /** Clear cache (for testing) */
111
+ export function clearDeploymentCache(): void {
112
+ deploymentCache.clear();
113
+ }
@@ -1,4 +1,6 @@
1
1
  import { consoleLogger } from "../../../src/utils/logger.js";
2
+ import { getApiBaseUrl } from "./jira-deployment-detector.js";
3
+ import { toCommentBody } from "./content-format-adapter.js";
2
4
  class JiraDuplicateDetector {
3
5
  constructor(options = {}) {
4
6
  this.domain = options.domain || process.env.JIRA_DOMAIN || "";
@@ -53,10 +55,9 @@ class JiraDuplicateDetector {
53
55
  } catch (error) {
54
56
  this.logger.log(`\u26A0\uFE0F Verification check failed: ${error.message}`);
55
57
  return {
56
- success: true,
57
- // Assume success on error
58
+ success: false,
58
59
  expectedCount,
59
- actualCount: expectedCount,
60
+ actualCount: -1,
60
61
  duplicates: []
61
62
  };
62
63
  }
@@ -178,7 +179,7 @@ class JiraDuplicateDetector {
178
179
  throw new Error("JIRA credentials not configured");
179
180
  }
180
181
  const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
181
- const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
182
+ const url = `${getApiBaseUrl(this.domain)}/search?jql=${jql}&fields=summary,status,created`;
182
183
  const response = await fetch(url, {
183
184
  headers: {
184
185
  Authorization: `Basic ${this.auth}`,
@@ -209,7 +210,7 @@ class JiraDuplicateDetector {
209
210
  if (!closeTransition) {
210
211
  throw new Error(`No close transition found. Available: ${transitions.map((t) => t.name).join(", ")}`);
211
212
  }
212
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
213
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
213
214
  const response = await fetch(url, {
214
215
  method: "POST",
215
216
  headers: {
@@ -229,7 +230,7 @@ class JiraDuplicateDetector {
229
230
  * Get available transitions for an issue
230
231
  */
231
232
  async getTransitions(issueKey) {
232
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
233
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
233
234
  const response = await fetch(url, {
234
235
  headers: {
235
236
  Authorization: `Basic ${this.auth}`,
@@ -246,38 +247,20 @@ class JiraDuplicateDetector {
246
247
  * Add duplicate comment to issue
247
248
  */
248
249
  async addComment(issueKey, originalKey) {
249
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
250
- const comment = `h2. Duplicate of ${originalKey}
250
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/comment`;
251
+ const commentText = `h2. Duplicate of ${originalKey}
251
252
 
252
253
  This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
253
254
 
254
- The original issue (${originalKey}) contains the same content and should be used for tracking instead.
255
-
256
- ----
257
- \u{1F916} Auto-closed by SpecWeave Duplicate Cleanup`;
255
+ The original issue (${originalKey}) contains the same content and should be used for tracking instead.`;
256
+ const body = toCommentBody(commentText, this.domain);
258
257
  const response = await fetch(url, {
259
258
  method: "POST",
260
259
  headers: {
261
260
  Authorization: `Basic ${this.auth}`,
262
261
  "Content-Type": "application/json"
263
262
  },
264
- body: JSON.stringify({
265
- body: {
266
- type: "doc",
267
- version: 1,
268
- content: [
269
- {
270
- type: "paragraph",
271
- content: [
272
- {
273
- type: "text",
274
- text: comment
275
- }
276
- ]
277
- }
278
- ]
279
- }
280
- })
263
+ body: JSON.stringify({ body })
281
264
  });
282
265
  if (!response.ok) {
283
266
  this.logger.log(` \u26A0\uFE0F Failed to add comment to ${issueKey}`);
@@ -10,6 +10,8 @@
10
10
  */
11
11
 
12
12
  import { Logger, consoleLogger } from '../../../src/utils/logger.js';
13
+ import { getApiBaseUrl } from './jira-deployment-detector.js';
14
+ import { toCommentBody } from './content-format-adapter.js';
13
15
 
14
16
  export interface JiraIssue {
15
17
  key: string;
@@ -122,9 +124,9 @@ export class JiraDuplicateDetector {
122
124
  } catch (error: any) {
123
125
  this.logger.log(`⚠️ Verification check failed: ${error.message}`);
124
126
  return {
125
- success: true, // Assume success on error
127
+ success: false,
126
128
  expectedCount,
127
- actualCount: expectedCount,
129
+ actualCount: -1,
128
130
  duplicates: [],
129
131
  };
130
132
  }
@@ -281,7 +283,7 @@ export class JiraDuplicateDetector {
281
283
  }
282
284
 
283
285
  const jql = encodeURIComponent(`summary ~ "${summaryPattern}" ORDER BY created ASC`);
284
- const url = `https://${this.domain}/rest/api/3/search?jql=${jql}&fields=summary,status,created`;
286
+ const url = `${getApiBaseUrl(this.domain)}/search?jql=${jql}&fields=summary,status,created`;
285
287
 
286
288
  const response = await fetch(url, {
287
289
  headers: {
@@ -328,7 +330,7 @@ export class JiraDuplicateDetector {
328
330
  throw new Error(`No close transition found. Available: ${transitions.map((t: any) => t.name).join(', ')}`);
329
331
  }
330
332
 
331
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
333
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
332
334
 
333
335
  const response = await fetch(url, {
334
336
  method: 'POST',
@@ -351,7 +353,7 @@ export class JiraDuplicateDetector {
351
353
  * Get available transitions for an issue
352
354
  */
353
355
  private async getTransitions(issueKey: string): Promise<any[]> {
354
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/transitions`;
356
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/transitions`;
355
357
 
356
358
  const response = await fetch(url, {
357
359
  headers: {
@@ -372,16 +374,16 @@ export class JiraDuplicateDetector {
372
374
  * Add duplicate comment to issue
373
375
  */
374
376
  private async addComment(issueKey: string, originalKey: string): Promise<void> {
375
- const url = `https://${this.domain}/rest/api/3/issue/${issueKey}/comment`;
377
+ const url = `${getApiBaseUrl(this.domain)}/issue/${issueKey}/comment`;
376
378
 
377
- const comment = `h2. Duplicate of ${originalKey}
379
+ const commentText = `h2. Duplicate of ${originalKey}
378
380
 
379
381
  This issue was automatically closed by SpecWeave cleanup because it is a duplicate.
380
382
 
381
- The original issue (${originalKey}) contains the same content and should be used for tracking instead.
383
+ The original issue (${originalKey}) contains the same content and should be used for tracking instead.`;
382
384
 
383
- ----
384
- 🤖 Auto-closed by SpecWeave Duplicate Cleanup`;
385
+ // Use format adapter for correct format (ADF for Cloud, wiki for Server)
386
+ const body = toCommentBody(commentText, this.domain);
385
387
 
386
388
  const response = await fetch(url, {
387
389
  method: 'POST',
@@ -389,23 +391,7 @@ The original issue (${originalKey}) contains the same content and should be used
389
391
  Authorization: `Basic ${this.auth}`,
390
392
  'Content-Type': 'application/json',
391
393
  },
392
- body: JSON.stringify({
393
- body: {
394
- type: 'doc',
395
- version: 1,
396
- content: [
397
- {
398
- type: 'paragraph',
399
- content: [
400
- {
401
- type: 'text',
402
- text: comment,
403
- },
404
- ],
405
- },
406
- ],
407
- },
408
- }),
394
+ body: JSON.stringify({ body }),
409
395
  });
410
396
 
411
397
  if (!response.ok) {
@@ -1,11 +1,13 @@
1
1
  import * as fs from "../../../src/utils/fs-native.js";
2
2
  import * as path from "path";
3
3
  import * as yaml from "yaml";
4
+ import { toDescription } from "./content-format-adapter.js";
4
5
  class JiraEpicSync {
5
- constructor(client, specsDir, projectKey) {
6
+ constructor(client, specsDir, projectKey, domain) {
6
7
  this.client = client;
7
8
  this.specsDir = specsDir;
8
9
  this.projectKey = projectKey;
10
+ this.domain = domain || client["credentials"]?.domain || "";
9
11
  }
10
12
  /**
11
13
  * Sync Epic folder to JIRA (Epic + Stories)
@@ -92,8 +94,14 @@ class JiraEpicSync {
92
94
  * Find Epic folder by ID (FS-001 or just 001)
93
95
  */
94
96
  async findEpicFolder(epicId) {
95
- const normalizedId = epicId.startsWith("FS-") ? epicId : `FS-${epicId.padStart(3, "0")}`;
96
97
  const folders = await fs.readdir(this.specsDir);
98
+ for (const folder of folders) {
99
+ if (folder.startsWith(epicId)) {
100
+ return path.join(this.specsDir, folder);
101
+ }
102
+ }
103
+ const prefix = `${this.projectKey}-`;
104
+ const normalizedId = epicId.startsWith(prefix) ? epicId : `${prefix}${epicId.padStart(3, "0")}`;
97
105
  for (const folder of folders) {
98
106
  if (folder.startsWith(normalizedId)) {
99
107
  return path.join(this.specsDir, folder);
@@ -142,12 +150,13 @@ class JiraEpicSync {
142
150
  */
143
151
  async createEpic(epic) {
144
152
  const summary = `[${epic.id}] ${epic.title}`;
145
- const description = `Epic: ${epic.title}
153
+ const rawDescription = `Epic: ${epic.title}
146
154
 
147
155
  Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
148
156
 
149
157
  Priority: ${epic.priority}
150
158
  Status: ${epic.status}`;
159
+ const description = toDescription(rawDescription, this.domain);
151
160
  const issueData = {
152
161
  issueType: "Epic",
153
162
  summary,
@@ -158,7 +167,7 @@ Status: ${epic.status}`;
158
167
  const issue = await this.client.createIssue(issueData, this.projectKey);
159
168
  return {
160
169
  key: issue.key,
161
- url: issue.self.replace("/rest/api/3/issue/", "/browse/")
170
+ url: issue.self.replace(/\/rest\/api\/\d+\/issue\//, "/browse/")
162
171
  };
163
172
  }
164
173
  /**
@@ -166,12 +175,13 @@ Status: ${epic.status}`;
166
175
  */
167
176
  async updateEpic(epicKey, epic) {
168
177
  const summary = `[${epic.id}] ${epic.title}`;
169
- const description = `Epic: ${epic.title}
178
+ const rawDescription = `Epic: ${epic.title}
170
179
 
171
180
  Progress: ${epic.completed_increments}/${epic.total_increments} increments (${epic.progress})
172
181
 
173
182
  Priority: ${epic.priority}
174
183
  Status: ${epic.status}`;
184
+ const description = toDescription(rawDescription, this.domain);
175
185
  await this.client.updateIssue({
176
186
  key: epicKey,
177
187
  summary,