@ainyc/canonry 3.6.4 → 4.1.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.
- package/README.md +10 -10
- package/assets/agent-workspace/AGENTS.md +4 -4
- package/assets/agent-workspace/USER.md +1 -1
- package/assets/agent-workspace/skills/aero/SKILL.md +4 -4
- package/assets/agent-workspace/skills/aero/references/memory-patterns.md +3 -3
- package/assets/agent-workspace/skills/aero/references/orchestration.md +6 -6
- package/assets/agent-workspace/skills/aero/references/regression-playbook.md +7 -7
- package/assets/agent-workspace/skills/aero/references/reporting.md +8 -8
- package/assets/agent-workspace/skills/aero/soul.md +1 -1
- package/assets/agent-workspace/skills/canonry-setup/SKILL.md +5 -5
- package/assets/agent-workspace/skills/canonry-setup/references/aeo-analysis.md +15 -15
- package/assets/agent-workspace/skills/canonry-setup/references/canonry-cli.md +8 -8
- package/assets/assets/index-BbhhYPML.js +302 -0
- package/assets/assets/index-D7T5wSBj.css +1 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-GP2P2WPS.js → chunk-AXMSAMKN.js} +113 -50
- package/dist/{chunk-JMVBV3AT.js → chunk-JV6X6AFT.js} +725 -474
- package/dist/{chunk-RQMOJEJT.js → chunk-KCETXLDF.js} +106 -16
- package/dist/{chunk-O7EVT3AF.js → chunk-O5JZQUPX.js} +71 -33
- package/dist/cli.js +484 -203
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-NL4BG3SM.js → intelligence-service-WPY4PDBU.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +8 -8
- package/assets/assets/index-C9XiA1Ol.js +0 -302
- package/assets/assets/index-D3wFrrZA.css +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CitationStates,
|
|
2
3
|
ContentActions,
|
|
3
4
|
RunKinds,
|
|
4
5
|
__export
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-O5JZQUPX.js";
|
|
6
7
|
|
|
7
8
|
// src/intelligence-service.ts
|
|
8
9
|
import { eq, desc, asc, and, or, inArray } from "drizzle-orm";
|
|
@@ -40,10 +41,10 @@ __export(schema_exports, {
|
|
|
40
41
|
gscUrlInspections: () => gscUrlInspections,
|
|
41
42
|
healthSnapshots: () => healthSnapshots,
|
|
42
43
|
insights: () => insights,
|
|
43
|
-
keywords: () => keywords,
|
|
44
44
|
migrationsTable: () => migrationsTable,
|
|
45
45
|
notifications: () => notifications,
|
|
46
46
|
projects: () => projects,
|
|
47
|
+
queries: () => queries,
|
|
47
48
|
querySnapshots: () => querySnapshots,
|
|
48
49
|
runs: () => runs,
|
|
49
50
|
schedules: () => schedules,
|
|
@@ -69,14 +70,14 @@ var projects = sqliteTable("projects", {
|
|
|
69
70
|
createdAt: text("created_at").notNull(),
|
|
70
71
|
updatedAt: text("updated_at").notNull()
|
|
71
72
|
});
|
|
72
|
-
var
|
|
73
|
+
var queries = sqliteTable("queries", {
|
|
73
74
|
id: text("id").primaryKey(),
|
|
74
75
|
projectId: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
|
|
75
|
-
|
|
76
|
+
query: text("query").notNull(),
|
|
76
77
|
createdAt: text("created_at").notNull()
|
|
77
78
|
}, (table) => [
|
|
78
|
-
index("
|
|
79
|
-
uniqueIndex("
|
|
79
|
+
index("idx_queries_project").on(table.projectId),
|
|
80
|
+
uniqueIndex("idx_queries_project_query").on(table.projectId, table.query)
|
|
80
81
|
]);
|
|
81
82
|
var competitors = sqliteTable("competitors", {
|
|
82
83
|
id: text("id").primaryKey(),
|
|
@@ -105,7 +106,7 @@ var runs = sqliteTable("runs", {
|
|
|
105
106
|
var querySnapshots = sqliteTable("query_snapshots", {
|
|
106
107
|
id: text("id").primaryKey(),
|
|
107
108
|
runId: text("run_id").notNull().references(() => runs.id, { onDelete: "cascade" }),
|
|
108
|
-
|
|
109
|
+
queryId: text("query_id").notNull().references(() => queries.id, { onDelete: "cascade" }),
|
|
109
110
|
provider: text("provider").notNull().default("gemini"),
|
|
110
111
|
model: text("model"),
|
|
111
112
|
citationState: text("citation_state").notNull(),
|
|
@@ -120,7 +121,7 @@ var querySnapshots = sqliteTable("query_snapshots", {
|
|
|
120
121
|
createdAt: text("created_at").notNull()
|
|
121
122
|
}, (table) => [
|
|
122
123
|
index("idx_snapshots_run").on(table.runId),
|
|
123
|
-
index("
|
|
124
|
+
index("idx_snapshots_query").on(table.queryId),
|
|
124
125
|
index("idx_snapshots_citation_state").on(table.citationState),
|
|
125
126
|
index("idx_snapshots_provider_model").on(table.provider, table.model),
|
|
126
127
|
index("idx_snapshots_location").on(table.location),
|
|
@@ -428,7 +429,7 @@ var insights = sqliteTable("insights", {
|
|
|
428
429
|
type: text("type").notNull(),
|
|
429
430
|
severity: text("severity").notNull(),
|
|
430
431
|
title: text("title").notNull(),
|
|
431
|
-
|
|
432
|
+
query: text("query").notNull(),
|
|
432
433
|
provider: text("provider").notNull(),
|
|
433
434
|
recommendation: text("recommendation"),
|
|
434
435
|
cause: text("cause"),
|
|
@@ -438,7 +439,7 @@ var insights = sqliteTable("insights", {
|
|
|
438
439
|
index("idx_insights_project").on(table.projectId),
|
|
439
440
|
index("idx_insights_run").on(table.runId),
|
|
440
441
|
index("idx_insights_created").on(table.createdAt),
|
|
441
|
-
index("
|
|
442
|
+
index("idx_insights_query_provider").on(table.query, table.provider)
|
|
442
443
|
]);
|
|
443
444
|
var healthSnapshots = sqliteTable("health_snapshots", {
|
|
444
445
|
id: text("id").primaryKey(),
|
|
@@ -580,12 +581,12 @@ CREATE TABLE IF NOT EXISTS projects (
|
|
|
580
581
|
updated_at TEXT NOT NULL
|
|
581
582
|
);
|
|
582
583
|
|
|
583
|
-
CREATE TABLE IF NOT EXISTS
|
|
584
|
+
CREATE TABLE IF NOT EXISTS queries (
|
|
584
585
|
id TEXT PRIMARY KEY,
|
|
585
586
|
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
586
|
-
|
|
587
|
+
query TEXT NOT NULL,
|
|
587
588
|
created_at TEXT NOT NULL,
|
|
588
|
-
UNIQUE(project_id,
|
|
589
|
+
UNIQUE(project_id, query)
|
|
589
590
|
);
|
|
590
591
|
|
|
591
592
|
CREATE TABLE IF NOT EXISTS competitors (
|
|
@@ -611,7 +612,7 @@ CREATE TABLE IF NOT EXISTS runs (
|
|
|
611
612
|
CREATE TABLE IF NOT EXISTS query_snapshots (
|
|
612
613
|
id TEXT PRIMARY KEY,
|
|
613
614
|
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
|
|
614
|
-
|
|
615
|
+
query_id TEXT NOT NULL REFERENCES queries(id) ON DELETE CASCADE,
|
|
615
616
|
provider TEXT NOT NULL DEFAULT 'gemini',
|
|
616
617
|
citation_state TEXT NOT NULL,
|
|
617
618
|
answer_text TEXT,
|
|
@@ -653,12 +654,12 @@ CREATE TABLE IF NOT EXISTS usage_counters (
|
|
|
653
654
|
UNIQUE(scope, period, metric)
|
|
654
655
|
);
|
|
655
656
|
|
|
656
|
-
CREATE INDEX IF NOT EXISTS
|
|
657
|
+
CREATE INDEX IF NOT EXISTS idx_queries_project ON queries(project_id);
|
|
657
658
|
CREATE INDEX IF NOT EXISTS idx_competitors_project ON competitors(project_id);
|
|
658
659
|
CREATE INDEX IF NOT EXISTS idx_runs_project ON runs(project_id);
|
|
659
660
|
CREATE INDEX IF NOT EXISTS idx_runs_status ON runs(status);
|
|
660
661
|
CREATE INDEX IF NOT EXISTS idx_snapshots_run ON query_snapshots(run_id);
|
|
661
|
-
CREATE INDEX IF NOT EXISTS
|
|
662
|
+
CREATE INDEX IF NOT EXISTS idx_snapshots_query ON query_snapshots(query_id);
|
|
662
663
|
CREATE INDEX IF NOT EXISTS idx_audit_log_project ON audit_log(project_id);
|
|
663
664
|
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at);
|
|
664
665
|
CREATE TABLE IF NOT EXISTS schedules (
|
|
@@ -975,7 +976,7 @@ var MIGRATION_VERSIONS = [
|
|
|
975
976
|
version: 19,
|
|
976
977
|
name: "named-unique-indexes",
|
|
977
978
|
statements: [
|
|
978
|
-
`CREATE UNIQUE INDEX IF NOT EXISTS
|
|
979
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_project_query ON queries(project_id, query)`,
|
|
979
980
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_competitors_project_domain ON competitors(project_id, domain)`,
|
|
980
981
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_schedules_project ON schedules(project_id)`,
|
|
981
982
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_scope_period_metric ON usage_counters(scope, period, metric)`,
|
|
@@ -1020,7 +1021,7 @@ var MIGRATION_VERSIONS = [
|
|
|
1020
1021
|
type TEXT NOT NULL,
|
|
1021
1022
|
severity TEXT NOT NULL,
|
|
1022
1023
|
title TEXT NOT NULL,
|
|
1023
|
-
|
|
1024
|
+
query TEXT NOT NULL,
|
|
1024
1025
|
provider TEXT NOT NULL,
|
|
1025
1026
|
recommendation TEXT,
|
|
1026
1027
|
cause TEXT,
|
|
@@ -1029,7 +1030,7 @@ var MIGRATION_VERSIONS = [
|
|
|
1029
1030
|
)`,
|
|
1030
1031
|
`CREATE INDEX IF NOT EXISTS idx_insights_project ON insights(project_id)`,
|
|
1031
1032
|
`CREATE INDEX IF NOT EXISTS idx_insights_created ON insights(created_at)`,
|
|
1032
|
-
`CREATE INDEX IF NOT EXISTS
|
|
1033
|
+
`CREATE INDEX IF NOT EXISTS idx_insights_query_provider ON insights(query, provider)`
|
|
1033
1034
|
]
|
|
1034
1035
|
},
|
|
1035
1036
|
{
|
|
@@ -1373,6 +1374,26 @@ var MIGRATION_VERSIONS = [
|
|
|
1373
1374
|
`CREATE INDEX IF NOT EXISTS idx_ga_window_summary_run
|
|
1374
1375
|
ON ga_traffic_window_summaries(sync_run_id)`
|
|
1375
1376
|
]
|
|
1377
|
+
},
|
|
1378
|
+
{
|
|
1379
|
+
version: 48,
|
|
1380
|
+
name: "rename-keywords-to-queries",
|
|
1381
|
+
// The actual legacy rename runs before bootstrap SQL so existing DBs never
|
|
1382
|
+
// see new-name indexes before their old columns have been renamed. This
|
|
1383
|
+
// version records the schema cutover and lands the final index names.
|
|
1384
|
+
statements: [
|
|
1385
|
+
`DROP INDEX IF EXISTS idx_keywords_project`,
|
|
1386
|
+
`DROP INDEX IF EXISTS idx_keywords_project_keyword`,
|
|
1387
|
+
`DROP INDEX IF EXISTS idx_snapshots_keyword`,
|
|
1388
|
+
`DROP INDEX IF EXISTS idx_insights_keyword_provider`,
|
|
1389
|
+
`CREATE INDEX IF NOT EXISTS idx_queries_project ON queries(project_id)`,
|
|
1390
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_queries_project_query ON queries(project_id, query)`,
|
|
1391
|
+
`CREATE INDEX IF NOT EXISTS idx_snapshots_query ON query_snapshots(query_id)`,
|
|
1392
|
+
`CREATE INDEX IF NOT EXISTS idx_insights_query_provider ON insights(query, provider)`
|
|
1393
|
+
],
|
|
1394
|
+
run: (tx) => {
|
|
1395
|
+
normalizeLegacyQuerySchema(tx);
|
|
1396
|
+
}
|
|
1376
1397
|
}
|
|
1377
1398
|
];
|
|
1378
1399
|
function isDuplicateColumnError(err) {
|
|
@@ -1387,6 +1408,44 @@ function columnExists(db, table, column) {
|
|
|
1387
1408
|
));
|
|
1388
1409
|
return (rows[0]?.c ?? 0) > 0;
|
|
1389
1410
|
}
|
|
1411
|
+
function tableExists(db, table) {
|
|
1412
|
+
const rows = db.all(sql.raw(
|
|
1413
|
+
`SELECT COUNT(*) as c FROM sqlite_master WHERE type = 'table' AND name = '${table}'`
|
|
1414
|
+
));
|
|
1415
|
+
return (rows[0]?.c ?? 0) > 0;
|
|
1416
|
+
}
|
|
1417
|
+
function tableIsEmpty(db, table) {
|
|
1418
|
+
const rows = db.all(sql.raw(`SELECT COUNT(*) as c FROM ${table}`));
|
|
1419
|
+
return (rows[0]?.c ?? 0) === 0;
|
|
1420
|
+
}
|
|
1421
|
+
function hasLegacyQuerySchema(db) {
|
|
1422
|
+
return tableExists(db, "keywords") || columnExists(db, "query_snapshots", "keyword_id") || columnExists(db, "insights", "keyword");
|
|
1423
|
+
}
|
|
1424
|
+
function normalizeLegacyQuerySchema(db) {
|
|
1425
|
+
if (!hasLegacyQuerySchema(db)) return;
|
|
1426
|
+
if (tableExists(db, "keywords") && tableExists(db, "queries")) {
|
|
1427
|
+
if (!tableIsEmpty(db, "queries")) {
|
|
1428
|
+
throw new Error("Cannot migrate keywords to queries because both tables contain data");
|
|
1429
|
+
}
|
|
1430
|
+
db.run(sql.raw(`DROP TABLE queries`));
|
|
1431
|
+
}
|
|
1432
|
+
db.run(sql.raw(`DROP INDEX IF EXISTS idx_keywords_project`));
|
|
1433
|
+
db.run(sql.raw(`DROP INDEX IF EXISTS idx_keywords_project_keyword`));
|
|
1434
|
+
db.run(sql.raw(`DROP INDEX IF EXISTS idx_snapshots_keyword`));
|
|
1435
|
+
db.run(sql.raw(`DROP INDEX IF EXISTS idx_insights_keyword_provider`));
|
|
1436
|
+
if (tableExists(db, "keywords")) {
|
|
1437
|
+
db.run(sql.raw(`ALTER TABLE keywords RENAME TO queries`));
|
|
1438
|
+
}
|
|
1439
|
+
if (columnExists(db, "queries", "keyword")) {
|
|
1440
|
+
db.run(sql.raw(`ALTER TABLE queries RENAME COLUMN keyword TO query`));
|
|
1441
|
+
}
|
|
1442
|
+
if (columnExists(db, "query_snapshots", "keyword_id")) {
|
|
1443
|
+
db.run(sql.raw(`ALTER TABLE query_snapshots RENAME COLUMN keyword_id TO query_id`));
|
|
1444
|
+
}
|
|
1445
|
+
if (columnExists(db, "insights", "keyword")) {
|
|
1446
|
+
db.run(sql.raw(`ALTER TABLE insights RENAME COLUMN keyword TO query`));
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1390
1449
|
function dropColumnIfExists(db, table, column) {
|
|
1391
1450
|
try {
|
|
1392
1451
|
db.run(sql.raw(`ALTER TABLE ${table} DROP COLUMN ${column}`));
|
|
@@ -1466,6 +1525,9 @@ function recordMigration(db, version, name) {
|
|
|
1466
1525
|
db.run(sql`INSERT OR IGNORE INTO _migrations (version, name) VALUES (${version}, ${name})`);
|
|
1467
1526
|
}
|
|
1468
1527
|
function migrate(db) {
|
|
1528
|
+
db.transaction((tx) => {
|
|
1529
|
+
normalizeLegacyQuerySchema(tx);
|
|
1530
|
+
});
|
|
1469
1531
|
const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1470
1532
|
for (const statement of statements) {
|
|
1471
1533
|
db.run(sql.raw(statement));
|
|
@@ -1482,6 +1544,7 @@ function migrate(db) {
|
|
|
1482
1544
|
throw err;
|
|
1483
1545
|
}
|
|
1484
1546
|
}
|
|
1547
|
+
mv.run?.(tx);
|
|
1485
1548
|
recordMigration(tx, mv.version, mv.name);
|
|
1486
1549
|
});
|
|
1487
1550
|
}
|
|
@@ -1493,18 +1556,18 @@ function detectRegressions(currentRun, previousRun) {
|
|
|
1493
1556
|
const previousCited = /* @__PURE__ */ new Map();
|
|
1494
1557
|
for (const snap of previousRun.snapshots) {
|
|
1495
1558
|
if (snap.cited) {
|
|
1496
|
-
previousCited.set(`${snap.
|
|
1559
|
+
previousCited.set(`${snap.query}:${snap.provider}`, {
|
|
1497
1560
|
citationUrl: snap.citationUrl,
|
|
1498
1561
|
position: snap.position
|
|
1499
1562
|
});
|
|
1500
1563
|
}
|
|
1501
1564
|
}
|
|
1502
1565
|
for (const snap of currentRun.snapshots) {
|
|
1503
|
-
const key = `${snap.
|
|
1566
|
+
const key = `${snap.query}:${snap.provider}`;
|
|
1504
1567
|
if (!snap.cited && previousCited.has(key)) {
|
|
1505
1568
|
const prev = previousCited.get(key);
|
|
1506
1569
|
regressions.push({
|
|
1507
|
-
|
|
1570
|
+
query: snap.query,
|
|
1508
1571
|
provider: snap.provider,
|
|
1509
1572
|
previousCitationUrl: prev.citationUrl,
|
|
1510
1573
|
previousPosition: prev.position,
|
|
@@ -1522,14 +1585,14 @@ function detectGains(currentRun, previousRun) {
|
|
|
1522
1585
|
const previousCited = /* @__PURE__ */ new Set();
|
|
1523
1586
|
for (const snap of previousRun.snapshots) {
|
|
1524
1587
|
if (snap.cited) {
|
|
1525
|
-
previousCited.add(`${snap.
|
|
1588
|
+
previousCited.add(`${snap.query}:${snap.provider}`);
|
|
1526
1589
|
}
|
|
1527
1590
|
}
|
|
1528
1591
|
for (const snap of currentRun.snapshots) {
|
|
1529
|
-
const key = `${snap.
|
|
1592
|
+
const key = `${snap.query}:${snap.provider}`;
|
|
1530
1593
|
if (snap.cited && !previousCited.has(key)) {
|
|
1531
1594
|
gains.push({
|
|
1532
|
-
|
|
1595
|
+
query: snap.query,
|
|
1533
1596
|
provider: snap.provider,
|
|
1534
1597
|
citationUrl: snap.citationUrl,
|
|
1535
1598
|
position: snap.position,
|
|
@@ -1588,18 +1651,18 @@ function computeHealthTrend(runs2) {
|
|
|
1588
1651
|
// ../intelligence/src/causes.ts
|
|
1589
1652
|
function analyzeCause(regression, currentSnapshots) {
|
|
1590
1653
|
const currentSnap = currentSnapshots.find(
|
|
1591
|
-
(s) => s.
|
|
1654
|
+
(s) => s.query === regression.query && s.provider === regression.provider && !s.cited && s.competitorDomain
|
|
1592
1655
|
);
|
|
1593
1656
|
if (currentSnap) {
|
|
1594
1657
|
return {
|
|
1595
1658
|
cause: "competitor_gain",
|
|
1596
1659
|
competitorDomain: currentSnap.competitorDomain,
|
|
1597
|
-
details: `Competitor ${currentSnap.competitorDomain} now cited for "${regression.
|
|
1660
|
+
details: `Competitor ${currentSnap.competitorDomain} now cited for "${regression.query}" on ${regression.provider}`
|
|
1598
1661
|
};
|
|
1599
1662
|
}
|
|
1600
1663
|
return {
|
|
1601
1664
|
cause: "unknown",
|
|
1602
|
-
details: `No specific cause identified for loss of "${regression.
|
|
1665
|
+
details: `No specific cause identified for loss of "${regression.query}" on ${regression.provider}`
|
|
1603
1666
|
};
|
|
1604
1667
|
}
|
|
1605
1668
|
|
|
@@ -1609,14 +1672,14 @@ function generateInsights(regressions, gains, health, causes) {
|
|
|
1609
1672
|
const insights2 = [];
|
|
1610
1673
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1611
1674
|
for (const reg of regressions) {
|
|
1612
|
-
const key = `${reg.
|
|
1675
|
+
const key = `${reg.query}:${reg.provider}`;
|
|
1613
1676
|
const cause = causes.get(key);
|
|
1614
1677
|
insights2.push({
|
|
1615
1678
|
id: `ins_${randomUUID().slice(0, 8)}`,
|
|
1616
1679
|
type: "regression",
|
|
1617
1680
|
severity: "high",
|
|
1618
|
-
title: `Lost ${reg.provider} citation for "${reg.
|
|
1619
|
-
|
|
1681
|
+
title: `Lost ${reg.provider} citation for "${reg.query}"`,
|
|
1682
|
+
query: reg.query,
|
|
1620
1683
|
provider: reg.provider,
|
|
1621
1684
|
recommendation: {
|
|
1622
1685
|
action: "audit",
|
|
@@ -1632,8 +1695,8 @@ function generateInsights(regressions, gains, health, causes) {
|
|
|
1632
1695
|
id: `ins_${randomUUID().slice(0, 8)}`,
|
|
1633
1696
|
type: "gain",
|
|
1634
1697
|
severity: "low",
|
|
1635
|
-
title: `New ${gain.provider} citation for "${gain.
|
|
1636
|
-
|
|
1698
|
+
title: `New ${gain.provider} citation for "${gain.query}"`,
|
|
1699
|
+
query: gain.query,
|
|
1637
1700
|
provider: gain.provider,
|
|
1638
1701
|
recommendation: {
|
|
1639
1702
|
action: "monitor",
|
|
@@ -1655,7 +1718,7 @@ function analyzeRuns(currentRun, previousRun, allRuns) {
|
|
|
1655
1718
|
const causes = /* @__PURE__ */ new Map();
|
|
1656
1719
|
for (const reg of regressions) {
|
|
1657
1720
|
const cause = analyzeCause(reg, currentRun.snapshots);
|
|
1658
|
-
causes.set(`${reg.
|
|
1721
|
+
causes.set(`${reg.query}:${reg.provider}`, cause);
|
|
1659
1722
|
}
|
|
1660
1723
|
const insights2 = generateInsights(regressions, gains, health, causes);
|
|
1661
1724
|
return {
|
|
@@ -2027,7 +2090,7 @@ function classifyRegressionSeverity(signals) {
|
|
|
2027
2090
|
}
|
|
2028
2091
|
|
|
2029
2092
|
// ../intelligence/src/insight-grouping.ts
|
|
2030
|
-
function groupInsights(insights2, keyFn = (i) => `${i.
|
|
2093
|
+
function groupInsights(insights2, keyFn = (i) => `${i.query} ${i.provider} ${i.type}`) {
|
|
2031
2094
|
const order = [];
|
|
2032
2095
|
const buckets = /* @__PURE__ */ new Map();
|
|
2033
2096
|
for (const i of insights2) {
|
|
@@ -2254,10 +2317,10 @@ var IntelligenceService = class {
|
|
|
2254
2317
|
}
|
|
2255
2318
|
persistResult(result, runId, projectId) {
|
|
2256
2319
|
const previouslyDismissed = /* @__PURE__ */ new Set();
|
|
2257
|
-
const existingInsights = this.db.select({
|
|
2320
|
+
const existingInsights = this.db.select({ query: insights.query, provider: insights.provider, type: insights.type, dismissed: insights.dismissed }).from(insights).where(eq(insights.runId, runId)).all();
|
|
2258
2321
|
for (const row of existingInsights) {
|
|
2259
2322
|
if (row.dismissed) {
|
|
2260
|
-
previouslyDismissed.add(`${row.
|
|
2323
|
+
previouslyDismissed.add(`${row.query}:${row.provider}:${row.type}`);
|
|
2261
2324
|
}
|
|
2262
2325
|
}
|
|
2263
2326
|
this.db.transaction((tx) => {
|
|
@@ -2265,7 +2328,7 @@ var IntelligenceService = class {
|
|
|
2265
2328
|
tx.delete(healthSnapshots).where(eq(healthSnapshots.runId, runId)).run();
|
|
2266
2329
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2267
2330
|
for (const insight of result.insights) {
|
|
2268
|
-
const wasDismissed = previouslyDismissed.has(`${insight.
|
|
2331
|
+
const wasDismissed = previouslyDismissed.has(`${insight.query}:${insight.provider}:${insight.type}`);
|
|
2269
2332
|
tx.insert(insights).values({
|
|
2270
2333
|
id: insight.id,
|
|
2271
2334
|
projectId,
|
|
@@ -2273,7 +2336,7 @@ var IntelligenceService = class {
|
|
|
2273
2336
|
type: insight.type,
|
|
2274
2337
|
severity: insight.severity,
|
|
2275
2338
|
title: insight.title,
|
|
2276
|
-
|
|
2339
|
+
query: insight.query,
|
|
2277
2340
|
provider: insight.provider,
|
|
2278
2341
|
recommendation: insight.recommendation ? JSON.stringify(insight.recommendation) : null,
|
|
2279
2342
|
cause: insight.cause ? JSON.stringify(insight.cause) : null,
|
|
@@ -2315,10 +2378,10 @@ var IntelligenceService = class {
|
|
|
2315
2378
|
if (regressions.length === 0) return rawInsights;
|
|
2316
2379
|
const gscRows = this.db.select({ query: gscSearchData.query, impressions: gscSearchData.impressions }).from(gscSearchData).where(eq(gscSearchData.projectId, projectId)).all();
|
|
2317
2380
|
const gscConnected = gscRows.length > 0;
|
|
2318
|
-
const
|
|
2381
|
+
const gscImpressionsByQuery = /* @__PURE__ */ new Map();
|
|
2319
2382
|
for (const row of gscRows) {
|
|
2320
2383
|
const key = row.query.toLowerCase();
|
|
2321
|
-
|
|
2384
|
+
gscImpressionsByQuery.set(key, (gscImpressionsByQuery.get(key) ?? 0) + row.impressions);
|
|
2322
2385
|
}
|
|
2323
2386
|
const recentRunIds = this.db.select({ id: runs.id }).from(runs).where(
|
|
2324
2387
|
and(
|
|
@@ -2330,16 +2393,16 @@ var IntelligenceService = class {
|
|
|
2330
2393
|
const haveHistory = recentRunIds.length > 0;
|
|
2331
2394
|
const priorRegressionsByPair = /* @__PURE__ */ new Map();
|
|
2332
2395
|
if (haveHistory) {
|
|
2333
|
-
const priorRows = this.db.select({
|
|
2396
|
+
const priorRows = this.db.select({ query: insights.query, provider: insights.provider }).from(insights).where(and(eq(insights.type, "regression"), inArray(insights.runId, recentRunIds))).all();
|
|
2334
2397
|
for (const row of priorRows) {
|
|
2335
|
-
const key = `${row.
|
|
2398
|
+
const key = `${row.query}:${row.provider}`;
|
|
2336
2399
|
priorRegressionsByPair.set(key, (priorRegressionsByPair.get(key) ?? 0) + 1);
|
|
2337
2400
|
}
|
|
2338
2401
|
}
|
|
2339
2402
|
return rawInsights.map((insight) => {
|
|
2340
2403
|
if (insight.type !== "regression") return insight;
|
|
2341
|
-
const gscImpressions = gscConnected ?
|
|
2342
|
-
const recurrenceCount = haveHistory ? priorRegressionsByPair.get(`${insight.
|
|
2404
|
+
const gscImpressions = gscConnected ? gscImpressionsByQuery.get(insight.query.toLowerCase()) ?? 0 : void 0;
|
|
2405
|
+
const recurrenceCount = haveHistory ? priorRegressionsByPair.get(`${insight.query}:${insight.provider}`) ?? 0 : void 0;
|
|
2343
2406
|
const severity = classifyRegressionSeverity({
|
|
2344
2407
|
gscImpressions,
|
|
2345
2408
|
recurrenceCount
|
|
@@ -2349,19 +2412,19 @@ var IntelligenceService = class {
|
|
|
2349
2412
|
}
|
|
2350
2413
|
buildRunData(runId, projectId, completedAt) {
|
|
2351
2414
|
const rows = this.db.select({
|
|
2352
|
-
|
|
2415
|
+
query: queries.query,
|
|
2353
2416
|
provider: querySnapshots.provider,
|
|
2354
2417
|
citationState: querySnapshots.citationState,
|
|
2355
2418
|
citedDomains: querySnapshots.citedDomains,
|
|
2356
2419
|
competitorOverlap: querySnapshots.competitorOverlap
|
|
2357
|
-
}).from(querySnapshots).leftJoin(
|
|
2420
|
+
}).from(querySnapshots).leftJoin(queries, eq(querySnapshots.queryId, queries.id)).where(eq(querySnapshots.runId, runId)).all();
|
|
2358
2421
|
const snapshots = rows.map((r) => {
|
|
2359
2422
|
const domains = parseJsonColumn(r.citedDomains, []);
|
|
2360
2423
|
const competitors2 = parseJsonColumn(r.competitorOverlap, []);
|
|
2361
2424
|
return {
|
|
2362
|
-
|
|
2425
|
+
query: r.query ?? "",
|
|
2363
2426
|
provider: r.provider,
|
|
2364
|
-
cited: r.citationState ===
|
|
2427
|
+
cited: r.citationState === CitationStates.cited,
|
|
2365
2428
|
citationUrl: domains[0] ?? void 0,
|
|
2366
2429
|
competitorDomain: competitors2[0] ?? void 0
|
|
2367
2430
|
};
|
|
@@ -2372,7 +2435,7 @@ var IntelligenceService = class {
|
|
|
2372
2435
|
|
|
2373
2436
|
export {
|
|
2374
2437
|
projects,
|
|
2375
|
-
|
|
2438
|
+
queries,
|
|
2376
2439
|
competitors,
|
|
2377
2440
|
runs,
|
|
2378
2441
|
querySnapshots,
|