@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/assets/index-BuSiyI7z.js +246 -0
- package/assets/index.html +1 -1
- package/dist/{chunk-YW4IZ34Z.js → chunk-5Q6LISDM.js} +187 -47
- package/dist/cli.js +217 -110
- package/dist/index.js +1 -1
- package/package.json +5 -5
- package/assets/assets/index-DmFB_uXa.js +0 -246
package/assets/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-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
|
|
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 ||
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
`${
|
|
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
|
|
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
|
-
`${
|
|
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:
|
|
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
|
};
|