@ainyc/canonry 4.106.2 → 4.108.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 (22) hide show
  1. package/assets/assets/{BacklinksPage-BheRgyA3.js → BacklinksPage-Dr6MK4h1.js} +1 -1
  2. package/assets/assets/{ChartPrimitives-BnYX5sRb.js → ChartPrimitives-DGO0EGqM.js} +1 -1
  3. package/assets/assets/{ProjectPage-Bd3YW6WQ.js → ProjectPage-fHczny77.js} +1 -1
  4. package/assets/assets/{RunRow-CZ1bXteZ.js → RunRow-Br6uOqD1.js} +1 -1
  5. package/assets/assets/{RunsPage-BBEuL03-.js → RunsPage-MMEhx2vt.js} +1 -1
  6. package/assets/assets/{SettingsPage-CJ6kyLmY.js → SettingsPage-BjZZODA1.js} +1 -1
  7. package/assets/assets/{TrafficPage-DPrsjoFM.js → TrafficPage-BtMtH6FT.js} +1 -1
  8. package/assets/assets/{TrafficSourceDetailPage-EdC_c26M.js → TrafficSourceDetailPage-DFFGJsVC.js} +1 -1
  9. package/assets/assets/{arrow-left-B4_PxNUG.js → arrow-left-DDnYTDM6.js} +1 -1
  10. package/assets/assets/{extract-error-message-DoIzPHJI.js → extract-error-message-B0KSa98X.js} +1 -1
  11. package/assets/assets/{index-Dr2rxkFR.js → index-imKX6Edq.js} +46 -46
  12. package/assets/assets/{trash-2-C9Ebw5p4.js → trash-2-BODtOdNo.js} +1 -1
  13. package/assets/index.html +1 -1
  14. package/dist/{chunk-F3MKVMS5.js → chunk-45YSA7GH.js} +84 -13
  15. package/dist/{chunk-VGMTLUUM.js → chunk-EKGKGKID.js} +2 -1
  16. package/dist/{chunk-2AWATO4I.js → chunk-EMG4LIUJ.js} +18 -5
  17. package/dist/{chunk-VOR5ZTUU.js → chunk-IQX7XXJN.js} +71 -0
  18. package/dist/cli.js +13 -8
  19. package/dist/index.js +4 -4
  20. package/dist/{intelligence-service-JGZU27U3.js → intelligence-service-YDPWS7OC.js} +2 -2
  21. package/dist/mcp.js +2 -2
  22. package/package.json +8 -8
@@ -1 +1 @@
1
- import{c}from"./index-Dr2rxkFR.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
1
+ import{c}from"./index-imKX6Edq.js";const a=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],h=c("circle-check",a);const e=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],n=c("circle-question-mark",e);const o=[["path",{d:"M12 15V3",key:"m9g1x1"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}],["path",{d:"m7 10 5 5 5-5",key:"brsn70"}]],s=c("download",o);const t=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],r=c("trash-2",t);export{h as C,s as D,r as T,n as a};
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-Dr2rxkFR.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-imKX6Edq.js"></script>
16
16
  <link rel="modulepreload" crossorigin href="./assets/vendor-tanstack-Be8JdV5M.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/vendor-radix-CQ77EO2y.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/vendor-recharts-C9EZkgbP.js">
@@ -125,6 +125,7 @@ import {
125
125
  emptyCitationVisibility,
126
126
  escapeLikePattern,
127
127
  extractAnswerMentions,
128
+ filterBrandedSeedCandidates,
128
129
  findDuplicateLocationLabels,
129
130
  forbidden,
130
131
  formatAverageDelta,
@@ -183,6 +184,7 @@ import {
183
184
  notFound,
184
185
  notImplemented,
185
186
  notificationDtoSchema,
187
+ orderLocationsDefaultFirst,
186
188
  parseInclusiveEndMs,
187
189
  parseReportPeriodDays,
188
190
  parseRunError,
@@ -267,10 +269,10 @@ import {
267
269
  wordpressSchemaDeployResultDtoSchema,
268
270
  wordpressSchemaStatusResultDtoSchema,
269
271
  wordpressStatusDtoSchema
270
- } from "./chunk-VOR5ZTUU.js";
272
+ } from "./chunk-IQX7XXJN.js";
271
273
 
272
274
  // src/intelligence-service.ts
273
- import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or5, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
275
+ import { eq as eq38, desc as desc18, asc as asc6, and as and28, ne as ne5, or as or6, inArray as inArray14, gte as gte7, lte as lte4 } from "drizzle-orm";
274
276
 
275
277
  // ../db/src/client.ts
276
278
  import { mkdirSync } from "fs";
@@ -1141,6 +1143,15 @@ var discoverySessions = sqliteTable("discovery_sessions", {
1141
1143
  // seed time; nullable for legacy sessions. No gate/warning math reads these.
1142
1144
  seedFromAnswerCount: integer("seed_from_answer_count"),
1143
1145
  seedFromGroundingCount: integer("seed_from_grounding_count"),
1146
+ // Diagnostics: raw candidates dropped by the branded self-query filter
1147
+ // before seed_count_raw was recorded. Nullable for legacy sessions.
1148
+ seedBrandFilteredCount: integer("seed_brand_filtered_count"),
1149
+ // Buyer definition the session was seeded with; part of session identity
1150
+ // for in-flight consolidation (same ICP + different buyer never reuses).
1151
+ buyerDescription: text("buyer_description"),
1152
+ // Resolved service areas the session was seeded/probed with; part of session
1153
+ // identity for in-flight consolidation. Null on legacy sessions.
1154
+ locations: text("locations", { mode: "json" }).$type(),
1144
1155
  dedupThreshold: real("dedup_threshold"),
1145
1156
  probeCount: integer("probe_count"),
1146
1157
  citedCount: integer("cited_count"),
@@ -3390,6 +3401,37 @@ var MIGRATION_VERSIONS = [
3390
3401
  `ALTER TABLE discovery_sessions ADD COLUMN seed_from_answer_count INTEGER`,
3391
3402
  `ALTER TABLE discovery_sessions ADD COLUMN seed_from_grounding_count INTEGER`
3392
3403
  ]
3404
+ },
3405
+ {
3406
+ version: 89,
3407
+ name: "discovery-session-brand-filter-count",
3408
+ statements: [
3409
+ // Diagnostics: raw candidates dropped by the branded self-query filter
3410
+ // before seed_count_raw was recorded. Nullable — legacy sessions stay
3411
+ // null. Idempotent (the runner swallows the duplicate-column error).
3412
+ `ALTER TABLE discovery_sessions ADD COLUMN seed_brand_filtered_count INTEGER`
3413
+ ]
3414
+ },
3415
+ {
3416
+ version: 90,
3417
+ name: "discovery-session-buyer-description",
3418
+ statements: [
3419
+ // Buyer definition the session was seeded with. Part of the in-flight
3420
+ // consolidation identity (a request with a different buyer must never
3421
+ // reuse another buyer's session) and auditability for seed provenance.
3422
+ `ALTER TABLE discovery_sessions ADD COLUMN buyer_description TEXT`
3423
+ ]
3424
+ },
3425
+ {
3426
+ version: 91,
3427
+ name: "discovery-session-locations",
3428
+ statements: [
3429
+ // Resolved service areas the session was seeded/probed with. Part of the
3430
+ // in-flight consolidation identity (a different location subset never
3431
+ // reuses another geo's session) and seed/probe provenance. Nullable —
3432
+ // legacy sessions stay null.
3433
+ `ALTER TABLE discovery_sessions ADD COLUMN locations TEXT`
3434
+ ]
3393
3435
  }
3394
3436
  ];
3395
3437
  function rebuildBacklinkTableWithSource(tx, table) {
@@ -3582,7 +3624,7 @@ function getAppliedVersion(db) {
3582
3624
  function recordMigration(db, version, name) {
3583
3625
  db.run(sql`INSERT OR IGNORE INTO _migrations (version, name) VALUES (${version}, ${name})`);
3584
3626
  }
3585
- function migrate(db) {
3627
+ function migrate(db, versions = MIGRATION_VERSIONS) {
3586
3628
  db.transaction((tx) => {
3587
3629
  normalizeLegacyQuerySchema(tx);
3588
3630
  });
@@ -3591,7 +3633,7 @@ function migrate(db) {
3591
3633
  db.run(sql.raw(statement));
3592
3634
  }
3593
3635
  const appliedVersion = getAppliedVersion(db);
3594
- for (const mv of MIGRATION_VERSIONS) {
3636
+ for (const mv of versions) {
3595
3637
  if (mv.version <= appliedVersion) continue;
3596
3638
  db.transaction((tx) => {
3597
3639
  for (const statement of mv.statements) {
@@ -18056,6 +18098,7 @@ var routeCatalog = [
18056
18098
  type: "object",
18057
18099
  properties: {
18058
18100
  icpDescription: { type: "string", description: "Free-text ICP. Required if the project does not have spec.icpDescription stored." },
18101
+ buyerDescription: { type: "string", description: "Who evaluates or buys the offering, separate from the ICP. When present, every generated seed query is anchored on this buyer and the seed prompt enforces buyer-fit." },
18059
18102
  dedupThreshold: { type: "number", description: "Cosine similarity threshold for clustering. Defaults to 0.95." },
18060
18103
  maxProbes: { type: "integer", description: "Max canonical queries to probe in this session. Default 100, hard cap 500." },
18061
18104
  probeConcurrency: {
@@ -33729,7 +33772,7 @@ async function doctorRoutes(app, opts) {
33729
33772
 
33730
33773
  // ../api-routes/src/discovery/routes.ts
33731
33774
  import crypto27 from "crypto";
33732
- import { and as and26, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12 } from "drizzle-orm";
33775
+ import { and as and26, desc as desc16, eq as eq35, gte as gte6, inArray as inArray12, isNull, or as or5 } from "drizzle-orm";
33733
33776
  var MAX_INFLIGHT_DISCOVERY_AGE_MS = 2 * 60 * 60 * 1e3;
33734
33777
  async function discoveryRoutes(app, opts) {
33735
33778
  app.post("/projects/:name/discover/run", async (request, reply) => {
@@ -33749,9 +33792,9 @@ async function discoveryRoutes(app, opts) {
33749
33792
  "icpDescription is required. Pass it in the request body or store it on the project (spec.icpDescription)."
33750
33793
  );
33751
33794
  }
33752
- const locations = resolveLocations(
33753
- project.locations,
33754
- parsed.data.locations
33795
+ const locations = orderLocationsDefaultFirst(
33796
+ resolveLocations(project.locations, parsed.data.locations),
33797
+ project.defaultLocation
33755
33798
  );
33756
33799
  if (!opts.onDiscoveryRunRequested) {
33757
33800
  throw validationError("Discovery is not available on this deployment.", {
@@ -33764,6 +33807,20 @@ async function discoveryRoutes(app, opts) {
33764
33807
  const existing = tx.select({ id: discoverySessions.id, runId: discoverySessions.runId }).from(discoverySessions).where(and26(
33765
33808
  eq35(discoverySessions.projectId, project.id),
33766
33809
  eq35(discoverySessions.icpDescription, icpDescription),
33810
+ // Buyer is part of session identity: it changes the seed prompt's
33811
+ // semantics, so a request with a different (or no) buyer must start
33812
+ // its own session, never adopt another buyer's probes.
33813
+ parsed.data.buyerDescription == null ? isNull(discoverySessions.buyerDescription) : eq35(discoverySessions.buyerDescription, parsed.data.buyerDescription),
33814
+ // Locations are identity too: a different service-area subset seeds
33815
+ // and probes a different geo, so it must never reuse another geo's
33816
+ // session. resolveLocations is deterministic (project-config order),
33817
+ // so equal subsets always serialize identically. Legacy in-flight
33818
+ // rows (pre-91) carry NULL: on a NO-location project that geo is
33819
+ // unambiguous (always []), so NULL matches; on a project WITH
33820
+ // locations a NULL row's subset is unknowable, so it conservatively
33821
+ // never reuses — a one-time, bounded (2h window) non-reuse after
33822
+ // upgrade, never a wrong reuse.
33823
+ locations.length === 0 ? or5(isNull(discoverySessions.locations), eq35(discoverySessions.locations, locations)) : eq35(discoverySessions.locations, locations),
33767
33824
  inArray12(discoverySessions.status, [
33768
33825
  DiscoverySessionStatuses.queued,
33769
33826
  DiscoverySessionStatuses.seeding,
@@ -33782,6 +33839,8 @@ async function discoveryRoutes(app, opts) {
33782
33839
  runId,
33783
33840
  status: DiscoverySessionStatuses.queued,
33784
33841
  icpDescription,
33842
+ buyerDescription: parsed.data.buyerDescription ?? null,
33843
+ locations,
33785
33844
  dedupThreshold: parsed.data.dedupThreshold,
33786
33845
  competitorMap: [],
33787
33846
  createdAt: now
@@ -33816,6 +33875,7 @@ async function discoveryRoutes(app, opts) {
33816
33875
  sessionId: decision.sessionId,
33817
33876
  projectId: project.id,
33818
33877
  icpDescription,
33878
+ buyerDescription: parsed.data.buyerDescription,
33819
33879
  dedupThreshold: parsed.data.dedupThreshold,
33820
33880
  maxProbes: parsed.data.maxProbes,
33821
33881
  probeConcurrency: parsed.data.probeConcurrency,
@@ -34070,6 +34130,9 @@ function serializeSession(row) {
34070
34130
  seedCount: row.seedCount ?? null,
34071
34131
  seedFromAnswerCount: row.seedFromAnswerCount ?? null,
34072
34132
  seedFromGroundingCount: row.seedFromGroundingCount ?? null,
34133
+ seedBrandFilteredCount: row.seedBrandFilteredCount ?? null,
34134
+ buyerDescription: row.buyerDescription ?? null,
34135
+ locations: row.locations ?? null,
34073
34136
  dedupThreshold: row.dedupThreshold ?? null,
34074
34137
  probeCount: row.probeCount ?? null,
34075
34138
  citedCount: row.citedCount ?? null,
@@ -34176,9 +34239,15 @@ async function executeDiscovery(opts) {
34176
34239
  const seedResult = await opts.deps.seed({
34177
34240
  project: opts.project,
34178
34241
  icpDescription: opts.icpDescription,
34242
+ buyerDescription: opts.buyerDescription,
34179
34243
  locations: opts.locations ?? []
34180
34244
  });
34181
- const rawCandidates = dedupeStrings(seedResult.candidates);
34245
+ const { kept: unbrandedCandidates, droppedBranded } = filterBrandedSeedCandidates({
34246
+ candidates: seedResult.candidates,
34247
+ brandNames: opts.project.brandNames ?? [],
34248
+ canonicalDomains: opts.project.canonicalDomains
34249
+ });
34250
+ const rawCandidates = dedupeStrings(unbrandedCandidates);
34182
34251
  const seedCountRaw = rawCandidates.length;
34183
34252
  const canonicals = await pickCanonicals(
34184
34253
  rawCandidates,
@@ -34201,12 +34270,14 @@ async function executeDiscovery(opts) {
34201
34270
  // the seed dep does not report the split. Not consumed by any gate.
34202
34271
  seedFromAnswerCount: seedResult.fromAnswerCount ?? null,
34203
34272
  seedFromGroundingCount: seedResult.fromGroundingCount ?? null,
34273
+ seedBrandFilteredCount: droppedBranded.length,
34204
34274
  warning
34205
34275
  }).where(eq36(discoverySessions.id, opts.sessionId)).run();
34276
+ const probeLocation = opts.locations?.[0];
34206
34277
  const probeResults = await mapWithConcurrency(
34207
34278
  probedCanonicals,
34208
34279
  probeConcurrency,
34209
- (query) => opts.deps.probe({ project: opts.project, query })
34280
+ (query) => opts.deps.probe({ project: opts.project, query, location: probeLocation })
34210
34281
  );
34211
34282
  const probeRows = [];
34212
34283
  const buckets = { cited: 0, aspirational: 0, "wasted-surface": 0 };
@@ -35025,7 +35096,7 @@ var IntelligenceService = class {
35025
35096
  const recentRuns = this.db.select().from(runs).where(
35026
35097
  and28(
35027
35098
  eq38(runs.projectId, projectId),
35028
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35099
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35029
35100
  // Defensive: RunCoordinator already skips probes before this is
35030
35101
  // called, but if a future call site invokes analyzeAndPersist
35031
35102
  // directly for a probe, probes still must not pollute the
@@ -35328,7 +35399,7 @@ var IntelligenceService = class {
35328
35399
  const allRuns = this.db.select().from(runs).where(
35329
35400
  and28(
35330
35401
  eq38(runs.projectId, project.id),
35331
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35402
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35332
35403
  // Backfill must not replay probe runs as if they were real sweeps.
35333
35404
  ne5(runs.trigger, RunTriggers.probe)
35334
35405
  )
@@ -35506,7 +35577,7 @@ var IntelligenceService = class {
35506
35577
  and28(
35507
35578
  eq38(runs.projectId, projectId),
35508
35579
  eq38(runs.kind, RunKinds["answer-visibility"]),
35509
- or5(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35580
+ or6(eq38(runs.status, "completed"), eq38(runs.status, "partial")),
35510
35581
  // Defensive — see top of file.
35511
35582
  ne5(runs.trigger, RunTriggers.probe)
35512
35583
  )
@@ -25,7 +25,7 @@ import {
25
25
  trafficConnectVercelRequestSchema,
26
26
  trafficConnectWordpressRequestSchema,
27
27
  trafficEventKindSchema
28
- } from "./chunk-VOR5ZTUU.js";
28
+ } from "./chunk-IQX7XXJN.js";
29
29
 
30
30
  // src/config.ts
31
31
  import fs from "fs";
@@ -5565,6 +5565,7 @@ var discoveryRunInputSchema = z2.object({
5565
5565
  // Stronger descriptions for the LLM. The base Zod schema enforces the
5566
5566
  // upper bound; this just clarifies the meaning of each knob.
5567
5567
  icpDescription: z2.string().min(1).optional().describe("Free-text ICP description. If omitted, the project must already have spec.icpDescription stored."),
5568
+ buyerDescription: z2.string().min(1).optional().describe("Who evaluates or buys the offering, separate from the ICP. When present, every generated query is anchored on this buyer."),
5568
5569
  dedupThreshold: z2.number().min(0).max(1).optional().describe("Cosine similarity threshold for clustering seed candidates. Defaults to 0.85. Lower values dedupe more aggressively."),
5569
5570
  maxProbes: z2.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional().describe(`Max canonical queries to probe in this session. Default 100, hard cap ${DISCOVERY_MAX_PROBES_CAP}.`),
5570
5571
  probeConcurrency: z2.number().int().min(1).max(DISCOVERY_PROBE_CONCURRENCY_CAP).optional().describe(`How many probes may run in parallel. Default 1 (strictly serial), hard cap ${DISCOVERY_PROBE_CONCURRENCY_CAP}. Probe rows are persisted in canonical order regardless of concurrency, so this only shortens wall-clock time.`)
@@ -10,7 +10,7 @@ import {
10
10
  loadConfig,
11
11
  loadConfigRaw,
12
12
  saveConfigPatch
13
- } from "./chunk-VGMTLUUM.js";
13
+ } from "./chunk-EKGKGKID.js";
14
14
  import {
15
15
  CC_CACHE_DIR,
16
16
  DUCKDB_SPEC,
@@ -115,7 +115,7 @@ import {
115
115
  siteAuditPages,
116
116
  siteAuditSnapshots,
117
117
  usageCounters
118
- } from "./chunk-F3MKVMS5.js";
118
+ } from "./chunk-45YSA7GH.js";
119
119
  import {
120
120
  AGENT_MEMORY_VALUE_MAX_BYTES,
121
121
  AGENT_PROVIDER_IDS,
@@ -178,7 +178,7 @@ import {
178
178
  validationError,
179
179
  winnabilityClassLabel,
180
180
  withRetry
181
- } from "./chunk-VOR5ZTUU.js";
181
+ } from "./chunk-IQX7XXJN.js";
182
182
 
183
183
  // src/telemetry.ts
184
184
  import crypto from "crypto";
@@ -5572,6 +5572,7 @@ async function executeDiscoveryRun(opts) {
5572
5572
  sessionId: opts.sessionId,
5573
5573
  project,
5574
5574
  icpDescription: opts.icpDescription,
5575
+ buyerDescription: opts.buyerDescription,
5575
5576
  dedupThreshold: opts.dedupThreshold,
5576
5577
  maxProbes: opts.maxProbes,
5577
5578
  probeConcurrency: opts.probeConcurrency,
@@ -5648,7 +5649,10 @@ function buildDefaultDeps(registry) {
5648
5649
  {
5649
5650
  query: input.query,
5650
5651
  canonicalDomains: input.project.canonicalDomains,
5651
- competitorDomains: input.project.competitorDomains
5652
+ competitorDomains: input.project.competitorDomains,
5653
+ // Same geo mechanism as sweeps: the provider renders the location
5654
+ // into the prompt so the probe measures from the buyer's area.
5655
+ ...input.location ? { location: input.location } : {}
5652
5656
  },
5653
5657
  cfg
5654
5658
  );
@@ -5765,11 +5769,17 @@ function buildLocationConstraint(locations) {
5765
5769
  function buildSeedPrompt(input) {
5766
5770
  const locationConstraint = buildLocationConstraint(input.locations ?? []);
5767
5771
  const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
5772
+ const buyer = input.buyerDescription?.trim() ?? "";
5773
+ const brandIdentities = [...input.project.brandNames ?? [], ...input.project.canonicalDomains];
5768
5774
  return [
5769
5775
  "You are an AEO (Answer Engine Optimization) analyst expanding a tracked-query basket for a customer.",
5770
5776
  "",
5771
5777
  `Customer: ${input.project.name} (domains: ${input.project.canonicalDomains.join(", ")})`,
5772
5778
  `ICP: ${input.icpDescription}`,
5779
+ ...buyer ? [
5780
+ `Buyer: ${buyer}`,
5781
+ "Every query must be one this buyer would plausibly type into an answer engine. Skip queries only an industry insider, investor, or job seeker would ask."
5782
+ ] : [],
5773
5783
  ...locationConstraint.length > 0 ? ["", ...locationConstraint] : [],
5774
5784
  "",
5775
5785
  "Brainstorm queries a member of this ICP would type into an AI answer engine (Gemini, ChatGPT, Perplexity). Generate candidates across the five intent buckets below \u2014 these are SEMANTICALLY DISTINCT search intents, not stylistic variants. A query should fit one bucket cleanly. Diversity across buckets is the point: it keeps the list from collapsing into near-synonyms of a single intent.",
@@ -5780,6 +5790,8 @@ function buildSeedPrompt(input) {
5780
5790
  ' 4. Comparative \u2014 the searcher is weighing named alternatives head-to-head. Templates: "X vs Y", "X or Y for Z", "alternatives to X".',
5781
5791
  ' 5. Transactional \u2014 the searcher is ready to act on a purchase or booking. Templates: "book X", "X pricing", "X discount code", "buy X online".',
5782
5792
  "",
5793
+ `Hard rule: NEVER include the customer's own brand name or domain (${brandIdentities.join(", ") || input.project.name}) in any query. A buyer who already knows the business by name is out of scope for this list; the goal is queries where the customer must EARN the mention. Naming competitors or directories is fine (Navigational queries should target directories and platforms, never the customer).`,
5794
+ "",
5783
5795
  `Generate EXACTLY ${QUERIES_PER_INTENT_BUCKET} queries per bucket \u2014 ${QUERIES_PER_INTENT_BUCKET * 5} total. Return ONE query per line. Plain text only \u2014 no numbering, bullets, quotes, bucket labels, or commentary.`
5784
5796
  ].join("\n");
5785
5797
  }
@@ -6473,7 +6485,7 @@ function readStoredGroundingSources(rawResponse) {
6473
6485
  return result;
6474
6486
  }
6475
6487
  async function backfillInsightsCommand(project, opts) {
6476
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JGZU27U3.js");
6488
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-YDPWS7OC.js");
6477
6489
  const config = loadConfig();
6478
6490
  const db = createClient(config.database);
6479
6491
  migrate(db);
@@ -11938,6 +11950,7 @@ async function createServer(opts) {
11938
11950
  sessionId: input.sessionId,
11939
11951
  projectId: input.projectId,
11940
11952
  icpDescription: input.icpDescription,
11953
+ buyerDescription: input.buyerDescription,
11941
11954
  dedupThreshold: input.dedupThreshold,
11942
11955
  maxProbes: input.maxProbes,
11943
11956
  probeConcurrency: input.probeConcurrency,
@@ -169,6 +169,12 @@ function hasLocationLabel(locations, label) {
169
169
  if (!label) return true;
170
170
  return locations.some((location) => location.label === label);
171
171
  }
172
+ function orderLocationsDefaultFirst(locations, defaultLabel) {
173
+ if (!defaultLabel) return [...locations];
174
+ const index = locations.findIndex((l) => l.label === defaultLabel);
175
+ if (index <= 0) return [...locations];
176
+ return [locations[index], ...locations.slice(0, index), ...locations.slice(index + 1)];
177
+ }
172
178
  function resolveLocations(projectLocations, requestedLabels) {
173
179
  const normalizedRequest = (requestedLabels ?? []).map((label) => label.trim()).filter((label) => label.length > 0);
174
180
  if (normalizedRequest.length === 0) return [...projectLocations];
@@ -2186,6 +2192,19 @@ var discoverySessionDtoSchema = z17.object({
2186
2192
  */
2187
2193
  seedFromAnswerCount: z17.number().int().nullable().optional(),
2188
2194
  seedFromGroundingCount: z17.number().int().nullable().optional(),
2195
+ /**
2196
+ * Diagnostics: how many raw seed candidates were dropped by the branded
2197
+ * self-query filter before seedCountRaw was recorded (the customer's own
2198
+ * brand or domain in the query text). Null on legacy sessions. Purely
2199
+ * additive: no gate or warning reads it.
2200
+ */
2201
+ seedBrandFilteredCount: z17.number().int().nullable().optional(),
2202
+ /** Buyer definition the session was seeded with (part of session identity
2203
+ * for in-flight consolidation). Null on legacy / no-buyer sessions. */
2204
+ buyerDescription: z17.string().nullable().optional(),
2205
+ /** Resolved service areas the session was seeded/probed with (part of
2206
+ * session identity for in-flight consolidation). Null on legacy sessions. */
2207
+ locations: z17.array(locationContextSchema).nullable().optional(),
2189
2208
  dedupThreshold: z17.number().nullable().optional(),
2190
2209
  probeCount: z17.number().int().nullable().optional(),
2191
2210
  citedCount: z17.number().int().nullable().default(null),
@@ -2219,8 +2238,58 @@ function seedCollapseWarning(input) {
2219
2238
  const noun = canonicalCount === 1 ? "query" : "queries";
2220
2239
  return `Seed dedup collapsed ${seedCountRaw} raw candidates into ${canonicalCount} canonical ${noun} at threshold ${dedupThreshold}. Distinct intents were likely merged into one cluster; re-run with a higher --dedup-threshold.`;
2221
2240
  }
2241
+ function filterBrandedSeedCandidates(input) {
2242
+ const tokens = brandMatchTokens(input.brandNames, input.canonicalDomains);
2243
+ if (tokens.length === 0) return { kept: [...input.candidates], droppedBranded: [] };
2244
+ const kept = [];
2245
+ const droppedBranded = [];
2246
+ for (const candidate of input.candidates) {
2247
+ const normalized = normalizeForBrandMatch(candidate);
2248
+ if (tokens.some((token) => containsWholeToken(normalized, token))) droppedBranded.push(candidate);
2249
+ else kept.push(candidate);
2250
+ }
2251
+ return { kept, droppedBranded };
2252
+ }
2253
+ function normalizeForBrandMatch(text) {
2254
+ return text.toLowerCase().replace(/\s+/g, " ").trim();
2255
+ }
2256
+ function brandMatchTokens(brandNames, canonicalDomains) {
2257
+ const tokens = /* @__PURE__ */ new Set();
2258
+ for (const name of brandNames) {
2259
+ const normalized = normalizeForBrandMatch(name);
2260
+ if (normalized.length < 3) continue;
2261
+ tokens.add(normalized);
2262
+ const squashed = normalized.replace(/[^a-z0-9]/g, "");
2263
+ if (squashed.length >= 4 && squashed !== normalized) tokens.add(squashed);
2264
+ }
2265
+ for (const domain of canonicalDomains) {
2266
+ const host = hostOf(domain) ?? normalizeForBrandMatch(domain).replace(/^www\./, "");
2267
+ if (!host) continue;
2268
+ tokens.add(host);
2269
+ tokens.add(`www.${host}`);
2270
+ }
2271
+ return [...tokens];
2272
+ }
2273
+ function containsWholeToken(normalized, token) {
2274
+ let index = normalized.indexOf(token);
2275
+ while (index !== -1) {
2276
+ const before = index === 0 ? " " : normalized[index - 1];
2277
+ const afterIndex = index + token.length;
2278
+ const after = afterIndex >= normalized.length ? " " : normalized[afterIndex];
2279
+ if (!/[a-z0-9]/.test(before) && !/[a-z0-9]/.test(after)) return true;
2280
+ index = normalized.indexOf(token, index + 1);
2281
+ }
2282
+ return false;
2283
+ }
2222
2284
  var discoveryRunRequestSchema = z17.object({
2223
2285
  icpDescription: z17.string().min(1).optional(),
2286
+ /**
2287
+ * Who evaluates or buys the offering, separate from what is sold
2288
+ * (icpDescription). When present, the seed prompt anchors every generated
2289
+ * query on this buyer, so discovery selects buyer-fit demand instead of
2290
+ * generic provider comparisons.
2291
+ */
2292
+ buyerDescription: z17.string().min(1).optional(),
2224
2293
  dedupThreshold: z17.number().min(0).max(1).optional(),
2225
2294
  maxProbes: z17.number().int().positive().max(DISCOVERY_MAX_PROBES_CAP).optional(),
2226
2295
  /**
@@ -5531,6 +5600,7 @@ export {
5531
5600
  runtimeStateMissing,
5532
5601
  findDuplicateLocationLabels,
5533
5602
  hasLocationLabel,
5603
+ orderLocationsDefaultFirst,
5534
5604
  resolveLocations,
5535
5605
  projectUpsertRequestSchema,
5536
5606
  projectDtoSchema,
@@ -5660,6 +5730,7 @@ export {
5660
5730
  DISCOVERY_DEFAULT_PROBE_CONCURRENCY,
5661
5731
  DISCOVERY_DEFAULT_DEDUP_THRESHOLD,
5662
5732
  seedCollapseWarning,
5733
+ filterBrandedSeedCandidates,
5663
5734
  discoveryRunRequestSchema,
5664
5735
  discoveryPromoteRequestSchema,
5665
5736
  discoveryPromotePreviewSchema,
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  setTelemetrySource,
30
30
  showFirstRunNotice,
31
31
  trackEvent
32
- } from "./chunk-2AWATO4I.js";
32
+ } from "./chunk-EMG4LIUJ.js";
33
33
  import {
34
34
  CliError,
35
35
  EXIT_SYSTEM_ERROR,
@@ -46,7 +46,7 @@ import {
46
46
  saveConfig,
47
47
  saveConfigPatch,
48
48
  usageError
49
- } from "./chunk-VGMTLUUM.js";
49
+ } from "./chunk-EKGKGKID.js";
50
50
  import {
51
51
  apiKeys,
52
52
  createClient,
@@ -54,7 +54,7 @@ import {
54
54
  projects,
55
55
  queries,
56
56
  renderReportHtml
57
- } from "./chunk-F3MKVMS5.js";
57
+ } from "./chunk-45YSA7GH.js";
58
58
  import {
59
59
  BacklinkSources,
60
60
  CcReleaseSyncStatuses,
@@ -80,7 +80,7 @@ import {
80
80
  providerQuotaPolicySchema,
81
81
  resolveProviderInput,
82
82
  winnabilityClassSchema
83
- } from "./chunk-VOR5ZTUU.js";
83
+ } from "./chunk-IQX7XXJN.js";
84
84
 
85
85
  // src/cli.ts
86
86
  import { pathToFileURL } from "url";
@@ -1655,6 +1655,7 @@ function getClient4() {
1655
1655
  function buildRunBody(opts, icpDescription) {
1656
1656
  const body = {};
1657
1657
  if (icpDescription) body.icpDescription = icpDescription;
1658
+ if (opts.buyer !== void 0 && opts.buyer.trim() !== "") body.buyerDescription = opts.buyer;
1658
1659
  if (opts.dedupThreshold !== void 0) body.dedupThreshold = opts.dedupThreshold;
1659
1660
  if (opts.maxProbes !== void 0) body.maxProbes = opts.maxProbes;
1660
1661
  if (opts.probeConcurrency !== void 0) body.probeConcurrency = opts.probeConcurrency;
@@ -2057,9 +2058,10 @@ Usage: ${usage}`,
2057
2058
  var DISCOVER_CLI_COMMANDS = [
2058
2059
  {
2059
2060
  path: ["discover", "run"],
2060
- usage: 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2061
+ usage: 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2061
2062
  options: {
2062
2063
  icp: stringOption(),
2064
+ buyer: stringOption(),
2063
2065
  "icp-angle": multiStringOption(),
2064
2066
  locations: multiStringOption(),
2065
2067
  "dedup-threshold": stringOption(),
@@ -2068,10 +2070,11 @@ var DISCOVER_CLI_COMMANDS = [
2068
2070
  wait: { type: "boolean", default: false }
2069
2071
  },
2070
2072
  run: async (input) => {
2071
- const usage = 'canonry discover run <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2073
+ const usage = 'canonry discover run <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2072
2074
  const project = requireProject(input, "discover.run", usage);
2073
2075
  await discoverRun(project, {
2074
2076
  icp: getString(input.values, "icp"),
2077
+ buyer: getString(input.values, "buyer"),
2075
2078
  icpAngles: getStringArray(input.values, "icp-angle"),
2076
2079
  locations: parseLocationsOption(input.values),
2077
2080
  dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
@@ -2092,9 +2095,10 @@ var DISCOVER_CLI_COMMANDS = [
2092
2095
  },
2093
2096
  {
2094
2097
  path: ["discover", "seed"],
2095
- usage: 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2098
+ usage: 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]',
2096
2099
  options: {
2097
2100
  icp: stringOption(),
2101
+ buyer: stringOption(),
2098
2102
  "icp-angle": multiStringOption(),
2099
2103
  locations: multiStringOption(),
2100
2104
  "dedup-threshold": stringOption(),
@@ -2103,10 +2107,11 @@ var DISCOVER_CLI_COMMANDS = [
2103
2107
  wait: { type: "boolean", default: false }
2104
2108
  },
2105
2109
  run: async (input) => {
2106
- const usage = 'canonry discover seed <project> [--icp "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2110
+ const usage = 'canonry discover seed <project> [--icp "..."] [--buyer "..."] [--icp-angle "..."] [--locations michigan,florida] [--dedup-threshold 0.95] [--max-probes 100] [--probe-concurrency 3] [--wait] [--format json]';
2107
2111
  const project = requireProject(input, "discover.seed", usage);
2108
2112
  await discoverSeed(project, {
2109
2113
  icp: getString(input.values, "icp"),
2114
+ buyer: getString(input.values, "buyer"),
2110
2115
  icpAngles: getStringArray(input.values, "icp-angle"),
2111
2116
  locations: parseLocationsOption(input.values),
2112
2117
  dedupThreshold: parseFloatOption(input.values, "dedup-threshold", usage),
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-2AWATO4I.js";
3
+ } from "./chunk-EMG4LIUJ.js";
4
4
  import {
5
5
  loadConfig
6
- } from "./chunk-VGMTLUUM.js";
7
- import "./chunk-F3MKVMS5.js";
8
- import "./chunk-VOR5ZTUU.js";
6
+ } from "./chunk-EKGKGKID.js";
7
+ import "./chunk-45YSA7GH.js";
8
+ import "./chunk-IQX7XXJN.js";
9
9
  export {
10
10
  createServer,
11
11
  loadConfig
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  IntelligenceService
3
- } from "./chunk-F3MKVMS5.js";
4
- import "./chunk-VOR5ZTUU.js";
3
+ } from "./chunk-45YSA7GH.js";
4
+ import "./chunk-IQX7XXJN.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-VGMTLUUM.js";
6
+ } from "./chunk-EKGKGKID.js";
7
7
  import {
8
8
  isReadOnlyKey
9
- } from "./chunk-VOR5ZTUU.js";
9
+ } from "./chunk-IQX7XXJN.js";
10
10
 
11
11
  // src/mcp/cli.ts
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ainyc/canonry",
3
- "version": "4.106.2",
3
+ "version": "4.108.0",
4
4
  "type": "module",
5
5
  "description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
6
6
  "license": "FSL-1.1-ALv2",
@@ -66,26 +66,26 @@
66
66
  "tsup": "^8.5.1",
67
67
  "tsx": "^4.19.0",
68
68
  "@ainyc/canonry-api-client": "0.0.0",
69
- "@ainyc/canonry-api-routes": "0.0.0",
70
- "@ainyc/canonry-db": "0.0.0",
71
69
  "@ainyc/canonry-config": "0.0.0",
70
+ "@ainyc/canonry-api-routes": "0.0.0",
72
71
  "@ainyc/canonry-contracts": "0.0.0",
72
+ "@ainyc/canonry-db": "0.0.0",
73
73
  "@ainyc/canonry-integration-bing": "0.0.0",
74
74
  "@ainyc/canonry-integration-openai-ads": "0.0.0",
75
75
  "@ainyc/canonry-integration-cloud-run": "0.0.0",
76
76
  "@ainyc/canonry-integration-commoncrawl": "0.0.0",
77
77
  "@ainyc/canonry-integration-google": "0.0.0",
78
- "@ainyc/canonry-integration-google-business-profile": "0.0.0",
79
78
  "@ainyc/canonry-integration-google-places": "0.0.0",
80
79
  "@ainyc/canonry-integration-traffic": "0.0.0",
80
+ "@ainyc/canonry-integration-google-business-profile": "0.0.0",
81
+ "@ainyc/canonry-integration-wordpress": "0.0.0",
81
82
  "@ainyc/canonry-intelligence": "0.0.0",
82
83
  "@ainyc/canonry-provider-cdp": "0.0.0",
83
- "@ainyc/canonry-integration-wordpress": "0.0.0",
84
- "@ainyc/canonry-provider-local": "0.0.0",
85
84
  "@ainyc/canonry-provider-claude": "0.0.0",
86
- "@ainyc/canonry-provider-openai": "0.0.0",
87
85
  "@ainyc/canonry-provider-gemini": "0.0.0",
88
- "@ainyc/canonry-provider-perplexity": "0.0.0"
86
+ "@ainyc/canonry-provider-perplexity": "0.0.0",
87
+ "@ainyc/canonry-provider-local": "0.0.0",
88
+ "@ainyc/canonry-provider-openai": "0.0.0"
89
89
  },
90
90
  "scripts": {
91
91
  "build": "tsx scripts/copy-agent-assets.ts && tsup && tsx build-web.ts",