@ainyc/canonry 1.31.0 → 1.32.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/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-DmFB_uXa.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-BuSiyI7z.js"></script>
16
16
  <link rel="stylesheet" crossorigin href="./assets/index-r6biprHB.css">
17
17
  </head>
18
18
  <body>
@@ -784,6 +784,7 @@ var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "fai
784
784
  var runKindSchema = z8.enum(["answer-visibility", "site-audit", "gsc-sync", "inspect-sitemap"]);
785
785
  var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
786
786
  var citationStateSchema = z8.enum(["cited", "not-cited"]);
787
+ var visibilityStateSchema = z8.enum(["visible", "not-visible"]);
787
788
  var computedTransitionSchema = z8.enum(["new", "cited", "lost", "emerging", "not-cited"]);
788
789
  var runDtoSchema = z8.object({
789
790
  id: z8.string(),
@@ -808,6 +809,8 @@ var querySnapshotDtoSchema = z8.object({
808
809
  keyword: z8.string().optional(),
809
810
  provider: providerNameSchema,
810
811
  citationState: citationStateSchema,
812
+ answerMentioned: z8.boolean().optional(),
813
+ visibilityState: visibilityStateSchema.optional(),
811
814
  transition: computedTransitionSchema.optional(),
812
815
  answerText: z8.string().nullable().optional(),
813
816
  citedDomains: z8.array(z8.string()).default([]),
@@ -1071,6 +1074,90 @@ var ga4TrafficSummaryDtoSchema = z11.object({
1071
1074
  lastSyncedAt: z11.string().nullable()
1072
1075
  });
1073
1076
 
1077
+ // ../contracts/src/answer-visibility.ts
1078
+ var GENERIC_TOKENS = /* @__PURE__ */ new Set([
1079
+ "agency",
1080
+ "app",
1081
+ "company",
1082
+ "corp",
1083
+ "group",
1084
+ "health",
1085
+ "inc",
1086
+ "llc",
1087
+ "online",
1088
+ "platform",
1089
+ "services",
1090
+ "site",
1091
+ "solutions",
1092
+ "software",
1093
+ "systems",
1094
+ "tech"
1095
+ ]);
1096
+ function determineAnswerMentioned(answerText, displayName, domains) {
1097
+ if (!answerText) return false;
1098
+ const lowerAnswer = answerText.toLowerCase();
1099
+ for (const domain of domains) {
1100
+ const normalizedDomain = normalizeProjectDomain(domain);
1101
+ if (!normalizedDomain || !normalizedDomain.includes(".")) continue;
1102
+ if (domainMentioned(lowerAnswer, normalizedDomain)) return true;
1103
+ }
1104
+ const normalizedDisplayName = normalizeText(displayName);
1105
+ if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
1106
+ return true;
1107
+ }
1108
+ const tokens = collectDistinctiveTokens(displayName, domains);
1109
+ if (tokens.length === 0) return false;
1110
+ let matches = 0;
1111
+ for (const token of tokens) {
1112
+ if (new RegExp(`\\b${escapeRegExp(token)}\\b`).test(lowerAnswer)) {
1113
+ matches++;
1114
+ }
1115
+ }
1116
+ if (tokens.length === 1) {
1117
+ return matches >= 1;
1118
+ }
1119
+ return matches >= Math.min(2, tokens.length);
1120
+ }
1121
+ function visibilityStateFromAnswerMentioned(answerMentioned) {
1122
+ return answerMentioned ? "visible" : "not-visible";
1123
+ }
1124
+ function domainMentioned(lowerAnswer, normalizedDomain) {
1125
+ const escapedDomain = escapeRegExp(normalizedDomain.toLowerCase());
1126
+ const patterns = [
1127
+ new RegExp(`(^|[^a-z0-9-])${escapedDomain}($|[^a-z0-9-])`),
1128
+ new RegExp(`https?://(?:www\\.)?${escapedDomain}(?:[/:?#]|$)`),
1129
+ new RegExp(`www\\.${escapedDomain}(?:[/:?#]|$)`)
1130
+ ];
1131
+ return patterns.some((pattern) => pattern.test(lowerAnswer));
1132
+ }
1133
+ function collectDistinctiveTokens(displayName, domains) {
1134
+ const tokens = /* @__PURE__ */ new Set();
1135
+ for (const token of extractDistinctiveTokens(displayName)) {
1136
+ tokens.add(token);
1137
+ }
1138
+ for (const domain of domains) {
1139
+ const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
1140
+ for (const label of hostname.split(".").filter(Boolean)) {
1141
+ const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
1142
+ if (isDistinctiveToken(token)) tokens.add(token);
1143
+ }
1144
+ }
1145
+ return [...tokens];
1146
+ }
1147
+ function extractDistinctiveTokens(value) {
1148
+ return normalizeText(value).split(" ").filter(isDistinctiveToken);
1149
+ }
1150
+ function isDistinctiveToken(token) {
1151
+ if (token.length < 4) return false;
1152
+ return !GENERIC_TOKENS.has(token);
1153
+ }
1154
+ function normalizeText(value) {
1155
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1156
+ }
1157
+ function escapeRegExp(value) {
1158
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1159
+ }
1160
+
1074
1161
  // ../api-routes/src/auth.ts
1075
1162
  import crypto2 from "crypto";
1076
1163
  import { eq } from "drizzle-orm";
@@ -1165,6 +1252,7 @@ var querySnapshots = sqliteTable("query_snapshots", {
1165
1252
  provider: text("provider").notNull().default("gemini"),
1166
1253
  model: text("model"),
1167
1254
  citationState: text("citation_state").notNull(),
1255
+ answerMentioned: integer("answer_mentioned", { mode: "boolean" }),
1168
1256
  answerText: text("answer_text"),
1169
1257
  citedDomains: text("cited_domains").notNull().default("[]"),
1170
1258
  competitorOverlap: text("competitor_overlap").notNull().default("[]"),
@@ -1731,7 +1819,9 @@ var MIGRATIONS = [
1731
1819
  )`,
1732
1820
  `CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_project_date ON ga_ai_referrals(project_id, date)`,
1733
1821
  `CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_source ON ga_ai_referrals(source)`,
1734
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`
1822
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`,
1823
+ // v18: Answer-level visibility derived from answer text
1824
+ `ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER`
1735
1825
  ];
1736
1826
  function migrate(db) {
1737
1827
  const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
@@ -1840,6 +1930,23 @@ function writeAuditLog(db, entry) {
1840
1930
  createdAt: now
1841
1931
  }).run();
1842
1932
  }
1933
+ function resolveSnapshotAnswerMentioned(snapshot, project) {
1934
+ if (typeof snapshot.answerMentioned === "boolean") {
1935
+ return snapshot.answerMentioned;
1936
+ }
1937
+ return determineAnswerMentioned(snapshot.answerText, project.displayName, effectiveDomains({
1938
+ canonicalDomain: project.canonicalDomain,
1939
+ ownedDomains: normalizeOwnedDomains(project.ownedDomains)
1940
+ }));
1941
+ }
1942
+ function resolveSnapshotVisibilityState(snapshot, project) {
1943
+ return visibilityStateFromAnswerMentioned(resolveSnapshotAnswerMentioned(snapshot, project));
1944
+ }
1945
+ function normalizeOwnedDomains(value) {
1946
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
1947
+ const parsed = parseJsonColumn(typeof value === "string" ? value : null, []);
1948
+ return parsed.filter((item) => typeof item === "string");
1949
+ }
1843
1950
 
1844
1951
  // ../api-routes/src/projects.ts
1845
1952
  async function projectRoutes(app, opts) {
@@ -2498,6 +2605,11 @@ async function runRoutes(app, opts) {
2498
2605
  app.get("/runs/:id", async (request, reply) => {
2499
2606
  const run = app.db.select().from(runs).where(eq7(runs.id, request.params.id)).get();
2500
2607
  if (!run) throw notFound("Run", request.params.id);
2608
+ const project = app.db.select({
2609
+ displayName: projects.displayName,
2610
+ canonicalDomain: projects.canonicalDomain,
2611
+ ownedDomains: projects.ownedDomains
2612
+ }).from(projects).where(eq7(projects.id, run.projectId)).get();
2501
2613
  const snapshots = app.db.select({
2502
2614
  id: querySnapshots.id,
2503
2615
  runId: querySnapshots.runId,
@@ -2506,6 +2618,7 @@ async function runRoutes(app, opts) {
2506
2618
  provider: querySnapshots.provider,
2507
2619
  model: querySnapshots.model,
2508
2620
  citationState: querySnapshots.citationState,
2621
+ answerMentioned: querySnapshots.answerMentioned,
2509
2622
  answerText: querySnapshots.answerText,
2510
2623
  citedDomains: querySnapshots.citedDomains,
2511
2624
  competitorOverlap: querySnapshots.competitorOverlap,
@@ -2518,6 +2631,7 @@ async function runRoutes(app, opts) {
2518
2631
  ...formatRun(run),
2519
2632
  snapshots: snapshots.map((s) => {
2520
2633
  const rawParsed = parseSnapshotRawResponse(s.rawResponse);
2634
+ const answerMentioned = project ? resolveSnapshotAnswerMentioned(s, project) : s.answerMentioned ?? false;
2521
2635
  return {
2522
2636
  id: s.id,
2523
2637
  runId: s.runId,
@@ -2525,6 +2639,8 @@ async function runRoutes(app, opts) {
2525
2639
  keyword: s.keyword,
2526
2640
  provider: s.provider,
2527
2641
  citationState: s.citationState,
2642
+ answerMentioned,
2643
+ visibilityState: project ? resolveSnapshotVisibilityState(s, project) : answerMentioned ? "visible" : "not-visible",
2528
2644
  answerText: s.answerText,
2529
2645
  citedDomains: parseJsonColumn(s.citedDomains, []),
2530
2646
  competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
@@ -3115,6 +3231,7 @@ async function historyRoutes(app) {
3115
3231
  provider: querySnapshots.provider,
3116
3232
  model: querySnapshots.model,
3117
3233
  citationState: querySnapshots.citationState,
3234
+ answerMentioned: querySnapshots.answerMentioned,
3118
3235
  answerText: querySnapshots.answerText,
3119
3236
  citedDomains: querySnapshots.citedDomains,
3120
3237
  competitorOverlap: querySnapshots.competitorOverlap,
@@ -3135,6 +3252,8 @@ async function historyRoutes(app) {
3135
3252
  provider: s.provider,
3136
3253
  model: s.model,
3137
3254
  citationState: s.citationState,
3255
+ answerMentioned: resolveSnapshotAnswerMentioned(s, project),
3256
+ visibilityState: resolveSnapshotVisibilityState(s, project),
3138
3257
  answerText: s.answerText,
3139
3258
  citedDomains: parseJsonColumn(s.citedDomains, []),
3140
3259
  competitorOverlap: parseJsonColumn(s.competitorOverlap, []),
@@ -3155,12 +3274,16 @@ async function historyRoutes(app) {
3155
3274
  const runIds = new Set(projectRuns.map((r) => r.id));
3156
3275
  const rawSnapshots = app.db.select().from(querySnapshots).where(inArray(querySnapshots.runId, [...runIds])).all();
3157
3276
  const timelineLocationFilter = request.query.location;
3158
- const allSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
3277
+ const filteredSnapshots = timelineLocationFilter !== void 0 ? rawSnapshots.filter((s) => s.location === (timelineLocationFilter || null)) : rawSnapshots;
3278
+ const allSnapshots = filteredSnapshots.map((snapshot) => ({
3279
+ ...snapshot,
3280
+ answerMentioned: resolveSnapshotAnswerMentioned(snapshot, project)
3281
+ }));
3159
3282
  const deduped = /* @__PURE__ */ new Map();
3160
3283
  for (const snap of allSnapshots) {
3161
3284
  const key = `${snap.runId}:${snap.keywordId}`;
3162
3285
  const existing = deduped.get(key);
3163
- if (!existing || snap.citationState === "cited") {
3286
+ if (!existing || !existing.answerMentioned && snap.answerMentioned || existing.answerMentioned === snap.answerMentioned && snap.citationState === "cited") {
3164
3287
  deduped.set(key, snap);
3165
3288
  }
3166
3289
  }
@@ -3183,8 +3306,10 @@ async function historyRoutes(app) {
3183
3306
  return snaps.map((snap, idx) => {
3184
3307
  const run = projectRuns.find((r) => r.id === snap.runId);
3185
3308
  let transition = snap.citationState === "cited" ? "cited" : "not-cited";
3309
+ let visibilityTransition = snap.answerMentioned ? "visible" : "not-visible";
3186
3310
  if (idx === 0) {
3187
3311
  transition = "new";
3312
+ visibilityTransition = "new";
3188
3313
  } else {
3189
3314
  const prev = snaps[idx - 1];
3190
3315
  if (prev.citationState === "not-cited" && snap.citationState === "cited") {
@@ -3192,12 +3317,20 @@ async function historyRoutes(app) {
3192
3317
  } else if (prev.citationState === "cited" && snap.citationState === "not-cited") {
3193
3318
  transition = "lost";
3194
3319
  }
3320
+ if (!prev.answerMentioned && snap.answerMentioned) {
3321
+ visibilityTransition = "emerging";
3322
+ } else if (prev.answerMentioned && !snap.answerMentioned) {
3323
+ visibilityTransition = "lost";
3324
+ }
3195
3325
  }
3196
3326
  return {
3197
3327
  runId: snap.runId,
3198
3328
  createdAt: run?.createdAt ?? snap.createdAt,
3199
3329
  citationState: snap.citationState,
3200
- transition
3330
+ transition,
3331
+ answerMentioned: snap.answerMentioned,
3332
+ visibilityState: snap.answerMentioned ? "visible" : "not-visible",
3333
+ visibilityTransition
3201
3334
  };
3202
3335
  });
3203
3336
  }
@@ -3228,7 +3361,7 @@ async function historyRoutes(app) {
3228
3361
  return reply.send(timeline);
3229
3362
  });
3230
3363
  app.get("/projects/:name/snapshots/diff", async (request, reply) => {
3231
- resolveProject(app.db, request.params.name);
3364
+ const project = resolveProject(app.db, request.params.name);
3232
3365
  const { run1, run2 } = request.query;
3233
3366
  if (!run1 || !run2) {
3234
3367
  throw validationError("Both run1 and run2 query params are required");
@@ -3236,22 +3369,40 @@ async function historyRoutes(app) {
3236
3369
  const snaps1 = app.db.select({
3237
3370
  keywordId: querySnapshots.keywordId,
3238
3371
  keyword: keywords.keyword,
3239
- citationState: querySnapshots.citationState
3372
+ citationState: querySnapshots.citationState,
3373
+ answerMentioned: querySnapshots.answerMentioned,
3374
+ answerText: querySnapshots.answerText
3240
3375
  }).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run1)).all();
3241
3376
  const snaps2 = app.db.select({
3242
3377
  keywordId: querySnapshots.keywordId,
3243
3378
  keyword: keywords.keyword,
3244
- citationState: querySnapshots.citationState
3379
+ citationState: querySnapshots.citationState,
3380
+ answerMentioned: querySnapshots.answerMentioned,
3381
+ answerText: querySnapshots.answerText
3245
3382
  }).from(querySnapshots).leftJoin(keywords, eq9(querySnapshots.keywordId, keywords.id)).where(eq9(querySnapshots.runId, run2)).all();
3246
3383
  const map1 = /* @__PURE__ */ new Map();
3247
3384
  for (const s of snaps1) {
3385
+ const resolved = {
3386
+ ...s,
3387
+ resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
3388
+ resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
3389
+ };
3248
3390
  const existing = map1.get(s.keywordId);
3249
- if (!existing || s.citationState === "cited") map1.set(s.keywordId, s);
3391
+ if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
3392
+ map1.set(s.keywordId, resolved);
3393
+ }
3250
3394
  }
3251
3395
  const map2 = /* @__PURE__ */ new Map();
3252
3396
  for (const s of snaps2) {
3397
+ const resolved = {
3398
+ ...s,
3399
+ resolvedAnswerMentioned: resolveSnapshotAnswerMentioned(s, project),
3400
+ resolvedVisibilityState: resolveSnapshotVisibilityState(s, project)
3401
+ };
3253
3402
  const existing = map2.get(s.keywordId);
3254
- if (!existing || s.citationState === "cited") map2.set(s.keywordId, s);
3403
+ if (!existing || !existing.resolvedAnswerMentioned && resolved.resolvedAnswerMentioned || existing.resolvedAnswerMentioned === resolved.resolvedAnswerMentioned && resolved.citationState === "cited") {
3404
+ map2.set(s.keywordId, resolved);
3405
+ }
3255
3406
  }
3256
3407
  const allKeywordIds = /* @__PURE__ */ new Set([...map1.keys(), ...map2.keys()]);
3257
3408
  const diff = [...allKeywordIds].map((kwId) => {
@@ -3262,7 +3413,12 @@ async function historyRoutes(app) {
3262
3413
  keyword: s2?.keyword ?? s1?.keyword ?? null,
3263
3414
  run1State: s1?.citationState ?? null,
3264
3415
  run2State: s2?.citationState ?? null,
3265
- changed: (s1?.citationState ?? null) !== (s2?.citationState ?? null)
3416
+ run1AnswerMentioned: s1?.resolvedAnswerMentioned ?? null,
3417
+ run2AnswerMentioned: s2?.resolvedAnswerMentioned ?? null,
3418
+ run1VisibilityState: s1?.resolvedVisibilityState ?? null,
3419
+ run2VisibilityState: s2?.resolvedVisibilityState ?? null,
3420
+ changed: (s1?.citationState ?? null) !== (s2?.citationState ?? null),
3421
+ visibilityChanged: (s1?.resolvedAnswerMentioned ?? null) !== (s2?.resolvedAnswerMentioned ?? null)
3266
3422
  };
3267
3423
  });
3268
3424
  return reply.send({ run1, run2, diff });
@@ -8919,12 +9075,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
8919
9075
  var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
8920
9076
  function stripCanonrySchema(content) {
8921
9077
  const regex = new RegExp(
8922
- `${escapeRegExp(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp(CANONRY_SCHEMA_END)}`,
9078
+ `${escapeRegExp2(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp2(CANONRY_SCHEMA_END)}`,
8923
9079
  "g"
8924
9080
  );
8925
9081
  return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
8926
9082
  }
8927
- function escapeRegExp(str) {
9083
+ function escapeRegExp2(str) {
8928
9084
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8929
9085
  }
8930
9086
  function injectCanonrySchema(content, schemas) {
@@ -9031,7 +9187,7 @@ async function getSchemaStatus(connection, env) {
9031
9187
  const thirdPartySchemas = [];
9032
9188
  if (hasCanonryMarker) {
9033
9189
  const markerRegex = new RegExp(
9034
- `${escapeRegExp(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp(CANONRY_SCHEMA_END)}`
9190
+ `${escapeRegExp2(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp2(CANONRY_SCHEMA_END)}`
9035
9191
  );
9036
9192
  const match = markerRegex.exec(rawContent);
9037
9193
  if (match?.[1]) {
@@ -12176,6 +12332,11 @@ var JobRunner = class {
12176
12332
  const normalized = adapter.normalizeResult(raw);
12177
12333
  log.info("query.result", { runId, provider: providerName, keyword: kw.keyword, citedDomains: normalized.citedDomains, groundingSources: normalized.groundingSources.map((s) => s.uri), matchDomains: allDomains });
12178
12334
  const citationState = determineCitationState(normalized, allDomains);
12335
+ const answerMentioned = determineAnswerMentioned(
12336
+ normalized.answerText,
12337
+ project.displayName,
12338
+ allDomains
12339
+ );
12179
12340
  const overlap = computeCompetitorOverlap(normalized, competitorDomains);
12180
12341
  const extractedCompetitors = extractRecommendedCompetitors(
12181
12342
  normalized.answerText,
@@ -12198,6 +12359,7 @@ var JobRunner = class {
12198
12359
  provider: providerName,
12199
12360
  model: raw.model,
12200
12361
  citationState,
12362
+ answerMentioned,
12201
12363
  answerText: normalized.answerText,
12202
12364
  citedDomains: JSON.stringify(normalized.citedDomains),
12203
12365
  competitorOverlap: JSON.stringify(overlap),
@@ -12220,6 +12382,7 @@ var JobRunner = class {
12220
12382
  provider: providerName,
12221
12383
  model: raw.model,
12222
12384
  citationState,
12385
+ answerMentioned,
12223
12386
  answerText: normalized.answerText,
12224
12387
  citedDomains: JSON.stringify(normalized.citedDomains),
12225
12388
  competitorOverlap: JSON.stringify(overlap),
@@ -12235,7 +12398,7 @@ var JobRunner = class {
12235
12398
  }).run();
12236
12399
  }
12237
12400
  totalSnapshotsInserted++;
12238
- log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState });
12401
+ log.info("query.citation", { runId, provider: providerName, keyword: kw.keyword, citationState, answerMentioned });
12239
12402
  });
12240
12403
  } catch (err) {
12241
12404
  if (err instanceof RunCancelledError) {
@@ -13561,11 +13724,12 @@ var SnapshotService = class {
13561
13724
  manualCompetitors: ctx.manualCompetitors,
13562
13725
  targetDomain: ctx.domain
13563
13726
  });
13727
+ const answerVisibilityDomains = [ctx.domain];
13564
13728
  return {
13565
13729
  provider: provider.adapter.name,
13566
13730
  displayName: provider.adapter.displayName,
13567
13731
  model: raw.model,
13568
- mentioned: mentionsTargetCompany(normalized.answerText, ctx.companyName, ctx.domain),
13732
+ mentioned: determineAnswerMentioned(normalized.answerText, ctx.companyName, answerVisibilityDomains),
13569
13733
  cited: citesTargetDomain(normalized.citedDomains, normalized.groundingSources, ctx.domain),
13570
13734
  describedAccurately: "unknown",
13571
13735
  accuracyNotes: null,
@@ -13822,26 +13986,6 @@ function buildFallbackRecommendedActions(audit) {
13822
13986
  ];
13823
13987
  return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
13824
13988
  }
13825
- function mentionsTargetCompany(answerText, companyName, domain) {
13826
- const haystack = normalizeText(answerText);
13827
- if (!haystack) return false;
13828
- const fullName = normalizeText(companyName);
13829
- if (fullName && haystack.includes(fullName)) {
13830
- return true;
13831
- }
13832
- const targetTokens = uniqueStrings([
13833
- ...extractDistinctiveTokens(companyName),
13834
- ...extractDistinctiveTokens(extractHostname2(domain).split(".")[0] ?? "")
13835
- ]);
13836
- if (targetTokens.length === 0) return false;
13837
- let matches = 0;
13838
- for (const token of targetTokens) {
13839
- if (new RegExp(`\\b${escapeRegExp2(token)}\\b`, "i").test(answerText)) {
13840
- matches++;
13841
- }
13842
- }
13843
- return matches >= Math.min(2, targetTokens.length) || matches >= 1 && targetTokens.length === 1;
13844
- }
13845
13989
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
13846
13990
  const normalizedTarget = extractHostname2(targetDomain);
13847
13991
  for (const domain of citedDomains) {
@@ -13912,9 +14056,6 @@ function parseJsonObject(input) {
13912
14056
  const json = start >= 0 && end >= start ? candidate.slice(start, end + 1) : candidate;
13913
14057
  return JSON.parse(json);
13914
14058
  }
13915
- function normalizeText(value) {
13916
- return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
13917
- }
13918
14059
  function normalizeStringList(values) {
13919
14060
  const items = values.flatMap((value) => value.split(","));
13920
14061
  return uniqueStrings(
@@ -13945,9 +14086,6 @@ function domainMatches2(candidate, target) {
13945
14086
  const normalizedTarget = normalizeDomain(target);
13946
14087
  return normalizedCandidate === normalizedTarget || normalizedCandidate.endsWith(`.${normalizedTarget}`);
13947
14088
  }
13948
- function extractDistinctiveTokens(value) {
13949
- return normalizeText(value).split(" ").filter((token) => token.length >= 4).filter((token) => !["llc", "inc", "corp", "company", "group", "services", "solutions", "agency"].includes(token));
13950
- }
13951
14089
  function isDomainLike(value) {
13952
14090
  const normalized = normalizeDomain(value);
13953
14091
  return normalized.includes(".") && !normalized.includes(" ");
@@ -13956,9 +14094,6 @@ function clipText(value, length) {
13956
14094
  if (value.length <= length) return value;
13957
14095
  return `${value.slice(0, length - 3)}...`;
13958
14096
  }
13959
- function escapeRegExp2(value) {
13960
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13961
- }
13962
14097
 
13963
14098
  // src/server.ts
13964
14099
  var _require2 = createRequire2(import.meta.url);
@@ -14725,14 +14860,19 @@ export {
14725
14860
  isFirstRun,
14726
14861
  showFirstRunNotice,
14727
14862
  trackEvent,
14863
+ projects,
14864
+ runs,
14865
+ querySnapshots,
14866
+ apiKeys,
14867
+ createClient,
14868
+ parseJsonColumn,
14869
+ migrate,
14728
14870
  providerQuotaPolicySchema,
14729
14871
  resolveProviderInput,
14730
14872
  notificationEventSchema,
14731
14873
  effectiveDomains,
14874
+ determineAnswerMentioned,
14732
14875
  setGoogleAuthConfig,
14733
14876
  formatAuditFactorScore,
14734
- apiKeys,
14735
- createClient,
14736
- migrate,
14737
14877
  createServer
14738
14878
  };