mcp-wordpress 2.6.3 → 2.7.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 (333) hide show
  1. package/README.md +1 -1
  2. package/dist/cache/CacheInvalidation.d.ts +25 -6
  3. package/dist/cache/CacheInvalidation.d.ts.map +1 -1
  4. package/dist/cache/CacheInvalidation.js +168 -16
  5. package/dist/cache/CacheInvalidation.js.map +1 -1
  6. package/dist/cache/HttpCacheWrapper.d.ts.map +1 -1
  7. package/dist/cache/HttpCacheWrapper.js +3 -4
  8. package/dist/cache/HttpCacheWrapper.js.map +1 -1
  9. package/dist/cache/SEOCacheManager.d.ts +150 -0
  10. package/dist/cache/SEOCacheManager.d.ts.map +1 -0
  11. package/dist/cache/SEOCacheManager.js +275 -0
  12. package/dist/cache/SEOCacheManager.js.map +1 -0
  13. package/dist/client/SEOWordPressClient.d.ts +164 -0
  14. package/dist/client/SEOWordPressClient.d.ts.map +1 -0
  15. package/dist/client/SEOWordPressClient.js +674 -0
  16. package/dist/client/SEOWordPressClient.js.map +1 -0
  17. package/dist/client/api.d.ts.map +1 -1
  18. package/dist/client/api.js +50 -20
  19. package/dist/client/api.js.map +1 -1
  20. package/dist/client/auth.js +19 -19
  21. package/dist/client/auth.js.map +1 -1
  22. package/dist/client/index.d.ts +11 -0
  23. package/dist/client/index.d.ts.map +1 -0
  24. package/dist/client/index.js +14 -0
  25. package/dist/client/index.js.map +1 -0
  26. package/dist/client/managers/AuthManager.d.ts +39 -0
  27. package/dist/client/managers/AuthManager.d.ts.map +1 -0
  28. package/dist/client/managers/AuthManager.js +142 -0
  29. package/dist/client/managers/AuthManager.js.map +1 -0
  30. package/dist/client/managers/AuthenticationManager.d.ts.map +1 -1
  31. package/dist/client/managers/AuthenticationManager.js +10 -9
  32. package/dist/client/managers/AuthenticationManager.js.map +1 -1
  33. package/dist/client/managers/BaseManager.d.ts.map +1 -1
  34. package/dist/client/managers/BaseManager.js +12 -0
  35. package/dist/client/managers/BaseManager.js.map +1 -1
  36. package/dist/client/managers/ComposedAuthenticationManager.d.ts +94 -0
  37. package/dist/client/managers/ComposedAuthenticationManager.d.ts.map +1 -0
  38. package/dist/client/managers/ComposedAuthenticationManager.js +340 -0
  39. package/dist/client/managers/ComposedAuthenticationManager.js.map +1 -0
  40. package/dist/client/managers/ComposedManagerFactory.d.ts +104 -0
  41. package/dist/client/managers/ComposedManagerFactory.d.ts.map +1 -0
  42. package/dist/client/managers/ComposedManagerFactory.js +180 -0
  43. package/dist/client/managers/ComposedManagerFactory.js.map +1 -0
  44. package/dist/client/managers/ComposedRequestManager.d.ts +82 -0
  45. package/dist/client/managers/ComposedRequestManager.d.ts.map +1 -0
  46. package/dist/client/managers/ComposedRequestManager.js +260 -0
  47. package/dist/client/managers/ComposedRequestManager.js.map +1 -0
  48. package/dist/client/managers/JWTAuthImplementation.d.ts +86 -0
  49. package/dist/client/managers/JWTAuthImplementation.d.ts.map +1 -0
  50. package/dist/client/managers/JWTAuthImplementation.js +240 -0
  51. package/dist/client/managers/JWTAuthImplementation.js.map +1 -0
  52. package/dist/client/managers/ManagersIndex.d.ts +6 -0
  53. package/dist/client/managers/ManagersIndex.d.ts.map +1 -0
  54. package/dist/client/managers/ManagersIndex.js +6 -0
  55. package/dist/client/managers/ManagersIndex.js.map +1 -0
  56. package/dist/client/managers/RequestManager.d.ts.map +1 -1
  57. package/dist/client/managers/RequestManager.js +5 -3
  58. package/dist/client/managers/RequestManager.js.map +1 -1
  59. package/dist/client/managers/composed/MigrationAdapter.d.ts +80 -0
  60. package/dist/client/managers/composed/MigrationAdapter.d.ts.map +1 -0
  61. package/dist/client/managers/composed/MigrationAdapter.js +214 -0
  62. package/dist/client/managers/composed/MigrationAdapter.js.map +1 -0
  63. package/dist/client/managers/composed/index.d.ts +23 -0
  64. package/dist/client/managers/composed/index.d.ts.map +1 -0
  65. package/dist/client/managers/composed/index.js +26 -0
  66. package/dist/client/managers/composed/index.js.map +1 -0
  67. package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts +27 -0
  68. package/dist/client/managers/implementations/ConfigurationProviderImpl.d.ts.map +1 -0
  69. package/dist/client/managers/implementations/ConfigurationProviderImpl.js +41 -0
  70. package/dist/client/managers/implementations/ConfigurationProviderImpl.js.map +1 -0
  71. package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts +31 -0
  72. package/dist/client/managers/implementations/ErrorHandlerImpl.d.ts.map +1 -0
  73. package/dist/client/managers/implementations/ErrorHandlerImpl.js +73 -0
  74. package/dist/client/managers/implementations/ErrorHandlerImpl.js.map +1 -0
  75. package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts +47 -0
  76. package/dist/client/managers/implementations/ParameterValidatorImpl.d.ts.map +1 -0
  77. package/dist/client/managers/implementations/ParameterValidatorImpl.js +141 -0
  78. package/dist/client/managers/implementations/ParameterValidatorImpl.js.map +1 -0
  79. package/dist/client/managers/interfaces/ManagerInterfaces.d.ts +147 -0
  80. package/dist/client/managers/interfaces/ManagerInterfaces.d.ts.map +1 -0
  81. package/dist/client/managers/interfaces/ManagerInterfaces.js +6 -0
  82. package/dist/client/managers/interfaces/ManagerInterfaces.js.map +1 -0
  83. package/dist/config/Config.d.ts +30 -0
  84. package/dist/config/Config.d.ts.map +1 -1
  85. package/dist/config/Config.js +30 -0
  86. package/dist/config/Config.js.map +1 -1
  87. package/dist/config/ConfigurationSchema.d.ts +75 -198
  88. package/dist/config/ConfigurationSchema.d.ts.map +1 -1
  89. package/dist/config/ConfigurationSchema.js +17 -17
  90. package/dist/config/ConfigurationSchema.js.map +1 -1
  91. package/dist/config/ServerConfiguration.d.ts +2 -2
  92. package/dist/config/ServerConfiguration.d.ts.map +1 -1
  93. package/dist/config/ServerConfiguration.js +15 -13
  94. package/dist/config/ServerConfiguration.js.map +1 -1
  95. package/dist/config/index.d.ts +8 -0
  96. package/dist/config/index.d.ts.map +1 -0
  97. package/dist/config/index.js +11 -0
  98. package/dist/config/index.js.map +1 -0
  99. package/dist/docs/DocumentationGenerator.js +2 -2
  100. package/dist/docs/DocumentationGenerator.js.map +1 -1
  101. package/dist/dxt-entry.js +3 -3
  102. package/dist/dxt-entry.js.map +1 -1
  103. package/dist/index.d.ts +1 -0
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +38 -37
  106. package/dist/index.js.map +1 -1
  107. package/dist/performance/MetricsCollector.d.ts.map +1 -1
  108. package/dist/performance/MetricsCollector.js +5 -4
  109. package/dist/performance/MetricsCollector.js.map +1 -1
  110. package/dist/security/AISecurityScanner.js +7 -7
  111. package/dist/security/AISecurityScanner.js.map +1 -1
  112. package/dist/security/AutomatedRemediation.d.ts.map +1 -1
  113. package/dist/security/AutomatedRemediation.js +11 -11
  114. package/dist/security/AutomatedRemediation.js.map +1 -1
  115. package/dist/security/InputValidator.d.ts +50 -126
  116. package/dist/security/InputValidator.d.ts.map +1 -1
  117. package/dist/security/InputValidator.js +9 -9
  118. package/dist/security/InputValidator.js.map +1 -1
  119. package/dist/security/SecurityCIPipeline.d.ts +47 -5
  120. package/dist/security/SecurityCIPipeline.d.ts.map +1 -1
  121. package/dist/security/SecurityCIPipeline.js +390 -49
  122. package/dist/security/SecurityCIPipeline.js.map +1 -1
  123. package/dist/security/SecurityConfigManager.js +10 -10
  124. package/dist/security/SecurityConfigManager.js.map +1 -1
  125. package/dist/security/SecurityMonitoring.js +4 -4
  126. package/dist/security/SecurityMonitoring.js.map +1 -1
  127. package/dist/security/SecurityReviewer.d.ts.map +1 -1
  128. package/dist/security/SecurityReviewer.js +13 -6
  129. package/dist/security/SecurityReviewer.js.map +1 -1
  130. package/dist/security/index.js +3 -3
  131. package/dist/security/index.js.map +1 -1
  132. package/dist/server/ConnectionTester.js +5 -5
  133. package/dist/server/ConnectionTester.js.map +1 -1
  134. package/dist/server/ToolRegistry.d.ts +2 -2
  135. package/dist/server/ToolRegistry.d.ts.map +1 -1
  136. package/dist/server/ToolRegistry.js +7 -6
  137. package/dist/server/ToolRegistry.js.map +1 -1
  138. package/dist/server/index.d.ts +7 -0
  139. package/dist/server/index.d.ts.map +1 -0
  140. package/dist/server/index.js +9 -0
  141. package/dist/server/index.js.map +1 -0
  142. package/dist/tools/BaseToolManager.d.ts.map +1 -1
  143. package/dist/tools/BaseToolManager.js +11 -11
  144. package/dist/tools/BaseToolManager.js.map +1 -1
  145. package/dist/tools/auth.d.ts.map +1 -1
  146. package/dist/tools/auth.js +7 -7
  147. package/dist/tools/auth.js.map +1 -1
  148. package/dist/tools/comments.d.ts.map +1 -1
  149. package/dist/tools/comments.js +14 -14
  150. package/dist/tools/comments.js.map +1 -1
  151. package/dist/tools/index.d.ts +1 -0
  152. package/dist/tools/index.d.ts.map +1 -1
  153. package/dist/tools/index.js +1 -0
  154. package/dist/tools/index.js.map +1 -1
  155. package/dist/tools/media.d.ts.map +1 -1
  156. package/dist/tools/media.js +14 -11
  157. package/dist/tools/media.js.map +1 -1
  158. package/dist/tools/pages.d.ts.map +1 -1
  159. package/dist/tools/pages.js +12 -12
  160. package/dist/tools/pages.js.map +1 -1
  161. package/dist/tools/performance.d.ts.map +1 -1
  162. package/dist/tools/performance.js +13 -11
  163. package/dist/tools/performance.js.map +1 -1
  164. package/dist/tools/posts/PostHandlers.d.ts.map +1 -1
  165. package/dist/tools/posts/PostHandlers.js +16 -16
  166. package/dist/tools/posts/PostHandlers.js.map +1 -1
  167. package/dist/tools/posts/PostToolDefinitions.d.ts.map +1 -1
  168. package/dist/tools/posts/index.d.ts.map +1 -1
  169. package/dist/tools/seo/BulkOperations.d.ts +113 -0
  170. package/dist/tools/seo/BulkOperations.d.ts.map +1 -0
  171. package/dist/tools/seo/BulkOperations.js +398 -0
  172. package/dist/tools/seo/BulkOperations.js.map +1 -0
  173. package/dist/tools/seo/SEOHandlers.d.ts +55 -0
  174. package/dist/tools/seo/SEOHandlers.d.ts.map +1 -0
  175. package/dist/tools/seo/SEOHandlers.js +255 -0
  176. package/dist/tools/seo/SEOHandlers.js.map +1 -0
  177. package/dist/tools/seo/SEOToolDefinitions.d.ts +59 -0
  178. package/dist/tools/seo/SEOToolDefinitions.d.ts.map +1 -0
  179. package/dist/tools/seo/SEOToolDefinitions.js +385 -0
  180. package/dist/tools/seo/SEOToolDefinitions.js.map +1 -0
  181. package/dist/tools/seo/SEOTools.d.ts +203 -0
  182. package/dist/tools/seo/SEOTools.d.ts.map +1 -0
  183. package/dist/tools/seo/SEOTools.js +708 -0
  184. package/dist/tools/seo/SEOTools.js.map +1 -0
  185. package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts +94 -0
  186. package/dist/tools/seo/analyzers/ContentAnalyzer.d.ts.map +1 -0
  187. package/dist/tools/seo/analyzers/ContentAnalyzer.js +402 -0
  188. package/dist/tools/seo/analyzers/ContentAnalyzer.js.map +1 -0
  189. package/dist/tools/seo/auditors/SiteAuditor.d.ts +121 -0
  190. package/dist/tools/seo/auditors/SiteAuditor.d.ts.map +1 -0
  191. package/dist/tools/seo/auditors/SiteAuditor.js +600 -0
  192. package/dist/tools/seo/auditors/SiteAuditor.js.map +1 -0
  193. package/dist/tools/seo/generators/MetaGenerator.d.ts +128 -0
  194. package/dist/tools/seo/generators/MetaGenerator.d.ts.map +1 -0
  195. package/dist/tools/seo/generators/MetaGenerator.js +547 -0
  196. package/dist/tools/seo/generators/MetaGenerator.js.map +1 -0
  197. package/dist/tools/seo/generators/SchemaGenerator.d.ts +204 -0
  198. package/dist/tools/seo/generators/SchemaGenerator.d.ts.map +1 -0
  199. package/dist/tools/seo/generators/SchemaGenerator.js +670 -0
  200. package/dist/tools/seo/generators/SchemaGenerator.js.map +1 -0
  201. package/dist/tools/seo/index.d.ts +17 -0
  202. package/dist/tools/seo/index.d.ts.map +1 -0
  203. package/dist/tools/seo/index.js +18 -0
  204. package/dist/tools/seo/index.js.map +1 -0
  205. package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts +186 -0
  206. package/dist/tools/seo/optimizers/InternalLinkingSuggester.d.ts.map +1 -0
  207. package/dist/tools/seo/optimizers/InternalLinkingSuggester.js +683 -0
  208. package/dist/tools/seo/optimizers/InternalLinkingSuggester.js.map +1 -0
  209. package/dist/tools/site.d.ts.map +1 -1
  210. package/dist/tools/site.js +12 -12
  211. package/dist/tools/site.js.map +1 -1
  212. package/dist/tools/taxonomies.d.ts.map +1 -1
  213. package/dist/tools/taxonomies.js +20 -20
  214. package/dist/tools/taxonomies.js.map +1 -1
  215. package/dist/tools/users.d.ts.map +1 -1
  216. package/dist/tools/users.js +12 -12
  217. package/dist/tools/users.js.map +1 -1
  218. package/dist/types/client.d.ts +8 -6
  219. package/dist/types/client.d.ts.map +1 -1
  220. package/dist/types/client.js.map +1 -1
  221. package/dist/types/seo.d.ts +473 -0
  222. package/dist/types/seo.d.ts.map +1 -0
  223. package/dist/types/seo.js +94 -0
  224. package/dist/types/seo.js.map +1 -0
  225. package/dist/utils/enhancedError.js +1 -1
  226. package/dist/utils/enhancedError.js.map +1 -1
  227. package/dist/utils/error.d.ts.map +1 -1
  228. package/dist/utils/error.js +0 -1
  229. package/dist/utils/error.js.map +1 -1
  230. package/dist/utils/index.d.ts +12 -0
  231. package/dist/utils/index.d.ts.map +1 -0
  232. package/dist/utils/index.js +18 -0
  233. package/dist/utils/index.js.map +1 -0
  234. package/dist/utils/logger.js +3 -3
  235. package/dist/utils/logger.js.map +1 -1
  236. package/dist/utils/toolWrapper.d.ts +2 -2
  237. package/dist/utils/toolWrapper.js +8 -8
  238. package/dist/utils/toolWrapper.js.map +1 -1
  239. package/dist/utils/validation/core.d.ts.map +1 -1
  240. package/dist/utils/validation/core.js.map +1 -1
  241. package/dist/utils/validation/index.d.ts.map +1 -1
  242. package/dist/utils/validation/index.js.map +1 -1
  243. package/dist/utils/validation/network.js +3 -3
  244. package/dist/utils/validation/network.js.map +1 -1
  245. package/dist/utils/validation/rateLimit.js.map +1 -1
  246. package/dist/utils/validation/security.js.map +1 -1
  247. package/dist/utils/validation/wordpress.js.map +1 -1
  248. package/dist/utils/version.d.ts +144 -0
  249. package/dist/utils/version.d.ts.map +1 -0
  250. package/dist/utils/version.js +318 -0
  251. package/dist/utils/version.js.map +1 -0
  252. package/package.json +24 -58
  253. package/src/cache/CacheInvalidation.ts +183 -20
  254. package/src/cache/HttpCacheWrapper.ts +8 -5
  255. package/src/cache/SEOCacheManager.ts +330 -0
  256. package/src/cache/__tests__/CacheInvalidation.test.ts +6 -11
  257. package/src/cache/__tests__/CachedWordPressClient.test.ts +37 -62
  258. package/src/client/SEOWordPressClient.ts +876 -0
  259. package/src/client/api.ts +50 -21
  260. package/src/client/auth.ts +19 -19
  261. package/src/client/index.ts +16 -0
  262. package/src/client/managers/AuthManager.ts +175 -0
  263. package/src/client/managers/AuthenticationManager.ts +16 -14
  264. package/src/client/managers/BaseManager.ts +24 -5
  265. package/src/client/managers/ComposedAuthenticationManager.ts +409 -0
  266. package/src/client/managers/ComposedManagerFactory.ts +231 -0
  267. package/src/client/managers/ComposedRequestManager.ts +336 -0
  268. package/src/client/managers/JWTAuthImplementation.ts +326 -0
  269. package/src/client/managers/ManagersIndex.ts +6 -0
  270. package/src/client/managers/RequestManager.ts +9 -7
  271. package/src/client/managers/composed/MigrationAdapter.ts +263 -0
  272. package/src/client/managers/composed/index.ts +47 -0
  273. package/src/client/managers/implementations/ConfigurationProviderImpl.ts +52 -0
  274. package/src/client/managers/implementations/ErrorHandlerImpl.ts +102 -0
  275. package/src/client/managers/implementations/ParameterValidatorImpl.ts +221 -0
  276. package/src/client/managers/interfaces/ManagerInterfaces.ts +171 -0
  277. package/src/config/Config.ts +63 -0
  278. package/src/config/ConfigurationSchema.ts +17 -17
  279. package/src/config/ServerConfiguration.ts +18 -16
  280. package/src/config/index.ts +13 -0
  281. package/src/docs/DocumentationGenerator.ts +2 -2
  282. package/src/dxt-entry.ts +3 -3
  283. package/src/index.ts +43 -43
  284. package/src/performance/MetricsCollector.ts +15 -11
  285. package/src/security/AISecurityScanner.ts +7 -7
  286. package/src/security/AutomatedRemediation.ts +13 -11
  287. package/src/security/InputValidator.ts +10 -9
  288. package/src/security/SecurityCIPipeline.ts +494 -56
  289. package/src/security/SecurityConfigManager.ts +10 -10
  290. package/src/security/SecurityMonitoring.ts +5 -5
  291. package/src/security/SecurityReviewer.ts +13 -6
  292. package/src/security/index.ts +3 -3
  293. package/src/server/ConnectionTester.ts +5 -5
  294. package/src/server/ToolRegistry.ts +9 -8
  295. package/src/server/index.ts +10 -0
  296. package/src/tools/BaseToolManager.ts +55 -83
  297. package/src/tools/auth.ts +21 -12
  298. package/src/tools/comments.ts +23 -19
  299. package/src/tools/index.ts +1 -0
  300. package/src/tools/media.ts +23 -20
  301. package/src/tools/pages.ts +20 -13
  302. package/src/tools/performance.ts +101 -32
  303. package/src/tools/posts/PostHandlers.ts +23 -23
  304. package/src/tools/posts/PostToolDefinitions.ts +1 -1
  305. package/src/tools/posts/index.ts +2 -2
  306. package/src/tools/seo/BulkOperations.ts +557 -0
  307. package/src/tools/seo/SEOHandlers.ts +296 -0
  308. package/src/tools/seo/SEOToolDefinitions.ts +402 -0
  309. package/src/tools/seo/SEOTools.ts +871 -0
  310. package/src/tools/seo/analyzers/ContentAnalyzer.ts +493 -0
  311. package/src/tools/seo/auditors/SiteAuditor.ts +787 -0
  312. package/src/tools/seo/generators/MetaGenerator.ts +694 -0
  313. package/src/tools/seo/generators/SchemaGenerator.ts +955 -0
  314. package/src/tools/seo/index.ts +47 -0
  315. package/src/tools/seo/optimizers/InternalLinkingSuggester.ts +934 -0
  316. package/src/tools/site.ts +27 -26
  317. package/src/tools/taxonomies.ts +29 -25
  318. package/src/tools/users.ts +20 -13
  319. package/src/types/client.ts +8 -6
  320. package/src/types/seo.ts +546 -0
  321. package/src/utils/enhancedError.ts +1 -1
  322. package/src/utils/error.ts +1 -2
  323. package/src/utils/index.ts +23 -0
  324. package/src/utils/logger.ts +3 -3
  325. package/src/utils/toolWrapper.ts +10 -10
  326. package/src/utils/validation/core.ts +2 -2
  327. package/src/utils/validation/index.ts +2 -2
  328. package/src/utils/validation/network.ts +5 -5
  329. package/src/utils/validation/rateLimit.ts +1 -1
  330. package/src/utils/validation/security.ts +1 -1
  331. package/src/utils/validation/wordpress.ts +1 -1
  332. package/src/utils/version.ts +402 -0
  333. package/src/dxt-entry.cjs +0 -68
@@ -0,0 +1,683 @@
1
+ /**
2
+ * Internal Linking Suggester
3
+ *
4
+ * This module analyzes WordPress content to suggest relevant internal linking
5
+ * opportunities based on semantic analysis, keyword matching, and content clustering.
6
+ * It helps improve site architecture, user navigation, and SEO link equity distribution.
7
+ *
8
+ * Features:
9
+ * - Semantic content analysis for relevance scoring
10
+ * - Keyword-based link suggestions with confidence metrics
11
+ * - Topic clustering for hub-and-spoke content architecture
12
+ * - Anchor text optimization recommendations
13
+ * - Link density and distribution analysis
14
+ * - Context-aware link placement suggestions
15
+ *
16
+ * @since 2.7.0
17
+ */
18
+ import { LoggerFactory } from "../../../utils/logger.js";
19
+ /**
20
+ * Internal Linking Suggester Class
21
+ */
22
+ export class InternalLinkingSuggester {
23
+ client;
24
+ logger = LoggerFactory.tool("internal_linking");
25
+ config;
26
+ constructor(client, config) {
27
+ this.client = client;
28
+ // Default configuration
29
+ this.config = {
30
+ maxSuggestions: 10,
31
+ minRelevanceScore: 30,
32
+ maxLinksPerPost: 5,
33
+ useSemanticAnalysis: true,
34
+ includeCategoryMatches: true,
35
+ includeTagMatches: true,
36
+ minWordCount: 300,
37
+ maxPostAge: 365, // 1 year
38
+ enableContextualPlacement: true,
39
+ ...config,
40
+ };
41
+ }
42
+ /**
43
+ * Generate internal linking suggestions for a specific post
44
+ */
45
+ async generateSuggestions(sourcePost, params) {
46
+ this.logger.debug("Generating internal linking suggestions", {
47
+ postId: sourcePost.id,
48
+ title: sourcePost.title?.rendered?.substring(0, 50),
49
+ maxSuggestions: this.config.maxSuggestions,
50
+ });
51
+ try {
52
+ // Analyze source post content
53
+ const sourceAnalysis = await this.analyzePostContent(sourcePost);
54
+ // Get candidate posts for linking
55
+ const candidatePosts = await this.getCandidatePosts(sourcePost, params);
56
+ // Analyze candidate posts
57
+ const candidateAnalyses = await Promise.all(candidatePosts.map((post) => this.analyzePostContent(post)));
58
+ // Calculate relevance scores
59
+ const scoredSuggestions = this.calculateRelevanceScores(sourcePost, sourceAnalysis, candidatePosts, candidateAnalyses);
60
+ // Filter and rank suggestions
61
+ const filteredSuggestions = scoredSuggestions
62
+ .filter((suggestion) => suggestion.relevance >= this.config.minRelevanceScore)
63
+ .sort((a, b) => b.relevance - a.relevance)
64
+ .slice(0, this.config.maxSuggestions);
65
+ // Add contextual placement information
66
+ const enhancedSuggestions = await Promise.all(filteredSuggestions.map((suggestion) => this.enhanceWithContextualPlacement(sourcePost, suggestion)));
67
+ this.logger.info("Generated internal linking suggestions", {
68
+ sourcePostId: sourcePost.id,
69
+ candidatesAnalyzed: candidatePosts.length,
70
+ suggestionsFound: enhancedSuggestions.length,
71
+ avgRelevanceScore: enhancedSuggestions.length > 0
72
+ ? (enhancedSuggestions.reduce((sum, s) => sum + s.relevance, 0) / enhancedSuggestions.length).toFixed(1)
73
+ : 0,
74
+ });
75
+ return enhancedSuggestions;
76
+ }
77
+ catch (_error) {
78
+ this.logger.error("Failed to generate internal linking suggestions", {
79
+ postId: sourcePost.id,
80
+ _error: _error instanceof Error ? _error.message : String(_error),
81
+ });
82
+ throw _error;
83
+ }
84
+ }
85
+ /**
86
+ * Analyze content clustering for site-wide link architecture
87
+ */
88
+ async analyzeContentClusters(params) {
89
+ this.logger.debug("Analyzing content clusters", {
90
+ site: params.site,
91
+ });
92
+ try {
93
+ // Get all published posts
94
+ const allPosts = await this.getAllPosts(params);
95
+ // Analyze all posts
96
+ const postAnalyses = await Promise.all(allPosts.map((post) => this.analyzePostContent(post)));
97
+ // Create topic clusters using similarity analysis
98
+ const clusters = this.createTopicClusters(allPosts, postAnalyses);
99
+ // Identify hub posts for each cluster
100
+ const enhancedClusters = clusters.map((cluster) => this.identifyHubPost(cluster));
101
+ this.logger.info("Content clustering analysis completed", {
102
+ totalPosts: allPosts.length,
103
+ clustersFound: enhancedClusters.length,
104
+ avgClusterSize: enhancedClusters.length > 0 ? (allPosts.length / enhancedClusters.length).toFixed(1) : 0,
105
+ });
106
+ return enhancedClusters;
107
+ }
108
+ catch (_error) {
109
+ this.logger.error("Failed to analyze content clusters", {
110
+ _error: _error instanceof Error ? _error.message : String(_error),
111
+ });
112
+ throw _error;
113
+ }
114
+ }
115
+ /**
116
+ * Get bulk linking suggestions for multiple posts
117
+ */
118
+ async generateBulkSuggestions(postIds, params) {
119
+ this.logger.debug("Generating bulk internal linking suggestions", {
120
+ postCount: postIds.length,
121
+ });
122
+ const results = [];
123
+ for (const postId of postIds) {
124
+ try {
125
+ const post = await this.client.getPost(postId);
126
+ if (post) {
127
+ const suggestions = await this.generateSuggestions(post, params);
128
+ results.push({ postId, suggestions });
129
+ }
130
+ }
131
+ catch (_error) {
132
+ this.logger.warn("Failed to generate suggestions for post", {
133
+ postId,
134
+ _error: _error instanceof Error ? _error.message : String(_error),
135
+ });
136
+ results.push({ postId, suggestions: [] });
137
+ }
138
+ }
139
+ return results;
140
+ }
141
+ /**
142
+ * Analyze post content for relevance scoring
143
+ */
144
+ async analyzePostContent(post) {
145
+ const content = this.extractTextContent(post.content?.rendered || "");
146
+ const title = post.title?.rendered || "";
147
+ const fullText = `${title} ${content}`;
148
+ // Extract keywords with TF-IDF scoring
149
+ const keywords = this.extractKeywords(fullText);
150
+ // Identify main topics
151
+ const topics = this.extractTopics(fullText, keywords);
152
+ // Determine content category
153
+ const category = this.categorizeContent(fullText, keywords);
154
+ // Generate semantic fingerprint
155
+ const semanticFingerprint = this.generateSemanticFingerprint(fullText, keywords);
156
+ return {
157
+ keywords,
158
+ topics,
159
+ category,
160
+ semanticFingerprint,
161
+ wordCount: this.countWords(content),
162
+ readingLevel: this.calculateReadingLevel(content),
163
+ };
164
+ }
165
+ /**
166
+ * Get candidate posts for internal linking
167
+ */
168
+ async getCandidatePosts(sourcePost, params) {
169
+ try {
170
+ // Get all published posts except the source post
171
+ const allPosts = await this.getAllPosts(params);
172
+ return allPosts.filter((post) => {
173
+ // Exclude source post
174
+ if (post.id === sourcePost.id)
175
+ return false;
176
+ // Only include published posts
177
+ if (post.status !== "publish")
178
+ return false;
179
+ // Check minimum word count
180
+ const wordCount = this.countWords(this.extractTextContent(post.content?.rendered || ""));
181
+ if (wordCount < this.config.minWordCount)
182
+ return false;
183
+ // Check post age if specified
184
+ if (this.config.maxPostAge > 0) {
185
+ const postDate = new Date(post.date || "");
186
+ const daysSincePost = (Date.now() - postDate.getTime()) / (1000 * 60 * 60 * 24);
187
+ if (daysSincePost > this.config.maxPostAge)
188
+ return false;
189
+ }
190
+ return true;
191
+ });
192
+ }
193
+ catch (_error) {
194
+ this.logger.error("Failed to get candidate posts", {
195
+ _error: _error instanceof Error ? _error.message : String(_error),
196
+ });
197
+ throw _error; // Re-throw error to maintain error propagation
198
+ }
199
+ }
200
+ /**
201
+ * Get all published posts
202
+ */
203
+ async getAllPosts(params) {
204
+ // In a real implementation, this would fetch from WordPress API
205
+ // const response = await this.client.getPosts({ per_page: 100, status: 'publish' });
206
+ // return response as WordPressPost[];
207
+ // For now, return empty array (tests will mock this method)
208
+ return [];
209
+ }
210
+ /**
211
+ * Calculate relevance scores between source and candidate posts
212
+ */
213
+ calculateRelevanceScores(sourcePost, sourceAnalysis, candidatePosts, candidateAnalyses) {
214
+ return candidatePosts.map((candidatePost, index) => {
215
+ const candidateAnalysis = candidateAnalyses[index];
216
+ // Calculate various relevance factors
217
+ const keywordSimilarity = this.calculateKeywordSimilarity(sourceAnalysis.keywords, candidateAnalysis.keywords);
218
+ const topicSimilarity = this.calculateTopicSimilarity(sourceAnalysis.topics, candidateAnalysis.topics);
219
+ const semanticSimilarity = this.calculateSemanticSimilarity(sourceAnalysis.semanticFingerprint, candidateAnalysis.semanticFingerprint);
220
+ const categorySimilarity = sourceAnalysis.category === candidateAnalysis.category ? 0.3 : 0;
221
+ // Weighted relevance score
222
+ const relevanceScore = Math.round(keywordSimilarity * 0.4 + topicSimilarity * 0.3 + semanticSimilarity * 0.2 + categorySimilarity * 0.1);
223
+ // Generate suggested anchor text
224
+ const anchorText = this.generateAnchorText(candidatePost, sourceAnalysis.keywords);
225
+ // Determine reason for suggestion
226
+ const reason = this.generateSuggestionReason(keywordSimilarity, topicSimilarity, semanticSimilarity, categorySimilarity);
227
+ return {
228
+ sourcePostId: sourcePost.id,
229
+ targetPostId: candidatePost.id,
230
+ targetTitle: candidatePost.title?.rendered || "Untitled",
231
+ targetUrl: candidatePost.link || `#${candidatePost.id}`,
232
+ anchorText,
233
+ relevance: relevanceScore,
234
+ reason,
235
+ context: "", // Will be filled by contextual placement
236
+ };
237
+ });
238
+ }
239
+ /**
240
+ * Enhance suggestions with contextual placement information
241
+ */
242
+ async enhanceWithContextualPlacement(sourcePost, suggestion) {
243
+ if (!this.config.enableContextualPlacement) {
244
+ return suggestion;
245
+ }
246
+ const content = sourcePost.content?.rendered || "";
247
+ const placements = this.findOptimalPlacements(content, suggestion.anchorText, suggestion.targetTitle);
248
+ if (placements.length > 0) {
249
+ const bestPlacement = placements[0];
250
+ suggestion.context = `${bestPlacement.contextBefore}[${suggestion.anchorText}]${bestPlacement.contextAfter}`;
251
+ }
252
+ return suggestion;
253
+ }
254
+ /**
255
+ * Create topic clusters from analyzed posts
256
+ */
257
+ createTopicClusters(posts, analyses) {
258
+ const clusters = [];
259
+ const clustered = new Set();
260
+ analyses.forEach((analysis, index) => {
261
+ if (clustered.has(index))
262
+ return;
263
+ const post = posts[index];
264
+ const cluster = {
265
+ clusterId: `cluster_${clusters.length + 1}`,
266
+ topic: analysis.topics[0] || analysis.category,
267
+ posts: [
268
+ {
269
+ postId: post.id,
270
+ title: post.title?.rendered || "Untitled",
271
+ url: post.link || `#${post.id}`,
272
+ relevanceScore: 100,
273
+ isHub: false,
274
+ },
275
+ ],
276
+ coherenceScore: 0,
277
+ };
278
+ // Find similar posts
279
+ analyses.forEach((otherAnalysis, otherIndex) => {
280
+ if (otherIndex === index || clustered.has(otherIndex))
281
+ return;
282
+ const similarity = this.calculateSemanticSimilarity(analysis.semanticFingerprint, otherAnalysis.semanticFingerprint);
283
+ if (similarity > 60) {
284
+ // Similarity threshold for clustering
285
+ const otherPost = posts[otherIndex];
286
+ cluster.posts.push({
287
+ postId: otherPost.id,
288
+ title: otherPost.title?.rendered || "Untitled",
289
+ url: otherPost.link || `#${otherPost.id}`,
290
+ relevanceScore: similarity,
291
+ isHub: false,
292
+ });
293
+ clustered.add(otherIndex);
294
+ }
295
+ });
296
+ if (cluster.posts.length > 1) {
297
+ clusters.push(cluster);
298
+ clustered.add(index);
299
+ }
300
+ });
301
+ return clusters;
302
+ }
303
+ /**
304
+ * Identify hub post for a cluster
305
+ */
306
+ identifyHubPost(cluster) {
307
+ // Find the post with highest average relevance to other posts in cluster
308
+ let bestHubScore = 0;
309
+ let hubPostId = cluster.posts[0].postId;
310
+ cluster.posts.forEach((post) => {
311
+ const avgRelevance = cluster.posts.filter((p) => p.postId !== post.postId).reduce((sum, p) => sum + p.relevanceScore, 0) /
312
+ (cluster.posts.length - 1);
313
+ if (avgRelevance > bestHubScore) {
314
+ bestHubScore = avgRelevance;
315
+ hubPostId = post.postId;
316
+ }
317
+ });
318
+ // Mark hub post
319
+ cluster.posts.forEach((post) => {
320
+ post.isHub = post.postId === hubPostId;
321
+ });
322
+ cluster.hubPost = hubPostId;
323
+ cluster.coherenceScore = bestHubScore;
324
+ return cluster;
325
+ }
326
+ // Utility methods for content analysis
327
+ /**
328
+ * Extract plain text from HTML content
329
+ */
330
+ extractTextContent(html) {
331
+ return html
332
+ .replace(/<[^>]*>/g, " ")
333
+ .replace(/\s+/g, " ")
334
+ .trim();
335
+ }
336
+ /**
337
+ * Count words in text
338
+ */
339
+ countWords(text) {
340
+ return text.split(/\s+/).filter((word) => word.length > 0).length;
341
+ }
342
+ /**
343
+ * Extract keywords with TF-IDF scoring
344
+ */
345
+ extractKeywords(text) {
346
+ const words = text
347
+ .toLowerCase()
348
+ .replace(/[^\w\s]/g, " ")
349
+ .split(/\s+/)
350
+ .filter((word) => word.length > 3)
351
+ .filter((word) => !this.isStopWord(word));
352
+ const wordCounts = new Map();
353
+ words.forEach((word) => {
354
+ wordCounts.set(word, (wordCounts.get(word) || 0) + 1);
355
+ });
356
+ const totalWords = words.length;
357
+ const uniqueWords = wordCounts.size;
358
+ return Array.from(wordCounts.entries())
359
+ .map(([word, count]) => ({
360
+ word,
361
+ frequency: count,
362
+ tfidf: (count / totalWords) * Math.log(uniqueWords / count), // Simplified TF-IDF
363
+ }))
364
+ .sort((a, b) => b.tfidf - a.tfidf)
365
+ .slice(0, 20); // Top 20 keywords
366
+ }
367
+ /**
368
+ * Check if word is a stop word
369
+ */
370
+ isStopWord(word) {
371
+ const stopWords = new Set([
372
+ "the",
373
+ "a",
374
+ "an",
375
+ "and",
376
+ "or",
377
+ "but",
378
+ "in",
379
+ "on",
380
+ "at",
381
+ "to",
382
+ "for",
383
+ "of",
384
+ "with",
385
+ "by",
386
+ "from",
387
+ "up",
388
+ "about",
389
+ "into",
390
+ "through",
391
+ "during",
392
+ "before",
393
+ "after",
394
+ "above",
395
+ "below",
396
+ "between",
397
+ "among",
398
+ "this",
399
+ "that",
400
+ "these",
401
+ "those",
402
+ "i",
403
+ "me",
404
+ "my",
405
+ "myself",
406
+ "we",
407
+ "our",
408
+ "ours",
409
+ "ourselves",
410
+ "you",
411
+ "your",
412
+ "yours",
413
+ "yourself",
414
+ "he",
415
+ "him",
416
+ "his",
417
+ "himself",
418
+ "she",
419
+ "her",
420
+ "hers",
421
+ "herself",
422
+ "it",
423
+ "its",
424
+ "itself",
425
+ "they",
426
+ "them",
427
+ "their",
428
+ "theirs",
429
+ "themselves",
430
+ "what",
431
+ "which",
432
+ "who",
433
+ "whom",
434
+ "whose",
435
+ "this",
436
+ "that",
437
+ "these",
438
+ "those",
439
+ "am",
440
+ "is",
441
+ "are",
442
+ "was",
443
+ "were",
444
+ "be",
445
+ "been",
446
+ "being",
447
+ "have",
448
+ "has",
449
+ "had",
450
+ "having",
451
+ "do",
452
+ "does",
453
+ "did",
454
+ "doing",
455
+ "will",
456
+ "would",
457
+ "should",
458
+ "could",
459
+ "can",
460
+ "may",
461
+ "might",
462
+ "must",
463
+ "shall",
464
+ ]);
465
+ return stopWords.has(word.toLowerCase());
466
+ }
467
+ /**
468
+ * Extract main topics from content
469
+ */
470
+ extractTopics(text, keywords) {
471
+ // Simple topic extraction based on high-frequency keywords
472
+ return keywords
473
+ .slice(0, 5)
474
+ .map((kw) => kw.word)
475
+ .filter((word) => word.length > 4); // Filter for meaningful topic words
476
+ }
477
+ /**
478
+ * Categorize content based on keywords and patterns
479
+ */
480
+ categorizeContent(text, keywords) {
481
+ const keywordSet = new Set(keywords.map((kw) => kw.word));
482
+ // Define category keywords
483
+ const categories = {
484
+ tutorial: ["tutorial", "guide", "how", "step", "learn", "beginners", "basics"],
485
+ review: ["review", "comparison", "versus", "pros", "cons", "rating", "opinion"],
486
+ news: ["news", "update", "announcement", "breaking", "latest", "recent"],
487
+ technical: ["code", "programming", "development", "api", "technical", "implementation"],
488
+ business: ["business", "marketing", "strategy", "growth", "revenue", "profit"],
489
+ general: [],
490
+ };
491
+ let bestMatch = "general";
492
+ let bestScore = 0;
493
+ Object.entries(categories).forEach(([category, categoryKeywords]) => {
494
+ const matches = categoryKeywords.filter((kw) => keywordSet.has(kw)).length;
495
+ if (matches > bestScore) {
496
+ bestScore = matches;
497
+ bestMatch = category;
498
+ }
499
+ });
500
+ return bestMatch;
501
+ }
502
+ /**
503
+ * Generate semantic fingerprint for similarity comparison
504
+ */
505
+ generateSemanticFingerprint(text, keywords) {
506
+ // Simple semantic fingerprint based on keyword TF-IDF scores
507
+ const fingerprint = new Array(50).fill(0);
508
+ keywords.slice(0, 50).forEach((keyword, index) => {
509
+ fingerprint[index] = keyword.tfidf;
510
+ });
511
+ return fingerprint;
512
+ }
513
+ /**
514
+ * Calculate reading level (simplified Flesch-Kincaid)
515
+ */
516
+ calculateReadingLevel(text) {
517
+ const sentences = text.split(/[.!?]+/).filter((s) => s.trim().length > 0);
518
+ const words = text.split(/\s+/).filter((w) => w.length > 0);
519
+ const syllables = words.reduce((count, word) => count + this.countSyllables(word), 0);
520
+ if (sentences.length === 0 || words.length === 0)
521
+ return 0;
522
+ const avgWordsPerSentence = words.length / sentences.length;
523
+ const avgSyllablesPerWord = syllables / words.length;
524
+ return 206.835 - 1.015 * avgWordsPerSentence - 84.6 * avgSyllablesPerWord;
525
+ }
526
+ /**
527
+ * Count syllables in a word (simplified)
528
+ */
529
+ countSyllables(word) {
530
+ word = word.toLowerCase();
531
+ if (word.length <= 3)
532
+ return 1;
533
+ const vowels = word.match(/[aeiouy]+/g);
534
+ let syllableCount = vowels ? vowels.length : 1;
535
+ // Adjust for silent e
536
+ if (word.endsWith("e")) {
537
+ syllableCount--;
538
+ }
539
+ return Math.max(syllableCount, 1);
540
+ }
541
+ /**
542
+ * Calculate keyword similarity between two posts
543
+ */
544
+ calculateKeywordSimilarity(keywords1, keywords2) {
545
+ const set1 = new Set(keywords1.map((kw) => kw.word));
546
+ const set2 = new Set(keywords2.map((kw) => kw.word));
547
+ const intersection = new Set([...set1].filter((word) => set2.has(word)));
548
+ const union = new Set([...set1, ...set2]);
549
+ return union.size > 0 ? (intersection.size / union.size) * 100 : 0;
550
+ }
551
+ /**
552
+ * Calculate topic similarity between two posts
553
+ */
554
+ calculateTopicSimilarity(topics1, topics2) {
555
+ const set1 = new Set(topics1);
556
+ const set2 = new Set(topics2);
557
+ const intersection = new Set([...set1].filter((topic) => set2.has(topic)));
558
+ const union = new Set([...set1, ...set2]);
559
+ return union.size > 0 ? (intersection.size / union.size) * 100 : 0;
560
+ }
561
+ /**
562
+ * Calculate semantic similarity using fingerprints
563
+ */
564
+ calculateSemanticSimilarity(fingerprint1, fingerprint2) {
565
+ // Cosine similarity
566
+ let dotProduct = 0;
567
+ let norm1 = 0;
568
+ let norm2 = 0;
569
+ for (let i = 0; i < Math.min(fingerprint1.length, fingerprint2.length); i++) {
570
+ dotProduct += fingerprint1[i] * fingerprint2[i];
571
+ norm1 += fingerprint1[i] * fingerprint1[i];
572
+ norm2 += fingerprint2[i] * fingerprint2[i];
573
+ }
574
+ const magnitude = Math.sqrt(norm1) * Math.sqrt(norm2);
575
+ return magnitude > 0 ? (dotProduct / magnitude) * 100 : 0;
576
+ }
577
+ /**
578
+ * Generate suggested anchor text for a link
579
+ */
580
+ generateAnchorText(targetPost, sourceKeywords) {
581
+ const title = targetPost.title?.rendered || "Untitled";
582
+ // Try to find relevant keywords from source in target title
583
+ const relevantKeywords = sourceKeywords
584
+ .filter((kw) => title.toLowerCase().includes(kw.word.toLowerCase()))
585
+ .slice(0, 3);
586
+ if (relevantKeywords.length > 0) {
587
+ // Use the most relevant keyword phrase from title
588
+ const keyword = relevantKeywords[0].word;
589
+ const titleWords = title.toLowerCase().split(/\s+/);
590
+ const keywordIndex = titleWords.findIndex((word) => word.includes(keyword.toLowerCase()));
591
+ if (keywordIndex >= 0) {
592
+ // Return 2-3 words around the keyword
593
+ const start = Math.max(0, keywordIndex - 1);
594
+ const end = Math.min(titleWords.length, keywordIndex + 2);
595
+ return titleWords.slice(start, end).join(" ");
596
+ }
597
+ }
598
+ // Fallback to title (truncated if necessary)
599
+ return title.length > 50 ? title.substring(0, 47) + "..." : title;
600
+ }
601
+ /**
602
+ * Generate human-readable reason for suggestion
603
+ */
604
+ generateSuggestionReason(keywordSim, topicSim, semanticSim, categorySim) {
605
+ if (keywordSim > 50) {
606
+ return "Strong keyword overlap suggests high relevance";
607
+ }
608
+ else if (topicSim > 60) {
609
+ return "Similar topics make this a good contextual link";
610
+ }
611
+ else if (semanticSim > 40) {
612
+ return "Semantic content analysis indicates relevance";
613
+ }
614
+ else if (categorySim > 0) {
615
+ return "Same content category suggests related information";
616
+ }
617
+ else {
618
+ return "General content relevance detected";
619
+ }
620
+ }
621
+ /**
622
+ * Find optimal placements for internal links within content
623
+ */
624
+ findOptimalPlacements(content, anchorText, targetTitle) {
625
+ const placements = [];
626
+ // Extract paragraphs
627
+ const paragraphs = content
628
+ .split(/<\/p>/i)
629
+ .map((p) => this.extractTextContent(p).trim())
630
+ .filter((p) => p.length > 50);
631
+ paragraphs.forEach((paragraph, pIndex) => {
632
+ // Look for relevant keywords or phrases
633
+ const lowerParagraph = paragraph.toLowerCase();
634
+ const lowerAnchor = anchorText.toLowerCase();
635
+ const lowerTitle = targetTitle.toLowerCase();
636
+ // Check for exact anchor text match
637
+ let position = lowerParagraph.indexOf(lowerAnchor);
638
+ if (position >= 0) {
639
+ placements.push({
640
+ paragraphIndex: pIndex,
641
+ characterPosition: position,
642
+ contextBefore: paragraph.substring(Math.max(0, position - 30), position),
643
+ contextAfter: paragraph.substring(position + anchorText.length, position + anchorText.length + 30),
644
+ suggestedAnchor: anchorText,
645
+ placementScore: 90,
646
+ });
647
+ return;
648
+ }
649
+ // Look for title keywords
650
+ const titleWords = lowerTitle.split(/\s+/);
651
+ titleWords.forEach((word) => {
652
+ if (word.length > 4) {
653
+ position = lowerParagraph.indexOf(word);
654
+ if (position >= 0 && placements.length < 3) {
655
+ placements.push({
656
+ paragraphIndex: pIndex,
657
+ characterPosition: position,
658
+ contextBefore: paragraph.substring(Math.max(0, position - 30), position),
659
+ contextAfter: paragraph.substring(position + word.length, position + word.length + 30),
660
+ suggestedAnchor: word,
661
+ placementScore: 70,
662
+ });
663
+ }
664
+ }
665
+ });
666
+ });
667
+ return placements.sort((a, b) => b.placementScore - a.placementScore).slice(0, 3);
668
+ }
669
+ /**
670
+ * Get current configuration
671
+ */
672
+ getConfig() {
673
+ return { ...this.config };
674
+ }
675
+ /**
676
+ * Update configuration
677
+ */
678
+ updateConfig(config) {
679
+ this.config = { ...this.config, ...config };
680
+ this.logger.debug("Configuration updated", { config: this.config });
681
+ }
682
+ }
683
+ //# sourceMappingURL=InternalLinkingSuggester.js.map