@ainyc/canonry 4.86.0 → 4.87.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 (25) hide show
  1. package/assets/assets/{BacklinksPage-BNrvc-gV.js → BacklinksPage-BPvsw_Bi.js} +1 -1
  2. package/assets/assets/{ChartPrimitives-BlIkdUdy.js → ChartPrimitives-BdlKCq7y.js} +1 -1
  3. package/assets/assets/ProjectPage-1Q8YC9Vd.js +6 -0
  4. package/assets/assets/{RunRow-CAPnKzi7.js → RunRow-DcSsnE5c.js} +1 -1
  5. package/assets/assets/{RunsPage-idnuzKBn.js → RunsPage-DKoIMkQL.js} +1 -1
  6. package/assets/assets/{SettingsPage-Bka67uJq.js → SettingsPage-bH3PdNKb.js} +1 -1
  7. package/assets/assets/{TrafficPage-C_o-rA5o.js → TrafficPage-IW_DX-0V.js} +1 -1
  8. package/assets/assets/{TrafficSourceDetailPage-D_jvoSTV.js → TrafficSourceDetailPage-DRHOGn9B.js} +1 -1
  9. package/assets/assets/{arrow-left-B-JfzARi.js → arrow-left-B5Du72nk.js} +1 -1
  10. package/assets/assets/{extract-error-message-BhPbjIX6.js → extract-error-message-C7Vhd5zH.js} +1 -1
  11. package/assets/assets/{index-uPSrDA8e.js → index-C_ZzKZfM.js} +107 -107
  12. package/assets/assets/index-ClkRAeHL.css +1 -0
  13. package/assets/assets/{trash-2-BbRvn40h.js → trash-2-DWcofmpv.js} +1 -1
  14. package/assets/index.html +2 -2
  15. package/dist/{chunk-DLBQU3VG.js → chunk-5LW7CJAO.js} +184 -52
  16. package/dist/{chunk-LLJPZKHG.js → chunk-6XMXBAEW.js} +2 -2
  17. package/dist/{chunk-SELXBOAP.js → chunk-DUDFNP5Y.js} +4 -4
  18. package/dist/{chunk-23HGQV22.js → chunk-MDRDX5R2.js} +197 -0
  19. package/dist/cli.js +14 -9
  20. package/dist/index.js +4 -4
  21. package/dist/{intelligence-service-ZHUJKZRO.js → intelligence-service-XUKYOHKL.js} +2 -2
  22. package/dist/mcp.js +2 -2
  23. package/package.json +9 -9
  24. package/assets/assets/ProjectPage-CAyx_xNr.js +0 -6
  25. package/assets/assets/index-BgWgJE7S.css +0 -1
@@ -170,6 +170,7 @@ import {
170
170
  missingDependency,
171
171
  normalizeProjectAliases,
172
172
  normalizeProjectDomain,
173
+ normalizeQueryText,
173
174
  normalizeUrlPath,
174
175
  notFound,
175
176
  notImplemented,
@@ -180,6 +181,7 @@ import {
180
181
  pickClusterRepresentative,
181
182
  projectConfigSchema,
182
183
  projectDtoSchema,
184
+ projectOverviewDtoSchema,
183
185
  projectReportDtoSchema,
184
186
  projectUpsertRequestSchema,
185
187
  providerError,
@@ -253,7 +255,7 @@ import {
253
255
  wordpressSchemaDeployResultDtoSchema,
254
256
  wordpressSchemaStatusResultDtoSchema,
255
257
  wordpressStatusDtoSchema
256
- } from "./chunk-23HGQV22.js";
258
+ } from "./chunk-MDRDX5R2.js";
257
259
 
258
260
  // src/intelligence-service.ts
259
261
  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 +4534,101 @@ function buildAiSourceOrigin(snapshots, projectDomains, competitorDomains, topDo
4532
4534
 
4533
4535
  // ../intelligence/src/movement-summary.ts
4534
4536
  function buildMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4537
+ return buildSignalMovementSummary(
4538
+ currentSnapshots,
4539
+ previousSnapshots,
4540
+ (snapshot) => snapshot.citationState === CitationStates.cited,
4541
+ options
4542
+ );
4543
+ }
4544
+ function buildCitationMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4545
+ return buildMovementSummary(currentSnapshots, previousSnapshots, options);
4546
+ }
4547
+ function buildMentionMovementSummary(currentSnapshots, previousSnapshots, options = {}) {
4548
+ return buildSignalMovementSummary(
4549
+ currentSnapshots,
4550
+ previousSnapshots,
4551
+ (snapshot) => snapshot.answerMentioned === true,
4552
+ options
4553
+ );
4554
+ }
4555
+ function buildMovementComparison(currentSnapshots, previousSnapshots, options = {}) {
4556
+ const currentIds = collectQueryIds(currentSnapshots);
4557
+ const previousIds = collectQueryIds(previousSnapshots);
4558
+ const hasPreviousRun = previousSnapshots.length > 0;
4559
+ if (!hasPreviousRun) {
4560
+ return {
4561
+ hasPreviousRun: false,
4562
+ comparable: false,
4563
+ querySetChanged: false,
4564
+ previousRunAt: null,
4565
+ currentQueryCount: currentIds.size,
4566
+ previousQueryCount: 0,
4567
+ comparableQueryCount: 0,
4568
+ addedQueryCount: 0,
4569
+ removedQueryCount: 0,
4570
+ addedQueries: [],
4571
+ removedQueries: []
4572
+ };
4573
+ }
4574
+ const comparableIds = intersection(currentIds, previousIds);
4575
+ const addedIds = difference(currentIds, previousIds);
4576
+ const removedIds = difference(previousIds, currentIds);
4577
+ const querySetChanged = addedIds.size > 0 || removedIds.size > 0;
4578
+ return {
4579
+ hasPreviousRun: true,
4580
+ comparable: !querySetChanged && currentIds.size > 0,
4581
+ querySetChanged,
4582
+ previousRunAt: options.previousRunAt ?? null,
4583
+ currentQueryCount: currentIds.size,
4584
+ previousQueryCount: previousIds.size,
4585
+ comparableQueryCount: comparableIds.size,
4586
+ addedQueryCount: addedIds.size,
4587
+ removedQueryCount: removedIds.size,
4588
+ addedQueries: resolveQueryTexts(addedIds, options.queryLookup),
4589
+ removedQueries: resolveQueryTexts(removedIds, options.queryLookup)
4590
+ };
4591
+ }
4592
+ function buildSignalMovementSummary(currentSnapshots, previousSnapshots, isActive, options) {
4535
4593
  if (previousSnapshots.length === 0) {
4536
- const citedIds = collectCitedQueryIds(currentSnapshots);
4537
- const citedCount = citedIds.size;
4538
- const tone2 = citedCount > 0 ? "positive" : "neutral";
4594
+ const activeIds = collectActiveQueryIds(currentSnapshots, isActive);
4539
4595
  return withQueryLists(
4540
- { gained: citedCount, lost: 0, tone: tone2, hasPreviousRun: false },
4541
- citedIds,
4596
+ {
4597
+ gained: activeIds.size,
4598
+ lost: 0,
4599
+ tone: activeIds.size > 0 ? "positive" : "neutral",
4600
+ hasPreviousRun: false
4601
+ },
4602
+ activeIds,
4542
4603
  /* @__PURE__ */ new Set(),
4543
4604
  options.queryLookup
4544
4605
  );
4545
4606
  }
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";
4607
+ const comparableIds = intersection(
4608
+ collectQueryIds(currentSnapshots),
4609
+ collectQueryIds(previousSnapshots)
4610
+ );
4611
+ const currentActive = intersection(collectActiveQueryIds(currentSnapshots, isActive), comparableIds);
4612
+ const previousActive = intersection(collectActiveQueryIds(previousSnapshots, isActive), comparableIds);
4613
+ const gainedIds = difference(currentActive, previousActive);
4614
+ const lostIds = difference(previousActive, currentActive);
4557
4615
  return withQueryLists(
4558
- { gained: gainedIds.size, lost: lostIds.size, tone, hasPreviousRun: true },
4616
+ {
4617
+ gained: gainedIds.size,
4618
+ lost: lostIds.size,
4619
+ tone: movementTone(gainedIds.size, lostIds.size),
4620
+ hasPreviousRun: true
4621
+ },
4559
4622
  gainedIds,
4560
4623
  lostIds,
4561
4624
  options.queryLookup
4562
4625
  );
4563
4626
  }
4627
+ function movementTone(gained, lost) {
4628
+ if (lost > gained) return "negative";
4629
+ if (gained > lost) return "positive";
4630
+ return "neutral";
4631
+ }
4564
4632
  function withQueryLists(base, gainedIds, lostIds, lookup) {
4565
4633
  if (!lookup) return base;
4566
4634
  return {
@@ -4570,6 +4638,7 @@ function withQueryLists(base, gainedIds, lostIds, lookup) {
4570
4638
  };
4571
4639
  }
4572
4640
  function resolveQueryTexts(ids, lookup) {
4641
+ if (!lookup) return [];
4573
4642
  const out = [];
4574
4643
  for (const id of ids) {
4575
4644
  const text2 = lookup.get(id);
@@ -4577,12 +4646,33 @@ function resolveQueryTexts(ids, lookup) {
4577
4646
  }
4578
4647
  return out.sort();
4579
4648
  }
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);
4649
+ function collectQueryIds(snapshots) {
4650
+ const ids = /* @__PURE__ */ new Set();
4651
+ for (const snapshot of snapshots) {
4652
+ if (snapshot.queryId) ids.add(snapshot.queryId);
4653
+ }
4654
+ return ids;
4655
+ }
4656
+ function collectActiveQueryIds(snapshots, isActive) {
4657
+ const active = /* @__PURE__ */ new Set();
4658
+ for (const snapshot of snapshots) {
4659
+ if (snapshot.queryId && isActive(snapshot)) active.add(snapshot.queryId);
4584
4660
  }
4585
- return cited;
4661
+ return active;
4662
+ }
4663
+ function intersection(left, right) {
4664
+ const out = /* @__PURE__ */ new Set();
4665
+ for (const value of left) {
4666
+ if (right.has(value)) out.add(value);
4667
+ }
4668
+ return out;
4669
+ }
4670
+ function difference(left, right) {
4671
+ const out = /* @__PURE__ */ new Set();
4672
+ for (const value of left) {
4673
+ if (!right.has(value)) out.add(value);
4674
+ }
4675
+ return out;
4586
4676
  }
4587
4677
 
4588
4678
  // ../intelligence/src/score-tones.ts
@@ -5027,12 +5117,12 @@ var DEFAULT_LIMIT = 10;
5027
5117
  function buildSuggestedQueries(gscRows, options) {
5028
5118
  const minImpressions = options.minImpressions ?? DEFAULT_MIN_IMPRESSIONS;
5029
5119
  const limit = options.limit ?? DEFAULT_LIMIT;
5030
- const trackedSet = new Set(options.trackedQueries.map(normalizeQuery));
5120
+ const trackedSet = new Set(options.trackedQueries.map(normalizeQueryText));
5031
5121
  let skippedAlreadyTracked = 0;
5032
5122
  const candidates = [];
5033
5123
  for (const row of gscRows) {
5034
5124
  if (row.impressions < minImpressions) continue;
5035
- const normalized = normalizeQuery(row.query);
5125
+ const normalized = normalizeQueryText(row.query);
5036
5126
  if (normalized.length === 0) continue;
5037
5127
  if (trackedSet.has(normalized)) {
5038
5128
  skippedAlreadyTracked++;
@@ -5054,9 +5144,6 @@ function buildSuggestedQueries(gscRows, options) {
5054
5144
  skippedAlreadyTracked
5055
5145
  };
5056
5146
  }
5057
- function normalizeQuery(value) {
5058
- return value.trim().toLowerCase();
5059
- }
5060
5147
  function buildReason(row) {
5061
5148
  const impressionsLabel = formatImpressions2(row.impressions);
5062
5149
  if (row.avgPosition <= 10) {
@@ -12924,18 +13011,31 @@ async function compositeRoutes(app) {
12924
13011
  const snapshotRunIds = new Set(sparklineRunIds);
12925
13012
  for (const run of latestVisRunGroup) snapshotRunIds.add(run.id);
12926
13013
  for (const run of previousVisRunGroup) snapshotRunIds.add(run.id);
12927
- const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds]);
13014
+ const projectQueries = app.db.select({ id: queries.id, query: queries.query }).from(queries).where(eq17(queries.projectId, project.id)).all();
13015
+ const queryIdByText = new Map(projectQueries.map((q) => [normalizeQueryText(q.query), q.id]));
13016
+ const snapshotsByRun = loadSnapshotsByRunIds(app, [...snapshotRunIds], queryIdByText);
12928
13017
  const latestSnapshots = latestVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
12929
13018
  const previousSnapshots = previousVisRunGroup.flatMap((r) => snapshotsByRun.get(r.id) ?? []);
12930
- const { queryCounts, providers } = summarizeFromSnapshots(latestSnapshots);
13019
+ const trackedLatest = latestSnapshots.filter((s) => !s.archived);
13020
+ const trackedPrevious = previousSnapshots.filter((s) => !s.archived);
13021
+ const trackedSnapshotsByRun = new Map(
13022
+ [...snapshotsByRun].map(([runId, snaps]) => [runId, snaps.filter((s) => !s.archived)])
13023
+ );
13024
+ const { queryCounts, providers } = summarizeFromSnapshots(trackedLatest);
12931
13025
  const transitions = summarizeTransitionsFromSnapshots(
12932
- latestSnapshots,
12933
- previousSnapshots,
13026
+ trackedLatest,
13027
+ trackedPrevious,
12934
13028
  previousVisibilityRun?.createdAt ?? null
12935
13029
  );
12936
13030
  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
13031
  const queryLookup = { byId: new Map(projectQueries.map((q) => [q.id, q.query])) };
13032
+ for (const snapshots of snapshotsByRun.values()) {
13033
+ for (const snapshot of snapshots) {
13034
+ if (snapshot.queryText && !queryLookup.byId.has(snapshot.queryId)) {
13035
+ queryLookup.byId.set(snapshot.queryId, snapshot.queryText);
13036
+ }
13037
+ }
13038
+ }
12939
13039
  const configuredApiProviders = project.providers.filter((p) => !p.startsWith("cdp:"));
12940
13040
  const mentionShareCompetitors = competitorRows.map((c) => ({
12941
13041
  domain: c.domain,
@@ -12945,32 +13045,39 @@ async function compositeRoutes(app) {
12945
13045
  brandTokens: [brandLabelFromDomain(c.domain)].filter((t) => t.length >= 3)
12946
13046
  }));
12947
13047
  const scores = {
12948
- mention: buildMentionCoverage(latestSnapshots, { configuredApiProviders }),
12949
- visibility: buildVisibilityScore(latestSnapshots, { configuredApiProviders }),
13048
+ mention: buildMentionCoverage(trackedLatest, { configuredApiProviders }),
13049
+ visibility: buildVisibilityScore(trackedLatest, { configuredApiProviders }),
12950
13050
  mentionShare: buildMentionShare(
12951
- latestSnapshots.map((s) => ({
13051
+ trackedLatest.map((s) => ({
12952
13052
  projectMentioned: s.answerMentioned === true,
12953
13053
  answerText: s.answerText
12954
13054
  })),
12955
13055
  { competitors: mentionShareCompetitors }
12956
13056
  ),
12957
- gapQueries: buildGapQueryScore(latestSnapshots),
12958
- mentionGaps: buildMentionGapScore(latestSnapshots),
13057
+ gapQueries: buildGapQueryScore(trackedLatest),
13058
+ mentionGaps: buildMentionGapScore(trackedLatest),
12959
13059
  indexCoverage: buildIndexCoverageScore(app, project.id),
12960
13060
  competitorPressure: buildCompetitorPressureScore(
12961
- latestSnapshots,
13061
+ trackedLatest,
12962
13062
  competitorRows.map((c) => c.domain),
12963
13063
  competitorRows.length
12964
13064
  ),
12965
13065
  runStatus: buildRunStatusScore(allRuns)
12966
13066
  };
12967
- const movementSummary = buildMovementSummary(latestSnapshots, previousSnapshots, {
13067
+ const citationMovement = buildCitationMovementSummary(latestSnapshots, previousSnapshots, {
12968
13068
  queryLookup: queryLookup.byId
12969
13069
  });
12970
- const providerScoresBase = buildProviderScores(latestSnapshots);
13070
+ const mentionMovement = buildMentionMovementSummary(latestSnapshots, previousSnapshots, {
13071
+ queryLookup: queryLookup.byId
13072
+ });
13073
+ const movementComparison = buildMovementComparison(latestSnapshots, previousSnapshots, {
13074
+ queryLookup: queryLookup.byId,
13075
+ previousRunAt: previousVisibilityRun?.createdAt ?? null
13076
+ });
13077
+ const providerScoresBase = buildProviderScores(trackedLatest);
12971
13078
  const providerTrends = buildProviderTrends(
12972
13079
  visibilityRuns.slice(0, DEFAULT_RUN_HISTORY_LIMIT).map((r) => ({ id: r.id, createdAt: r.createdAt })),
12973
- snapshotsByRun,
13080
+ trackedSnapshotsByRun,
12974
13081
  DEFAULT_RUN_HISTORY_LIMIT
12975
13082
  );
12976
13083
  const providerScores = providerScoresBase.map((score) => {
@@ -12978,13 +13085,13 @@ async function compositeRoutes(app) {
12978
13085
  return trend.length > 1 ? { ...score, trend: trend.map((p) => p.rate) } : score;
12979
13086
  });
12980
13087
  const overviewCompetitors = buildOverviewCompetitors(
12981
- latestSnapshots,
13088
+ trackedLatest,
12982
13089
  competitorRows.map((c) => ({ id: c.id, domain: c.domain })),
12983
13090
  queryLookup
12984
13091
  );
12985
13092
  const attentionItems = buildAttentionItems(insightRows, allRuns);
12986
13093
  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);
13094
+ const runHistory = buildRunHistory(sparklineRuns, trackedSnapshotsByRun);
12988
13095
  scores.mention.trend = runHistory.map((p) => p.mentionRate);
12989
13096
  scores.visibility.trend = runHistory.map((p) => p.citationRate);
12990
13097
  const suggestedQueries = buildSuggestedQueriesFromGsc(
@@ -13001,7 +13108,12 @@ async function compositeRoutes(app) {
13001
13108
  providers,
13002
13109
  transitions,
13003
13110
  scores,
13004
- movementSummary,
13111
+ // Keep the legacy citation-only field for API compatibility. New
13112
+ // consumers read the explicitly named siblings below.
13113
+ movementSummary: citationMovement,
13114
+ citationMovement,
13115
+ mentionMovement,
13116
+ movementComparison,
13005
13117
  competitors: overviewCompetitors,
13006
13118
  providerScores,
13007
13119
  attentionItems,
@@ -13111,12 +13223,13 @@ function summarizeRun(run) {
13111
13223
  createdAt: run.createdAt
13112
13224
  };
13113
13225
  }
13114
- function loadSnapshotsByRunIds(app, runIds) {
13226
+ function loadSnapshotsByRunIds(app, runIds, queryIdByText) {
13115
13227
  const result = /* @__PURE__ */ new Map();
13116
13228
  if (runIds.length === 0) return result;
13117
- const rows = filterTrackedSnapshots(app.db.select({
13229
+ const rows = app.db.select({
13118
13230
  runId: querySnapshots.runId,
13119
13231
  queryId: querySnapshots.queryId,
13232
+ queryText: querySnapshots.queryText,
13120
13233
  provider: querySnapshots.provider,
13121
13234
  model: querySnapshots.model,
13122
13235
  citationState: querySnapshots.citationState,
@@ -13124,11 +13237,30 @@ function loadSnapshotsByRunIds(app, runIds) {
13124
13237
  answerText: querySnapshots.answerText,
13125
13238
  competitorOverlap: querySnapshots.competitorOverlap,
13126
13239
  citedDomains: querySnapshots.citedDomains
13127
- }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all());
13240
+ }).from(querySnapshots).where(inArray9(querySnapshots.runId, [...runIds])).all();
13128
13241
  for (const row of rows) {
13242
+ const queryText = row.queryText?.trim() || null;
13243
+ let queryId;
13244
+ let archived = false;
13245
+ if (row.queryId) {
13246
+ queryId = row.queryId;
13247
+ } else if (queryText) {
13248
+ const tracked = queryIdByText.get(normalizeQueryText(queryText));
13249
+ if (tracked) {
13250
+ queryId = tracked;
13251
+ } else {
13252
+ queryId = `archived:${normalizeQueryText(queryText)}`;
13253
+ archived = true;
13254
+ }
13255
+ } else {
13256
+ queryId = null;
13257
+ }
13258
+ if (!queryId) continue;
13129
13259
  const list = result.get(row.runId) ?? [];
13130
13260
  list.push({
13131
- queryId: row.queryId,
13261
+ queryId,
13262
+ queryText,
13263
+ archived,
13132
13264
  provider: row.provider,
13133
13265
  model: row.model,
13134
13266
  citationState: row.citationState,
@@ -13618,6 +13750,7 @@ var SCHEMA_TABLE = {
13618
13750
  LocationContext: locationContextSchema,
13619
13751
  NotificationDto: notificationDtoSchema,
13620
13752
  ProjectDto: projectDtoSchema,
13753
+ ProjectOverviewDto: projectOverviewDtoSchema,
13621
13754
  ProjectReportDto: projectReportDtoSchema,
13622
13755
  QueryDto: queryDtoSchema,
13623
13756
  RunDetailDto: runDetailDtoSchema,
@@ -16851,12 +16984,11 @@ var routeCatalog = [
16851
16984
  method: "get",
16852
16985
  path: "/api/v1/projects/{name}/overview",
16853
16986
  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.',
16987
+ 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
16988
  tags: ["intelligence"],
16856
16989
  parameters: [nameParameter],
16857
16990
  responses: {
16858
- // TODO: Add `ProjectOverviewDto` Zod schema in contracts.
16859
- 200: rawJsonResponse("Overview returned.", looseObjectSchema),
16991
+ 200: jsonResponse("Overview returned.", "ProjectOverviewDto"),
16860
16992
  404: errorResponse("Project not found.")
16861
16993
  }
16862
16994
  },
@@ -23,7 +23,7 @@ import {
23
23
  trafficConnectVercelRequestSchema,
24
24
  trafficConnectWordpressRequestSchema,
25
25
  trafficEventKindSchema
26
- } from "./chunk-23HGQV22.js";
26
+ } from "./chunk-MDRDX5R2.js";
27
27
 
28
28
  // src/config.ts
29
29
  import fs from "fs";
@@ -5597,7 +5597,7 @@ var canonryMcpTools = [
5597
5597
  defineTool({
5598
5598
  name: "canonry_project_overview",
5599
5599
  title: "Get project overview (composite)",
5600
- description: 'One-call summary for "how is project X doing?" \u2014 bundles project info, latest run, top undismissed insights, latest health snapshot, query cited rate, per-provider breakdown, gained/lost/emerging vs the previous run, the five score gauges (visibility, gap queries, index coverage, competitor pressure, run status), per-(provider, model) scores, configured competitors with pressure labels, an attention queue of critical/high insights, and a recent-runs sparkline. Filterable by location and time window. Prefer this over fanning out to separate tools.',
5600
+ description: 'One-call summary for "how is project X doing?". Returns independent mention and citation coverage, separate query-level movement for each signal, query-basket comparability with added/removed counts, latest run and health, insights, provider/model breakdowns, competitors, attention items, and recent history. Movement excludes queries not shared by both sweeps. Filterable by location and time window. Prefer this over fanning out to separate tools.',
5601
5601
  access: "read",
5602
5602
  tier: "core",
5603
5603
  inputSchema: z2.object({
@@ -9,7 +9,7 @@ import {
9
9
  loadConfig,
10
10
  loadConfigRaw,
11
11
  saveConfigPatch
12
- } from "./chunk-LLJPZKHG.js";
12
+ } from "./chunk-6XMXBAEW.js";
13
13
  import {
14
14
  CC_CACHE_DIR,
15
15
  DUCKDB_SPEC,
@@ -104,7 +104,7 @@ import {
104
104
  siteAuditPages,
105
105
  siteAuditSnapshots,
106
106
  usageCounters
107
- } from "./chunk-DLBQU3VG.js";
107
+ } from "./chunk-5LW7CJAO.js";
108
108
  import {
109
109
  AGENT_MEMORY_VALUE_MAX_BYTES,
110
110
  AGENT_PROVIDER_IDS,
@@ -160,7 +160,7 @@ import {
160
160
  validationError,
161
161
  winnabilityClassLabel,
162
162
  withRetry
163
- } from "./chunk-23HGQV22.js";
163
+ } from "./chunk-MDRDX5R2.js";
164
164
 
165
165
  // src/telemetry.ts
166
166
  import crypto from "crypto";
@@ -6289,7 +6289,7 @@ function readStoredGroundingSources(rawResponse) {
6289
6289
  return result;
6290
6290
  }
6291
6291
  async function backfillInsightsCommand(project, opts) {
6292
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-ZHUJKZRO.js");
6292
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-XUKYOHKL.js");
6293
6293
  const config = loadConfig();
6294
6294
  const db = createClient(config.database);
6295
6295
  migrate(db);
@@ -3163,6 +3163,196 @@ var ccCachedReleaseSchema = z23.object({
3163
3163
 
3164
3164
  // ../contracts/src/composites.ts
3165
3165
  import { z as z24 } from "zod";
3166
+ var metricToneSchema = z24.enum(["positive", "caution", "negative", "neutral"]);
3167
+ var scoreSummarySchema = z24.object({
3168
+ label: z24.string(),
3169
+ value: z24.string(),
3170
+ delta: z24.string(),
3171
+ tone: metricToneSchema,
3172
+ description: z24.string(),
3173
+ tooltip: z24.string().optional(),
3174
+ trend: z24.array(z24.number()),
3175
+ progress: z24.number().optional(),
3176
+ providerCoverage: z24.string().optional()
3177
+ });
3178
+ var mentionShareSchema = scoreSummarySchema.extend({
3179
+ breakdown: z24.object({
3180
+ projectMentionSnapshots: z24.number().int().nonnegative(),
3181
+ competitorMentionSnapshots: z24.number().int().nonnegative(),
3182
+ perCompetitor: z24.array(z24.object({
3183
+ domain: z24.string(),
3184
+ mentionSnapshots: z24.number().int().nonnegative(),
3185
+ shareOfCompetitiveTotal: z24.number()
3186
+ })),
3187
+ snapshotsWithAnswerText: z24.number().int().nonnegative(),
3188
+ snapshotsTotal: z24.number().int().nonnegative()
3189
+ })
3190
+ });
3191
+ var movementSummarySchema = z24.object({
3192
+ gained: z24.number().int().nonnegative(),
3193
+ lost: z24.number().int().nonnegative(),
3194
+ tone: metricToneSchema,
3195
+ hasPreviousRun: z24.boolean(),
3196
+ gainedQueries: z24.array(z24.string()).optional(),
3197
+ lostQueries: z24.array(z24.string()).optional()
3198
+ });
3199
+ var movementComparisonSchema = z24.object({
3200
+ hasPreviousRun: z24.boolean(),
3201
+ comparable: z24.boolean(),
3202
+ querySetChanged: z24.boolean(),
3203
+ previousRunAt: z24.string().nullable(),
3204
+ currentQueryCount: z24.number().int().nonnegative(),
3205
+ previousQueryCount: z24.number().int().nonnegative(),
3206
+ comparableQueryCount: z24.number().int().nonnegative(),
3207
+ addedQueryCount: z24.number().int().nonnegative(),
3208
+ removedQueryCount: z24.number().int().nonnegative(),
3209
+ addedQueries: z24.array(z24.string()),
3210
+ removedQueries: z24.array(z24.string())
3211
+ });
3212
+ var projectOverviewInsightSchema = z24.object({
3213
+ id: z24.string(),
3214
+ projectId: z24.string(),
3215
+ runId: z24.string().nullable(),
3216
+ type: z24.enum([
3217
+ "regression",
3218
+ "gain",
3219
+ "opportunity",
3220
+ "first-citation",
3221
+ "provider-pickup",
3222
+ "persistent-gap",
3223
+ "competitor-gained",
3224
+ "competitor-lost",
3225
+ "gbp-lodging-gap",
3226
+ "gbp-listing-discrepancy",
3227
+ "gbp-cta-gap",
3228
+ "gbp-metric-drop",
3229
+ "gbp-keyword-drop"
3230
+ ]),
3231
+ severity: z24.enum(["critical", "high", "medium", "low"]),
3232
+ title: z24.string(),
3233
+ query: z24.string(),
3234
+ provider: z24.string(),
3235
+ recommendation: z24.object({
3236
+ action: z24.string(),
3237
+ target: z24.string().optional(),
3238
+ reason: z24.string()
3239
+ }).optional(),
3240
+ cause: z24.object({
3241
+ cause: z24.string(),
3242
+ competitorDomain: z24.string().optional(),
3243
+ details: z24.string().optional()
3244
+ }).optional(),
3245
+ dismissed: z24.boolean(),
3246
+ createdAt: z24.string()
3247
+ });
3248
+ var projectOverviewHealthSchema = z24.object({
3249
+ id: z24.string(),
3250
+ projectId: z24.string(),
3251
+ runId: z24.string().nullable(),
3252
+ overallCitedRate: z24.number(),
3253
+ overallMentionRate: z24.number(),
3254
+ totalPairs: z24.number().int().nonnegative(),
3255
+ citedPairs: z24.number().int().nonnegative(),
3256
+ mentionedPairs: z24.number().int().nonnegative(),
3257
+ providerBreakdown: z24.record(z24.string(), z24.object({
3258
+ citedRate: z24.number(),
3259
+ mentionRate: z24.number(),
3260
+ cited: z24.number().int().nonnegative(),
3261
+ mentioned: z24.number().int().nonnegative(),
3262
+ total: z24.number().int().nonnegative()
3263
+ })),
3264
+ createdAt: z24.string(),
3265
+ status: z24.enum(["ready", "no-data"]),
3266
+ reason: z24.literal("no-runs-yet").optional()
3267
+ });
3268
+ var projectOverviewDtoSchema = z24.object({
3269
+ project: projectDtoSchema,
3270
+ latestRun: latestProjectRunDtoSchema,
3271
+ health: projectOverviewHealthSchema.nullable(),
3272
+ topInsights: z24.array(projectOverviewInsightSchema),
3273
+ queryCounts: z24.object({
3274
+ totalQueries: z24.number().int().nonnegative(),
3275
+ citedQueries: z24.number().int().nonnegative(),
3276
+ notCitedQueries: z24.number().int().nonnegative(),
3277
+ citedRate: z24.number(),
3278
+ mentionedQueries: z24.number().int().nonnegative(),
3279
+ notMentionedQueries: z24.number().int().nonnegative(),
3280
+ mentionRate: z24.number()
3281
+ }),
3282
+ providers: z24.array(z24.object({
3283
+ provider: z24.string(),
3284
+ citedRate: z24.number(),
3285
+ cited: z24.number().int().nonnegative(),
3286
+ total: z24.number().int().nonnegative()
3287
+ })),
3288
+ transitions: z24.object({
3289
+ since: z24.string().nullable(),
3290
+ gained: z24.number().int().nonnegative(),
3291
+ lost: z24.number().int().nonnegative(),
3292
+ emerging: z24.number().int().nonnegative()
3293
+ }),
3294
+ scores: z24.object({
3295
+ mention: scoreSummarySchema,
3296
+ visibility: scoreSummarySchema,
3297
+ mentionShare: mentionShareSchema,
3298
+ gapQueries: scoreSummarySchema,
3299
+ mentionGaps: scoreSummarySchema,
3300
+ indexCoverage: scoreSummarySchema,
3301
+ competitorPressure: scoreSummarySchema,
3302
+ runStatus: scoreSummarySchema
3303
+ }),
3304
+ movementSummary: movementSummarySchema,
3305
+ citationMovement: movementSummarySchema,
3306
+ mentionMovement: movementSummarySchema,
3307
+ movementComparison: movementComparisonSchema,
3308
+ competitors: z24.array(z24.object({
3309
+ id: z24.string(),
3310
+ domain: z24.string(),
3311
+ citationCount: z24.number().int().nonnegative(),
3312
+ totalQueries: z24.number().int().nonnegative(),
3313
+ pressureLabel: z24.enum(["None", "Low", "Moderate", "High"]),
3314
+ citedQueries: z24.array(z24.string())
3315
+ })),
3316
+ providerScores: z24.array(z24.object({
3317
+ provider: z24.string(),
3318
+ model: z24.string().nullable(),
3319
+ score: z24.number(),
3320
+ cited: z24.number().int().nonnegative(),
3321
+ total: z24.number().int().nonnegative(),
3322
+ trend: z24.array(z24.number()).optional()
3323
+ })),
3324
+ attentionItems: z24.array(z24.object({
3325
+ id: z24.string(),
3326
+ tone: metricToneSchema,
3327
+ title: z24.string(),
3328
+ detail: z24.string(),
3329
+ actionLabel: z24.string(),
3330
+ href: z24.string()
3331
+ })),
3332
+ runHistory: z24.array(z24.object({
3333
+ runId: z24.string(),
3334
+ createdAt: z24.string(),
3335
+ citedCount: z24.number().int().nonnegative(),
3336
+ totalCount: z24.number().int().nonnegative(),
3337
+ citationRate: z24.number(),
3338
+ mentionedCount: z24.number().int().nonnegative(),
3339
+ mentionRate: z24.number(),
3340
+ status: z24.string()
3341
+ })),
3342
+ suggestedQueries: z24.object({
3343
+ rows: z24.array(z24.object({
3344
+ query: z24.string(),
3345
+ impressions: z24.number(),
3346
+ clicks: z24.number(),
3347
+ avgPosition: z24.number(),
3348
+ reason: z24.string()
3349
+ })),
3350
+ totalCandidates: z24.number().int().nonnegative(),
3351
+ skippedAlreadyTracked: z24.number().int().nonnegative()
3352
+ }),
3353
+ dateRangeLabel: z24.string(),
3354
+ contextLabel: z24.string()
3355
+ });
3166
3356
  var searchHitKindSchema = z24.enum(["snapshot", "insight"]);
3167
3357
  var projectSearchSnapshotHitSchema = z24.object({
3168
3358
  kind: z24.literal("snapshot"),
@@ -3533,6 +3723,11 @@ function summarizeCheckResults(results) {
3533
3723
  return summary;
3534
3724
  }
3535
3725
 
3726
+ // ../contracts/src/query-normalize.ts
3727
+ function normalizeQueryText(value) {
3728
+ return value.trim().toLowerCase();
3729
+ }
3730
+
3536
3731
  // ../contracts/src/citations.ts
3537
3732
  import { z as z27 } from "zod";
3538
3733
  var citationCoverageProviderSchema = z27.object({
@@ -5029,6 +5224,7 @@ export {
5029
5224
  backlinksInstallResultDtoSchema,
5030
5225
  ccAvailableReleaseSchema,
5031
5226
  ccCachedReleaseSchema,
5227
+ projectOverviewDtoSchema,
5032
5228
  ContentActions,
5033
5229
  contentActionLabel,
5034
5230
  actionConfidenceLabel,
@@ -5052,6 +5248,7 @@ export {
5052
5248
  CheckCategories,
5053
5249
  doctorReportSchema,
5054
5250
  summarizeCheckResults,
5251
+ normalizeQueryText,
5055
5252
  citationVisibilityResponseSchema,
5056
5253
  emptyCitationVisibility,
5057
5254
  citationStateToCited,