@ainyc/canonry 4.55.1 → 4.55.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.
Files changed (24) hide show
  1. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +9 -4
  2. package/assets/assets/{BacklinksPage-DVmaM864.js → BacklinksPage-buvZ4ZOd.js} +1 -1
  3. package/assets/assets/{ProjectPage-DtL3LFne.js → ProjectPage-D0UqSqe7.js} +1 -1
  4. package/assets/assets/{RunRow-BRqiLxj2.js → RunRow-D-DTu1PA.js} +1 -1
  5. package/assets/assets/{RunsPage-UxZ93-cg.js → RunsPage-CrBpgwkO.js} +1 -1
  6. package/assets/assets/{SettingsPage-Cr5_EGbk.js → SettingsPage-Bgsi9tZ2.js} +1 -1
  7. package/assets/assets/{TrafficPage-CUC_lfTe.js → TrafficPage-DAHXrzqz.js} +1 -1
  8. package/assets/assets/TrafficSourceDetailPage-DCcDN3VD.js +1 -0
  9. package/assets/assets/{extract-error-message-DD5MibWI.js → extract-error-message-BGhWiJPr.js} +1 -1
  10. package/assets/assets/{index-nnF1LnyK.js → index-CbDkoDBH.js} +2 -2
  11. package/assets/assets/{index-Bm3JQsW0.css → index-dxdJhCQO.css} +1 -1
  12. package/assets/assets/{server-traffic-DjRISEZ-.js → server-traffic-3xxyOEIX.js} +1 -1
  13. package/assets/assets/{trash-2-CJ5M--Le.js → trash-2-dppRdHYI.js} +1 -1
  14. package/assets/index.html +2 -2
  15. package/dist/{chunk-2OI7HFAB.js → chunk-5EAGNVCJ.js} +131 -188
  16. package/dist/{chunk-ZY3EDW3S.js → chunk-UOQ62KDD.js} +8 -3
  17. package/dist/{chunk-UTM3FPAJ.js → chunk-XB6Y63NI.js} +181 -3
  18. package/dist/{chunk-OFY3Z2F7.js → chunk-XHU35P3S.js} +359 -361
  19. package/dist/cli.js +12 -10
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-NKAEHHJ5.js → intelligence-service-4PT22FED.js} +2 -2
  22. package/dist/mcp.js +2 -2
  23. package/package.json +6 -6
  24. package/assets/assets/TrafficSourceDetailPage-DARPL2TU.js +0 -1
@@ -9,8 +9,10 @@ import {
9
9
  categorizeSourceWithCompetitors,
10
10
  categoryLabel,
11
11
  determineAnswerMentioned,
12
- normalizeProjectDomain
13
- } from "./chunk-OFY3Z2F7.js";
12
+ effectiveDomains,
13
+ normalizeProjectDomain,
14
+ registrableDomain
15
+ } from "./chunk-XHU35P3S.js";
14
16
 
15
17
  // src/intelligence-service.ts
16
18
  import { eq, desc, asc, and, ne, or, inArray } from "drizzle-orm";
@@ -4156,6 +4158,172 @@ function createLogger(module) {
4156
4158
  };
4157
4159
  }
4158
4160
 
4161
+ // src/citation-utils.ts
4162
+ function domainMatches(domain, canonicalDomain) {
4163
+ const normalized = normalizeProjectDomain(canonicalDomain);
4164
+ const d = normalizeProjectDomain(domain);
4165
+ return d === normalized || d.endsWith(`.${normalized}`);
4166
+ }
4167
+ function pickProjectCitedDomain(citedDomains, projectDomains) {
4168
+ for (const cited of citedDomains) {
4169
+ if (projectDomains.some((pd) => domainMatches(cited, pd))) return cited;
4170
+ }
4171
+ return void 0;
4172
+ }
4173
+ function determineCitationState(normalized, domains) {
4174
+ for (const canonicalDomain of domains) {
4175
+ const bareDomain = normalizeProjectDomain(canonicalDomain);
4176
+ if (normalized.citedDomains.some((d) => domainMatches(d, bareDomain))) {
4177
+ return "cited";
4178
+ }
4179
+ const lowerDomain = bareDomain.toLowerCase();
4180
+ for (const source of normalized.groundingSources) {
4181
+ try {
4182
+ const uri = source.uri.toLowerCase();
4183
+ if (lowerDomain.includes(".") && uri.includes(lowerDomain)) {
4184
+ return "cited";
4185
+ }
4186
+ } catch {
4187
+ }
4188
+ if (source.title) {
4189
+ const titleLower = source.title.toLowerCase().replace(/^www\./, "");
4190
+ if (titleLower === lowerDomain || titleLower.endsWith(`.${lowerDomain}`)) {
4191
+ return "cited";
4192
+ }
4193
+ }
4194
+ }
4195
+ }
4196
+ return "not-cited";
4197
+ }
4198
+ function computeCompetitorOverlap(normalized, competitorDomains) {
4199
+ const overlapSet = /* @__PURE__ */ new Set();
4200
+ for (const d of normalized.citedDomains) {
4201
+ for (const cd of competitorDomains) {
4202
+ if (domainMatches(d, cd)) {
4203
+ overlapSet.add(cd);
4204
+ }
4205
+ }
4206
+ }
4207
+ for (const source of normalized.groundingSources) {
4208
+ const uri = source.uri.toLowerCase();
4209
+ for (const cd of competitorDomains) {
4210
+ if (uri.includes(cd.toLowerCase())) {
4211
+ overlapSet.add(cd);
4212
+ }
4213
+ }
4214
+ }
4215
+ if (normalized.answerText) {
4216
+ const lowerAnswer = normalized.answerText.toLowerCase();
4217
+ for (const cd of competitorDomains) {
4218
+ if (lowerAnswer.includes(cd.toLowerCase())) {
4219
+ overlapSet.add(cd);
4220
+ }
4221
+ const brand = brandLabelFromDomain(cd);
4222
+ if (brand.length >= 4 && new RegExp(`\\b${escapeRegExp(brand)}\\b`, "i").test(lowerAnswer)) {
4223
+ overlapSet.add(cd);
4224
+ }
4225
+ }
4226
+ }
4227
+ return [...overlapSet];
4228
+ }
4229
+ function escapeRegExp(value) {
4230
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4231
+ }
4232
+ function extractRecommendedCompetitors(answerText, ownDomains, citedDomains, competitorDomains, ownBrandNames = []) {
4233
+ if (!answerText || answerText.length < 20) return [];
4234
+ const ownBrandKeys = new Set(
4235
+ ownDomains.flatMap((domain) => collectBrandKeysFromDomain(domain))
4236
+ );
4237
+ for (const name of ownBrandNames) {
4238
+ const key = brandKeyFromText(name);
4239
+ if (key.length >= 4) ownBrandKeys.add(key);
4240
+ }
4241
+ const knownCompetitorKeys = new Set(
4242
+ [...citedDomains, ...competitorDomains].flatMap((domain) => collectBrandKeysFromDomain(domain)).filter((key) => !ownBrandKeys.has(key))
4243
+ );
4244
+ if (knownCompetitorKeys.size === 0) return [];
4245
+ const candidatePatterns = [
4246
+ /^\s*(?:[-*]|\d+\.)\s+(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?\s*[:\u2014\u2013-]/gm,
4247
+ /\*\*([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\*\*/g,
4248
+ /^#{1,4}\s+(?:\d+\.\s+)?(?:\*\*)?([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50}?)(?:\*\*)?$/gm,
4249
+ /\[([A-Z0-9][A-Za-z0-9][\w\s.&',/()-]{1,50})\]\(https?:\/\/[^\s)]+\)/g
4250
+ ];
4251
+ const genericKeys = /* @__PURE__ */ new Set([
4252
+ "additional",
4253
+ "best",
4254
+ "benefits",
4255
+ "bottomline",
4256
+ "comparison",
4257
+ "conclusion",
4258
+ "directorylisting",
4259
+ "example",
4260
+ "expertise",
4261
+ "features",
4262
+ "finalthoughts",
4263
+ "howitworks",
4264
+ "important",
4265
+ "keybenefits",
4266
+ "keyfeatures",
4267
+ "major",
4268
+ "note",
4269
+ "notable",
4270
+ "option",
4271
+ "other",
4272
+ "overview",
4273
+ "pricing",
4274
+ "pros",
4275
+ "reviews",
4276
+ "step",
4277
+ "summary",
4278
+ "top",
4279
+ "verdict",
4280
+ "whattolookfor",
4281
+ "whyitmatters",
4282
+ "whyitstandsout",
4283
+ "whywechoseit"
4284
+ ]);
4285
+ const seen = /* @__PURE__ */ new Map();
4286
+ for (const pattern of candidatePatterns) {
4287
+ let match;
4288
+ while ((match = pattern.exec(answerText)) !== null) {
4289
+ const candidate = cleanCandidateName(match[1] ?? "");
4290
+ const candidateKey = brandKeyFromText(candidate);
4291
+ if (!candidateKey) continue;
4292
+ if (genericKeys.has(candidateKey)) continue;
4293
+ if (candidate.split(/\s+/).length > 6) continue;
4294
+ if (matchesBrandKey(candidateKey, ownBrandKeys)) continue;
4295
+ if (!matchesBrandKey(candidateKey, knownCompetitorKeys)) continue;
4296
+ if (!seen.has(candidateKey)) seen.set(candidateKey, candidate);
4297
+ }
4298
+ }
4299
+ return [...seen.values()].slice(0, 10);
4300
+ }
4301
+ function cleanCandidateName(candidate) {
4302
+ return candidate.replace(/^[\s"'`]+|[\s"'`.,:;!?]+$/g, "").replace(/\s+/g, " ").trim();
4303
+ }
4304
+ function collectBrandKeysFromDomain(domain) {
4305
+ const reg = registrableDomain(domain);
4306
+ if (!reg) {
4307
+ const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
4308
+ const fallback = hostname.replace(/[^a-z0-9]/gi, "").toLowerCase();
4309
+ return fallback.length >= 4 ? [fallback] : [];
4310
+ }
4311
+ const keys = /* @__PURE__ */ new Set();
4312
+ const fullKey = reg.replace(/[^a-z0-9]/gi, "").toLowerCase();
4313
+ if (fullKey.length >= 4) keys.add(fullKey);
4314
+ const brand = brandLabelFromDomain(reg).replace(/[^a-z0-9]/gi, "").toLowerCase();
4315
+ if (brand.length >= 4) keys.add(brand);
4316
+ return [...keys];
4317
+ }
4318
+ function matchesBrandKey(candidateKey, brandKeys) {
4319
+ for (const brandKey of brandKeys) {
4320
+ if (candidateKey === brandKey) return true;
4321
+ if (candidateKey.startsWith(brandKey) || candidateKey.endsWith(brandKey)) return true;
4322
+ if (brandKey.startsWith(candidateKey) || brandKey.endsWith(candidateKey)) return true;
4323
+ }
4324
+ return false;
4325
+ }
4326
+
4159
4327
  // src/intelligence-service.ts
4160
4328
  var RECURRENCE_LOOKBACK_RUNS = 5;
4161
4329
  var HISTORY_WINDOW_RUNS = Math.max(PERSISTENT_GAP_THRESHOLD, 5);
@@ -4537,6 +4705,11 @@ var IntelligenceService = class {
4537
4705
  });
4538
4706
  }
4539
4707
  buildRunData(runId, projectId, completedAt, location = null) {
4708
+ const projectDomainRow = this.db.select({ canonicalDomain: projects.canonicalDomain, ownedDomains: projects.ownedDomains }).from(projects).where(eq(projects.id, projectId)).get();
4709
+ const projectDomains = projectDomainRow ? effectiveDomains({
4710
+ canonicalDomain: projectDomainRow.canonicalDomain,
4711
+ ownedDomains: projectDomainRow.ownedDomains
4712
+ }) : [];
4540
4713
  const rows = this.db.select({
4541
4714
  query: queries.query,
4542
4715
  // Denormalized query text persisted by v58 — the fallback when the
@@ -4563,7 +4736,9 @@ var IntelligenceService = class {
4563
4736
  query: resolvedQuery,
4564
4737
  provider: r.provider,
4565
4738
  cited: r.citationState === CitationStates.cited,
4566
- citationUrl: domains[0] ?? void 0,
4739
+ // The project's OWN cited domain — never a co-cited competitor that
4740
+ // happens to sort first in the full citedDomains set.
4741
+ citationUrl: pickProjectCitedDomain(domains, projectDomains),
4567
4742
  // Snapshots carry their own location for downstream detectors. In
4568
4743
  // practice every snapshot in a single runId shares the run's
4569
4744
  // location; the per-row column is the same value duplicated, but
@@ -4629,6 +4804,9 @@ export {
4629
4804
  groupRunsByCreatedAt,
4630
4805
  pickGroupRepresentative,
4631
4806
  filterTrackedSnapshots,
4807
+ determineCitationState,
4808
+ computeCompetitorOverlap,
4809
+ extractRecommendedCompetitors,
4632
4810
  isBlogShapedQuery,
4633
4811
  buildInventory,
4634
4812
  buildContentTargetRows,