@ainyc/canonry 4.72.2 → 4.72.3

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.
@@ -53,7 +53,7 @@ Shows which source categories AI models cite for your queries. Helps identify:
53
53
  - Content format opportunities (FAQ, how-to, comparison pages)
54
54
 
55
55
  ### Winnability gate + briefs (`canonry content targets|map|brief`)
56
- Every content target carries a deterministic `winnabilityClass`: **ownable** (worth writing for) or **ceded** (the cited surface is dominated by aggregators/OTAs or editorial media a head term first-party content cannot realistically win). It is derived (no LLM) from the discovery domain classifier, so **run `canonry discover run` first** to populate it; with no classifications every target fails open to `ownable`. `canonry doctor --project <project> --check content.winnability.coverage` reports whether the cited-surface domains behind the gate have enough discovery coverage.
56
+ Every content target carries a deterministic `winnabilityClass`: **ownable** (worth writing for) or **ceded** (the cited surface is dominated by aggregators/OTAs or editorial media, a head term first-party content cannot realistically win). It is derived (no LLM) by classifying each cited domain through the shared surface classifier (own > tracked competitor > stored discovery class > static allow-list), so well-known aggregators/editorial cede immediately, no discovery run required. **`canonry discover run`** improves recall for niche domains the allow-list misses; a target only fails open to `ownable` when none of its cited surface is recognized. `canonry doctor --project <project> --check content.winnability.coverage` reports recognized-coverage of the cited surface and, when the project has no ICP, nudges you to set one (discovery needs it).
57
57
  - `canonry content targets <project> --ownable` — the winnable shortlist (ownable rows sort first by default).
58
58
  - `canonry content map <project>` — the winnability map: which cited surfaces are ceded vs the ranked ownable targets.
59
59
  - `canonry content brief <project> <targetRef>` — synthesize a structured brief (angle, why-winnable, schema hookup) for an **ownable** target. Ceded targets are rejected — don't chase them.
@@ -65,6 +65,7 @@ import {
65
65
  citationStateSchema,
66
66
  citationStateToCited,
67
67
  citationVisibilityResponseSchema,
68
+ classifyCitedSurface,
68
69
  classifySkillFile,
69
70
  classifySurfaceFromCategory,
70
71
  clusterByCosine,
@@ -222,7 +223,7 @@ import {
222
223
  wordpressSchemaDeployResultDtoSchema,
223
224
  wordpressSchemaStatusResultDtoSchema,
224
225
  wordpressStatusDtoSchema
225
- } from "./chunk-SJI6JGPN.js";
226
+ } from "./chunk-JXFNERK4.js";
226
227
 
227
228
  // src/intelligence-service.ts
228
229
  import { eq as eq32, desc as desc16, asc as asc3, and as and23, ne as ne5, or as or5, inArray as inArray11, gte as gte6, lte as lte3 } from "drizzle-orm";
@@ -3636,7 +3637,12 @@ function buildContentTargetRows(input) {
3636
3637
  gscAvgPosition: cq.gscPosition,
3637
3638
  organicSessions: input.gaTrafficByPage.get(ourPage.url) ?? 0
3638
3639
  } : null;
3639
- const { winnabilityClass, winnability } = deriveWinnabilityClass(cq.citedSurfaceDomains, input.domainClasses);
3640
+ const surfaceClasses = classifyCitedSurface(
3641
+ cq.citedSurfaceDomains,
3642
+ { projectDomains: [input.ownDomain], competitorDomains: input.competitors },
3643
+ input.domainClasses
3644
+ );
3645
+ const { winnabilityClass, winnability } = deriveWinnabilityClass(cq.citedSurfaceDomains, surfaceClasses);
3640
3646
  rows.push({
3641
3647
  targetRef,
3642
3648
  query: cq.query,
@@ -29667,23 +29673,30 @@ var winnabilityCoverageCheck = {
29667
29673
  }
29668
29674
  };
29669
29675
  }
29670
- const coveredDomains = citedDomains.filter((domain) => input.domainClasses.has(domain));
29671
- const unclassifiedDomains = citedDomains.filter((domain) => !input.domainClasses.has(domain));
29676
+ const surfaceClasses = classifyCitedSurface(
29677
+ citedDomains.map((domain) => ({ domain })),
29678
+ { projectDomains: [input.ownDomain], competitorDomains: input.competitors },
29679
+ input.domainClasses
29680
+ );
29681
+ const coveredDomains = citedDomains.filter((domain) => surfaceClasses.has(domain));
29682
+ const unclassifiedDomains = citedDomains.filter((domain) => !surfaceClasses.has(domain));
29672
29683
  const coverage = coveredDomains.length / citedDomains.length;
29673
29684
  const details = {
29674
29685
  citedSurfaceDomainCount: citedDomains.length,
29675
- classifiedDomainCount: input.domainClasses.size,
29676
29686
  coveredDomainCount: coveredDomains.length,
29687
+ classifiedDomainCount: input.domainClasses.size,
29677
29688
  coverage,
29678
29689
  threshold: WINNABILITY_COVERAGE_WARN_THRESHOLD,
29679
29690
  unclassifiedDomains: unclassifiedDomains.slice(0, UNCLASSIFIED_DOMAIN_SAMPLE_LIMIT)
29680
29691
  };
29692
+ const hasIcp = Boolean(project.icpDescription && project.icpDescription.trim().length > 0);
29693
+ const discoverRemediation = hasIcp ? `Run \`canonry discover run ${ctx.project.name} --wait\` to classify the unrecognized domains before relying on ownable/ceded content targets.` : `This project has no ICP, which discovery requires. Run \`canonry discover run ${ctx.project.name} --icp "<who the project sells to>" --wait\` (or set \`spec.icpDescription\`) to classify the unrecognized domains.`;
29681
29694
  if (coveredDomains.length === 0) {
29682
29695
  return {
29683
29696
  status: CheckStatuses.warn,
29684
29697
  code: "content.winnability.no-classifications",
29685
- summary: `0 of ${citedDomains.length} cited-surface domain(s) have discovery classifications; the winnability gate is failing open.`,
29686
- remediation: `Run \`canonry discover run ${ctx.project.name} --wait\` to classify cited domains before trusting ownable/ceded content targets.`,
29698
+ summary: `0 of ${citedDomains.length} cited-surface domain(s) are recognized (own/competitor/aggregator/editorial); the winnability gate is failing open.`,
29699
+ remediation: discoverRemediation,
29687
29700
  details
29688
29701
  };
29689
29702
  }
@@ -29691,15 +29704,15 @@ var winnabilityCoverageCheck = {
29691
29704
  return {
29692
29705
  status: CheckStatuses.warn,
29693
29706
  code: "content.winnability.low-coverage",
29694
- summary: `${coveredDomains.length} of ${citedDomains.length} cited-surface domain(s) classified (${percent(coverage)}%); the winnability gate may miss ceded surfaces.`,
29695
- remediation: `Run \`canonry discover run ${ctx.project.name} --wait\` to raise classification coverage before relying on ownable/ceded content targets.`,
29707
+ summary: `${coveredDomains.length} of ${citedDomains.length} cited-surface domain(s) recognized (${percent(coverage)}%); the winnability gate may miss ceded surfaces in the unrecognized tail.`,
29708
+ remediation: discoverRemediation,
29696
29709
  details
29697
29710
  };
29698
29711
  }
29699
29712
  return {
29700
29713
  status: CheckStatuses.ok,
29701
29714
  code: "content.winnability.covered",
29702
- summary: `${coveredDomains.length} of ${citedDomains.length} cited-surface domain(s) classified (${percent(coverage)}%); the winnability gate is active.`,
29715
+ summary: `${coveredDomains.length} of ${citedDomains.length} cited-surface domain(s) recognized (${percent(coverage)}%); the winnability gate is active.`,
29703
29716
  remediation: null,
29704
29717
  details
29705
29718
  };
@@ -1898,6 +1898,18 @@ function classifySurfaceFromCategory(domain, category, context, storedClass) {
1898
1898
  return SurfaceClasses.other;
1899
1899
  }
1900
1900
  }
1901
+ function classifyCitedSurface(domains, context, storedClasses) {
1902
+ const out = /* @__PURE__ */ new Map();
1903
+ for (const { domain } of domains) {
1904
+ if (out.has(domain)) continue;
1905
+ const stored = storedClasses?.get(domain);
1906
+ const storedClass = stored ? surfaceClassFromCompetitorType(stored) : void 0;
1907
+ const { category } = categorizeSource(domain);
1908
+ const cls = classifySurfaceFromCategory(domain, category, context, storedClass);
1909
+ if (cls !== SurfaceClasses.other) out.set(domain, cls);
1910
+ }
1911
+ return out;
1912
+ }
1901
1913
 
1902
1914
  // ../contracts/src/analytics.ts
1903
1915
  var metricsWindowSchema = z18.enum(["7d", "30d", "90d", "all"]);
@@ -2575,17 +2587,17 @@ function winnabilityClassLabel(winnabilityClass) {
2575
2587
  }
2576
2588
  }
2577
2589
  var CEDED_SURFACE_THRESHOLD = 0.6;
2578
- function deriveWinnabilityClass(citedSurfaceDomains, domainClasses, threshold = CEDED_SURFACE_THRESHOLD) {
2579
- const hasCoverage = citedSurfaceDomains.some((d) => domainClasses.has(d.domain));
2580
- if (citedSurfaceDomains.length === 0 || domainClasses.size === 0 || !hasCoverage) {
2590
+ function deriveWinnabilityClass(citedSurfaceDomains, surfaceClasses, threshold = CEDED_SURFACE_THRESHOLD) {
2591
+ const hasCoverage = citedSurfaceDomains.some((d) => surfaceClasses.has(d.domain));
2592
+ if (citedSurfaceDomains.length === 0 || surfaceClasses.size === 0 || !hasCoverage) {
2581
2593
  return { winnabilityClass: WinnabilityClasses.ownable, winnability: null };
2582
2594
  }
2583
2595
  let total = 0;
2584
2596
  let ceded = 0;
2585
2597
  for (const { domain, citationCount } of citedSurfaceDomains) {
2586
2598
  total += citationCount;
2587
- const cls = domainClasses.get(domain);
2588
- if (cls === DiscoveryCompetitorTypes["ota-aggregator"] || cls === DiscoveryCompetitorTypes["editorial-media"]) {
2599
+ const cls = surfaceClasses.get(domain);
2600
+ if (cls === SurfaceClasses["ota-aggregator"] || cls === SurfaceClasses["editorial-media"]) {
2589
2601
  ceded += citationCount;
2590
2602
  }
2591
2603
  }
@@ -4331,6 +4343,7 @@ export {
4331
4343
  surfaceClassLabel,
4332
4344
  surfaceClassFromCompetitorType,
4333
4345
  classifySurfaceFromCategory,
4346
+ classifyCitedSurface,
4334
4347
  brandMetricsDtoSchema,
4335
4348
  sourceBreakdownDtoSchema,
4336
4349
  parseWindow,
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-NYZSY5QJ.js";
12
+ } from "./chunk-ZUBBADMR.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -95,7 +95,7 @@ import {
95
95
  runs,
96
96
  schedules,
97
97
  usageCounters
98
- } from "./chunk-J7SDOU2J.js";
98
+ } from "./chunk-HSX32G47.js";
99
99
  import {
100
100
  AGENT_MEMORY_VALUE_MAX_BYTES,
101
101
  AGENT_PROVIDER_IDS,
@@ -145,7 +145,7 @@ import {
145
145
  validationError,
146
146
  winnabilityClassLabel,
147
147
  withRetry
148
- } from "./chunk-SJI6JGPN.js";
148
+ } from "./chunk-JXFNERK4.js";
149
149
 
150
150
  // src/telemetry.ts
151
151
  import crypto from "crypto";
@@ -5620,7 +5620,7 @@ function readStoredGroundingSources(rawResponse) {
5620
5620
  return result;
5621
5621
  }
5622
5622
  async function backfillInsightsCommand(project, opts) {
5623
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JNF3JRFR.js");
5623
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-ZW3ARLJT.js");
5624
5624
  const config = loadConfig();
5625
5625
  const db = createClient(config.database);
5626
5626
  migrate(db);
@@ -22,7 +22,7 @@ import {
22
22
  trafficConnectVercelRequestSchema,
23
23
  trafficConnectWordpressRequestSchema,
24
24
  trafficEventKindSchema
25
- } from "./chunk-SJI6JGPN.js";
25
+ } from "./chunk-JXFNERK4.js";
26
26
 
27
27
  // src/config.ts
28
28
  import fs from "fs";
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-BRXQKUGY.js";
30
+ } from "./chunk-SIB4NMEH.js";
31
31
  import {
32
32
  CliError,
33
33
  EXIT_SYSTEM_ERROR,
@@ -44,7 +44,7 @@ import {
44
44
  saveConfig,
45
45
  saveConfigPatch,
46
46
  usageError
47
- } from "./chunk-NYZSY5QJ.js";
47
+ } from "./chunk-ZUBBADMR.js";
48
48
  import {
49
49
  apiKeys,
50
50
  createClient,
@@ -52,7 +52,7 @@ import {
52
52
  projects,
53
53
  queries,
54
54
  renderReportHtml
55
- } from "./chunk-J7SDOU2J.js";
55
+ } from "./chunk-HSX32G47.js";
56
56
  import {
57
57
  CcReleaseSyncStatuses,
58
58
  CheckScopes,
@@ -71,7 +71,7 @@ import {
71
71
  providerQuotaPolicySchema,
72
72
  resolveProviderInput,
73
73
  winnabilityClassSchema
74
- } from "./chunk-SJI6JGPN.js";
74
+ } from "./chunk-JXFNERK4.js";
75
75
 
76
76
  // src/cli.ts
77
77
  import { pathToFileURL } from "url";
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-BRXQKUGY.js";
3
+ } from "./chunk-SIB4NMEH.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-NYZSY5QJ.js";
7
- import "./chunk-J7SDOU2J.js";
8
- import "./chunk-SJI6JGPN.js";
6
+ } from "./chunk-ZUBBADMR.js";
7
+ import "./chunk-HSX32G47.js";
8
+ import "./chunk-JXFNERK4.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-J7SDOU2J.js";
4
- import "./chunk-SJI6JGPN.js";
3
+ } from "./chunk-HSX32G47.js";
4
+ import "./chunk-JXFNERK4.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-NYZSY5QJ.js";
7
- import "./chunk-SJI6JGPN.js";
6
+ } from "./chunk-ZUBBADMR.js";
7
+ import "./chunk-JXFNERK4.js";
8
8
 
9
9
  // src/mcp/cli.ts
10
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.72.2",
3
+ "version": "4.72.3",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -62,24 +62,24 @@
62
62
  "@types/node-cron": "^3.0.11",
63
63
  "tsup": "^8.5.1",
64
64
  "tsx": "^4.19.0",
65
- "@ainyc/canonry-api-routes": "0.0.0",
66
65
  "@ainyc/canonry-api-client": "0.0.0",
67
66
  "@ainyc/canonry-config": "0.0.0",
67
+ "@ainyc/canonry-api-routes": "0.0.0",
68
68
  "@ainyc/canonry-contracts": "0.0.0",
69
69
  "@ainyc/canonry-db": "0.0.0",
70
- "@ainyc/canonry-integration-bing": "0.0.0",
71
70
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
72
- "@ainyc/canonry-integration-cloud-run": "0.0.0",
73
71
  "@ainyc/canonry-integration-google": "0.0.0",
74
- "@ainyc/canonry-integration-google-places": "0.0.0",
75
72
  "@ainyc/canonry-integration-google-business-profile": "0.0.0",
76
- "@ainyc/canonry-integration-wordpress": "0.0.0",
73
+ "@ainyc/canonry-integration-cloud-run": "0.0.0",
74
+ "@ainyc/canonry-integration-google-places": "0.0.0",
75
+ "@ainyc/canonry-integration-bing": "0.0.0",
77
76
  "@ainyc/canonry-integration-traffic": "0.0.0",
78
- "@ainyc/canonry-intelligence": "0.0.0",
77
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
78
+ "@ainyc/canonry-provider-cdp": "0.0.0",
79
79
  "@ainyc/canonry-provider-claude": "0.0.0",
80
+ "@ainyc/canonry-intelligence": "0.0.0",
80
81
  "@ainyc/canonry-provider-gemini": "0.0.0",
81
82
  "@ainyc/canonry-provider-local": "0.0.0",
82
- "@ainyc/canonry-provider-cdp": "0.0.0",
83
83
  "@ainyc/canonry-provider-openai": "0.0.0",
84
84
  "@ainyc/canonry-provider-perplexity": "0.0.0"
85
85
  },