@canonry/canonry 7.3.0 → 7.5.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/agent-workspace/skills/aero/references/orchestration.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +2 -1
- package/assets/assets/{AuditHistoryPanel-DP92khTY.js → AuditHistoryPanel-B5aLMnWE.js} +1 -1
- package/assets/assets/{BacklinksPage-BI_9t1iI.js → BacklinksPage-Biq1jrqe.js} +1 -1
- package/assets/assets/{HistoryPage-tDE6Nv0J.js → HistoryPage-CGGkAgnz.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-BwyWGGBg.js +1 -0
- package/assets/assets/ProjectPage-DEnKyD3e.js +8 -0
- package/assets/assets/{RunsPage-Bl_c9-vA.js → RunsPage-DNBn0Rau.js} +1 -1
- package/assets/assets/{SettingsPage-DQ5_Q2rc.js → SettingsPage-D3m-BLuf.js} +1 -1
- package/assets/assets/SiteHealthSection-CPMu1l-l.js +4 -0
- package/assets/assets/{TrafficPage-CROectSE.js → TrafficPage-BCK_V__i.js} +3 -3
- package/assets/assets/{TrafficSourceDetailPage-Bthvvwg5.js → TrafficSourceDetailPage-DaCehUm8.js} +1 -1
- package/assets/assets/{extract-error-message-DOLCGQMa.js → extract-error-message-CqBLzzuR.js} +1 -1
- package/assets/assets/{index-CCOcEGdz.js → index-C2X_UARl.js} +20 -20
- package/assets/assets/{react-sigma_core.esm.min-TWiq-VWU.js → react-sigma_core.esm.min-BvO4SZKT.js} +1 -1
- package/assets/assets/v2-overview-adapter-CgDChBa5.js +1 -0
- package/assets/index.html +1 -1
- package/dist/{chunk-YQXQBFHT.js → chunk-3L7QPB37.js} +9850 -9767
- package/dist/{chunk-RVELNJAW.js → chunk-6Z5MRX24.js} +2 -2
- package/dist/{chunk-3JEUZ2K5.js → chunk-E7EWAAH3.js} +3 -3
- package/dist/{chunk-FWV6IIYY.js → chunk-JURAGZOO.js} +7 -6
- package/dist/{chunk-QND5LEAC.js → chunk-KS66E5H6.js} +2 -2
- package/dist/{chunk-MQPBOQUH.js → chunk-OY5QVO34.js} +7 -7
- package/dist/{chunk-XODXE6PT.js → chunk-ZHI2MCMD.js} +83 -32
- package/dist/cli.js +64 -24
- package/dist/{demo-server-BXABH765.js → demo-server-55M7DF3U.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-EIWYQYVG.js → intelligence-service-HZSRDMFP.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +14 -14
- package/assets/assets/MeasurementPropertyPage-Y2F334Qs.js +0 -1
- package/assets/assets/ProjectPage-DW581TNr.js +0 -8
- package/assets/assets/SiteHealthSection-DdZruU72.js +0 -4
- package/assets/assets/v2-overview-adapter-CxmV639A.js +0 -1
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
PACKAGE_VERSION,
|
|
6
6
|
canonryMcpTools,
|
|
7
7
|
isMachineFormat
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-JURAGZOO.js";
|
|
9
9
|
import {
|
|
10
10
|
AGENT_PROVIDER_IDS,
|
|
11
11
|
CANONRY_NPM_PACKAGE_URL,
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
skillsClientSchema,
|
|
51
51
|
upgradeCaveatFor,
|
|
52
52
|
upgradeCommandFor
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-3L7QPB37.js";
|
|
54
54
|
|
|
55
55
|
// src/config.ts
|
|
56
56
|
import fs from "fs";
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
installSkills,
|
|
5
5
|
loadConfigRaw,
|
|
6
6
|
saveConfigPatch
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-6Z5MRX24.js";
|
|
8
8
|
import {
|
|
9
9
|
PACKAGE_VERSION
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-JURAGZOO.js";
|
|
11
11
|
import {
|
|
12
12
|
SKILL_MANIFEST_FILENAME,
|
|
13
13
|
SkillsClients
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-3L7QPB37.js";
|
|
15
15
|
|
|
16
16
|
// src/skills-autosync.ts
|
|
17
17
|
import fs from "fs";
|
|
@@ -124,6 +124,7 @@ import {
|
|
|
124
124
|
sentimentSummaryIncludeSchema,
|
|
125
125
|
sentimentSummaryReadSchema,
|
|
126
126
|
settingsDtoSchema,
|
|
127
|
+
siteAuditPageBudgetSchema,
|
|
127
128
|
snapshotRequestInputSchema,
|
|
128
129
|
telemetryStatusDtoSchema,
|
|
129
130
|
trafficAnalyticsPeriodSchema,
|
|
@@ -134,7 +135,7 @@ import {
|
|
|
134
135
|
trafficSeriesGranularitySchema,
|
|
135
136
|
visibilityCompareSelectionSchema,
|
|
136
137
|
visibilityReportRequestSchema
|
|
137
|
-
} from "./chunk-
|
|
138
|
+
} from "./chunk-3L7QPB37.js";
|
|
138
139
|
|
|
139
140
|
// src/cli-error.ts
|
|
140
141
|
function isMachineFormat(format) {
|
|
@@ -1209,7 +1210,7 @@ var technicalAeoRunInputSchema = z3.object({
|
|
|
1209
1210
|
project: projectNameSchema,
|
|
1210
1211
|
sitemapUrl: z3.string().url().optional().describe("Override the sitemap URL. Defaults to https://<canonicalDomain>/sitemap.xml."),
|
|
1211
1212
|
limit: z3.number().int().positive().max(2e3).optional().describe("Deprecated compatibility alias for maxPages."),
|
|
1212
|
-
maxPages:
|
|
1213
|
+
maxPages: siteAuditPageBudgetSchema.optional().describe("Maximum pages crawled and audited (1 to 50,000). Omitted uses the project's saved budget (siteAuditMaxPages, set with canonry_project_upsert or canonry_apply_config), else the full site, up to 50,000; pass a smaller number for a quick look."),
|
|
1213
1214
|
maxEdges: z3.number().int().positive().max(1e6).optional().describe("Maximum link observations retained for this crawl. When omitted the crawl engine derives the budget from the page count; hard maximum 1,000,000."),
|
|
1214
1215
|
maxDepth: z3.number().int().min(0).max(100).optional().describe("Maximum internal-link depth from the root page."),
|
|
1215
1216
|
checkDeadLinks: z3.boolean().optional().describe("Opt in to internal dead-link checks. Omitted and false both disable checks.")
|
|
@@ -2694,7 +2695,7 @@ var canonryMcpTools = [
|
|
|
2694
2695
|
defineTool({
|
|
2695
2696
|
name: "canonry_project_upsert",
|
|
2696
2697
|
title: "Create or replace project",
|
|
2697
|
-
description: "Create or replace a Canonry project. PUT semantics: fields not in the request are reset to their defaults. Provide the full intended project shape. Exceptions: an omitted providerDispatchModes (provider \u2192 sync|batch for scheduled sweeps) keeps the stored preference; send {} to clear it. An omitted qualifiedAliases (the aliases Simple sentiment treats as this brand's own names) keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it. Competitors are not part of this request, but a new identity (display name, aliases, domains) that claims a tracked competitor's curated alias drops that alias, and the drop is audited.",
|
|
2698
|
+
description: "Create or replace a Canonry project. PUT semantics: fields not in the request are reset to their defaults. Provide the full intended project shape. Exceptions: an omitted providerDispatchModes (provider \u2192 sync|batch for scheduled sweeps) keeps the stored preference; send {} to clear it. An omitted qualifiedAliases (the aliases Simple sentiment treats as this brand's own names) keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it. Competitors are not part of this request, but a new identity (display name, aliases, domains) that claims a tracked competitor's curated alias drops that alias, and the drop is audited. An omitted siteAuditMaxPages (the Site Health page budget for scans that set none, 1 to 50,000) keeps the stored budget; send null for the full site.",
|
|
2698
2699
|
access: "write",
|
|
2699
2700
|
tier: "setup",
|
|
2700
2701
|
inputSchema: projectUpsertInputSchema,
|
|
@@ -2705,7 +2706,7 @@ var canonryMcpTools = [
|
|
|
2705
2706
|
defineTool({
|
|
2706
2707
|
name: "canonry_apply_config",
|
|
2707
2708
|
title: "Apply project config",
|
|
2708
|
-
description: "Apply one Canonry config-as-code project document. Replaces the project to match the config; fields omitted from the spec are reset to defaults, with these exceptions: a spec with neither queries nor keywords leaves the tracked-query basket unchanged (to clear the basket, pass an explicit empty queries list); an omitted providerDispatchModes keeps the stored preference; an omitted qualifiedAliases keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it; and a competitor given as a plain domain keeps its stored curated aliases, while `{ domain, aliases }` sets them exactly (`[]` clears). A spec identity that claims a competitor's alias drops that alias (audited); a new competitor whose name overlaps another competitor's alias (one brand key contains the other) or that is the project's own site fails the apply. For multi-document YAML, call this tool once per project document.",
|
|
2709
|
+
description: "Apply one Canonry config-as-code project document. Replaces the project to match the config; fields omitted from the spec are reset to defaults, with these exceptions: a spec with neither queries nor keywords leaves the tracked-query basket unchanged (to clear the basket, pass an explicit empty queries list); an omitted providerDispatchModes keeps the stored preference; an omitted qualifiedAliases keeps the stored list, minus names that no longer qualify (a removed alias, a spelling of the display name, or a competitor's name); send [] to clear it; and a competitor given as a plain domain keeps its stored curated aliases, while `{ domain, aliases }` sets them exactly (`[]` clears). A spec identity that claims a competitor's alias drops that alias (audited); a new competitor whose name overlaps another competitor's alias (one brand key contains the other) or that is the project's own site fails the apply. An omitted siteAuditMaxPages keeps the stored Site Health page budget (null means the full site). For multi-document YAML, call this tool once per project document.",
|
|
2709
2710
|
access: "write",
|
|
2710
2711
|
tier: "core",
|
|
2711
2712
|
inputSchema: applyConfigInputSchema,
|
|
@@ -2910,7 +2911,7 @@ var canonryMcpTools = [
|
|
|
2910
2911
|
defineTool({
|
|
2911
2912
|
name: "canonry_measurement_portfolio_summary",
|
|
2912
2913
|
title: "Summarize measured Properties",
|
|
2913
|
-
description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention. Denominators count answers, one per query per engine (queries x engines = answers). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. weakestAnswerSources.ownDomainAnswers counts those answers that cite any of your own domains. A Property's citationCoverage counts only answers citing that Property's own page, so 0% citation does not mean your domain went uncited; use ownDomainAnswers for that. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
|
|
2914
|
+
description: "Start here for best and worst Properties. One call returns the weakest Properties, the names answers wrote instead of them, and the domains engines cited for them. Defaults to non-brand queries; state the returned queryClass and never pool branded with non-brand. Returns at most 4 Property rows per list, the most that fit one result; a larger limit is lowered and limitNote says so. For other rows pass groupKey (one metro's weakest), or page canonry_measurement_overview. Mention (answer text names the Property) and citation (a source URL on its page) are separate: report each as numerator/denominator, with any unattributed count beside mention and any unchecked count beside citation (saved answers whose source capture was incomplete; they are in neither side). Denominators count answers, one per query per engine (queries x engines = answers, less any unattributed or unchecked). Group Properties only by each row's metro and submarkets, never by label. namedInsteadInAnswerText lists names written in the answer text of answers that neither named nor cited the Property, counted by answer: they are not citations, and namedInsteadInAnswerTextTotal counts distinct names, not answers. citedDomains and weakestAnswerSources count answers citing each domain, every engine included; weakestAnswerSources pools the weakest rows plus every tied Property, each answer once, and its domainTotal counts distinct domains, not citations. weakestAnswerSources.ownDomainAnswers counts those answers that cite any of your own domains. A Property's citationCoverage counts only answers citing that Property's own page, so 0% citation does not mean your domain went uncited; use ownDomainAnswers for that. When tiedAtWeakest is set, tiedAtWeakest.count Properties share the weakest rates and the rows are the first of them by name, not a rank: report the count, tiedAtWeakest.byMetro (tied Properties per metro over the whole tie; a Property in two metros counts in both) and tiedAtWeakest.namedInstead (names written instead across the whole tie, counted by distinct answer; namedInsteadTotal is distinct names), and call the rows examples. mentionRanking.strongest/.weakest rank every Property with an available mention rate; .excluded lists the rest with reasons. markets lists every market at one level, worst-first (every metro, or a groupKey's submarkets), whatever the limit. Markets may share Properties and never sum to portfolio totals. Reads stored data only; never starts provider work.",
|
|
2914
2915
|
access: "read",
|
|
2915
2916
|
tier: "monitoring",
|
|
2916
2917
|
inputSchema: measurementPortfolioSummaryInputSchema,
|
|
@@ -3815,7 +3816,7 @@ var canonryMcpTools = [
|
|
|
3815
3816
|
defineTool({
|
|
3816
3817
|
name: "canonry_technical_aeo_run",
|
|
3817
3818
|
title: "Run Technical AEO site audit",
|
|
3818
|
-
description: "Start a site-audit run. The run discovers root, sitemap, and linked pages.
|
|
3819
|
+
description: "Start a site-audit run. The run discovers root, sitemap, and linked pages. Without maxPages it uses the project's saved page budget (siteAuditMaxPages), else the full site (up to 50,000 pages), and the engine derives the link-observation budget from that page count with a 100,000 floor, so raising maxPages raises it too. An explicit maxEdges is a ceiling that replaces the derivation and can sit below it. It returns {runId, status} and continues in the background. If an active run has identical effective options, this tool returns it; different options are refused. Poll canonry_run_get, then read the crawl and score tools.",
|
|
3819
3820
|
access: "write",
|
|
3820
3821
|
tier: "monitoring",
|
|
3821
3822
|
inputSchema: technicalAeoRunInputSchema,
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
siteCrawlGraphNodes,
|
|
6
6
|
siteCrawlPages,
|
|
7
7
|
siteCrawlSnapshots
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-ZHI2MCMD.js";
|
|
9
9
|
import {
|
|
10
10
|
SITE_CRAWL_GRAPH_MAX_EDGES,
|
|
11
11
|
SITE_CRAWL_GRAPH_MAX_NODES,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
TEMPLATE_LINK_RATIO_THRESHOLD,
|
|
14
14
|
factorStatusFromScore,
|
|
15
15
|
roundPreservingTotal
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-3L7QPB37.js";
|
|
17
17
|
|
|
18
18
|
// src/site-audit-factors.ts
|
|
19
19
|
function recordedSharePct(factor) {
|
|
@@ -20,20 +20,20 @@ import {
|
|
|
20
20
|
saveConfig,
|
|
21
21
|
saveConfigPatch,
|
|
22
22
|
sentimentInstallReadiness
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-6Z5MRX24.js";
|
|
24
24
|
import {
|
|
25
25
|
computeFactorAverages,
|
|
26
26
|
deleteSiteCrawlGraphLayout,
|
|
27
27
|
persistSiteCrawlGraphLayout,
|
|
28
28
|
prepareSiteCrawlGraphLayout,
|
|
29
29
|
recordedSharePct
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-KS66E5H6.js";
|
|
31
31
|
import {
|
|
32
32
|
CanonryMcpToolNames,
|
|
33
33
|
CliError,
|
|
34
34
|
PACKAGE_VERSION,
|
|
35
35
|
canonryMcpTools
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-JURAGZOO.js";
|
|
37
37
|
import {
|
|
38
38
|
CC_CACHE_DIR,
|
|
39
39
|
DUCKDB_SPEC,
|
|
@@ -205,7 +205,7 @@ import {
|
|
|
205
205
|
siteLivenessState,
|
|
206
206
|
toAlertView,
|
|
207
207
|
usageCounters
|
|
208
|
-
} from "./chunk-
|
|
208
|
+
} from "./chunk-ZHI2MCMD.js";
|
|
209
209
|
import {
|
|
210
210
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
211
211
|
AGENT_NONE,
|
|
@@ -411,7 +411,7 @@ import {
|
|
|
411
411
|
visibilityReportRequestSchema,
|
|
412
412
|
winnabilityClassLabel,
|
|
413
413
|
withRetry
|
|
414
|
-
} from "./chunk-
|
|
414
|
+
} from "./chunk-3L7QPB37.js";
|
|
415
415
|
|
|
416
416
|
// src/runtime-context.ts
|
|
417
417
|
function cliRuntimeContext(env = process.env, stdio = process) {
|
|
@@ -7869,7 +7869,7 @@ function nonBlank(value) {
|
|
|
7869
7869
|
return trimmed ? trimmed : void 0;
|
|
7870
7870
|
}
|
|
7871
7871
|
function resolveBuildCommit(env = process.env) {
|
|
7872
|
-
const embedded = true ? "
|
|
7872
|
+
const embedded = true ? "1fae609bb90f155eec013fe7e84214c3306cc9b7" : void 0;
|
|
7873
7873
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
7874
7874
|
}
|
|
7875
7875
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -14780,7 +14780,7 @@ var Scheduler = class {
|
|
|
14780
14780
|
return;
|
|
14781
14781
|
}
|
|
14782
14782
|
const runId2 = crypto21.randomUUID();
|
|
14783
|
-
const effectiveRequest = normalizeSiteAuditRunRequest({
|
|
14783
|
+
const effectiveRequest = normalizeSiteAuditRunRequest({}, project.siteAuditMaxPages);
|
|
14784
14784
|
this.db.transaction((tx) => {
|
|
14785
14785
|
tx.insert(runs).values({
|
|
14786
14786
|
id: runId2,
|
|
@@ -626,6 +626,7 @@ import {
|
|
|
626
626
|
queryTrackingWorkspaceResponseSchema,
|
|
627
627
|
quotaExceeded,
|
|
628
628
|
rankCriticizedProperties,
|
|
629
|
+
rateOverChecked,
|
|
629
630
|
recommendationBriefDtoSchema,
|
|
630
631
|
recommendationExplainRequestSchema,
|
|
631
632
|
recommendationExplanationDtoSchema,
|
|
@@ -795,7 +796,7 @@ import {
|
|
|
795
796
|
wordpressSchemaDeployResultDtoSchema,
|
|
796
797
|
wordpressSchemaStatusResultDtoSchema,
|
|
797
798
|
wordpressStatusDtoSchema
|
|
798
|
-
} from "./chunk-
|
|
799
|
+
} from "./chunk-3L7QPB37.js";
|
|
799
800
|
|
|
800
801
|
// src/intelligence-service.ts
|
|
801
802
|
import { eq as eq84, desc as desc37, asc as asc15, and as and71, ne as ne15, or as or20, inArray as inArray33, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists as exists2 } from "drizzle-orm";
|
|
@@ -953,6 +954,8 @@ var projects = sqliteTable("projects", {
|
|
|
953
954
|
* sidecar at dispatch; never read by mention detection or query classes.
|
|
954
955
|
*/
|
|
955
956
|
qualifiedAliases: text("qualified_aliases", { mode: "json" }).$type().notNull().default([]),
|
|
957
|
+
/** Site Health page budget for scans that set none; null means the full site (up to 50,000 pages). */
|
|
958
|
+
siteAuditMaxPages: integer("site_audit_max_pages"),
|
|
956
959
|
country: text("country").notNull(),
|
|
957
960
|
language: text("language").notNull(),
|
|
958
961
|
tags: text("tags", { mode: "json" }).$type().notNull().default([]),
|
|
@@ -8152,6 +8155,16 @@ var MIGRATION_VERSIONS = [
|
|
|
8152
8155
|
statements: [
|
|
8153
8156
|
`ALTER TABLE competitors ADD COLUMN aliases TEXT NOT NULL DEFAULT '[]'`
|
|
8154
8157
|
]
|
|
8158
|
+
},
|
|
8159
|
+
{
|
|
8160
|
+
// Saved Site Health page budget for scans that set none. Nullable with no
|
|
8161
|
+
// default: every existing project reads as full site, and an older writer
|
|
8162
|
+
// that never sets it leaves it null.
|
|
8163
|
+
version: 169,
|
|
8164
|
+
name: "projects-site-audit-max-pages",
|
|
8165
|
+
statements: [
|
|
8166
|
+
`ALTER TABLE projects ADD COLUMN site_audit_max_pages INTEGER`
|
|
8167
|
+
]
|
|
8155
8168
|
}
|
|
8156
8169
|
];
|
|
8157
8170
|
var AERO_LEDGER_REBUILDS = [
|
|
@@ -11041,7 +11054,7 @@ function buildRunHistory(runs2, snapshotsByRunId, limit = DEFAULT_RUN_HISTORY_LI
|
|
|
11041
11054
|
}
|
|
11042
11055
|
const totalCount = queryCited.size;
|
|
11043
11056
|
const citedCount = [...queryCited.values()].filter(Boolean).length;
|
|
11044
|
-
const
|
|
11057
|
+
const citationRate2 = percentOf(citedCount, totalCount) ?? 0;
|
|
11045
11058
|
const mentionedCount = [...queryMentioned.values()].filter(Boolean).length;
|
|
11046
11059
|
const mentionRate3 = percentOf(mentionedCount, totalCount) ?? 0;
|
|
11047
11060
|
return {
|
|
@@ -11049,7 +11062,7 @@ function buildRunHistory(runs2, snapshotsByRunId, limit = DEFAULT_RUN_HISTORY_LI
|
|
|
11049
11062
|
createdAt: run.createdAt,
|
|
11050
11063
|
citedCount,
|
|
11051
11064
|
totalCount,
|
|
11052
|
-
citationRate,
|
|
11065
|
+
citationRate: citationRate2,
|
|
11053
11066
|
mentionedCount,
|
|
11054
11067
|
mentionRate: mentionRate3,
|
|
11055
11068
|
status: run.status
|
|
@@ -15719,11 +15732,10 @@ function coverageRate(slots, edges, prepared) {
|
|
|
15719
15732
|
if (slots.length === 0) return { numerator: null, denominator: null, rate: null, reason: "no-population" };
|
|
15720
15733
|
if (!status.complete) return { numerator: null, denominator: null, rate: null, reason: "incomplete" };
|
|
15721
15734
|
const basis = sourceCompleteSlots(slots, prepared);
|
|
15722
|
-
if (basis.length === 0) return { numerator: null, denominator: null, rate: null, reason: "evidence-incomplete" };
|
|
15723
15735
|
const edgeIds = new Set(edges.map((edge) => edge.id));
|
|
15724
15736
|
const assignedSlots = new Set(prepared.evidence.filter((row) => edgeIds.has(row.usageEdgeId) && row.classification === "assigned").map((row) => row.expectedSlotId));
|
|
15725
15737
|
const numerator = basis.filter((slot) => assignedSlots.has(slot.id)).length;
|
|
15726
|
-
return
|
|
15738
|
+
return rateOverChecked(numerator, slots.length, slots.length - basis.length, "unchecked") ?? unavailable("evidence-incomplete");
|
|
15727
15739
|
}
|
|
15728
15740
|
function targetCoverageRate(targetIds, slots, edges, prepared) {
|
|
15729
15741
|
const status = completeness(slots, prepared);
|
|
@@ -15751,9 +15763,7 @@ function mentionRate(target, slots, prepared) {
|
|
|
15751
15763
|
return attributableMentionRate(numerator, slots.length, unattributed);
|
|
15752
15764
|
}
|
|
15753
15765
|
function attributableMentionRate(numerator, answered2, unattributed) {
|
|
15754
|
-
|
|
15755
|
-
if (denominator <= 0) return unavailable("identity-ambiguous");
|
|
15756
|
-
return unattributed > 0 ? { numerator, denominator, rate: numerator / denominator, unattributed } : { numerator, denominator, rate: numerator / denominator };
|
|
15766
|
+
return rateOverChecked(numerator, answered2, unattributed, "unattributed") ?? unavailable("identity-ambiguous");
|
|
15757
15767
|
}
|
|
15758
15768
|
function providersFor(slots) {
|
|
15759
15769
|
return sortedUnique(slots.map((slot) => slot.provider));
|
|
@@ -15867,11 +15877,13 @@ function buildMeasurementOverviewIndexes(input, prepared, onEvidenceIndexed) {
|
|
|
15867
15877
|
slots.push(slot);
|
|
15868
15878
|
slotsByExecutionId.set(slot.executionId, slots);
|
|
15869
15879
|
}
|
|
15880
|
+
const observedSlotIds = /* @__PURE__ */ new Set();
|
|
15870
15881
|
const answeredSlotIds = /* @__PURE__ */ new Set();
|
|
15871
15882
|
const sourceCompleteSlotIds = /* @__PURE__ */ new Set();
|
|
15872
15883
|
const mentionedSlotIdsByTargetId = /* @__PURE__ */ new Map();
|
|
15873
15884
|
const unknownMentionSlotIdsByTargetId = /* @__PURE__ */ new Map();
|
|
15874
15885
|
for (const [slotId, observation] of prepared.observationsBySlot) {
|
|
15886
|
+
observedSlotIds.add(slotId);
|
|
15875
15887
|
if (observation.sourceComplete) sourceCompleteSlotIds.add(slotId);
|
|
15876
15888
|
if (observation.input.answerText === null) continue;
|
|
15877
15889
|
answeredSlotIds.add(slotId);
|
|
@@ -15902,6 +15914,7 @@ function buildMeasurementOverviewIndexes(input, prepared, onEvidenceIndexed) {
|
|
|
15902
15914
|
targetsById,
|
|
15903
15915
|
targetEdgesByTargetId,
|
|
15904
15916
|
slotsByExecutionId,
|
|
15917
|
+
observedSlotIds,
|
|
15905
15918
|
answeredSlotIds,
|
|
15906
15919
|
sourceCompleteSlotIds,
|
|
15907
15920
|
mentionedSlotIdsByTargetId,
|
|
@@ -15956,14 +15969,14 @@ function scopeMentionRate(input, targetIds, slots, answered2, prepared, edges) {
|
|
|
15956
15969
|
}
|
|
15957
15970
|
function indexedScopeCitationRate(slots, edges, indexes) {
|
|
15958
15971
|
if (slots.length === 0) return unavailable("no-population");
|
|
15972
|
+
if (slots.some((slot) => !indexes.observedSlotIds.has(slot.id))) return unavailable("evidence-incomplete");
|
|
15959
15973
|
const basis = slots.filter((slot) => indexes.sourceCompleteSlotIds.has(slot.id));
|
|
15960
|
-
if (basis.length !== slots.length) return unavailable("evidence-incomplete");
|
|
15961
15974
|
const assignedSlots = /* @__PURE__ */ new Set();
|
|
15962
15975
|
for (const edge of edges) {
|
|
15963
15976
|
for (const slotId of indexes.assignedSlotIdsByEdgeId.get(edge.id) ?? []) assignedSlots.add(slotId);
|
|
15964
15977
|
}
|
|
15965
15978
|
const numerator = basis.filter((slot) => assignedSlots.has(slot.id)).length;
|
|
15966
|
-
return
|
|
15979
|
+
return rateOverChecked(numerator, slots.length, slots.length - basis.length, "unchecked") ?? unavailable("evidence-incomplete");
|
|
15967
15980
|
}
|
|
15968
15981
|
function targetMentionRate(targets, slots, answered2, indexes) {
|
|
15969
15982
|
const mentionable = targets?.filter((target) => target.aliases.some((alias2) => words(alias2).length > 0)) ?? [];
|
|
@@ -17088,6 +17101,7 @@ async function projectRoutes(app, opts) {
|
|
|
17088
17101
|
locations: nextLocations,
|
|
17089
17102
|
defaultLocation: nextDefaultLocation,
|
|
17090
17103
|
autoExtractBacklinks: body.autoExtractBacklinks ?? false,
|
|
17104
|
+
siteAuditMaxPages: body.siteAuditMaxPages ?? null,
|
|
17091
17105
|
configSource: body.configSource ?? "api",
|
|
17092
17106
|
configRevision: 1,
|
|
17093
17107
|
createdAt: now,
|
|
@@ -17160,6 +17174,7 @@ async function projectRoutes(app, opts) {
|
|
|
17160
17174
|
});
|
|
17161
17175
|
}
|
|
17162
17176
|
const nextAutoExtractBacklinks = body.autoExtractBacklinks !== void 0 ? body.autoExtractBacklinks : existing?.autoExtractBacklinks ?? false;
|
|
17177
|
+
const nextSiteAuditMaxPages = body.siteAuditMaxPages !== void 0 ? body.siteAuditMaxPages : existing?.siteAuditMaxPages ?? null;
|
|
17163
17178
|
const nextNegativeReviewMaxStars = body.negativeReviewMaxStars !== void 0 ? body.negativeReviewMaxStars : existing ? readNegativeReviewMaxStars(app.db, existing.id) : null;
|
|
17164
17179
|
const nextMeasurement = body.measurement ?? existing?.measurement ?? DEFAULT_MEASUREMENT_CONFIG;
|
|
17165
17180
|
const nextAliases = normalizeProjectAliases(body.displayName, body.aliases ?? []);
|
|
@@ -17199,6 +17214,7 @@ async function projectRoutes(app, opts) {
|
|
|
17199
17214
|
locations: nextLocations,
|
|
17200
17215
|
defaultLocation: nextDefaultLocation,
|
|
17201
17216
|
autoExtractBacklinks: nextAutoExtractBacklinks,
|
|
17217
|
+
siteAuditMaxPages: nextSiteAuditMaxPages,
|
|
17202
17218
|
configSource: body.configSource ?? "api",
|
|
17203
17219
|
configRevision: existing.configRevision + 1,
|
|
17204
17220
|
updatedAt: now
|
|
@@ -17247,6 +17263,7 @@ async function projectRoutes(app, opts) {
|
|
|
17247
17263
|
locations: nextLocations,
|
|
17248
17264
|
defaultLocation: nextDefaultLocation,
|
|
17249
17265
|
autoExtractBacklinks: nextAutoExtractBacklinks,
|
|
17266
|
+
siteAuditMaxPages: nextSiteAuditMaxPages,
|
|
17250
17267
|
configSource: body.configSource ?? "api",
|
|
17251
17268
|
configRevision: 1,
|
|
17252
17269
|
createdAt: now,
|
|
@@ -17457,6 +17474,7 @@ async function projectRoutes(app, opts) {
|
|
|
17457
17474
|
...project.defaultLocation ? { defaultLocation: project.defaultLocation } : {},
|
|
17458
17475
|
...project.autoExtractBacklinks ? { autoExtractBacklinks: true } : {},
|
|
17459
17476
|
...negativeReviewMaxStars !== null ? { negativeReviewMaxStars } : {},
|
|
17477
|
+
...project.siteAuditMaxPages !== null ? { siteAuditMaxPages: project.siteAuditMaxPages } : {},
|
|
17460
17478
|
notifications: notificationRows.map((row) => {
|
|
17461
17479
|
const cfg = row.config;
|
|
17462
17480
|
return {
|
|
@@ -17536,6 +17554,7 @@ function formatProject(row, negativeReviewMaxStars) {
|
|
|
17536
17554
|
defaultLocation: row.defaultLocation,
|
|
17537
17555
|
autoExtractBacklinks: row.autoExtractBacklinks,
|
|
17538
17556
|
negativeReviewMaxStars,
|
|
17557
|
+
siteAuditMaxPages: row.siteAuditMaxPages ?? null,
|
|
17539
17558
|
configSource: row.configSource,
|
|
17540
17559
|
configRevision: row.configRevision,
|
|
17541
17560
|
createdAt: row.createdAt,
|
|
@@ -18398,7 +18417,9 @@ function coverageMetric(rate2) {
|
|
|
18398
18417
|
numerator: rate2.numerator,
|
|
18399
18418
|
denominator: rate2.denominator,
|
|
18400
18419
|
// Answers the mention rate left out because their identity was unresolved.
|
|
18401
|
-
...rate2.unattributed === void 0 ? {} : { unattributed: rate2.unattributed }
|
|
18420
|
+
...rate2.unattributed === void 0 ? {} : { unattributed: rate2.unattributed },
|
|
18421
|
+
// Saved answers the citation rate left out because their source capture was incomplete.
|
|
18422
|
+
...rate2.unchecked === void 0 ? {} : { unchecked: rate2.unchecked }
|
|
18402
18423
|
};
|
|
18403
18424
|
}
|
|
18404
18425
|
function providerRows(property) {
|
|
@@ -18562,6 +18583,10 @@ function measurementOutcomeCounts(rows) {
|
|
|
18562
18583
|
continue;
|
|
18563
18584
|
}
|
|
18564
18585
|
const cited = (citation.numerator ?? citation.value) > 0;
|
|
18586
|
+
if (!cited && citation.unchecked !== void 0) {
|
|
18587
|
+
counts2.notMeasured += 1;
|
|
18588
|
+
continue;
|
|
18589
|
+
}
|
|
18565
18590
|
if (mentioned && cited) counts2.bothSignals += 1;
|
|
18566
18591
|
else if (mentioned) counts2.mentionedOnly += 1;
|
|
18567
18592
|
else if (cited) counts2.citedOnly += 1;
|
|
@@ -25478,7 +25503,9 @@ function coverageMetric2(rate2) {
|
|
|
25478
25503
|
numerator: rate2.numerator,
|
|
25479
25504
|
denominator: rate2.denominator,
|
|
25480
25505
|
// Answers the mention rate left out because their identity was unresolved.
|
|
25481
|
-
...rate2.unattributed === void 0 ? {} : { unattributed: rate2.unattributed }
|
|
25506
|
+
...rate2.unattributed === void 0 ? {} : { unattributed: rate2.unattributed },
|
|
25507
|
+
// Saved answers the citation rate left out because their source capture was incomplete.
|
|
25508
|
+
...rate2.unchecked === void 0 ? {} : { unchecked: rate2.unchecked }
|
|
25482
25509
|
};
|
|
25483
25510
|
}
|
|
25484
25511
|
function countMetric2(rate2) {
|
|
@@ -25936,15 +25963,15 @@ function weakestTie(ranked) {
|
|
|
25936
25963
|
return { summary: null, rows: [] };
|
|
25937
25964
|
}
|
|
25938
25965
|
const mentionRate3 = first.mentionCoverage.value;
|
|
25939
|
-
const
|
|
25940
|
-
const rows = ranked.filter((row) => row.mentionCoverage.state === "available" && row.mentionCoverage.value === mentionRate3 && row.citationCoverage.state === "available" && row.citationCoverage.value ===
|
|
25966
|
+
const citationRate2 = first.citationCoverage.value;
|
|
25967
|
+
const rows = ranked.filter((row) => row.mentionCoverage.state === "available" && row.mentionCoverage.value === mentionRate3 && row.citationCoverage.state === "available" && row.citationCoverage.value === citationRate2);
|
|
25941
25968
|
if (rows.length < 2) return { summary: null, rows: [] };
|
|
25942
25969
|
const namedInstead = tieNamedInstead(rows);
|
|
25943
25970
|
return {
|
|
25944
25971
|
summary: {
|
|
25945
25972
|
count: rows.length,
|
|
25946
25973
|
mentionRate: mentionRate3,
|
|
25947
|
-
citationRate,
|
|
25974
|
+
citationRate: citationRate2,
|
|
25948
25975
|
note: MEASUREMENT_PORTFOLIO_TIE_NOTE,
|
|
25949
25976
|
byMetro: tieByMetro(rows),
|
|
25950
25977
|
namedInstead: namedInstead.rows,
|
|
@@ -26736,8 +26763,27 @@ function targetValues(candidate, targets, targetKey) {
|
|
|
26736
26763
|
}
|
|
26737
26764
|
return { mention: false, citation };
|
|
26738
26765
|
}
|
|
26739
|
-
function citationForCoverage(candidate, targets
|
|
26740
|
-
return candidate.observation?.citationComplete ? targetValues(candidate, targets
|
|
26766
|
+
function citationForCoverage(candidate, targets) {
|
|
26767
|
+
return candidate.observation?.citationComplete ? targetValues(candidate, targets).citation : null;
|
|
26768
|
+
}
|
|
26769
|
+
function citationRate(candidates, isCited) {
|
|
26770
|
+
if (candidates.length === 0) return unavailable4("no-population");
|
|
26771
|
+
let numerator = 0;
|
|
26772
|
+
let unchecked = 0;
|
|
26773
|
+
for (const candidate of candidates) {
|
|
26774
|
+
if (candidate.observation === null) return unavailable4("evidence-incomplete");
|
|
26775
|
+
if (!candidate.observation.citationComplete) {
|
|
26776
|
+
unchecked++;
|
|
26777
|
+
continue;
|
|
26778
|
+
}
|
|
26779
|
+
const cited = isCited(candidate);
|
|
26780
|
+
if (cited === null) return unavailable4("evidence-incomplete");
|
|
26781
|
+
if (cited) numerator++;
|
|
26782
|
+
}
|
|
26783
|
+
return rateOverChecked(numerator, candidates.length, unchecked, "unchecked") ?? unavailable4("evidence-incomplete");
|
|
26784
|
+
}
|
|
26785
|
+
function targetCitationRate(candidates, targets, targetKey) {
|
|
26786
|
+
return citationRate(candidates, (candidate) => targetValues(candidate, targets, targetKey).citation);
|
|
26741
26787
|
}
|
|
26742
26788
|
function mentionRate2(candidates, targets, targetKey) {
|
|
26743
26789
|
const values = candidates.map((candidate) => targetValues(candidate, targets, targetKey));
|
|
@@ -26751,9 +26797,7 @@ function mentionRate2(candidates, targets, targetKey) {
|
|
|
26751
26797
|
unattributed++;
|
|
26752
26798
|
}
|
|
26753
26799
|
}
|
|
26754
|
-
|
|
26755
|
-
if (denominator === 0) return unavailable4("identity-ambiguous");
|
|
26756
|
-
return unattributed > 0 ? { numerator, denominator, rate: numerator / denominator, unattributed } : { numerator, denominator, rate: numerator / denominator };
|
|
26800
|
+
return rateOverChecked(numerator, values.length, unattributed, "unattributed") ?? unavailable4("identity-ambiguous");
|
|
26757
26801
|
}
|
|
26758
26802
|
function answered(candidate) {
|
|
26759
26803
|
return candidate.observation !== null;
|
|
@@ -26768,16 +26812,18 @@ function targetMetrics(candidates, targets, targetKey) {
|
|
|
26768
26812
|
const own = candidates.filter((candidate) => candidate.edges.some((edge) => edge.targetKey === targetKey));
|
|
26769
26813
|
return {
|
|
26770
26814
|
mentionCoverage: mentionRate2(own, targets, targetKey),
|
|
26771
|
-
citationCoverage:
|
|
26815
|
+
citationCoverage: targetCitationRate(own, targets, targetKey)
|
|
26772
26816
|
};
|
|
26773
26817
|
}
|
|
26774
26818
|
function targetPresence(candidates, targets, targetKey) {
|
|
26775
|
-
const
|
|
26819
|
+
const scoped = candidates.filter((candidate) => candidate.edges.some((edge) => edge.targetKey === targetKey));
|
|
26820
|
+
const values = scoped.map((candidate) => targetValues(candidate, targets, targetKey));
|
|
26821
|
+
const citations = scoped.map((candidate, index2) => candidate.observation?.citationComplete === false ? null : values[index2].citation);
|
|
26776
26822
|
const anyKnown = (signals) => signals.includes(true) ? true : signals.length === 0 || signals.includes(null) ? null : false;
|
|
26777
26823
|
return {
|
|
26778
26824
|
targetKey,
|
|
26779
26825
|
mention: anyKnown(values.map((value) => value.mention)),
|
|
26780
|
-
citation: anyKnown(
|
|
26826
|
+
citation: anyKnown(citations),
|
|
26781
26827
|
identityAmbiguous: values.some((value) => value.mentionUnavailableReason === "identity-ambiguous")
|
|
26782
26828
|
};
|
|
26783
26829
|
}
|
|
@@ -26806,7 +26852,7 @@ function coverageSummary(candidates, definition, targets = targetMap(definition)
|
|
|
26806
26852
|
queryCount: new Set(rows.map((candidate) => candidate.slot.queryKey)).size,
|
|
26807
26853
|
answerCount: rows.filter(answered).length,
|
|
26808
26854
|
mentionCoverage: mentionRate2(rows, targets),
|
|
26809
|
-
citationCoverage:
|
|
26855
|
+
citationCoverage: targetCitationRate(rows, targets)
|
|
26810
26856
|
};
|
|
26811
26857
|
}
|
|
26812
26858
|
function summary2(candidates, definition, populationTargetKeys = selectedTargetKeys(candidates)) {
|
|
@@ -26902,15 +26948,12 @@ function competitorRows(candidates) {
|
|
|
26902
26948
|
const observation = candidate.observation;
|
|
26903
26949
|
return observation === null || observation.answerText === null ? null : observation.competitorMentionDomains.includes(domain);
|
|
26904
26950
|
});
|
|
26905
|
-
const citationValues = unique3.map((candidate) => {
|
|
26906
|
-
const observation = candidate.observation;
|
|
26907
|
-
return observation === null || !observation.citationComplete ? null : observation.competitorCitationDomains.includes(domain);
|
|
26908
|
-
});
|
|
26909
26951
|
return {
|
|
26910
26952
|
domain,
|
|
26911
26953
|
answerCount: unique3.filter(answered).length,
|
|
26912
26954
|
mentionCoverage: rate(mentionValues, unique3.length),
|
|
26913
|
-
|
|
26955
|
+
// The Property rule: an unchecked answer leaves both sides of the rate.
|
|
26956
|
+
citationCoverage: citationRate(unique3, (candidate) => candidate.observation.competitorCitationDomains.includes(domain))
|
|
26914
26957
|
};
|
|
26915
26958
|
});
|
|
26916
26959
|
}
|
|
@@ -26950,7 +26993,7 @@ function breakdown(candidates, definition, populationTargetKeys = selectedTarget
|
|
|
26950
26993
|
const groupTargetKeys = new Set(targetKeys);
|
|
26951
26994
|
const own = candidates.map((candidate) => narrowedToTargetKeys(candidate, groupTargetKeys)).filter((candidate) => candidate !== null);
|
|
26952
26995
|
const metrics = mentionRate2(own, targets);
|
|
26953
|
-
const citations =
|
|
26996
|
+
const citations = targetCitationRate(own, targets);
|
|
26954
26997
|
return {
|
|
26955
26998
|
id: group.id,
|
|
26956
26999
|
label: group.label,
|
|
@@ -27578,6 +27621,8 @@ function frozenSimpleRun(run, definition, snapshots, includeEvidence = true) {
|
|
|
27578
27621
|
mentionComplete: snapshot.answerText !== null || snapshot.answerMentioned !== null,
|
|
27579
27622
|
mentionedTargetKeys: mentioned ? ["project"] : [],
|
|
27580
27623
|
citedTargetKeys: snapshot.citationState === "cited" ? ["project"] : [],
|
|
27624
|
+
// Simple citation state comes from the stored cited domains, not from
|
|
27625
|
+
// source-URL capture, so a saved Simple answer is never `unchecked`.
|
|
27581
27626
|
citationComplete: true,
|
|
27582
27627
|
competitorMentionDomains: competitorSignalsForSnapshot.mentioned,
|
|
27583
27628
|
competitorCitationDomains: competitorSignalsForSnapshot.cited,
|
|
@@ -27660,6 +27705,7 @@ function legacySimpleRun(project, run, snapshots, includeEvidence = true) {
|
|
|
27660
27705
|
mentionComplete: snapshot.answerMentioned !== null,
|
|
27661
27706
|
mentionedTargetKeys: snapshot.answerMentioned === true ? ["project"] : [],
|
|
27662
27707
|
citedTargetKeys: snapshot.citationState === "cited" ? ["project"] : [],
|
|
27708
|
+
// Legacy rows carry only the stored citation state (see the frozen-simple note).
|
|
27663
27709
|
citationComplete: true,
|
|
27664
27710
|
competitorMentionDomains: [],
|
|
27665
27711
|
competitorCitationDomains: [],
|
|
@@ -29977,6 +30023,8 @@ async function applyRoutes(app, opts) {
|
|
|
29977
30023
|
locations: config.spec.locations ?? [],
|
|
29978
30024
|
defaultLocation: config.spec.defaultLocation ?? null,
|
|
29979
30025
|
autoExtractBacklinks: config.spec.autoExtractBacklinks ?? false,
|
|
30026
|
+
// Present sets (null = full site); absent leaves the stored budget alone.
|
|
30027
|
+
...config.spec.siteAuditMaxPages !== void 0 ? { siteAuditMaxPages: config.spec.siteAuditMaxPages } : {},
|
|
29980
30028
|
configSource: "config-file",
|
|
29981
30029
|
configRevision: existing.configRevision + 1,
|
|
29982
30030
|
updatedAt: now
|
|
@@ -30010,6 +30058,7 @@ async function applyRoutes(app, opts) {
|
|
|
30010
30058
|
locations: config.spec.locations ?? [],
|
|
30011
30059
|
defaultLocation: config.spec.defaultLocation ?? null,
|
|
30012
30060
|
autoExtractBacklinks: config.spec.autoExtractBacklinks ?? false,
|
|
30061
|
+
siteAuditMaxPages: config.spec.siteAuditMaxPages ?? null,
|
|
30013
30062
|
configSource: "config-file",
|
|
30014
30063
|
configRevision: 1,
|
|
30015
30064
|
createdAt: now,
|
|
@@ -30156,6 +30205,7 @@ async function applyRoutes(app, opts) {
|
|
|
30156
30205
|
defaultLocation: project.defaultLocation,
|
|
30157
30206
|
autoExtractBacklinks: project.autoExtractBacklinks,
|
|
30158
30207
|
negativeReviewMaxStars: readNegativeReviewMaxStars(app.db, project.id),
|
|
30208
|
+
siteAuditMaxPages: project.siteAuditMaxPages ?? null,
|
|
30159
30209
|
configSource: project.configSource,
|
|
30160
30210
|
configRevision: project.configRevision,
|
|
30161
30211
|
createdAt: project.createdAt,
|
|
@@ -35327,6 +35377,7 @@ function formatProject2(row, negativeReviewMaxStars) {
|
|
|
35327
35377
|
defaultLocation: row.defaultLocation,
|
|
35328
35378
|
autoExtractBacklinks: row.autoExtractBacklinks,
|
|
35329
35379
|
negativeReviewMaxStars,
|
|
35380
|
+
siteAuditMaxPages: row.siteAuditMaxPages ?? null,
|
|
35330
35381
|
configSource: row.configSource,
|
|
35331
35382
|
configRevision: row.configRevision,
|
|
35332
35383
|
createdAt: row.createdAt,
|
|
@@ -43838,7 +43889,7 @@ var routeCatalog = [
|
|
|
43838
43889
|
properties: {
|
|
43839
43890
|
sitemapUrl: { ...stringSchema, description: "Deprecated compatibility alias for the sitemap override." },
|
|
43840
43891
|
limit: { ...integerSchema, minimum: 1, maximum: 2e3, description: "Deprecated compatibility alias for maxPages. Max 2000." },
|
|
43841
|
-
maxPages: { ...integerSchema, minimum: 1, maximum: 5e4, description: "Crawl page budget.
|
|
43892
|
+
maxPages: { ...integerSchema, minimum: 1, maximum: 5e4, description: "Crawl page budget. When omitted, the project's saved budget (siteAuditMaxPages) applies, else the full site; max 50000." },
|
|
43842
43893
|
maxEdges: { ...integerSchema, minimum: 1, maximum: 1e6, description: "Internal-link observation budget. When omitted the crawl engine derives it from the page count; max 1000000." },
|
|
43843
43894
|
maxDepth: { ...integerSchema, minimum: 0, maximum: 100, description: "Maximum crawl depth from the root. Max 100." },
|
|
43844
43895
|
checkDeadLinks: { ...booleanSchema, default: false, description: "Enable dead-link checking. Defaults to false." }
|
|
@@ -86752,7 +86803,7 @@ async function technicalAeoRoutes(app, opts) {
|
|
|
86752
86803
|
reason: "no-site-audit-handler"
|
|
86753
86804
|
});
|
|
86754
86805
|
}
|
|
86755
|
-
const effectiveRequest = normalizeSiteAuditRunRequest(parsed.data);
|
|
86806
|
+
const effectiveRequest = normalizeSiteAuditRunRequest(parsed.data, project.siteAuditMaxPages);
|
|
86756
86807
|
const identityKey = siteAuditRequestIdentity(effectiveRequest);
|
|
86757
86808
|
const result = app.db.transaction((tx) => {
|
|
86758
86809
|
const existing = tx.select({
|