@ainyc/canonry 4.86.0 → 4.88.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.
Files changed (27) hide show
  1. package/assets/agent-workspace/skills/canonry/references/server-side-traffic.md +19 -0
  2. package/assets/assets/{BacklinksPage-BNrvc-gV.js → BacklinksPage-Dpx6ylmU.js} +1 -1
  3. package/assets/assets/{ChartPrimitives-BlIkdUdy.js → ChartPrimitives-CG5EGu62.js} +1 -1
  4. package/assets/assets/ProjectPage-B_xnYE7W.js +6 -0
  5. package/assets/assets/{RunRow-CAPnKzi7.js → RunRow-C0V4vf-u.js} +1 -1
  6. package/assets/assets/{RunsPage-idnuzKBn.js → RunsPage-9gWTjteG.js} +1 -1
  7. package/assets/assets/{SettingsPage-Bka67uJq.js → SettingsPage-kBwz4Glg.js} +1 -1
  8. package/assets/assets/{TrafficPage-C_o-rA5o.js → TrafficPage-D4adjs8S.js} +1 -1
  9. package/assets/assets/TrafficSourceDetailPage-CmURRHaU.js +1 -0
  10. package/assets/assets/{arrow-left-B-JfzARi.js → arrow-left-DJ0-oFbd.js} +1 -1
  11. package/assets/assets/{extract-error-message-BhPbjIX6.js → extract-error-message-DfBzETgi.js} +1 -1
  12. package/assets/assets/{index-uPSrDA8e.js → index-BeuGfMy8.js} +107 -107
  13. package/assets/assets/index-ClkRAeHL.css +1 -0
  14. package/assets/assets/{trash-2-BbRvn40h.js → trash-2-Bjf6mCqw.js} +1 -1
  15. package/assets/index.html +2 -2
  16. package/dist/{chunk-SELXBOAP.js → chunk-2VEFNAQ3.js} +4 -4
  17. package/dist/{chunk-23HGQV22.js → chunk-JZ67YCHT.js} +365 -0
  18. package/dist/{chunk-LLJPZKHG.js → chunk-RH3QYFX3.js} +2 -2
  19. package/dist/{chunk-DLBQU3VG.js → chunk-WMZJO56S.js} +213 -57
  20. package/dist/cli.js +23 -12
  21. package/dist/index.js +4 -4
  22. package/dist/{intelligence-service-ZHUJKZRO.js → intelligence-service-WTEEV46F.js} +2 -2
  23. package/dist/mcp.js +2 -2
  24. package/package.json +6 -6
  25. package/assets/assets/ProjectPage-CAyx_xNr.js +0 -6
  26. package/assets/assets/TrafficSourceDetailPage-D_jvoSTV.js +0 -1
  27. package/assets/assets/index-BgWgJE7S.css +0 -1
@@ -86,6 +86,7 @@ import {
86
86
  classifyCitedSurface,
87
87
  classifySkillFile,
88
88
  classifySurfaceFromCategory,
89
+ classifyTrafficPath,
89
90
  clusterByCosine,
90
91
  coerceSkillManifest,
91
92
  competitorBatchRequestSchema,
@@ -170,6 +171,7 @@ import {
170
171
  missingDependency,
171
172
  normalizeProjectAliases,
172
173
  normalizeProjectDomain,
174
+ normalizeQueryText,
173
175
  normalizeUrlPath,
174
176
  notFound,
175
177
  notImplemented,
@@ -180,6 +182,7 @@ import {
180
182
  pickClusterRepresentative,
181
183
  projectConfigSchema,
182
184
  projectDtoSchema,
185
+ projectOverviewDtoSchema,
183
186
  projectReportDtoSchema,
184
187
  projectUpsertRequestSchema,
185
188
  providerError,
@@ -208,6 +211,7 @@ import {
208
211
  scheduleDtoSchema,
209
212
  scheduleUpsertRequestSchema,
210
213
  seedCollapseWarning,
214
+ segmentCrawlerHits,
211
215
  serializeRunError,
212
216
  settingsDtoSchema,
213
217
  siteAuditPagesResponseSchema,
@@ -220,6 +224,7 @@ import {
220
224
  snapshotReportSchema,
221
225
  snapshotRequestSchema,
222
226
  sourceBreakdownDtoSchema,
227
+ sumInfraHits,
223
228
  summarizeCheckResults,
224
229
  surfaceClassFromCompetitorType,
225
230
  surfaceClassLabel,
@@ -253,7 +258,7 @@ import {
253
258
  wordpressSchemaDeployResultDtoSchema,
254
259
  wordpressSchemaStatusResultDtoSchema,
255
260
  wordpressStatusDtoSchema
256
- } from "./chunk-23HGQV22.js";
261
+ } from "./chunk-JZ67YCHT.js";
257
262
 
258
263
  // src/intelligence-service.ts
259
264
  import { eq as eq37, desc as desc18, asc as asc5, and as and27, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
@@ -4532,35 +4537,101 @@ function buildAiSourceOrigin(snapshots, projectDomains, competitorDomains, topDo
4532
4537
 
4533
4538
  // ../intelligence/src/movement-summary.ts
4534
4539
  function buildMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4540
+ return buildSignalMovementSummary(
4541
+ currentSnapshots,
4542
+ previousSnapshots,
4543
+ (snapshot) => snapshot.citationState === CitationStates.cited,
4544
+ options
4545
+ );
4546
+ }
4547
+ function buildCitationMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4548
+ return buildMovementSummary(currentSnapshots, previousSnapshots, options);
4549
+ }
4550
+ function buildMentionMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4551
+ return buildSignalMovementSummary(
4552
+ currentSnapshots,
4553
+ previousSnapshots,
4554
+ (snapshot) => snapshot.answerMentioned === true,
4555
+ options
4556
+ );
4557
+ }
4558
+ function buildMovementComparison(currentSnapshots, previousSnapshots, options = {}) {
4559
+ const currentIds = collectQueryIds(currentSnapshots);
4560
+ const previousIds = collectQueryIds(previousSnapshots);
4561
+ const hasPreviousRun = previousSnapshots.length > 0;
4562
+ if (!hasPreviousRun) {
4563
+ return {
4564
+ hasPreviousRun: false,
4565
+ comparable: false,
4566
+ querySetChanged: false,
4567
+ previousRunAt: null,
4568
+ currentQueryCount: currentIds.size,
4569
+ previousQueryCount: 0,
4570
+ comparableQueryCount: 0,
4571
+ addedQueryCount: 0,
4572
+ removedQueryCount: 0,
4573
+ addedQueries: [],
4574
+ removedQueries: []
4575
+ };
4576
+ }
4577
+ const comparableIds = intersection(currentIds, previousIds);
4578
+ const addedIds = difference(currentIds, previousIds);
4579
+ const removedIds = difference(previousIds, currentIds);
4580
+ const querySetChanged = addedIds.size > 0 || removedIds.size > 0;
4581
+ return {
4582
+ hasPreviousRun: true,
4583
+ comparable: !querySetChanged && currentIds.size > 0,
4584
+ querySetChanged,
4585
+ previousRunAt: options.previousRunAt ?? null,
4586
+ currentQueryCount: currentIds.size,
4587
+ previousQueryCount: previousIds.size,
4588
+ comparableQueryCount: comparableIds.size,
4589
+ addedQueryCount: addedIds.size,
4590
+ removedQueryCount: removedIds.size,
4591
+ addedQueries: resolveQueryTexts(addedIds, options.queryLookup),
4592
+ removedQueries: resolveQueryTexts(removedIds, options.queryLookup)
4593
+ };
4594
+ }
4595
+ function buildSignalMovementSummary(currentSnapshots, previousSnapshots, isActive, options) {
4535
4596
  if (previousSnapshots.length === 0) {
4536
- const citedIds = collectCitedQueryIds(currentSnapshots);
4537
- const citedCount = citedIds.size;
4538
- const tone2 = citedCount > 0 ? "positive" : "neutral";
4597
+ const activeIds = collectActiveQueryIds(currentSnapshots, isActive);
4539
4598
  return withQueryLists(
4540
- { gained: citedCount, lost: 0, tone: tone2, hasPreviousRun: false },
4541
- citedIds,
4599
+ {
4600
+ gained: activeIds.size,
4601
+ lost: 0,
4602
+ tone: activeIds.size > 0 ? "positive" : "neutral",
4603
+ hasPreviousRun: false
4604
+ },
4605
+ activeIds,
4542
4606
  /* @__PURE__ */ new Set(),
4543
4607
  options.queryLookup
4544
4608
  );
4545
4609
  }
4546
- const latestCited = collectCitedQueryIds(currentSnapshots);
4547
- const previousCited = collectCitedQueryIds(previousSnapshots);
4548
- const gainedIds = /* @__PURE__ */ new Set();
4549
- const lostIds = /* @__PURE__ */ new Set();
4550
- for (const id of latestCited) {
4551
- if (!previousCited.has(id)) gainedIds.add(id);
4552
- }
4553
- for (const id of previousCited) {
4554
- if (!latestCited.has(id)) lostIds.add(id);
4555
- }
4556
- const tone = lostIds.size > gainedIds.size ? "negative" : gainedIds.size > lostIds.size ? "positive" : "neutral";
4610
+ const comparableIds = intersection(
4611
+ collectQueryIds(currentSnapshots),
4612
+ collectQueryIds(previousSnapshots)
4613
+ );
4614
+ const currentActive = intersection(collectActiveQueryIds(currentSnapshots, isActive), comparableIds);
4615
+ const previousActive = intersection(collectActiveQueryIds(previousSnapshots, isActive), comparableIds);
4616
+ const gainedIds = difference(currentActive, previousActive);
4617
+ const lostIds = difference(previousActive, currentActive);
4557
4618
  return withQueryLists(
4558
- { gained: gainedIds.size, lost: lostIds.size, tone, hasPreviousRun: true },
4619
+ {
4620
+ gained: gainedIds.size,
4621
+ lost: lostIds.size,
4622
+ tone: movementTone(gainedIds.size, lostIds.size),
4623
+ hasPreviousRun: true
4624
+ },
4559
4625
  gainedIds,
4560
4626
  lostIds,
4561
4627
  options.queryLookup
4562
4628
  );
4563
4629
  }
4630
+ function movementTone(gained, lost) {
4631
+ if (lost > gained) return "negative";
4632
+ if (gained > lost) return "positive";
4633
+ return "neutral";
4634
+ }
4564
4635
  function withQueryLists(base, gainedIds, lostIds, lookup) {
4565
4636
  if (!lookup) return base;
4566
4637
  return {
@@ -4570,6 +4641,7 @@ function withQueryLists(base, gainedIds, lostIds, lookup) {
4570
4641
  };
4571
4642
  }
4572
4643
  function resolveQueryTexts(ids, lookup) {
4644
+ if (!lookup) return [];
4573
4645
  const out = [];
4574
4646
  for (const id of ids) {
4575
4647
  const text2 = lookup.get(id);
@@ -4577,12 +4649,33 @@ function resolveQueryTexts(ids, lookup) {
4577
4649
  }
4578
4650
  return out.sort();
4579
4651
  }
4580
- function collectCitedQueryIds(snapshots) {
4581
- const cited = /* @__PURE__ */ new Set();
4582
- for (const s of snapshots) {
4583
- if (s.citationState === CitationStates.cited && s.queryId) cited.add(s.queryId);
4652
+ function collectQueryIds(snapshots) {
4653
+ const ids = /* @__PURE__ */ new Set();
4654
+ for (const snapshot of snapshots) {
4655
+ if (snapshot.queryId) ids.add(snapshot.queryId);
4656
+ }
4657
+ return ids;
4658
+ }
4659
+ function collectActiveQueryIds(snapshots, isActive) {
4660
+ const active = /* @__PURE__ */ new Set();
4661
+ for (const snapshot of snapshots) {
4662
+ if (snapshot.queryId && isActive(snapshot)) active.add(snapshot.queryId);
4584
4663
  }
4585
- return cited;
4664
+ return active;
4665
+ }
4666
+ function intersection(left, right) {
4667
+ const out = /* @__PURE__ */ new Set();
4668
+ for (const value of left) {
4669
+ if (right.has(value)) out.add(value);
4670
+ }
4671
+ return out;
4672
+ }
4673
+ function difference(left, right) {
4674
+ const out = /* @__PURE__ */ new Set();
4675
+ for (const value of left) {
4676
+ if (!right.has(value)) out.add(value);
4677
+ }
4678
+ return out;
4586
4679
  }
4587
4680
 
4588
4681
  // ../intelligence/src/score-tones.ts
@@ -5027,12 +5120,12 @@ var DEFAULT_LIMIT = 10;
5027
5120
  function buildSuggestedQueries(gscRows, options) {
5028
5121
  const minImpressions = options.minImpressions ?? DEFAULT_MIN_IMPRESSIONS;
5029
5122
  const limit = options.limit ?? DEFAULT_LIMIT;
5030
- const trackedSet = new Set(options.trackedQueries.map(normalizeQuery));
5123
+ const trackedSet = new Set(options.trackedQueries.map(normalizeQueryText));
5031
5124
  let skippedAlreadyTracked = 0;
5032
5125
  const candidates = [];
5033
5126
  for (const row of gscRows) {
5034
5127
  if (row.impressions < minImpressions) continue;
5035
- const normalized = normalizeQuery(row.query);
5128
+ const normalized = normalizeQueryText(row.query);
5036
5129
  if (normalized.length === 0) continue;
5037
5130
  if (trackedSet.has(normalized)) {
5038
5131
  skippedAlreadyTracked++;
@@ -5054,9 +5147,6 @@ function buildSuggestedQueries(gscRows, options) {
5054
5147
  skippedAlreadyTracked
5055
5148
  };
5056
5149
  }
5057
- function normalizeQuery(value) {
5058
- return value.trim().toLowerCase();
5059
- }
5060
5150
  function buildReason(row) {
5061
5151
  const impressionsLabel = formatImpressions2(row.impressions);
5062
5152
  if (row.avgPosition <= 10) {
@@ -12924,18 +13014,31 @@ async function compositeRoutes(app) {
12924
13014
  const snapshotRunIds = new Set(sparklineRunIds);
12925
13015
  for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
12926
13016
  for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
12927
- const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds]);
13017
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
13018
+ const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
13019
+ const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
12928
13020
  const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
12929
13021
  const previousSnapshots = previousVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
12930
- const { queryCounts, providers } = summarizeFromSnapshots(latestSnapshots);
13022
+ const trackedLatest = latestSnapshots.filter((s) => !s.archived);
13023
+ const trackedPrevious = previousSnapshots.filter((s) => !s.archived);
13024
+ const trackedSnapshotsByRun = new Map(
13025
+ [...snapshotsByRun].map(([runId, snaps]) => [runId, snaps.filter((s) => !s.archived)])
13026
+ );
13027
+ const { queryCounts, providers } = summarizeFromSnapshots(trackedLatest);
12931
13028
  const transitions = summarizeTransitionsFromSnapshots(
12932
- latestSnapshots,
12933
- previousSnapshots,
13029
+ trackedLatest,
13030
+ trackedPrevious,
12934
13031
  previousVisibilityRun?.createdAt ?? null
12935
13032
  );
12936
13033
  const competitorRows = app.db.select().from(competitors).where(eq17(competitors.projectId, project.id)).all();
12937
- const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
12938
13034
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
13035
+ for (const snapshots of snapshotsByRun.values()) {
13036
+ for (const snapshot of snapshots) {
13037
+ if (snapshot.queryText && !queryLookup.byId.has(snapshot.queryId)) {
13038
+ queryLookup.byId.set(snapshot.queryId, snapshot.queryText);
13039
+ }
13040
+ }
13041
+ }
12939
13042
  const configuredApiProviders = project.providers.filter((p) => !p.startsWith("cdp:"));
12940
13043
  const mentionShareCompetitors = competitorRows.map((c) => ({
12941
13044
  domain: c.domain,
@@ -12945,32 +13048,39 @@ async function compositeRoutes(app) {
12945
13048
  brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
12946
13049
  }));
12947
13050
  const scores = {
12948
- mention: buildMentionCoverage(latestSnapshots, { configuredApiProviders }),
12949
- visibility: buildVisibilityScore(latestSnapshots, { configuredApiProviders }),
13051
+ mention: buildMentionCoverage(trackedLatest, { configuredApiProviders }),
13052
+ visibility: buildVisibilityScore(trackedLatest, { configuredApiProviders }),
12950
13053
  mentionShare: buildMentionShare(
12951
- latestSnapshots.map((s) => ({
13054
+ trackedLatest.map((s) => ({
12952
13055
  projectMentioned: s.answerMentioned === true,
12953
13056
  answerText: s.answerText
12954
13057
  })),
12955
13058
  { competitors: mentionShareCompetitors }
12956
13059
  ),
12957
- gapQueries: buildGapQueryScore(latestSnapshots),
12958
- mentionGaps: buildMentionGapScore(latestSnapshots),
13060
+ gapQueries: buildGapQueryScore(trackedLatest),
13061
+ mentionGaps: buildMentionGapScore(trackedLatest),
12959
13062
  indexCoverage: buildIndexCoverageScore(app, project.id),
12960
13063
  competitorPressure: buildCompetitorPressureScore(
12961
- latestSnapshots,
13064
+ trackedLatest,
12962
13065
  competitorRows.map((c) => c.domain),
12963
13066
  competitorRows.length
12964
13067
  ),
12965
13068
  runStatus: buildRunStatusScore(allRuns)
12966
13069
  };
12967
- const movementSummary = buildMovementSummary(latestSnapshots, previousSnapshots, {
13070
+ const citationMovement = buildCitationMovementSummary(latestSnapshots, previousSnapshots, {
12968
13071
  queryLookup: queryLookup.byId
12969
13072
  });
12970
- const providerScoresBase = buildProviderScores(latestSnapshots);
13073
+ const mentionMovement = buildMentionMovementSummary(latestSnapshots, previousSnapshots, {
13074
+ queryLookup: queryLookup.byId
13075
+ });
13076
+ const movementComparison = buildMovementComparison(latestSnapshots, previousSnapshots, {
13077
+ queryLookup: queryLookup.byId,
13078
+ previousRunAt: previousVisibilityRun?.createdAt ?? null
13079
+ });
13080
+ const providerScoresBase = buildProviderScores(trackedLatest);
12971
13081
  const providerTrends = buildProviderTrends(
12972
13082
  visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt })),
12973
- snapshotsByRun,
13083
+ trackedSnapshotsByRun,
12974
13084
  DEFAULT_RUN_HISTORY_LIMIT
12975
13085
  );
12976
13086
  const providerScores = providerScoresBase.map((score) => {
@@ -12978,13 +13088,13 @@ async function compositeRoutes(app) {
12978
13088
  return trend.length > 1 ? { ...score, trend: trend.map((p) => p.rate) } : score;
12979
13089
  });
12980
13090
  const overviewCompetitors = buildOverviewCompetitors(
12981
- latestSnapshots,
13091
+ trackedLatest,
12982
13092
  competitorRows.map((c) => ({ id: c.id, domain: c.domain })),
12983
13093
  queryLookup
12984
13094
  );
12985
13095
  const attentionItems = buildAttentionItems(insightRows, allRuns);
12986
13096
  const sparklineRuns = visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt, status: r.status }));
12987
- const runHistory = buildRunHistory(sparklineRuns, snapshotsByRun);
13097
+ const runHistory = buildRunHistory(sparklineRuns, trackedSnapshotsByRun);
12988
13098
  scores.mention.trend = runHistory.map((p) => p.mentionRate);
12989
13099
  scores.visibility.trend = runHistory.map((p) => p.citationRate);
12990
13100
  const suggestedQueries = buildSuggestedQueriesFromGsc(
@@ -13001,7 +13111,12 @@ async function compositeRoutes(app) {
13001
13111
  providers,
13002
13112
  transitions,
13003
13113
  scores,
13004
- movementSummary,
13114
+ // Keep the legacy citation-only field for API compatibility. New
13115
+ // consumers read the explicitly named siblings below.
13116
+ movementSummary: citationMovement,
13117
+ citationMovement,
13118
+ mentionMovement,
13119
+ movementComparison,
13005
13120
  competitors: overviewCompetitors,
13006
13121
  providerScores,
13007
13122
  attentionItems,
@@ -13111,12 +13226,13 @@ function summarizeRun(run) {
13111
13226
  createdAt: run.createdAt
13112
13227
  };
13113
13228
  }
13114
- function loadSnapshotsByRunIds(app, runIds) {
13229
+ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
13115
13230
  const result = /* @__PURE__ */ new Map();
13116
13231
  if (runIds.length === 0) return result;
13117
- const rows = filterTrackedSnapshots(app.db.select({
13232
+ const rows = app.db.select({
13118
13233
  runId: querySnapshots.runId,
13119
13234
  queryId: querySnapshots.queryId,
13235
+ queryText: querySnapshots.queryText,
13120
13236
  provider: querySnapshots.provider,
13121
13237
  model: querySnapshots.model,
13122
13238
  citationState: querySnapshots.citationState,
@@ -13124,11 +13240,30 @@ function loadSnapshotsByRunIds(app, runIds) {
13124
13240
  answerText: querySnapshots.answerText,
13125
13241
  competitorOverlap: querySnapshots.competitorOverlap,
13126
13242
  citedDomains: querySnapshots.citedDomains
13127
- }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all());
13243
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all();
13128
13244
  for (const row of rows) {
13245
+ const queryText = row.queryText?.trim() || null;
13246
+ let queryId;
13247
+ let archived = false;
13248
+ if (row.queryId) {
13249
+ queryId = row.queryId;
13250
+ } else if (queryText) {
13251
+ const tracked = queryIdByText.get(normalizeQueryText(queryText));
13252
+ if (tracked) {
13253
+ queryId = tracked;
13254
+ } else {
13255
+ queryId = `archived:${normalizeQueryText(queryText)}`;
13256
+ archived = true;
13257
+ }
13258
+ } else {
13259
+ queryId = null;
13260
+ }
13261
+ if (!queryId) continue;
13129
13262
  const list = result.get(row.runId) ?? [];
13130
13263
  list.push({
13131
- queryId: row.queryId,
13264
+ queryId,
13265
+ queryText,
13266
+ archived,
13132
13267
  provider: row.provider,
13133
13268
  model: row.model,
13134
13269
  citationState: row.citationState,
@@ -13618,6 +13753,7 @@ var SCHEMA_TABLE = {
13618
13753
  LocationContext: locationContextSchema,
13619
13754
  NotificationDto: notificationDtoSchema,
13620
13755
  ProjectDto: projectDtoSchema,
13756
+ ProjectOverviewDto: projectOverviewDtoSchema,
13621
13757
  ProjectReportDto: projectReportDtoSchema,
13622
13758
  QueryDto: queryDtoSchema,
13623
13759
  RunDetailDto: runDetailDtoSchema,
@@ -16851,12 +16987,11 @@ var routeCatalog = [
16851
16987
  method: "get",
16852
16988
  path: "/api/v1/projects/{name}/overview",
16853
16989
  summary: "Get a composite overview of project health",
16854
- description: 'Bundles project info, latest run, top undismissed insights, the latest health snapshot, query cited rate, per-provider breakdown, and transitions vs. the previous run. Designed for the "how is project X doing?" question so agents can answer in one call.',
16990
+ description: 'Bundles project info, latest run, top undismissed insights, health, independent mention and citation coverage, query-basket comparability, and separate mention/citation movement over the shared query cohort. Designed for the "how is project X doing?" question so agents can answer in one call.',
16855
16991
  tags: ["intelligence"],
16856
16992
  parameters: [nameParameter],
16857
16993
  responses: {
16858
- // TODO: Add `ProjectOverviewDto` Zod schema in contracts.
16859
- 200: rawJsonResponse("Overview returned.", looseObjectSchema),
16994
+ 200: jsonResponse("Overview returned.", "ProjectOverviewDto"),
16860
16995
  404: errorResponse("Project not found.")
16861
16996
  }
16862
16997
  },
@@ -30493,12 +30628,19 @@ async function trafficRoutes(app, opts) {
30493
30628
  return response;
30494
30629
  });
30495
30630
  function buildSourceDetail(projectId, row, since) {
30496
- const crawlerTotals = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(
30631
+ const crawlerPathRows = app.db.select({
30632
+ pathNormalized: crawlerEventsHourly.pathNormalized,
30633
+ hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
30634
+ }).from(crawlerEventsHourly).where(
30497
30635
  and21(
30498
30636
  eq27(crawlerEventsHourly.sourceId, row.id),
30499
30637
  gte4(crawlerEventsHourly.tsHour, since)
30500
30638
  )
30501
- ).get();
30639
+ ).groupBy(crawlerEventsHourly.pathNormalized).all();
30640
+ const crawlerSegments = segmentCrawlerHits(
30641
+ crawlerPathRows.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
30642
+ );
30643
+ const crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
30502
30644
  const aiUserFetchTotals = app.db.select({ total: sql11`COALESCE(SUM(${aiUserFetchEventsHourly.hits}), 0)` }).from(aiUserFetchEventsHourly).where(
30503
30645
  and21(
30504
30646
  eq27(aiUserFetchEventsHourly.sourceId, row.id),
@@ -30527,7 +30669,10 @@ async function trafficRoutes(app, opts) {
30527
30669
  return {
30528
30670
  ...rowToDto(row),
30529
30671
  totals24h: {
30530
- crawlerHits: Number(crawlerTotals?.total ?? 0),
30672
+ crawlerHits: crawlerTotal,
30673
+ crawlerContentHits: crawlerSegments.content,
30674
+ crawlerInfraHits: sumInfraHits(crawlerSegments),
30675
+ crawlerSegments,
30531
30676
  aiUserFetchHits: Number(aiUserFetchTotals?.total ?? 0),
30532
30677
  aiReferralHits: Number(aiTotals?.total ?? 0),
30533
30678
  sampleCount: Number(sampleTotals?.total ?? 0)
@@ -30644,6 +30789,7 @@ async function trafficRoutes(app, opts) {
30644
30789
  const untilIso = until.toISOString();
30645
30790
  const events = [];
30646
30791
  let crawlerTotal = 0;
30792
+ let crawlerSegments = { content: 0, sitemap: 0, robots: 0, asset: 0, other: 0 };
30647
30793
  let aiUserFetchTotal = 0;
30648
30794
  let aiReferralTotal = 0;
30649
30795
  if (kind === "all" || kind === TrafficEventKinds.crawler) {
@@ -30654,8 +30800,14 @@ async function trafficRoutes(app, opts) {
30654
30800
  ];
30655
30801
  if (sourceIdParam) crawlerFilters.push(eq27(crawlerEventsHourly.sourceId, sourceIdParam));
30656
30802
  const crawlerWhere = and21(...crawlerFilters);
30657
- const total = app.db.select({ total: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)` }).from(crawlerEventsHourly).where(crawlerWhere).get();
30658
- crawlerTotal = Number(total?.total ?? 0);
30803
+ const pathTotals = app.db.select({
30804
+ pathNormalized: crawlerEventsHourly.pathNormalized,
30805
+ hits: sql11`COALESCE(SUM(${crawlerEventsHourly.hits}), 0)`
30806
+ }).from(crawlerEventsHourly).where(crawlerWhere).groupBy(crawlerEventsHourly.pathNormalized).all();
30807
+ crawlerSegments = segmentCrawlerHits(
30808
+ pathTotals.map((r) => ({ pathNormalized: r.pathNormalized, hits: Number(r.hits) }))
30809
+ );
30810
+ crawlerTotal = crawlerSegments.content + crawlerSegments.sitemap + crawlerSegments.robots + crawlerSegments.asset + crawlerSegments.other;
30659
30811
  const rows = app.db.select().from(crawlerEventsHourly).where(crawlerWhere).orderBy(desc15(crawlerEventsHourly.tsHour)).limit(limit).all();
30660
30812
  for (const r of rows) {
30661
30813
  events.push({
@@ -30666,6 +30818,7 @@ async function trafficRoutes(app, opts) {
30666
30818
  operator: r.operator,
30667
30819
  verificationStatus: r.verificationStatus,
30668
30820
  pathNormalized: r.pathNormalized,
30821
+ pathClass: classifyTrafficPath(r.pathNormalized),
30669
30822
  status: r.status,
30670
30823
  hits: r.hits
30671
30824
  });
@@ -30729,6 +30882,9 @@ async function trafficRoutes(app, opts) {
30729
30882
  windowEnd: untilIso,
30730
30883
  totals: {
30731
30884
  crawlerHits: crawlerTotal,
30885
+ crawlerContentHits: crawlerSegments.content,
30886
+ crawlerInfraHits: sumInfraHits(crawlerSegments),
30887
+ crawlerSegments,
30732
30888
  aiUserFetchHits: aiUserFetchTotal,
30733
30889
  aiReferralHits: aiReferralTotal
30734
30890
  },
package/dist/cli.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  setTelemetrySource,
28
28
  showFirstRunNotice,
29
29
  trackEvent
30
- } from "./chunk-SELXBOAP.js";
30
+ } from "./chunk-2VEFNAQ3.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-LLJPZKHG.js";
47
+ } from "./chunk-RH3QYFX3.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-DLBQU3VG.js";
55
+ } from "./chunk-WMZJO56S.js";
56
56
  import {
57
57
  BacklinkSources,
58
58
  CcReleaseSyncStatuses,
@@ -77,7 +77,7 @@ import {
77
77
  providerQuotaPolicySchema,
78
78
  resolveProviderInput,
79
79
  winnabilityClassSchema
80
- } from "./chunk-23HGQV22.js";
80
+ } from "./chunk-JZ67YCHT.js";
81
81
 
82
82
  // src/cli.ts
83
83
  import { pathToFileURL } from "url";
@@ -4245,7 +4245,10 @@ async function trafficStatus(project, opts) {
4245
4245
  console.log(` Status: ${d.status}`);
4246
4246
  console.log(` Last synced: ${d.lastSyncedAt ?? "never"}`);
4247
4247
  if (d.lastError) console.log(` Last error: ${d.lastError}`);
4248
- console.log(` 24h crawler: ${d.totals24h.crawlerHits} hits`);
4248
+ console.log(` 24h content: ${d.totals24h.crawlerContentHits} crawls`);
4249
+ console.log(` 24h infra: ${d.totals24h.crawlerInfraHits} sitemap/robots/asset fetches`);
4250
+ console.log(` 24h other: ${d.totals24h.crawlerSegments.other} fetches`);
4251
+ console.log(` 24h crawler: ${d.totals24h.crawlerHits} hits total`);
4249
4252
  console.log(` 24h AI referral: ${d.totals24h.aiReferralHits} sessions`);
4250
4253
  console.log(` 24h samples: ${d.totals24h.sampleCount}`);
4251
4254
  if (d.latestRun) {
@@ -4268,7 +4271,7 @@ function formatEventLine(event) {
4268
4271
  event.botId,
4269
4272
  event.verificationStatus,
4270
4273
  String(event.status),
4271
- event.pathNormalized,
4274
+ `${event.pathNormalized} [${event.pathClass}]`,
4272
4275
  `${event.hits} hits`
4273
4276
  ].join(" ");
4274
4277
  case TrafficEventKinds["ai-user-fetch"]:
@@ -4328,7 +4331,10 @@ async function trafficEvents(project, opts) {
4328
4331
  return;
4329
4332
  }
4330
4333
  console.log(`Traffic events for "${project}" ${result.windowStart} \u2192 ${result.windowEnd}`);
4331
- console.log(` Crawler hits (window): ${result.totals.crawlerHits}`);
4334
+ console.log(` Content crawls (window): ${result.totals.crawlerContentHits}`);
4335
+ console.log(` Infra fetches (window): ${result.totals.crawlerInfraHits} (sitemap ${result.totals.crawlerSegments.sitemap} \xB7 robots ${result.totals.crawlerSegments.robots} \xB7 asset ${result.totals.crawlerSegments.asset})`);
4336
+ console.log(` Other fetches (window): ${result.totals.crawlerSegments.other}`);
4337
+ console.log(` Crawler hits total (window): ${result.totals.crawlerHits}`);
4332
4338
  console.log(` AI user-fetch hits (window): ${result.totals.aiUserFetchHits}`);
4333
4339
  console.log(` AI referral sessions (window): ${result.totals.aiReferralHits}`);
4334
4340
  console.log("");
@@ -9405,7 +9411,9 @@ function renderHuman(overview) {
9405
9411
  providers,
9406
9412
  transitions,
9407
9413
  scores,
9408
- movementSummary,
9414
+ citationMovement,
9415
+ mentionMovement,
9416
+ movementComparison,
9409
9417
  competitors,
9410
9418
  providerScores,
9411
9419
  attentionItems,
@@ -9438,10 +9446,13 @@ function renderHuman(overview) {
9438
9446
  console.log(`
9439
9447
  Queries cited: ${queryCounts.citedQueries}/${queryCounts.totalQueries} (${pct2(queryCounts.citedRate)})`);
9440
9448
  console.log(` Queries mentioned: ${queryCounts.mentionedQueries}/${queryCounts.totalQueries} (${pct2(queryCounts.mentionRate)})`);
9441
- if (movementSummary.hasPreviousRun) {
9442
- console.log(` Movement: +${movementSummary.gained} gained, -${movementSummary.lost} lost (${movementSummary.tone})`);
9443
- } else if (movementSummary.gained > 0) {
9444
- console.log(` Movement: ${movementSummary.gained} cited in first run`);
9449
+ if (movementComparison.hasPreviousRun) {
9450
+ const comparisonLabel = movementComparison.querySetChanged ? `changed (+${movementComparison.addedQueryCount} added, -${movementComparison.removedQueryCount} removed); movement compares ${movementComparison.comparableQueryCount} shared` : `unchanged; ${movementComparison.comparableQueryCount} comparable`;
9451
+ console.log(` Query basket: ${comparisonLabel}`);
9452
+ console.log(` Citation movement: +${citationMovement.gained} gained, -${citationMovement.lost} lost (${citationMovement.tone})`);
9453
+ console.log(` Mention movement: +${mentionMovement.gained} gained, -${mentionMovement.lost} lost (${mentionMovement.tone})`);
9454
+ } else {
9455
+ console.log(" Movement: first sweep; no comparison yet");
9445
9456
  }
9446
9457
  if (providers.length > 0) {
9447
9458
  console.log("\n Providers:");
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-SELXBOAP.js";
3
+ } from "./chunk-2VEFNAQ3.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-LLJPZKHG.js";
7
- import "./chunk-DLBQU3VG.js";
8
- import "./chunk-23HGQV22.js";
6
+ } from "./chunk-RH3QYFX3.js";
7
+ import "./chunk-WMZJO56S.js";
8
+ import "./chunk-JZ67YCHT.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-DLBQU3VG.js";
4
- import "./chunk-23HGQV22.js";
3
+ } from "./chunk-WMZJO56S.js";
4
+ import "./chunk-JZ67YCHT.js";
5
5
  export {
6
6
  IntelligenceService
7
7
  };
package/dist/mcp.js CHANGED
@@ -3,10 +3,10 @@ import {
3
3
  PACKAGE_VERSION,
4
4
  canonryMcpTools,
5
5
  createApiClient
6
- } from "./chunk-LLJPZKHG.js";
6
+ } from "./chunk-RH3QYFX3.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-23HGQV22.js";
9
+ } from "./chunk-JZ67YCHT.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";