@canonry/canonry 4.162.0 → 4.163.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.
- package/assets/assets/{AuditHistoryPanel-BTtqk1mK.js → AuditHistoryPanel-FDQx9TS5.js} +1 -1
- package/assets/assets/{BacklinksPage-wqRYkJFG.js → BacklinksPage-DVvsQsdk.js} +1 -1
- package/assets/assets/{HistoryPage-Dj1FU5WV.js → HistoryPage-31HR6TIJ.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-CPCYzznG.js → MeasurementPropertyPage-Dps7MF7r.js} +1 -1
- package/assets/assets/{ProjectPage-7vg1El9D.js → ProjectPage-BijTUrpk.js} +6 -6
- package/assets/assets/{RunRow-CTgtgq5x.js → RunRow-9DiFrBlc.js} +1 -1
- package/assets/assets/{RunsPage-qS7etC5f.js → RunsPage-DQlAc4J9.js} +1 -1
- package/assets/assets/{SettingsPage-BlUWPiA7.js → SettingsPage-c1nxJsbL.js} +1 -1
- package/assets/assets/SiteHealthSection-0B0oUUQx.js +4 -0
- package/assets/assets/{TrafficPage-BSWUsXM1.js → TrafficPage-HzwQyLzl.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DOTtU4SK.js → TrafficSourceDetailPage-BTVAQjRd.js} +1 -1
- package/assets/assets/{extract-error-message-DOWCsWL0.js → extract-error-message-B5bhJRmf.js} +1 -1
- package/assets/assets/{index-BYGqta_M.js → index-CQsvCUM0.js} +19 -19
- package/assets/assets/{react-sigma_core.esm.min-C4kgfZfm.js → react-sigma_core.esm.min-6BxcniCA.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-NG7OR6GO.js → chunk-ESH3LPYQ.js} +120 -7
- package/dist/{chunk-C6ZZH3XI.js → chunk-G5UZTODF.js} +108 -32
- package/dist/{chunk-2TLRE2L3.js → chunk-INXI6PO7.js} +3 -3
- package/dist/{chunk-FZAAGXTK.js → chunk-MRHYSEZB.js} +80 -30
- package/dist/cli.js +4 -4
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-ANRFEQWX.js → intelligence-service-AFAFGLCM.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +12 -12
- package/assets/assets/SiteHealthSection-tUFchSy-.js +0 -4
package/assets/assets/{react-sigma_core.esm.min-C4kgfZfm.js → react-sigma_core.esm.min-6BxcniCA.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{g as Bt,t as S,r as C}from"./vendor-tanstack-Dock0eH1.js";import{e as Ht,G as Wt}from"./SiteHealthSection-
|
|
1
|
+
import{g as Bt,t as S,r as C}from"./vendor-tanstack-Dock0eH1.js";import{e as Ht,G as Wt}from"./SiteHealthSection-0B0oUUQx.js";import"./index-CQsvCUM0.js";import"./vendor-radix-4UOaVWza.js";import"./vendor-recharts-D8rt5lKn.js";import"./vendor-lucide-nYKv-BB7.js";import"./vendor-markdown-Bn7fRwrx.js";import"./vendor-yaml-GWJz5H2G.js";import"./ChartPrimitives-BoYe2M-i.js";function Vt(r,n){if(typeof r!="object"||!r)return r;var i=r[Symbol.toPrimitive];if(i!==void 0){var e=i.call(r,n);if(typeof e!="object")return e;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(r)}function ae(r){var n=Vt(r,"string");return typeof n=="symbol"?n:n+""}function A(r,n){if(!(r instanceof n))throw new TypeError("Cannot call a class as a function")}function He(r,n){for(var i=0;i<n.length;i++){var e=n[i];e.enumerable=e.enumerable||!1,e.configurable=!0,"value"in e&&(e.writable=!0),Object.defineProperty(r,ae(e.key),e)}}function L(r,n,i){return n&&He(r.prototype,n),i&&He(r,i),Object.defineProperty(r,"prototype",{writable:!1}),r}function Q(r){return Q=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},Q(r)}function yt(){try{var r=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch{}return(yt=function(){return!!r})()}function jt(r){if(r===void 0)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return r}function Zt(r,n){if(n&&(typeof n=="object"||typeof n=="function"))return n;if(n!==void 0)throw new TypeError("Derived constructors may only return object or undefined");return jt(r)}function I(r,n,i){return n=Q(n),Zt(r,yt()?Reflect.construct(n,i||[],Q(r).constructor):n.apply(r,i))}function xe(r,n){return xe=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(i,e){return i.__proto__=e,i},xe(r,n)}function G(r,n){if(typeof n!="function"&&n!==null)throw new TypeError("Super expression must either be null or a function");r.prototype=Object.create(n&&n.prototype,{constructor:{value:r,writable:!0,configurable:!0}}),Object.defineProperty(r,"prototype",{writable:!1}),n&&xe(r,n)}function qt(r){if(Array.isArray(r))return r}function Yt(r,n){var i=r==null?null:typeof Symbol<"u"&&r[Symbol.iterator]||r["@@iterator"];if(i!=null){var e,t,a,o,s=[],l=!0,h=!1;try{if(a=(i=i.call(r)).next,n===0){if(Object(i)!==i)return;l=!1}else for(;!(l=(e=a.call(i)).done)&&(s.push(e.value),s.length!==n);l=!0);}catch(u){h=!0,t=u}finally{try{if(!l&&i.return!=null&&(o=i.return(),Object(o)!==o))return}finally{if(h)throw t}}return s}}function Se(r,n){(n==null||n>r.length)&&(n=r.length);for(var i=0,e=Array(n);i<n;i++)e[i]=r[i];return e}function Tt(r,n){if(r){if(typeof r=="string")return Se(r,n);var i={}.toString.call(r).slice(8,-1);return i==="Object"&&r.constructor&&(i=r.constructor.name),i==="Map"||i==="Set"?Array.from(r):i==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?Se(r,n):void 0}}function Xt(){throw new TypeError(`Invalid attempt to destructure non-iterable instance.
|
|
2
2
|
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function ee(r,n){return qt(r)||Yt(r,n)||Tt(r,n)||Xt()}var pe={black:"#000000",silver:"#C0C0C0",gray:"#808080",grey:"#808080",white:"#FFFFFF",maroon:"#800000",red:"#FF0000",purple:"#800080",fuchsia:"#FF00FF",green:"#008000",lime:"#00FF00",olive:"#808000",yellow:"#FFFF00",navy:"#000080",blue:"#0000FF",teal:"#008080",aqua:"#00FFFF",darkblue:"#00008B",mediumblue:"#0000CD",darkgreen:"#006400",darkcyan:"#008B8B",deepskyblue:"#00BFFF",darkturquoise:"#00CED1",mediumspringgreen:"#00FA9A",springgreen:"#00FF7F",cyan:"#00FFFF",midnightblue:"#191970",dodgerblue:"#1E90FF",lightseagreen:"#20B2AA",forestgreen:"#228B22",seagreen:"#2E8B57",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",limegreen:"#32CD32",mediumseagreen:"#3CB371",turquoise:"#40E0D0",royalblue:"#4169E1",steelblue:"#4682B4",darkslateblue:"#483D8B",mediumturquoise:"#48D1CC",indigo:"#4B0082",darkolivegreen:"#556B2F",cadetblue:"#5F9EA0",cornflowerblue:"#6495ED",rebeccapurple:"#663399",mediumaquamarine:"#66CDAA",dimgray:"#696969",dimgrey:"#696969",slateblue:"#6A5ACD",olivedrab:"#6B8E23",slategray:"#708090",slategrey:"#708090",lightslategray:"#778899",lightslategrey:"#778899",mediumslateblue:"#7B68EE",lawngreen:"#7CFC00",chartreuse:"#7FFF00",aquamarine:"#7FFFD4",skyblue:"#87CEEB",lightskyblue:"#87CEFA",blueviolet:"#8A2BE2",darkred:"#8B0000",darkmagenta:"#8B008B",saddlebrown:"#8B4513",darkseagreen:"#8FBC8F",lightgreen:"#90EE90",mediumpurple:"#9370DB",darkviolet:"#9400D3",palegreen:"#98FB98",darkorchid:"#9932CC",yellowgreen:"#9ACD32",sienna:"#A0522D",brown:"#A52A2A",darkgray:"#A9A9A9",darkgrey:"#A9A9A9",lightblue:"#ADD8E6",greenyellow:"#ADFF2F",paleturquoise:"#AFEEEE",lightsteelblue:"#B0C4DE",powderblue:"#B0E0E6",firebrick:"#B22222",darkgoldenrod:"#B8860B",mediumorchid:"#BA55D3",rosybrown:"#BC8F8F",darkkhaki:"#BDB76B",mediumvioletred:"#C71585",indianred:"#CD5C5C",peru:"#CD853F",chocolate:"#D2691E",tan:"#D2B48C",lightgray:"#D3D3D3",lightgrey:"#D3D3D3",thistle:"#D8BFD8",orchid:"#DA70D6",goldenrod:"#DAA520",palevioletred:"#DB7093",crimson:"#DC143C",gainsboro:"#DCDCDC",plum:"#DDA0DD",burlywood:"#DEB887",lightcyan:"#E0FFFF",lavender:"#E6E6FA",darksalmon:"#E9967A",violet:"#EE82EE",palegoldenrod:"#EEE8AA",lightcoral:"#F08080",khaki:"#F0E68C",aliceblue:"#F0F8FF",honeydew:"#F0FFF0",azure:"#F0FFFF",sandybrown:"#F4A460",wheat:"#F5DEB3",beige:"#F5F5DC",whitesmoke:"#F5F5F5",mintcream:"#F5FFFA",ghostwhite:"#F8F8FF",salmon:"#FA8072",antiquewhite:"#FAEBD7",linen:"#FAF0E6",lightgoldenrodyellow:"#FAFAD2",oldlace:"#FDF5E6",magenta:"#FF00FF",deeppink:"#FF1493",orangered:"#FF4500",tomato:"#FF6347",hotpink:"#FF69B4",coral:"#FF7F50",darkorange:"#FF8C00",lightsalmon:"#FFA07A",orange:"#FFA500",lightpink:"#FFB6C1",pink:"#FFC0CB",gold:"#FFD700",peachpuff:"#FFDAB9",navajowhite:"#FFDEAD",moccasin:"#FFE4B5",bisque:"#FFE4C4",mistyrose:"#FFE4E1",blanchedalmond:"#FFEBCD",papayawhip:"#FFEFD5",lavenderblush:"#FFF0F5",seashell:"#FFF5EE",cornsilk:"#FFF8DC",lemonchiffon:"#FFFACD",floralwhite:"#FFFAF0",snow:"#FFFAFA",lightyellow:"#FFFFE0",ivory:"#FFFFF0"},Ct=new Int8Array(4),be=new Int32Array(Ct.buffer,0,1),$t=new Float32Array(Ct.buffer,0,1),Kt=/^\s*rgba?\s*\(/,Jt=/^\s*rgba?\s*\(\s*([0-9]*)\s*,\s*([0-9]*)\s*,\s*([0-9]*)(?:\s*,\s*(.*)?)?\)\s*$/;function Qt(r){var n=0,i=0,e=0,t=1;if(r[0]==="#")r.length===4?(n=parseInt(r.charAt(1)+r.charAt(1),16),i=parseInt(r.charAt(2)+r.charAt(2),16),e=parseInt(r.charAt(3)+r.charAt(3),16)):(n=parseInt(r.charAt(1)+r.charAt(2),16),i=parseInt(r.charAt(3)+r.charAt(4),16),e=parseInt(r.charAt(5)+r.charAt(6),16)),r.length===9&&(t=parseInt(r.charAt(7)+r.charAt(8),16)/255);else if(Kt.test(r)){var a=r.match(Jt);a&&(n=+a[1],i=+a[2],e=+a[3],a[4]&&(t=+a[4]))}return{r:n,g:i,b:e,a:t}}var J={};for(var le in pe)J[le]=oe(pe[le]),J[pe[le]]=J[le];function Rt(r,n,i,e,t){return be[0]=e<<24|i<<16|n<<8|r,be[0]=be[0]&4278190079,$t[0]}function oe(r){if(r=r.toLowerCase(),typeof J[r]<"u")return J[r];var n=Qt(r),i=n.r,e=n.g,t=n.b,a=n.a;a=a*255|0;var o=Rt(i,e,t,a);return J[r]=o,o}var Ee={};function _t(r){if(typeof Ee[r]<"u")return Ee[r];var n=(r&16711680)>>>16,i=(r&65280)>>>8,e=r&255,t=255,a=Rt(n,i,e,t);return Ee[r]=a,a}function We(r,n,i,e){return i+(n<<8)+(r<<16)}function Ve(r,n,i,e,t,a){var o=Math.floor(i/a*t),s=Math.floor(r.drawingBufferHeight/a-e/a*t),l=new Uint8Array(4);r.bindFramebuffer(r.FRAMEBUFFER,n),r.readPixels(o,s,1,1,r.RGBA,r.UNSIGNED_BYTE,l);var h=ee(l,4),u=h[0],d=h[1],f=h[2],g=h[3];return[u,d,f,g]}function c(r,n,i){return(n=ae(n))in r?Object.defineProperty(r,n,{value:i,enumerable:!0,configurable:!0,writable:!0}):r[n]=i,r}function je(r,n){var i=Object.keys(r);if(Object.getOwnPropertySymbols){var e=Object.getOwnPropertySymbols(r);n&&(e=e.filter(function(t){return Object.getOwnPropertyDescriptor(r,t).enumerable})),i.push.apply(i,e)}return i}function b(r){for(var n=1;n<arguments.length;n++){var i=arguments[n]!=null?arguments[n]:{};n%2?je(Object(i),!0).forEach(function(e){c(r,e,i[e])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(i)):je(Object(i)).forEach(function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(i,e))})}return r}function ei(r,n){for(;!{}.hasOwnProperty.call(r,n)&&(r=Q(r))!==null;);return r}function Ae(){return Ae=typeof Reflect<"u"&&Reflect.get?Reflect.get.bind():function(r,n,i){var e=ei(r,n);if(e){var t=Object.getOwnPropertyDescriptor(e,n);return t.get?t.get.call(arguments.length<3?r:i):t.value}},Ae.apply(null,arguments)}function wt(r,n,i,e){var t=Ae(Q(r.prototype),n,i);return typeof t=="function"?function(a){return t.apply(i,a)}:t}function ti(r){return r.normalized?1:r.size}function ye(r){var n=0;return r.forEach(function(i){return n+=ti(i)}),n}function xt(r,n,i){var e=r==="VERTEX"?n.VERTEX_SHADER:n.FRAGMENT_SHADER,t=n.createShader(e);if(t===null)throw new Error("loadShader: error while creating the shader");n.shaderSource(t,i),n.compileShader(t);var a=n.getShaderParameter(t,n.COMPILE_STATUS);if(!a){var o=n.getShaderInfoLog(t);throw n.deleteShader(t),new Error(`loadShader: error while compiling the shader:
|
|
3
3
|
`.concat(o,`
|
|
4
4
|
`).concat(i))}return t}function ii(r,n){return xt("VERTEX",r,n)}function ri(r,n){return xt("FRAGMENT",r,n)}function ni(r,n){var i=r.createProgram();if(i===null)throw new Error("loadProgram: error while creating the program.");var e,t;for(e=0,t=n.length;e<t;e++)r.attachShader(i,n[e]);r.linkProgram(i);var a=r.getProgramParameter(i,r.LINK_STATUS);if(!a){var o=r.getProgramInfoLog(i);throw r.deleteProgram(i),new Error("loadProgram: error while linking the program: ".concat(o))}return i}function Ze(r){var n=r.gl,i=r.buffer,e=r.program,t=r.vertexShader,a=r.fragmentShader;n.deleteShader(t),n.deleteShader(a),n.deleteProgram(e),n.deleteBuffer(i)}var qe=`#define PICKING_MODE
|
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-CQsvCUM0.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Dock0eH1.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/vendor-radix-4UOaVWza.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="./assets/vendor-recharts-D8rt5lKn.js">
|
|
@@ -8820,17 +8820,56 @@ function deriveSiteHealthState(input) {
|
|
|
8820
8820
|
}
|
|
8821
8821
|
var TEMPLATE_LINK_RATIO_THRESHOLD = 0.7;
|
|
8822
8822
|
var TEMPLATE_LINK_MIN_FETCHED_PAGES = 15;
|
|
8823
|
+
var siteCrawlLinkPlacementSchema = z25.enum(["navigation", "content", "unknown"]);
|
|
8824
|
+
var SiteCrawlLinkPlacements = siteCrawlLinkPlacementSchema.enum;
|
|
8825
|
+
var siteCrawlPlacementOccurrencesSchema = z25.object({
|
|
8826
|
+
navigation: z25.number().int().nonnegative(),
|
|
8827
|
+
content: z25.number().int().nonnegative(),
|
|
8828
|
+
unknown: z25.number().int().nonnegative()
|
|
8829
|
+
});
|
|
8830
|
+
var siteHealthLinkClassificationSourceSchema = z25.enum([
|
|
8831
|
+
"placement",
|
|
8832
|
+
"ubiquity",
|
|
8833
|
+
"unmeasured"
|
|
8834
|
+
]);
|
|
8835
|
+
var SiteHealthLinkClassificationSources = siteHealthLinkClassificationSourceSchema.enum;
|
|
8823
8836
|
var siteHealthTemplateDetectionSchema = z25.enum([
|
|
8824
8837
|
"applied",
|
|
8838
|
+
"applied-placement",
|
|
8839
|
+
"applied-placement-with-ubiquity",
|
|
8840
|
+
"applied-placement-partial",
|
|
8825
8841
|
"unavailable-too-few-pages",
|
|
8826
8842
|
"unavailable-legacy-scan"
|
|
8827
8843
|
]);
|
|
8828
8844
|
var SiteHealthTemplateDetections = siteHealthTemplateDetectionSchema.enum;
|
|
8845
|
+
function isPlacementTemplateDetection(detection) {
|
|
8846
|
+
switch (detection) {
|
|
8847
|
+
case "applied-placement":
|
|
8848
|
+
case "applied-placement-with-ubiquity":
|
|
8849
|
+
case "applied-placement-partial":
|
|
8850
|
+
return true;
|
|
8851
|
+
case "applied":
|
|
8852
|
+
case "unavailable-too-few-pages":
|
|
8853
|
+
case "unavailable-legacy-scan":
|
|
8854
|
+
return false;
|
|
8855
|
+
}
|
|
8856
|
+
}
|
|
8829
8857
|
var siteHealthLinkKindSchema = z25.enum(["all", "content", "template"]);
|
|
8830
8858
|
var SiteHealthLinkKinds = siteHealthLinkKindSchema.enum;
|
|
8831
8859
|
function normalizeTemplateAnchorText(anchor) {
|
|
8832
8860
|
return anchor.trim().replace(/\s+/gu, " ").toLowerCase();
|
|
8833
8861
|
}
|
|
8862
|
+
var siteCrawlEdgeRelationSchema = z25.enum(["anchor", "redirect", "canonical", "link"]);
|
|
8863
|
+
var SiteCrawlEdgeRelations = siteCrawlEdgeRelationSchema.enum;
|
|
8864
|
+
function isAnchorLinkRelation(relation) {
|
|
8865
|
+
return relation === SiteCrawlEdgeRelations.anchor;
|
|
8866
|
+
}
|
|
8867
|
+
function placementLinkDecision(occurrences) {
|
|
8868
|
+
if (!occurrences) return null;
|
|
8869
|
+
if (occurrences.content > 0) return SiteCrawlLinkPlacements.content;
|
|
8870
|
+
if (occurrences.navigation > 0) return SiteCrawlLinkPlacements.navigation;
|
|
8871
|
+
return null;
|
|
8872
|
+
}
|
|
8834
8873
|
function createTemplateLinkPairIndex() {
|
|
8835
8874
|
return { sourcePagesByPair: /* @__PURE__ */ new Map() };
|
|
8836
8875
|
}
|
|
@@ -8865,8 +8904,63 @@ function templateLinkRatio(index, pagesFetched, edge) {
|
|
|
8865
8904
|
function isTemplateLinkRatio(ratio) {
|
|
8866
8905
|
return ratio != null && ratio >= TEMPLATE_LINK_RATIO_THRESHOLD;
|
|
8867
8906
|
}
|
|
8868
|
-
function
|
|
8869
|
-
return Number.isFinite(pagesFetched) && pagesFetched >= TEMPLATE_LINK_MIN_FETCHED_PAGES
|
|
8907
|
+
function templateLinkUbiquityAvailable(pagesFetched) {
|
|
8908
|
+
return Number.isFinite(pagesFetched) && pagesFetched >= TEMPLATE_LINK_MIN_FETCHED_PAGES;
|
|
8909
|
+
}
|
|
8910
|
+
function templateLinkPlacementAvailable(rulesetVersion) {
|
|
8911
|
+
return typeof rulesetVersion === "string" && rulesetVersion.trim().length > 0;
|
|
8912
|
+
}
|
|
8913
|
+
function createTemplateLinkDetectionTally() {
|
|
8914
|
+
return { usedUbiquityFallback: false, leftUnmeasuredAnchor: false };
|
|
8915
|
+
}
|
|
8916
|
+
function observeTemplateLinkDetection(tally, edge, classification) {
|
|
8917
|
+
if (classification.source === SiteHealthLinkClassificationSources.ubiquity) {
|
|
8918
|
+
tally.usedUbiquityFallback = true;
|
|
8919
|
+
}
|
|
8920
|
+
if (classification.source === SiteHealthLinkClassificationSources.unmeasured && isAnchorLinkRelation(edge.relation)) {
|
|
8921
|
+
tally.leftUnmeasuredAnchor = true;
|
|
8922
|
+
}
|
|
8923
|
+
}
|
|
8924
|
+
function templateLinkDetection(input) {
|
|
8925
|
+
if (!input.placementAvailable) {
|
|
8926
|
+
return input.ubiquityAvailable ? SiteHealthTemplateDetections.applied : SiteHealthTemplateDetections["unavailable-too-few-pages"];
|
|
8927
|
+
}
|
|
8928
|
+
if (input.tally.usedUbiquityFallback) return SiteHealthTemplateDetections["applied-placement-with-ubiquity"];
|
|
8929
|
+
if (input.tally.leftUnmeasuredAnchor) return SiteHealthTemplateDetections["applied-placement-partial"];
|
|
8930
|
+
return SiteHealthTemplateDetections["applied-placement"];
|
|
8931
|
+
}
|
|
8932
|
+
function classifyTemplateLinkEdge(edge, context) {
|
|
8933
|
+
const placement = context.placementAvailable ? placementLinkDecision(edge.placementOccurrences) : null;
|
|
8934
|
+
if (placement !== null) {
|
|
8935
|
+
return {
|
|
8936
|
+
edgeKey: edge.edgeKey,
|
|
8937
|
+
isTemplate: placement === SiteCrawlLinkPlacements.navigation,
|
|
8938
|
+
templateRatio: null,
|
|
8939
|
+
source: SiteHealthLinkClassificationSources.placement
|
|
8940
|
+
};
|
|
8941
|
+
}
|
|
8942
|
+
const ratio = context.ubiquityAvailable ? templateLinkRatio(context.index, context.pagesFetched, edge) : null;
|
|
8943
|
+
if (ratio == null) {
|
|
8944
|
+
return {
|
|
8945
|
+
edgeKey: edge.edgeKey,
|
|
8946
|
+
isTemplate: false,
|
|
8947
|
+
templateRatio: null,
|
|
8948
|
+
source: SiteHealthLinkClassificationSources.unmeasured
|
|
8949
|
+
};
|
|
8950
|
+
}
|
|
8951
|
+
return {
|
|
8952
|
+
edgeKey: edge.edgeKey,
|
|
8953
|
+
isTemplate: isTemplateLinkRatio(ratio),
|
|
8954
|
+
templateRatio: ratio,
|
|
8955
|
+
source: SiteHealthLinkClassificationSources.ubiquity
|
|
8956
|
+
};
|
|
8957
|
+
}
|
|
8958
|
+
function templateLinkSource(detection, edge) {
|
|
8959
|
+
if (edge.isTemplate == null) return SiteHealthLinkClassificationSources.unmeasured;
|
|
8960
|
+
if (isPlacementTemplateDetection(detection) && placementLinkDecision(edge.placementOccurrences) != null) {
|
|
8961
|
+
return SiteHealthLinkClassificationSources.placement;
|
|
8962
|
+
}
|
|
8963
|
+
return edge.templateRatio != null ? SiteHealthLinkClassificationSources.ubiquity : SiteHealthLinkClassificationSources.unmeasured;
|
|
8870
8964
|
}
|
|
8871
8965
|
var siteCrawlPageSchema = z25.object({
|
|
8872
8966
|
nodeKey: z25.string(),
|
|
@@ -8987,17 +9081,29 @@ var siteCrawlEdgeSchema = z25.object({
|
|
|
8987
9081
|
nofollowOccurrences: z25.number().int().nonnegative(),
|
|
8988
9082
|
anchors: z25.array(z25.string()).default([]),
|
|
8989
9083
|
/**
|
|
8990
|
-
* True when this link
|
|
8991
|
-
*
|
|
8992
|
-
*
|
|
9084
|
+
* True when this link is nav, header, or footer chrome. Null means this link
|
|
9085
|
+
* was never classified, which `templateSource` and the response's
|
|
9086
|
+
* `templateDetection` explain; it never means "not a nav link".
|
|
8993
9087
|
*/
|
|
8994
9088
|
isTemplate: z25.union([z25.boolean(), z25.null()]),
|
|
8995
9089
|
/**
|
|
8996
9090
|
* Share of fetched pages carrying this link's LEAST ubiquitous anchor: the
|
|
8997
9091
|
* most editorial thing anyone says when linking these two pages. At or above
|
|
8998
|
-
* the threshold means every anchor on the link is chrome.
|
|
9092
|
+
* the threshold means every anchor on the link is chrome. Present exactly
|
|
9093
|
+
* when `templateSource` is `ubiquity`, because it is that rule's evidence.
|
|
9094
|
+
*/
|
|
9095
|
+
templateRatio: z25.union([z25.number(), z25.null()]),
|
|
9096
|
+
/**
|
|
9097
|
+
* Which rule decided `isTemplate`. Read this before comparing counts across
|
|
9098
|
+
* scans: `placement` and `ubiquity` do not measure the same thing.
|
|
9099
|
+
*/
|
|
9100
|
+
templateSource: siteHealthLinkClassificationSourceSchema,
|
|
9101
|
+
/**
|
|
9102
|
+
* Where this link's occurrences sat in the page. Null when the scan predates
|
|
9103
|
+
* the crawler's landmark ruleset, which is why the ubiquity fallback still
|
|
9104
|
+
* exists.
|
|
8999
9105
|
*/
|
|
9000
|
-
|
|
9106
|
+
placementOccurrences: z25.union([siteCrawlPlacementOccurrencesSchema, z25.null()])
|
|
9001
9107
|
});
|
|
9002
9108
|
var siteCrawlGraphNodeSchema = z25.object({
|
|
9003
9109
|
nodeKey: z25.string(),
|
|
@@ -15147,6 +15253,7 @@ export {
|
|
|
15147
15253
|
SiteCrawlFetchedStates,
|
|
15148
15254
|
deriveSiteHealthState,
|
|
15149
15255
|
TEMPLATE_LINK_RATIO_THRESHOLD,
|
|
15256
|
+
SiteHealthLinkClassificationSources,
|
|
15150
15257
|
siteHealthTemplateDetectionSchema,
|
|
15151
15258
|
SiteHealthTemplateDetections,
|
|
15152
15259
|
siteHealthLinkKindSchema,
|
|
@@ -15155,7 +15262,13 @@ export {
|
|
|
15155
15262
|
observeTemplateLinkEdges,
|
|
15156
15263
|
templateLinkRatio,
|
|
15157
15264
|
isTemplateLinkRatio,
|
|
15265
|
+
templateLinkUbiquityAvailable,
|
|
15266
|
+
templateLinkPlacementAvailable,
|
|
15267
|
+
createTemplateLinkDetectionTally,
|
|
15268
|
+
observeTemplateLinkDetection,
|
|
15158
15269
|
templateLinkDetection,
|
|
15270
|
+
classifyTemplateLinkEdge,
|
|
15271
|
+
templateLinkSource,
|
|
15159
15272
|
siteCrawlAuditFactorSchema,
|
|
15160
15273
|
siteCrawlCriticalDefectSchema,
|
|
15161
15274
|
siteCrawlPageAuditSchema,
|
|
@@ -539,8 +539,9 @@ import {
|
|
|
539
539
|
surfaceClassFromCompetitorType,
|
|
540
540
|
surfaceClassLabel,
|
|
541
541
|
telemetryEventAcceptedDtoSchema,
|
|
542
|
-
templateLinkDetection,
|
|
543
542
|
templateLinkRatio,
|
|
543
|
+
templateLinkSource,
|
|
544
|
+
templateLinkUbiquityAvailable,
|
|
544
545
|
textContainsAnyBrandAlias,
|
|
545
546
|
textContainsBrandAlias,
|
|
546
547
|
textContainsDomain,
|
|
@@ -580,7 +581,7 @@ import {
|
|
|
580
581
|
wordpressSchemaDeployResultDtoSchema,
|
|
581
582
|
wordpressSchemaStatusResultDtoSchema,
|
|
582
583
|
wordpressStatusDtoSchema
|
|
583
|
-
} from "./chunk-
|
|
584
|
+
} from "./chunk-ESH3LPYQ.js";
|
|
584
585
|
|
|
585
586
|
// src/intelligence-service.ts
|
|
586
587
|
import { eq as eq59, desc as desc26, asc as asc11, and as and47, ne as ne7, or as or13, inArray as inArray21, gte as gte14, lte as lte12 } from "drizzle-orm";
|
|
@@ -1344,6 +1345,14 @@ var siteCrawlSnapshots = sqliteTable("site_crawl_snapshots", {
|
|
|
1344
1345
|
* pass for "this site has no nav".
|
|
1345
1346
|
*/
|
|
1346
1347
|
templateDetection: text("template_detection"),
|
|
1348
|
+
/**
|
|
1349
|
+
* The crawler's landmark ruleset version, from the crawl summary's
|
|
1350
|
+
* `linkPlacementRulesetVersion`. NULL means this scan recorded no placement
|
|
1351
|
+
* at all, which is the one thing that decides whether its links could be
|
|
1352
|
+
* classified by where they sit or only by how often they repeat. It cannot be
|
|
1353
|
+
* backfilled: a pre-4.7.0 crawl never observed placement.
|
|
1354
|
+
*/
|
|
1355
|
+
linkPlacementRulesetVersion: text("link_placement_ruleset_version"),
|
|
1347
1356
|
createdAt: text("created_at").notNull(),
|
|
1348
1357
|
updatedAt: text("updated_at").notNull()
|
|
1349
1358
|
}, (table) => [
|
|
@@ -1437,28 +1446,46 @@ var siteCrawlEdges = sqliteTable("site_crawl_edges", {
|
|
|
1437
1446
|
nofollowOccurrences: integer("nofollow_occurrences").notNull().default(0),
|
|
1438
1447
|
anchors: text("anchors", { mode: "json" }).$type().notNull().default([]),
|
|
1439
1448
|
/**
|
|
1440
|
-
* True
|
|
1441
|
-
*
|
|
1442
|
-
*
|
|
1443
|
-
* publish time by the contract's `classifyTemplateLinks`, so the map, the
|
|
1444
|
-
* API filters, and the agents all read the same decision.
|
|
1449
|
+
* True for a nav, header, or footer link rather than an editorial one.
|
|
1450
|
+
* Computed once at publish time by the contract's `classifyTemplateLinkEdge`,
|
|
1451
|
+
* so the map, the API filters, and the agents all read the same decision.
|
|
1445
1452
|
*
|
|
1446
|
-
*
|
|
1447
|
-
*
|
|
1448
|
-
*
|
|
1453
|
+
* The rule is DOM placement where the crawl recorded it: any occurrence
|
|
1454
|
+
* inside a main or article landmark makes the whole link editorial, and
|
|
1455
|
+
* navigation with no content occurrence makes it chrome. Where the page
|
|
1456
|
+
* declares no landmark that answers the question, `template_ratio` below is
|
|
1457
|
+
* the fallback.
|
|
1449
1458
|
*
|
|
1450
|
-
*
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1459
|
+
* Classification writes a STRICT BOOLEAN and never a NULL. A link no rule
|
|
1460
|
+
* could measure is a real `false` whose `templateSource` reads `unmeasured`,
|
|
1461
|
+
* because "not shown to be chrome" is what a content link means here. A third
|
|
1462
|
+
* stored state would be invisible to every reader that treats this as a
|
|
1463
|
+
* boolean (the layout input, the graph sample, the totals, the map legend,
|
|
1464
|
+
* the inspector tiles) while only the two SQL link filters honoured it, and
|
|
1465
|
+
* one definition of a content link is the whole point of the column.
|
|
1466
|
+
*
|
|
1467
|
+
* NULL therefore means exactly one thing: this row predates classification
|
|
1468
|
+
* entirely, which reads report as `unavailable-legacy-scan`. It never means
|
|
1469
|
+
* "not a template link".
|
|
1454
1470
|
*/
|
|
1455
1471
|
isTemplate: integer("is_template", { mode: "boolean" }),
|
|
1456
1472
|
/**
|
|
1457
|
-
* Share of fetched pages carrying this link's LEAST ubiquitous anchor
|
|
1458
|
-
*
|
|
1459
|
-
*
|
|
1473
|
+
* Share of fetched pages carrying this link's LEAST ubiquitous anchor. NULL
|
|
1474
|
+
* when the link has no measurable pair (an unresolved target or no anchor)
|
|
1475
|
+
* OR when DOM placement decided it, because it is the fallback rule's own
|
|
1476
|
+
* evidence and had no vote.
|
|
1460
1477
|
*/
|
|
1461
1478
|
templateRatio: real("template_ratio"),
|
|
1479
|
+
/**
|
|
1480
|
+
* Occurrences of this link split by where they sat in the source page, from
|
|
1481
|
+
* the crawler's `placementOccurrences`. NULL means this scan recorded no
|
|
1482
|
+
* placement for this link, which is a real state (a pre-4.7.0 crawl) and
|
|
1483
|
+
* never zeros: `{0, 0, 0}` is what a redirect or canonical edge legitimately
|
|
1484
|
+
* carries, since a non-anchor edge has no position in a page.
|
|
1485
|
+
*/
|
|
1486
|
+
placementNavigationOccurrences: integer("placement_navigation_occurrences"),
|
|
1487
|
+
placementContentOccurrences: integer("placement_content_occurrences"),
|
|
1488
|
+
placementUnknownOccurrences: integer("placement_unknown_occurrences"),
|
|
1462
1489
|
createdAt: text("created_at").notNull(),
|
|
1463
1490
|
updatedAt: text("updated_at").notNull()
|
|
1464
1491
|
}, (table) => [
|
|
@@ -3001,7 +3028,7 @@ function* streamSiteCrawlTemplateLinkEdges(tx, attemptId, batchSize = 2e3) {
|
|
|
3001
3028
|
let after = "";
|
|
3002
3029
|
for (; ; ) {
|
|
3003
3030
|
const rows = tx.all(sql2`
|
|
3004
|
-
SELECT edge_key, source_node_key, target_node_key, anchors
|
|
3031
|
+
SELECT edge_key, source_node_key, target_node_key, anchors, relation
|
|
3005
3032
|
FROM site_crawl_edges
|
|
3006
3033
|
WHERE attempt_id = ${attemptId} AND edge_key > ${after}
|
|
3007
3034
|
ORDER BY edge_key
|
|
@@ -3012,7 +3039,8 @@ function* streamSiteCrawlTemplateLinkEdges(tx, attemptId, batchSize = 2e3) {
|
|
|
3012
3039
|
edgeKey: row.edge_key,
|
|
3013
3040
|
sourceNodeKey: row.source_node_key,
|
|
3014
3041
|
targetNodeKey: row.target_node_key,
|
|
3015
|
-
anchors: parseJsonColumn(row.anchors, [])
|
|
3042
|
+
anchors: parseJsonColumn(row.anchors, []),
|
|
3043
|
+
relation: row.relation
|
|
3016
3044
|
}));
|
|
3017
3045
|
after = rows[rows.length - 1].edge_key;
|
|
3018
3046
|
if (rows.length < batchSize) return;
|
|
@@ -3035,7 +3063,8 @@ function backfillSiteCrawlTemplateLinks(tx) {
|
|
|
3035
3063
|
ON s.project_id = a.project_id AND s.run_id = a.run_id AND s.attempt_id = a.id
|
|
3036
3064
|
`));
|
|
3037
3065
|
for (const attempt of attempts) {
|
|
3038
|
-
const
|
|
3066
|
+
const ubiquityAvailable = templateLinkUbiquityAvailable(attempt.pages_fetched);
|
|
3067
|
+
const detection = ubiquityAvailable ? SiteHealthTemplateDetections.applied : SiteHealthTemplateDetections["unavailable-too-few-pages"];
|
|
3039
3068
|
const scope = { attemptId: attempt.attempt_id, projectId: attempt.project_id, runId: attempt.run_id };
|
|
3040
3069
|
tx.run(sql2`
|
|
3041
3070
|
UPDATE site_crawl_edges SET is_template = 0, template_ratio = NULL
|
|
@@ -6206,6 +6235,35 @@ var MIGRATION_VERSIONS = [
|
|
|
6206
6235
|
updated_at TEXT NOT NULL
|
|
6207
6236
|
)`
|
|
6208
6237
|
]
|
|
6238
|
+
},
|
|
6239
|
+
{
|
|
6240
|
+
version: 138,
|
|
6241
|
+
name: "site-crawl-link-placement",
|
|
6242
|
+
// The crawler (aeo-audit 4.7.0) now reports where each link occurrence sat
|
|
6243
|
+
// in its page, from the page's own landmarks. That is ground truth about a
|
|
6244
|
+
// link, and it replaces ubiquity as the rule that separates nav from
|
|
6245
|
+
// content: ubiquity keys on (target URL, anchor text), so it cannot see an
|
|
6246
|
+
// editorial link whose anchor text matches the nav's, which is the common
|
|
6247
|
+
// case because good anchor text reuses the destination's name. Measured on
|
|
6248
|
+
// canonry.ai: 53 newly added editorial links moved the content-link count
|
|
6249
|
+
// by ZERO.
|
|
6250
|
+
//
|
|
6251
|
+
// These columns are deliberately nullable and deliberately NOT backfilled.
|
|
6252
|
+
// A crawl captured before the landmark ruleset existed never observed
|
|
6253
|
+
// placement, so there is nothing to derive from and any value written here
|
|
6254
|
+
// would be invented. Those scans keep the ubiquity classification they
|
|
6255
|
+
// already have, and their snapshots keep a NULL ruleset version, which is
|
|
6256
|
+
// exactly what makes a read say `applied` rather than `applied-placement`.
|
|
6257
|
+
// Re-running a scan is what upgrades it.
|
|
6258
|
+
//
|
|
6259
|
+
// Idempotent: `ALTER TABLE ADD COLUMN` errors with "duplicate column name",
|
|
6260
|
+
// which the runner already swallows, and there is no data pass to repeat.
|
|
6261
|
+
statements: [
|
|
6262
|
+
`ALTER TABLE site_crawl_edges ADD COLUMN placement_navigation_occurrences INTEGER`,
|
|
6263
|
+
`ALTER TABLE site_crawl_edges ADD COLUMN placement_content_occurrences INTEGER`,
|
|
6264
|
+
`ALTER TABLE site_crawl_edges ADD COLUMN placement_unknown_occurrences INTEGER`,
|
|
6265
|
+
`ALTER TABLE site_crawl_snapshots ADD COLUMN link_placement_ruleset_version TEXT`
|
|
6266
|
+
]
|
|
6209
6267
|
}
|
|
6210
6268
|
];
|
|
6211
6269
|
function addRunsMeasurementPlanVersionForeignKey(tx) {
|
|
@@ -28445,7 +28503,7 @@ var crawlLimitParameter = {
|
|
|
28445
28503
|
var linkKindParameter = {
|
|
28446
28504
|
name: "linkKind",
|
|
28447
28505
|
in: "query",
|
|
28448
|
-
description: 'Restrict links to `content` (excludes nav, header, and footer links) or `template` (only those). Defaults to `all`. A
|
|
28506
|
+
description: 'Restrict links to `content` (excludes nav, header, and footer links) or `template` (only those). Defaults to `all`. A link neither rule could classify matches neither filter, which is why every link response also carries `templateDetection` and every edge carries `templateSource`: an empty `content` list means "could not tell" under an `unavailable-*` detection, and `templateSource` says whether a link that IS classified was decided by where it sits in the page (`placement`) or by how many pages repeat it (`ubiquity`).',
|
|
28449
28507
|
schema: { type: "string", enum: ["all", "content", "template"], default: "all" }
|
|
28450
28508
|
};
|
|
28451
28509
|
var measurementOverviewSortParameter = {
|
|
@@ -34010,7 +34068,7 @@ var routeCatalog = [
|
|
|
34010
34068
|
method: "get",
|
|
34011
34069
|
path: "/api/v1/projects/{name}/technical-aeo/graph",
|
|
34012
34070
|
summary: "Get a persisted Site Health graph projection",
|
|
34013
|
-
description: "Returns the deterministic graph projection computed once when the latest complete or selected historical crawl was published. ForceAtlas2 positions and the exact internal-anchor edge sample are persisted, so reads run no layout physics and never rescan the crawl edge table. Nav, header, and footer links are excluded from the layout physics but retained in the sample and tagged `isTemplate`, so a viewer can draw them without a refetch and without any node moving. The response is bounded to 20,000 nodes / 50,000 edges; `layout`, `omittedNodes`, `omittedEdges`, and `sampled` disclose legacy/unavailable layouts and intentional truncation, and `templateDetection` says whether template links could be told apart at all.",
|
|
34071
|
+
description: "Returns the deterministic graph projection computed once when the latest complete or selected historical crawl was published. ForceAtlas2 positions and the exact internal-anchor edge sample are persisted, so reads run no layout physics and never rescan the crawl edge table. Nav, header, and footer links are excluded from the layout physics but retained in the sample and tagged `isTemplate`, so a viewer can draw them without a refetch and without any node moving. The response is bounded to 20,000 nodes / 50,000 edges; `layout`, `omittedNodes`, `omittedEdges`, and `sampled` disclose legacy/unavailable layouts and intentional truncation, and `templateDetection` says whether template links could be told apart at all and by which rule.",
|
|
34014
34072
|
tags: ["technical-aeo"],
|
|
34015
34073
|
parameters: [
|
|
34016
34074
|
nameParameter,
|
|
@@ -34150,7 +34208,7 @@ var routeCatalog = [
|
|
|
34150
34208
|
method: "get",
|
|
34151
34209
|
path: "/api/v1/projects/{name}/technical-aeo/internal-links",
|
|
34152
34210
|
summary: "List persisted internal crawl links",
|
|
34153
|
-
description: "Cursor-paged internal edges for the latest or selected crawl. Optional source/target/followability/link-kind filters remain project-, run-, and attempt-scoped. `total` counts exactly what the requested filters match,
|
|
34211
|
+
description: "Cursor-paged internal edges for the latest or selected crawl. Optional source/target/followability/link-kind filters remain project-, run-, and attempt-scoped. `total` counts exactly what the requested filters match, `templateDetection` says whether nav and footer links could be told apart for this scan and by which rule, and the `templateSource` plus `placementOccurrences` on each edge show the evidence behind its own classification.",
|
|
34154
34212
|
tags: ["technical-aeo"],
|
|
34155
34213
|
parameters: [
|
|
34156
34214
|
nameParameter,
|
|
@@ -59124,7 +59182,15 @@ function countLivePageHealthChecks(auditFields) {
|
|
|
59124
59182
|
}
|
|
59125
59183
|
return checks;
|
|
59126
59184
|
}
|
|
59127
|
-
function
|
|
59185
|
+
function mapPlacementOccurrences(row) {
|
|
59186
|
+
const navigation = row.placementNavigationOccurrences;
|
|
59187
|
+
const content = row.placementContentOccurrences;
|
|
59188
|
+
const unknown = row.placementUnknownOccurrences;
|
|
59189
|
+
if (navigation == null || content == null || unknown == null) return null;
|
|
59190
|
+
return { navigation, content, unknown };
|
|
59191
|
+
}
|
|
59192
|
+
function mapCrawlEdge(row, detection) {
|
|
59193
|
+
const placementOccurrences = mapPlacementOccurrences(row);
|
|
59128
59194
|
return {
|
|
59129
59195
|
edgeKey: row.edgeKey,
|
|
59130
59196
|
sourceNodeKey: row.sourceNodeKey,
|
|
@@ -59139,7 +59205,13 @@ function mapCrawlEdge(row) {
|
|
|
59139
59205
|
nofollowOccurrences: row.nofollowOccurrences,
|
|
59140
59206
|
anchors: row.anchors,
|
|
59141
59207
|
isTemplate: row.isTemplate,
|
|
59142
|
-
templateRatio: row.templateRatio
|
|
59208
|
+
templateRatio: row.templateRatio,
|
|
59209
|
+
templateSource: templateLinkSource(detection, {
|
|
59210
|
+
isTemplate: row.isTemplate,
|
|
59211
|
+
templateRatio: row.templateRatio,
|
|
59212
|
+
placementOccurrences
|
|
59213
|
+
}),
|
|
59214
|
+
placementOccurrences
|
|
59143
59215
|
};
|
|
59144
59216
|
}
|
|
59145
59217
|
function templateDetectionOf(value) {
|
|
@@ -59772,7 +59844,8 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
59772
59844
|
distance: distances.get(row.nodeKey) ?? 0,
|
|
59773
59845
|
relationToFocus: row.nodeKey === focus.nodeKey ? "focus" : directRelations.get(row.nodeKey) ?? "transitive"
|
|
59774
59846
|
})).sort((a, b) => a.distance - b.distance || a.nodeKey.localeCompare(b.nodeKey));
|
|
59775
|
-
const
|
|
59847
|
+
const subgraphDetection = templateDetectionOf(snapshot.templateDetection);
|
|
59848
|
+
const edges = [...edgeRows.values()].filter((edge) => edge.targetNodeKey && persistedNodeKeys.has(edge.sourceNodeKey) && persistedNodeKeys.has(edge.targetNodeKey)).sort((a, b) => a.edgeKey.localeCompare(b.edgeKey)).map((row) => mapCrawlEdge(row, subgraphDetection));
|
|
59776
59849
|
const omittedNodes = omittedNodeKeys.size + Math.max(0, distances.size - pageRows.length);
|
|
59777
59850
|
const omittedEdges = omittedEdgeKeys.size + (queryTruncated ? 1 : 0) + Math.max(0, edgeRows.size - edges.length);
|
|
59778
59851
|
const truncated = omittedNodes > 0 || omittedEdges > 0;
|
|
@@ -59921,6 +59994,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
59921
59994
|
inArray20(siteCrawlPages.nodeKey, pathKeys)
|
|
59922
59995
|
)).all();
|
|
59923
59996
|
const pageByKey = new Map(pathRows.map((row) => [row.nodeKey, row]));
|
|
59997
|
+
const pathDetection = templateDetectionOf(snapshot.templateDetection);
|
|
59924
59998
|
return {
|
|
59925
59999
|
project: project.name,
|
|
59926
60000
|
runId: snapshot.runId,
|
|
@@ -59932,7 +60006,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
59932
60006
|
maxDepth,
|
|
59933
60007
|
visitedNodes: visited.size,
|
|
59934
60008
|
nodes: pathKeys.map((key) => mapCrawlPage(pageByKey.get(key))),
|
|
59935
|
-
edges: pathEdges.map(mapCrawlEdge)
|
|
60009
|
+
edges: pathEdges.map((row) => mapCrawlEdge(row, pathDetection))
|
|
59936
60010
|
};
|
|
59937
60011
|
});
|
|
59938
60012
|
app.get("/projects/:name/technical-aeo/changes", async (request) => {
|
|
@@ -60210,8 +60284,10 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
60210
60284
|
)).all();
|
|
60211
60285
|
const beforePages = new Map(pageRowsFor(beforeScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
60212
60286
|
const afterPages = new Map(pageRowsFor(afterScope).map((row) => [row.nodeKey, mapCrawlPage(row)]));
|
|
60213
|
-
const
|
|
60214
|
-
const
|
|
60287
|
+
const beforeDetection = templateDetectionOf(beforeSnapshot.templateDetection);
|
|
60288
|
+
const afterDetection = templateDetectionOf(afterSnapshot.templateDetection);
|
|
60289
|
+
const beforeLinks = new Map(edgeRowsFor(beforeScope).map((row) => [row.edgeKey, mapCrawlEdge(row, beforeDetection)]));
|
|
60290
|
+
const afterLinks = new Map(edgeRowsFor(afterScope).map((row) => [row.edgeKey, mapCrawlEdge(row, afterDetection)]));
|
|
60215
60291
|
const changes = visibleKeys.map((row) => {
|
|
60216
60292
|
if (row.entity === "page") {
|
|
60217
60293
|
const before2 = beforePages.get(row.key) ?? null;
|
|
@@ -60502,7 +60578,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
60502
60578
|
nextCursor: nextOffset < total ? encodeCursor2(nextOffset) : null,
|
|
60503
60579
|
templateDetection,
|
|
60504
60580
|
linkKind,
|
|
60505
|
-
edges: rows.map(mapCrawlEdge)
|
|
60581
|
+
edges: rows.map((row) => mapCrawlEdge(row, templateDetection))
|
|
60506
60582
|
};
|
|
60507
60583
|
});
|
|
60508
60584
|
app.get("/projects/:name/technical-aeo/internal-links/neighbors", async (request) => {
|
|
@@ -60565,8 +60641,8 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
60565
60641
|
url: request.query.url ?? null,
|
|
60566
60642
|
templateDetection,
|
|
60567
60643
|
linkKind,
|
|
60568
|
-
inbound: inboundRows.slice(0, limit).map(mapCrawlEdge),
|
|
60569
|
-
outbound: outboundRows.slice(0, limit).map(mapCrawlEdge),
|
|
60644
|
+
inbound: inboundRows.slice(0, limit).map((row) => mapCrawlEdge(row, templateDetection)),
|
|
60645
|
+
outbound: outboundRows.slice(0, limit).map((row) => mapCrawlEdge(row, templateDetection)),
|
|
60570
60646
|
inboundTruncated: inboundRows.length > limit,
|
|
60571
60647
|
outboundTruncated: outboundRows.length > limit
|
|
60572
60648
|
};
|
|
@@ -81,7 +81,7 @@ import {
|
|
|
81
81
|
trafficConnectWordpressRequestSchema,
|
|
82
82
|
trafficEventKindSchema,
|
|
83
83
|
trafficSeriesGranularitySchema
|
|
84
|
-
} from "./chunk-
|
|
84
|
+
} from "./chunk-ESH3LPYQ.js";
|
|
85
85
|
|
|
86
86
|
// src/config.ts
|
|
87
87
|
import fs from "fs";
|
|
@@ -8637,7 +8637,7 @@ var technicalAeoStructureInputSchema = z3.object({
|
|
|
8637
8637
|
limit: z3.number().int().positive().max(100).optional()
|
|
8638
8638
|
});
|
|
8639
8639
|
var linkKindSchema = z3.enum(["all", "content", "template"]).optional().describe(
|
|
8640
|
-
"Restrict to content links (excludes nav, header, and footer links) or to template links only. Defaults to all. Check templateDetection before reading an empty content list as a real zero."
|
|
8640
|
+
"Restrict to content links (excludes nav, header, and footer links) or to template links only. Defaults to all. Check templateDetection before reading an empty content list as a real zero, and before comparing a count with an older scan: it says whether the split came from where each link sits in the page or from how many pages repeat it."
|
|
8641
8641
|
);
|
|
8642
8642
|
var technicalAeoInternalLinksInputSchema = z3.object({
|
|
8643
8643
|
project: projectNameSchema,
|
|
@@ -10651,7 +10651,7 @@ var canonryMcpTools = [
|
|
|
10651
10651
|
defineTool({
|
|
10652
10652
|
name: "canonry_technical_aeo_internal_links",
|
|
10653
10653
|
title: "List Technical AEO internal links",
|
|
10654
|
-
description: "Read a bounded, cursor-paged list of persisted internal crawl edges. Filter by source URL, target URL, followability, or link kind. Nav, header, and footer links are marked isTemplate; templateDetection reports
|
|
10654
|
+
description: "Read a bounded, cursor-paged list of persisted internal crawl edges. Filter by source URL, target URL, followability, or link kind. Nav, header, and footer links are marked isTemplate; templateSource says which rule decided each one (placement, ubiquity, or unmeasured when neither had evidence) and templateDetection reports the same for the scan, so an empty content-only list is not evidence of no content links and two scans classified by different rules are never compared as if they were the same measurement. Use the neighbors tool for one page rather than loading a graph.",
|
|
10655
10655
|
access: "read",
|
|
10656
10656
|
tier: "monitoring",
|
|
10657
10657
|
inputSchema: technicalAeoInternalLinksInputSchema,
|