lurqrun 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/lurq.js CHANGED
@@ -26,7 +26,7 @@ var init_constants = __esm({
26
26
  init_esm_shims();
27
27
  SERVER_NAME = "lurq";
28
28
  PACKAGE_NAME = "lurqrun";
29
- VERSION = "0.0.4";
29
+ VERSION = "0.0.6";
30
30
  DEFAULT_ENDPOINT = "https://api.lurq.run/mcp";
31
31
  API_KEY_PREFIX = "lurq_live_";
32
32
  EMBEDDING_DIM = 1536;
@@ -96,18 +96,12 @@ var init_types = __esm({
96
96
  });
97
97
 
98
98
  // src/core/config.ts
99
- var config_exports = {};
100
- __export(config_exports, {
101
- ConfigError: () => ConfigError,
102
- getConfig: () => getConfig,
103
- loadEnv: () => loadEnv,
104
- requireConfig: () => requireConfig,
105
- resetConfigCache: () => resetConfigCache
106
- });
107
99
  import { config as dotenvConfig } from "dotenv";
108
100
  import { z } from "zod";
109
101
  function loadEnv() {
110
102
  if (envLoaded) return;
103
+ const overrideFile = process.env.LURQ_ENV_FILE;
104
+ if (overrideFile) dotenvConfig({ path: overrideFile });
111
105
  dotenvConfig();
112
106
  envLoaded = true;
113
107
  }
@@ -123,9 +117,6 @@ ${issues}`);
123
117
  cached = parsed.data;
124
118
  return cached;
125
119
  }
126
- function resetConfigCache() {
127
- cached = void 0;
128
- }
129
120
  function requireConfig(keys) {
130
121
  const config = getConfig();
131
122
  const missing = keys.filter((k) => config[k] === void 0 || config[k] === "");
@@ -183,7 +174,9 @@ var init_config = __esm({
183
174
  E2B_API_KEY: z.string().min(1).optional(),
184
175
  // E2B template to launch. Must provide node + npm on PATH; omit for E2B's
185
176
  // default. Provision a Node-versioned template here for reproducible runs.
186
- E2B_TEMPLATE: z.string().min(1).optional()
177
+ E2B_TEMPLATE: z.string().min(1).optional(),
178
+ // Lurq bakeoff testing toggle b/w local and prod DB
179
+ USE_LIVE_API: z.string().min(1).optional()
187
180
  });
188
181
  ConfigError = class extends Error {
189
182
  constructor(message) {
@@ -198,11 +191,19 @@ var init_config = __esm({
198
191
  var schema_exports = {};
199
192
  __export(schema_exports, {
200
193
  apiKeys: () => apiKeys,
194
+ apiSurfaces: () => apiSurfaces,
195
+ claims: () => claims,
201
196
  compatEdges: () => compatEdges,
197
+ compatVerifyQueue: () => compatVerifyQueue,
202
198
  discoveryQueue: () => discoveryQueue,
199
+ entities: () => entities,
200
+ environments: () => environments,
201
+ observations: () => observations,
202
+ ownerUsageDaily: () => ownerUsageDaily,
203
203
  packageVersions: () => packageVersions,
204
204
  packages: () => packages,
205
205
  recommendationOutcomes: () => recommendationOutcomes,
206
+ resolvedClosures: () => resolvedClosures,
206
207
  seedPackages: () => seedPackages,
207
208
  syncRuns: () => syncRuns,
208
209
  verificationRuns: () => verificationRuns,
@@ -213,6 +214,7 @@ import {
213
214
  bigint,
214
215
  boolean,
215
216
  customType,
217
+ date,
216
218
  index,
217
219
  integer,
218
220
  jsonb,
@@ -225,7 +227,7 @@ import {
225
227
  uniqueIndex,
226
228
  vector
227
229
  } from "drizzle-orm/pg-core";
228
- var tsvector, ts, packages, syncRuns, seedPackages, discoveryQueue, apiKeys, packageVersions, watchState, verificationRuns, compatEdges, recommendationOutcomes;
230
+ var tsvector, ts, packages, syncRuns, seedPackages, discoveryQueue, compatVerifyQueue, apiKeys, packageVersions, watchState, verificationRuns, compatEdges, resolvedClosures, apiSurfaces, recommendationOutcomes, ownerUsageDaily, entities, environments, claims, observations;
229
231
  var init_schema = __esm({
230
232
  "src/db/schema.ts"() {
231
233
  "use strict";
@@ -295,8 +297,19 @@ var init_schema = __esm({
295
297
  ),
296
298
  // Freshness + bookkeeping
297
299
  dataAsOf: timestamp("data_as_of", { withTimezone: true, mode: "date" }),
300
+ /** latest_version this package's direct deps were last expanded for by the
301
+ * discovery graph channel (§2B). Discovery re-scans a package only when this
302
+ * differs from latest_version — deps are version-pinned, so an unchanged
303
+ * version has unchanged neighbors. NULL = never scanned. */
304
+ graphScannedVersion: text("graph_scanned_version"),
298
305
  createdAt: ts("created_at").notNull().defaultNow(),
299
- updatedAt: ts("updated_at").notNull().defaultNow()
306
+ updatedAt: ts("updated_at").notNull().defaultNow(),
307
+ /** The individual account (api_keys.owner_id) whose on-demand query first
308
+ * caused this package to be ingested — nobody had asked for it before them.
309
+ * Null for crawler/_changes-created packages, or ones ingested before
310
+ * dashboard accounts existed. Stamped once via a standalone WHERE ... IS NULL
311
+ * update kept out of upsertPackage, so re-syncs can never clobber it. */
312
+ firstRequestedByOwnerId: text("first_requested_by_owner_id")
300
313
  },
301
314
  (table2) => [
302
315
  index("packages_category_idx").on(table2.category),
@@ -304,7 +317,8 @@ var init_schema = __esm({
304
317
  // pgvector HNSW index for cosine similarity search (§11).
305
318
  index("packages_embedding_idx").using("hnsw", table2.embedding.op("vector_cosine_ops")),
306
319
  // GIN index for lexical full-text search (§3).
307
- index("packages_search_vector_idx").using("gin", table2.searchVector)
320
+ index("packages_search_vector_idx").using("gin", table2.searchVector),
321
+ index("packages_first_requested_by_idx").on(table2.firstRequestedByOwnerId)
308
322
  ]
309
323
  );
310
324
  syncRuns = pgTable("sync_runs", {
@@ -334,6 +348,20 @@ var init_schema = __esm({
334
348
  },
335
349
  (table2) => [index("discovery_queue_status_idx").on(table2.status)]
336
350
  );
351
+ compatVerifyQueue = pgTable(
352
+ "compat_verify_queue",
353
+ {
354
+ id: serial("id").primaryKey(),
355
+ /** Canonical order-independent key of the package set — dedups pending requests. */
356
+ setKey: text("set_key").notNull().unique(),
357
+ /** The package names to co-install in the sandbox. */
358
+ packages: jsonb("packages").$type().notNull(),
359
+ /** Failed drains bump this; the worker drops a set that keeps failing. */
360
+ attempts: integer("attempts").notNull().default(0),
361
+ requestedAt: ts("requested_at").notNull().defaultNow()
362
+ },
363
+ (table2) => [index("compat_verify_queue_requested_idx").on(table2.requestedAt)]
364
+ );
337
365
  apiKeys = pgTable(
338
366
  "api_keys",
339
367
  {
@@ -396,6 +424,12 @@ var init_schema = __esm({
396
424
  packageB: text("package_b").notNull(),
397
425
  versionB: text("version_b").notNull(),
398
426
  status: text("status").$type().notNull(),
427
+ /** Evidence class (§4B). Existing rows are sandbox co-installs, so the column
428
+ * defaults to `verified` to preserve their meaning. */
429
+ provenance: text("provenance").$type().notNull().default("verified"),
430
+ /** Distinct resolved graphs an `observed` edge was witnessed in (confidence).
431
+ * Ignored for verified/conflict. Accumulates on conflict, never overwritten. */
432
+ witnessCount: integer("witness_count").notNull().default(0),
399
433
  driver: text("driver").notNull(),
400
434
  ranAt: ts("ran_at")
401
435
  },
@@ -408,14 +442,40 @@ var init_schema = __esm({
408
442
  )
409
443
  ]
410
444
  );
445
+ resolvedClosures = pgTable(
446
+ "resolved_closures",
447
+ {
448
+ id: serial("id").primaryKey(),
449
+ packageName: text("package_name").notNull(),
450
+ version: text("version").notNull(),
451
+ /** Full closure: [{ name, version }, …] — every node in node_modules. */
452
+ nodes: jsonb("nodes").$type().notNull(),
453
+ fetchedAt: ts("fetched_at").notNull().defaultNow()
454
+ },
455
+ (table2) => [
456
+ uniqueIndex("resolved_closures_pkg_idx").on(table2.packageName, table2.version)
457
+ ]
458
+ );
459
+ apiSurfaces = pgTable(
460
+ "api_surfaces",
461
+ {
462
+ id: serial("id").primaryKey(),
463
+ packageName: text("package_name").notNull(),
464
+ version: text("version").notNull(),
465
+ /** Normalized export list: [{ name, kind, signature }, …]. */
466
+ surface: jsonb("surface").$type().notNull(),
467
+ extractedAt: ts("extracted_at").notNull().defaultNow()
468
+ },
469
+ (table2) => [uniqueIndex("api_surfaces_pkg_idx").on(table2.packageName, table2.version)]
470
+ );
411
471
  recommendationOutcomes = pgTable(
412
472
  "recommendation_outcomes",
413
473
  {
414
474
  id: serial("id").primaryKey(),
415
- /** The org this outcome belongs to (api_keys.owner_id). Null for anonymous /
416
- * operator-issued keys. This is what turns the flywheel from a global blob
417
- * into a per-org asset — "what did *this* org succeed with." Server-injected
418
- * from the authenticated key, never caller-supplied. */
475
+ /** The individual user this outcome belongs to (api_keys.owner_id). Null for
476
+ * anonymous/operator-issued keys. This is what turns the flywheel from a
477
+ * global blob into a per-user asset — "what did *this* person succeed
478
+ * with." Server-injected from the authenticated key, never caller-supplied. */
419
479
  ownerId: text("owner_id"),
420
480
  packageName: text("package_name").notNull(),
421
481
  accepted: boolean("accepted").notNull(),
@@ -430,6 +490,93 @@ var init_schema = __esm({
430
490
  index("recommendation_outcomes_owner_idx").on(table2.ownerId)
431
491
  ]
432
492
  );
493
+ ownerUsageDaily = pgTable(
494
+ "owner_usage_daily",
495
+ {
496
+ ownerId: text("owner_id").notNull(),
497
+ /** UTC day, 'YYYY-MM-DD'. */
498
+ date: date("date").notNull(),
499
+ /** recommend | evaluate | compare | compat | verify | usage | diagram | plan | report_outcome */
500
+ tool: text("tool").notNull(),
501
+ count: integer("count").notNull().default(0)
502
+ },
503
+ (table2) => [
504
+ primaryKey({ columns: [table2.ownerId, table2.date, table2.tool] }),
505
+ index("owner_usage_daily_owner_date_idx").on(table2.ownerId, table2.date)
506
+ ]
507
+ );
508
+ entities = pgTable(
509
+ "entities",
510
+ {
511
+ id: serial("id").primaryKey(),
512
+ kind: text("kind").$type().notNull(),
513
+ /** Registry or authority: npm, api.stripe.com, … */
514
+ namespace: text("namespace").notNull(),
515
+ name: text("name").notNull(),
516
+ /** Null for unversioned kinds. */
517
+ version: text("version"),
518
+ /** `kind:namespace:name:version` */
519
+ canonicalKey: text("canonical_key").notNull(),
520
+ /**
521
+ * 0 = the public graph; a real id = a private deployment's tenant. The spec
522
+ * models public as NULL, but Postgres UNIQUE treats NULLs as distinct, so
523
+ * (key, NULL) would insert unlimited duplicates and the dedup silently fails.
524
+ * A non-null sentinel is the cheap fix that works on every PG version.
525
+ */
526
+ tenantId: bigint("tenant_id", { mode: "number" }).notNull().default(0),
527
+ firstSeen: ts("first_seen").notNull().defaultNow()
528
+ },
529
+ (table2) => [
530
+ uniqueIndex("entities_canonical_idx").on(table2.canonicalKey, table2.tenantId),
531
+ index("entities_kind_idx").on(table2.kind, table2.namespace, table2.name)
532
+ ]
533
+ );
534
+ environments = pgTable("environments", {
535
+ id: serial("id").primaryKey(),
536
+ os: text("os").notNull(),
537
+ arch: text("arch").notNull(),
538
+ runtime: text("runtime").notNull(),
539
+ runtimeVer: text("runtime_ver").notNull(),
540
+ resolver: text("resolver"),
541
+ /** Hash of the above; the dedup key. */
542
+ fingerprint: text("fingerprint").notNull().unique()
543
+ });
544
+ claims = pgTable(
545
+ "claims",
546
+ {
547
+ id: serial("id").primaryKey(),
548
+ subjectId: integer("subject_id").notNull().references(() => entities.id),
549
+ /** Null for unary claims ("does this install at all"). */
550
+ objectId: integer("object_id").references(() => entities.id),
551
+ relation: text("relation").notNull(),
552
+ environmentId: integer("environment_id").notNull().references(() => environments.id),
553
+ tenantId: bigint("tenant_id", { mode: "number" }).notNull().default(0)
554
+ },
555
+ (table2) => [
556
+ uniqueIndex("claims_tuple_idx").on(
557
+ table2.subjectId,
558
+ table2.objectId,
559
+ table2.relation,
560
+ table2.environmentId,
561
+ table2.tenantId
562
+ )
563
+ ]
564
+ );
565
+ observations = pgTable(
566
+ "observations",
567
+ {
568
+ id: serial("id").primaryKey(),
569
+ claimId: integer("claim_id").notNull().references(() => claims.id),
570
+ verdict: text("verdict").$type().notNull(),
571
+ /** Evidence that makes the verdict auditable. Null only for `unknown`. */
572
+ evidence: text("evidence"),
573
+ oracleId: text("oracle_id").notNull(),
574
+ oracleVer: text("oracle_ver").notNull(),
575
+ costMillis: integer("cost_millis"),
576
+ observedAt: ts("observed_at").notNull().defaultNow()
577
+ },
578
+ (table2) => [index("observations_claim_idx").on(table2.claimId, table2.observedAt)]
579
+ );
433
580
  }
434
581
  });
435
582
 
@@ -438,10 +585,10 @@ import { drizzle } from "drizzle-orm/postgres-js";
438
585
  import postgres from "postgres";
439
586
  function createDb(opts = {}) {
440
587
  const { DATABASE_URL } = requireConfig(["DATABASE_URL"]);
441
- const sql6 = postgres(DATABASE_URL, { max: opts.max ?? 10, onnotice: () => {
588
+ const sql8 = postgres(DATABASE_URL, { max: opts.max ?? 10, onnotice: () => {
442
589
  } });
443
- const db = drizzle(sql6, { schema: schema_exports });
444
- return { db, sql: sql6, close: () => sql6.end() };
590
+ const db = drizzle(sql8, { schema: schema_exports });
591
+ return { db, sql: sql8, close: () => sql8.end() };
445
592
  }
446
593
  var init_client = __esm({
447
594
  "src/db/client.ts"() {
@@ -574,7 +721,7 @@ var init_cache = __esm({
574
721
  });
575
722
 
576
723
  // src/db/compat.ts
577
- import { and, inArray } from "drizzle-orm";
724
+ import { and, eq, inArray, sql as sql2 } from "drizzle-orm";
578
725
  async function getCompatMetadata(db, names) {
579
726
  if (names.length === 0) return [];
580
727
  return db.select({
@@ -589,26 +736,87 @@ function canonicalPair(a, b) {
589
736
  const [low, high] = a.name <= b.name ? [a, b] : [b, a];
590
737
  return { packageA: low.name, versionA: low.version, packageB: high.name, versionB: high.version };
591
738
  }
739
+ function provenanceRank(col) {
740
+ return sql2`case ${col} when 'conflict' then 3 when 'verified' then 2 when 'observed' then 1 else 0 end`;
741
+ }
742
+ function conflictSet() {
743
+ const incomingWins = sql2`${provenanceRank(sql2`excluded.provenance`)} >= ${provenanceRank(compatEdges.provenance)}`;
744
+ return {
745
+ status: sql2`case when ${incomingWins} then excluded.status else ${compatEdges.status} end`,
746
+ provenance: sql2`case when ${incomingWins} then excluded.provenance else ${compatEdges.provenance} end`,
747
+ driver: sql2`case when ${incomingWins} then excluded.driver else ${compatEdges.driver} end`,
748
+ ranAt: sql2`case when ${incomingWins} then excluded.ran_at else ${compatEdges.ranAt} end`,
749
+ witnessCount: sql2`${compatEdges.witnessCount} + excluded.witness_count`
750
+ };
751
+ }
592
752
  async function upsertCompatEdge(db, edge) {
593
- await db.insert(compatEdges).values(edge).onConflictDoUpdate({
594
- target: [
595
- compatEdges.packageA,
596
- compatEdges.versionA,
597
- compatEdges.packageB,
598
- compatEdges.versionB
599
- ],
600
- set: { status: edge.status, driver: edge.driver, ranAt: edge.ranAt }
753
+ await db.insert(compatEdges).values(edge).onConflictDoUpdate({ target: [...CONFLICT_TARGET], set: conflictSet() });
754
+ }
755
+ function chunk(items, size) {
756
+ if (size <= 0) throw new Error(`chunk size must be positive, got ${size}`);
757
+ const out = [];
758
+ for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
759
+ return out;
760
+ }
761
+ async function upsertCompatEdgesBatch(db, edges) {
762
+ if (edges.length === 0) return;
763
+ for (const part of chunk(edges, EDGE_UPSERT_CHUNK)) {
764
+ await db.insert(compatEdges).values(part).onConflictDoUpdate({ target: [...CONFLICT_TARGET], set: conflictSet() });
765
+ }
766
+ }
767
+ async function persistClosure(db, packageName, version, nodes) {
768
+ await db.insert(resolvedClosures).values({ packageName, version, nodes }).onConflictDoUpdate({
769
+ target: [resolvedClosures.packageName, resolvedClosures.version],
770
+ set: { nodes, fetchedAt: /* @__PURE__ */ new Date() }
601
771
  });
602
772
  }
603
773
  async function getCompatEdges(db, names) {
604
774
  if (names.length === 0) return [];
605
775
  return db.select().from(compatEdges).where(and(inArray(compatEdges.packageA, names), inArray(compatEdges.packageB, names)));
606
776
  }
777
+ function pairKey(a, b) {
778
+ return a <= b ? `${a}|${b}` : `${b}|${a}`;
779
+ }
780
+ function fullyCovered(batch, covered) {
781
+ for (let i = 0; i < batch.length; i++) {
782
+ for (let j = i + 1; j < batch.length; j++) {
783
+ if (!covered.has(pairKey(batch[i], batch[j]))) return false;
784
+ }
785
+ }
786
+ return true;
787
+ }
788
+ function compatSetKey(names) {
789
+ return [...new Set(names)].sort().join("|");
790
+ }
791
+ async function enqueueCompatVerify(db, names) {
792
+ const packages2 = [...new Set(names)].filter(Boolean);
793
+ if (packages2.length < 2) return false;
794
+ const inserted = await db.insert(compatVerifyQueue).values({ setKey: compatSetKey(packages2), packages: packages2 }).onConflictDoNothing({ target: compatVerifyQueue.setKey }).returning({ id: compatVerifyQueue.id });
795
+ return inserted.length > 0;
796
+ }
797
+ async function getPendingCompatVerify(db, limit) {
798
+ return db.select().from(compatVerifyQueue).orderBy(compatVerifyQueue.requestedAt).limit(limit);
799
+ }
800
+ async function deleteCompatVerify(db, id) {
801
+ await db.delete(compatVerifyQueue).where(eq(compatVerifyQueue.id, id));
802
+ }
803
+ async function bumpCompatVerifyAttempt(db, id) {
804
+ const [row] = await db.update(compatVerifyQueue).set({ attempts: sql2`${compatVerifyQueue.attempts} + 1` }).where(eq(compatVerifyQueue.id, id)).returning({ attempts: compatVerifyQueue.attempts });
805
+ return row?.attempts ?? 0;
806
+ }
807
+ var CONFLICT_TARGET, EDGE_UPSERT_CHUNK;
607
808
  var init_compat = __esm({
608
809
  "src/db/compat.ts"() {
609
810
  "use strict";
610
811
  init_esm_shims();
611
812
  init_schema();
813
+ CONFLICT_TARGET = [
814
+ compatEdges.packageA,
815
+ compatEdges.versionA,
816
+ compatEdges.packageB,
817
+ compatEdges.versionB
818
+ ];
819
+ EDGE_UPSERT_CHUNK = 250;
612
820
  }
613
821
  });
614
822
 
@@ -626,9 +834,6 @@ function limiterFor(host) {
626
834
  }
627
835
  return limiter;
628
836
  }
629
- function setCacheBypassRead(value) {
630
- bypassCacheRead = value;
631
- }
632
837
  function cacheDir() {
633
838
  return process.env.LURQ_CACHE_DIR ?? join(homedir(), ".cache", "lurq", "http");
634
839
  }
@@ -733,8 +938,8 @@ function parseRetryAfter(value) {
733
938
  if (!value) return void 0;
734
939
  const seconds = Number(value);
735
940
  if (!Number.isNaN(seconds)) return seconds * 1e3;
736
- const date = Date.parse(value);
737
- return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
941
+ const date2 = Date.parse(value);
942
+ return Number.isNaN(date2) ? void 0 : Math.max(0, date2 - Date.now());
738
943
  }
739
944
  function delay(ms) {
740
945
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -937,6 +1142,28 @@ async function fetchNpmRegistry(name, fetchImpl) {
937
1142
  });
938
1143
  return parseNpmRegistry(data);
939
1144
  }
1145
+ async function fetchNpmCompatAtVersion(name, version, fetchImpl) {
1146
+ const url = `https://${HOST}/${encodeNpmName(name)}`;
1147
+ try {
1148
+ const { data } = await httpGetJson(url, {
1149
+ host: HOST,
1150
+ ttlMs: CACHE_TTL.npmRegistry,
1151
+ fetchImpl
1152
+ });
1153
+ const latest = data?.["dist-tags"]?.latest ?? null;
1154
+ const wanted = version && data?.versions?.[version] ? version : latest && data?.versions?.[latest] ? latest : null;
1155
+ if (!wanted) return null;
1156
+ const manifest = data.versions[wanted] ?? {};
1157
+ return {
1158
+ version: wanted,
1159
+ peerDependencies: parseDepMap(manifest.peerDependencies),
1160
+ peerDependenciesMeta: parsePeerMeta(manifest.peerDependenciesMeta),
1161
+ engines: parseDepMap(manifest.engines)
1162
+ };
1163
+ } catch {
1164
+ return null;
1165
+ }
1166
+ }
940
1167
  async function npmPackageExists(name, fetchImpl) {
941
1168
  const url = `https://${HOST}/${encodeNpmName(name)}`;
942
1169
  try {
@@ -966,28 +1193,12 @@ var init_npmRegistry = __esm({
966
1193
  });
967
1194
 
968
1195
  // src/ingestion/sources/npmSearch.ts
969
- async function searchNpm(query, size = 20, fetchImpl) {
970
- const url = `https://${HOST2}/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`;
971
- try {
972
- const { data } = await httpGetJson(url, {
973
- host: HOST2,
974
- ttlMs: CACHE_TTL.npmRegistry,
975
- fetchImpl
976
- });
977
- const objects = Array.isArray(data?.objects) ? data.objects : [];
978
- return objects.map((o) => ({ name: o?.package?.name, date: o?.package?.date ?? null })).filter((h) => typeof h.name === "string");
979
- } catch {
980
- return [];
981
- }
982
- }
983
- var HOST2;
984
1196
  var init_npmSearch = __esm({
985
1197
  "src/ingestion/sources/npmSearch.ts"() {
986
1198
  "use strict";
987
1199
  init_esm_shims();
988
1200
  init_constants();
989
1201
  init_http();
990
- HOST2 = "registry.npmjs.org";
991
1202
  }
992
1203
  });
993
1204
 
@@ -1008,36 +1219,19 @@ function parseDownloadGrowth(json2) {
1008
1219
  if (priorAvg <= 0) return recentAvg > 0 ? 1 : 0;
1009
1220
  return Math.round((recentAvg - priorAvg) / priorAvg * 1e3) / 1e3;
1010
1221
  }
1011
- function ymd(date) {
1012
- return date.toISOString().slice(0, 10);
1222
+ function ymd(date2) {
1223
+ return date2.toISOString().slice(0, 10);
1013
1224
  }
1014
1225
  function last90DayRange(now = /* @__PURE__ */ new Date()) {
1015
1226
  const end = now;
1016
1227
  const start = new Date(end.getTime() - 90 * 24 * 60 * 60 * 1e3);
1017
1228
  return `${ymd(start)}:${ymd(end)}`;
1018
1229
  }
1019
- function parseBulkWeekly(json2, requested) {
1020
- const map = /* @__PURE__ */ new Map();
1021
- if (json2 && typeof json2.downloads === "number" && typeof json2.package === "string") {
1022
- map.set(json2.package, json2.downloads);
1023
- return map;
1024
- }
1025
- for (const name of requested) {
1026
- const entry = json2?.[name];
1027
- map.set(name, entry && typeof entry.downloads === "number" ? entry.downloads : null);
1028
- }
1029
- return map;
1030
- }
1031
- function chunk(items, size) {
1032
- const out = [];
1033
- for (let i = 0; i < items.length; i += size) out.push(items.slice(i, i + size));
1034
- return out;
1035
- }
1036
1230
  async function fetchWeeklyDownloads(name, fetchImpl) {
1037
1231
  try {
1038
1232
  const { data } = await httpGetJson(
1039
- `https://${HOST3}/downloads/point/last-week/${name}`,
1040
- { host: HOST3, ttlMs: CACHE_TTL.npmDownloads, retries: 5, fetchImpl }
1233
+ `https://${HOST2}/downloads/point/last-week/${name}`,
1234
+ { host: HOST2, ttlMs: CACHE_TTL.npmDownloads, retries: 5, fetchImpl }
1041
1235
  );
1042
1236
  return parseWeeklyDownloads(data);
1043
1237
  } catch (err) {
@@ -1048,45 +1242,22 @@ async function fetchWeeklyDownloads(name, fetchImpl) {
1048
1242
  async function fetchDownloadGrowth(name, fetchImpl) {
1049
1243
  try {
1050
1244
  const { data } = await httpGetJson(
1051
- `https://${HOST3}/downloads/range/${last90DayRange()}/${name}`,
1052
- { host: HOST3, ttlMs: CACHE_TTL.npmDownloads, retries: 3, fetchImpl }
1245
+ `https://${HOST2}/downloads/range/${last90DayRange()}/${name}`,
1246
+ { host: HOST2, ttlMs: CACHE_TTL.npmDownloads, retries: 3, fetchImpl }
1053
1247
  );
1054
1248
  return parseDownloadGrowth(data);
1055
1249
  } catch {
1056
1250
  return null;
1057
1251
  }
1058
1252
  }
1059
- async function fetchBulkWeeklyDownloads(names, fetchImpl) {
1060
- const result = /* @__PURE__ */ new Map();
1061
- const unscoped = names.filter((n) => !n.startsWith("@"));
1062
- const scoped = names.filter((n) => n.startsWith("@"));
1063
- for (const batch of chunk(unscoped, BULK_CHUNK)) {
1064
- try {
1065
- const { data } = await httpGetJson(
1066
- `https://${HOST3}/downloads/point/last-week/${batch.join(",")}`,
1067
- { host: HOST3, ttlMs: CACHE_TTL.npmDownloads, retries: 5, fetchImpl }
1068
- );
1069
- for (const [k, v] of parseBulkWeekly(data, batch)) result.set(k, v);
1070
- } catch {
1071
- }
1072
- }
1073
- for (const name of scoped) {
1074
- try {
1075
- result.set(name, await fetchWeeklyDownloads(name, fetchImpl));
1076
- } catch {
1077
- }
1078
- }
1079
- return result;
1080
- }
1081
- var HOST3, BULK_CHUNK;
1253
+ var HOST2;
1082
1254
  var init_npmDownloads = __esm({
1083
1255
  "src/ingestion/sources/npmDownloads.ts"() {
1084
1256
  "use strict";
1085
1257
  init_esm_shims();
1086
1258
  init_constants();
1087
1259
  init_http();
1088
- HOST3 = "api.npmjs.org";
1089
- BULK_CHUNK = 128;
1260
+ HOST2 = "api.npmjs.org";
1090
1261
  }
1091
1262
  });
1092
1263
 
@@ -1109,7 +1280,7 @@ function parseGithub(json2, now = /* @__PURE__ */ new Date()) {
1109
1280
  async function fetchGithubRepo(owner, repo, token, fetchImpl) {
1110
1281
  const body = JSON.stringify({ query: QUERY, variables: { owner, name: repo } });
1111
1282
  const { data } = await httpRequest(ENDPOINT, {
1112
- host: HOST4,
1283
+ host: HOST3,
1113
1284
  ttlMs: CACHE_TTL.github,
1114
1285
  method: "POST",
1115
1286
  headers: {
@@ -1124,15 +1295,15 @@ async function fetchGithubRepo(owner, repo, token, fetchImpl) {
1124
1295
  });
1125
1296
  return parseGithub(data);
1126
1297
  }
1127
- var HOST4, ENDPOINT, QUERY, ONE_YEAR_MS;
1298
+ var HOST3, ENDPOINT, QUERY, ONE_YEAR_MS;
1128
1299
  var init_github = __esm({
1129
1300
  "src/ingestion/sources/github.ts"() {
1130
1301
  "use strict";
1131
1302
  init_esm_shims();
1132
1303
  init_constants();
1133
1304
  init_http();
1134
- HOST4 = "api.github.com";
1135
- ENDPOINT = `https://${HOST4}/graphql`;
1305
+ HOST3 = "api.github.com";
1306
+ ENDPOINT = `https://${HOST3}/graphql`;
1136
1307
  QUERY = `query($owner:String!, $name:String!) {
1137
1308
  repository(owner:$owner, name:$name) {
1138
1309
  stargazerCount
@@ -1178,10 +1349,10 @@ function parseAdvisoryDetail(json2) {
1178
1349
  }
1179
1350
  async function fetchScorecard(owner, repo, fetchImpl) {
1180
1351
  const projectKey = encodeURIComponent(`github.com/${owner}/${repo}`);
1181
- const url = `https://${HOST5}/v3/projects/${projectKey}`;
1352
+ const url = `https://${HOST4}/v3/projects/${projectKey}`;
1182
1353
  try {
1183
1354
  const { data } = await httpGetJson(url, {
1184
- host: HOST5,
1355
+ host: HOST4,
1185
1356
  ttlMs: CACHE_TTL.depsDev,
1186
1357
  fetchImpl
1187
1358
  });
@@ -1191,11 +1362,11 @@ async function fetchScorecard(owner, repo, fetchImpl) {
1191
1362
  }
1192
1363
  }
1193
1364
  async function fetchAdvisories(name, version, fetchImpl) {
1194
- const versionUrl = `https://${HOST5}/v3/systems/npm/packages/${encodeName(name)}/versions/${encodeURIComponent(version)}`;
1365
+ const versionUrl = `https://${HOST4}/v3/systems/npm/packages/${encodeName(name)}/versions/${encodeURIComponent(version)}`;
1195
1366
  let keys = [];
1196
1367
  try {
1197
1368
  const { data } = await httpGetJson(versionUrl, {
1198
- host: HOST5,
1369
+ host: HOST4,
1199
1370
  ttlMs: CACHE_TTL.depsDev,
1200
1371
  fetchImpl
1201
1372
  });
@@ -1205,8 +1376,8 @@ async function fetchAdvisories(name, version, fetchImpl) {
1205
1376
  }
1206
1377
  const details = await Promise.allSettled(
1207
1378
  keys.map(
1208
- (key) => httpGetJson(`https://${HOST5}/v3/advisories/${encodeURIComponent(key)}`, {
1209
- host: HOST5,
1379
+ (key) => httpGetJson(`https://${HOST4}/v3/advisories/${encodeURIComponent(key)}`, {
1380
+ host: HOST4,
1210
1381
  ttlMs: CACHE_TTL.depsDev,
1211
1382
  fetchImpl
1212
1383
  })
@@ -1216,17 +1387,27 @@ async function fetchAdvisories(name, version, fetchImpl) {
1216
1387
  (d) => d.status === "fulfilled"
1217
1388
  ).map((d) => parseAdvisoryDetail(d.value.data));
1218
1389
  }
1219
- async function fetchDependencyNames(name, version, fetchImpl) {
1220
- const url = `https://${HOST5}/v3/systems/npm/packages/${encodeName(name)}/versions/${encodeURIComponent(version)}:dependencies`;
1390
+ async function fetchResolvedGraph(name, version, fetchImpl) {
1391
+ const url = `https://${HOST4}/v3/systems/npm/packages/${encodeName(name)}/versions/${encodeURIComponent(version)}:dependencies`;
1221
1392
  try {
1222
1393
  const { data } = await httpGetJson(url, {
1223
- host: HOST5,
1394
+ host: HOST4,
1224
1395
  ttlMs: CACHE_TTL.depsDev,
1225
1396
  fetchImpl
1226
1397
  });
1227
1398
  const nodes = Array.isArray(data?.nodes) ? data.nodes : [];
1228
- const names = nodes.map((n) => n?.versionKey?.name).filter((n) => typeof n === "string" && n !== name);
1229
- return [...new Set(names)];
1399
+ const out = [];
1400
+ const seen = /* @__PURE__ */ new Set();
1401
+ for (const n of nodes) {
1402
+ const nm = n?.versionKey?.name;
1403
+ const ver = n?.versionKey?.version;
1404
+ if (typeof nm !== "string" || typeof ver !== "string") continue;
1405
+ const key = `${nm}@${ver}`;
1406
+ if (seen.has(key)) continue;
1407
+ seen.add(key);
1408
+ out.push({ name: nm, version: ver });
1409
+ }
1410
+ return out;
1230
1411
  } catch {
1231
1412
  return [];
1232
1413
  }
@@ -1238,14 +1419,14 @@ async function fetchDepsDev(name, version, repo, fetchImpl) {
1238
1419
  ]);
1239
1420
  return { scorecard, advisories };
1240
1421
  }
1241
- var HOST5, MAX_ADVISORIES;
1422
+ var HOST4, MAX_ADVISORIES;
1242
1423
  var init_depsDev = __esm({
1243
1424
  "src/ingestion/sources/depsDev.ts"() {
1244
1425
  "use strict";
1245
1426
  init_esm_shims();
1246
1427
  init_constants();
1247
1428
  init_http();
1248
- HOST5 = "api.deps.dev";
1429
+ HOST4 = "api.deps.dev";
1249
1430
  MAX_ADVISORIES = 10;
1250
1431
  }
1251
1432
  });
@@ -1258,10 +1439,10 @@ function parseBundleSize(json2) {
1258
1439
  }
1259
1440
  async function fetchBundlephobia(name, category, fetchImpl) {
1260
1441
  if (!isFrontendCategory(category)) return { bundleMinGzipKb: null };
1261
- const url = `https://${HOST6}/api/size?package=${encodeURIComponent(name)}`;
1442
+ const url = `https://${HOST5}/api/size?package=${encodeURIComponent(name)}`;
1262
1443
  try {
1263
1444
  const { data } = await httpGetJson(url, {
1264
- host: HOST6,
1445
+ host: HOST5,
1265
1446
  ttlMs: CACHE_TTL.bundlephobia,
1266
1447
  timeoutMs: 12e3,
1267
1448
  retries: 1,
@@ -1272,7 +1453,7 @@ async function fetchBundlephobia(name, category, fetchImpl) {
1272
1453
  return { bundleMinGzipKb: null };
1273
1454
  }
1274
1455
  }
1275
- var HOST6;
1456
+ var HOST5;
1276
1457
  var init_bundlephobia = __esm({
1277
1458
  "src/ingestion/sources/bundlephobia.ts"() {
1278
1459
  "use strict";
@@ -1280,7 +1461,7 @@ var init_bundlephobia = __esm({
1280
1461
  init_constants();
1281
1462
  init_http();
1282
1463
  init_types();
1283
- HOST6 = "bundlephobia.com";
1464
+ HOST5 = "bundlephobia.com";
1284
1465
  }
1285
1466
  });
1286
1467
 
@@ -1299,34 +1480,55 @@ var init_sources = __esm({
1299
1480
  });
1300
1481
 
1301
1482
  // src/compat/members.ts
1302
- async function assembleMembers(db, names) {
1483
+ async function assembleMembers(db, refs) {
1484
+ const normalized = refs.map(
1485
+ (r) => typeof r === "string" ? { name: r, version: null } : r
1486
+ );
1487
+ const names = [...new Set(normalized.map((r) => r.name))];
1303
1488
  const tracked = new Map((await getCompatMetadata(db, names)).map((r) => [r.name, r]));
1304
1489
  const members = [];
1305
1490
  const unverified = [];
1306
1491
  await Promise.all(
1307
- names.map(async (name) => {
1308
- const row = tracked.get(name);
1309
- if (row) {
1492
+ normalized.map(async (ref) => {
1493
+ const row = tracked.get(ref.name);
1494
+ const pin = ref.version?.trim() || null;
1495
+ const canUseIndexed = row && (!pin || !row.latestVersion || pin === row.latestVersion);
1496
+ if (canUseIndexed && row) {
1310
1497
  members.push({
1311
- name,
1312
- version: row.latestVersion,
1498
+ name: ref.name,
1499
+ version: pin ?? row.latestVersion,
1313
1500
  peerDependencies: row.peerDependencies,
1314
1501
  peerDependenciesMeta: row.peerDependenciesMeta,
1315
1502
  engines: row.engines
1316
1503
  });
1317
1504
  return;
1318
1505
  }
1319
- const reg = await fetchNpmRegistry(name).catch(() => null);
1506
+ if (pin) {
1507
+ const at = await fetchNpmCompatAtVersion(ref.name, pin).catch(() => null);
1508
+ if (at) {
1509
+ members.push({
1510
+ name: ref.name,
1511
+ version: at.version,
1512
+ peerDependencies: at.peerDependencies,
1513
+ peerDependenciesMeta: at.peerDependenciesMeta,
1514
+ engines: at.engines
1515
+ });
1516
+ return;
1517
+ }
1518
+ unverified.push(ref.name);
1519
+ return;
1520
+ }
1521
+ const reg = await fetchNpmRegistry(ref.name).catch(() => null);
1320
1522
  if (reg) {
1321
1523
  members.push({
1322
- name,
1524
+ name: ref.name,
1323
1525
  version: reg.latestVersion,
1324
1526
  peerDependencies: reg.peerDependencies,
1325
1527
  peerDependenciesMeta: reg.peerDependenciesMeta,
1326
1528
  engines: reg.engines
1327
1529
  });
1328
1530
  } else {
1329
- unverified.push(name);
1531
+ unverified.push(ref.name);
1330
1532
  }
1331
1533
  })
1332
1534
  );
@@ -1417,6 +1619,30 @@ function resolveArchitectureCompat(members) {
1417
1619
  }
1418
1620
  return conflicts;
1419
1621
  }
1622
+ function resolveRuntimeEngineConflicts(members, nodeVersion) {
1623
+ const runtime = normalizeNodeVersion(nodeVersion);
1624
+ if (!runtime) return [];
1625
+ const conflicts = [];
1626
+ for (const m of members) {
1627
+ const range = m.engines?.node;
1628
+ if (!range) continue;
1629
+ if (satisfiesRange(runtime, range) === false) {
1630
+ conflicts.push({
1631
+ source: "engines",
1632
+ packages: [m.name],
1633
+ detail: `${m.name}@${m.version ?? "?"} needs node ${range}, but the runtime is node ${runtime}`
1634
+ });
1635
+ }
1636
+ }
1637
+ return conflicts;
1638
+ }
1639
+ function normalizeNodeVersion(raw) {
1640
+ const trimmed = raw.trim().replace(/^v/i, "");
1641
+ if (!trimmed) return null;
1642
+ if (semver.valid(trimmed)) return trimmed;
1643
+ const coerced = semver.coerce(trimmed);
1644
+ return coerced?.version ?? null;
1645
+ }
1420
1646
  var init_peerCompat = __esm({
1421
1647
  "src/compat/peerCompat.ts"() {
1422
1648
  "use strict";
@@ -1425,11 +1651,33 @@ var init_peerCompat = __esm({
1425
1651
  });
1426
1652
 
1427
1653
  // src/compat/check.ts
1428
- async function checkCompat(db, packages2) {
1654
+ function gradeOverall(args) {
1655
+ if (args.hasConflict) return "conflict";
1656
+ if (args.hasUnverifiedMember) return "unknown";
1657
+ if (args.memberNames.length < 2 || fullyCovered(args.memberNames, args.provenCompatible))
1658
+ return "compatible";
1659
+ return "likely";
1660
+ }
1661
+ async function checkCompat(db, packages2, opts = {}) {
1429
1662
  const names = [...new Set(packages2)];
1430
- const { members, unverified } = await assembleMembers(db, names);
1431
- const conflicts = resolveArchitectureCompat(members);
1432
- for (const edge of await getCompatEdges(db, names)) {
1663
+ const refs = names.map((name) => ({
1664
+ name,
1665
+ version: opts.versions?.[name] ?? null
1666
+ }));
1667
+ const { members, unverified } = await assembleMembers(db, refs);
1668
+ const conflicts = [
1669
+ ...resolveArchitectureCompat(members),
1670
+ ...opts.node ? resolveRuntimeEngineConflicts(members, opts.node) : []
1671
+ ];
1672
+ const edges = await getCompatEdges(db, names);
1673
+ const evidence = edges.map((edge) => ({
1674
+ packages: [edge.packageA, edge.packageB],
1675
+ versions: [edge.versionA, edge.versionB],
1676
+ status: edge.status,
1677
+ provenance: edge.provenance,
1678
+ witnessCount: edge.witnessCount
1679
+ }));
1680
+ for (const edge of edges) {
1433
1681
  if (edge.status === "conflict") {
1434
1682
  conflicts.push({
1435
1683
  source: "sandbox",
@@ -1438,13 +1686,27 @@ async function checkCompat(db, packages2) {
1438
1686
  });
1439
1687
  }
1440
1688
  }
1441
- const overall = conflicts.length ? "conflict" : unverified.length ? "unknown" : "compatible";
1689
+ const checkedNames = members.map((m) => m.name);
1690
+ const provenCompatible = new Set(
1691
+ edges.filter((e) => e.status === "compatible").map((e) => pairKey(e.packageA, e.packageB))
1692
+ );
1693
+ if (checkedNames.length >= 2 && !conflicts.length && !fullyCovered(checkedNames, provenCompatible)) {
1694
+ await enqueueCompatVerify(db, checkedNames).catch(() => {
1695
+ });
1696
+ }
1697
+ const overall = gradeOverall({
1698
+ hasConflict: conflicts.length > 0,
1699
+ hasUnverifiedMember: unverified.length > 0,
1700
+ memberNames: checkedNames,
1701
+ provenCompatible
1702
+ });
1442
1703
  return {
1443
1704
  packages: names,
1444
1705
  overall,
1445
1706
  conflicts,
1446
1707
  unverified,
1447
- checked: members.map((m) => ({ name: m.name, version: m.version }))
1708
+ checked: members.map((m) => ({ name: m.name, version: m.version })),
1709
+ evidence
1448
1710
  };
1449
1711
  }
1450
1712
  var init_check = __esm({
@@ -1458,13 +1720,9 @@ var init_check = __esm({
1458
1720
  });
1459
1721
 
1460
1722
  // src/db/packages.ts
1461
- import { desc, eq, isNotNull } from "drizzle-orm";
1462
- async function getSeedTargets(db) {
1463
- const rows = await db.select({ name: seedPackages.name, category: seedPackages.category }).from(seedPackages);
1464
- return rows.map((r) => ({ name: r.name, category: r.category ?? null }));
1465
- }
1723
+ import { and as and2, desc, eq as eq2, isNotNull, isNull, sql as sql3 } from "drizzle-orm";
1466
1724
  async function getPackageByName(db, name) {
1467
- const rows = await db.select().from(packages).where(eq(packages.name, name)).limit(1);
1725
+ const rows = await db.select().from(packages).where(eq2(packages.name, name)).limit(1);
1468
1726
  return rows[0] ?? null;
1469
1727
  }
1470
1728
  async function getAllPackageNames(db) {
@@ -1483,7 +1741,7 @@ async function upsertPackageVersions(db, name, versions) {
1483
1741
  }
1484
1742
  }
1485
1743
  async function getPackageVersions(db, name, limit = 50) {
1486
- const rows = await db.select({ version: packageVersions.version, publishedAt: packageVersions.publishedAt }).from(packageVersions).where(eq(packageVersions.packageName, name)).orderBy(desc(packageVersions.publishedAt)).limit(limit);
1744
+ const rows = await db.select({ version: packageVersions.version, publishedAt: packageVersions.publishedAt }).from(packageVersions).where(eq2(packageVersions.packageName, name)).orderBy(desc(packageVersions.publishedAt)).limit(limit);
1487
1745
  return rows.map((r) => ({ version: r.version, publishedAt: r.publishedAt }));
1488
1746
  }
1489
1747
  async function getTopPackageNames(db, limit = 1e3) {
@@ -1500,18 +1758,22 @@ async function upsertPackage(db, row) {
1500
1758
  set: { ...mutable, updatedAt: /* @__PURE__ */ new Date() }
1501
1759
  });
1502
1760
  }
1503
- async function startSyncRun(db) {
1504
- const [row] = await db.insert(syncRuns).values({ status: "running" }).returning({ id: syncRuns.id });
1505
- return row.id;
1761
+ async function stampFirstRequester(db, name, ownerId) {
1762
+ await db.update(packages).set({ firstRequestedByOwnerId: ownerId }).where(and2(eq2(packages.name, name), isNull(packages.firstRequestedByOwnerId)));
1506
1763
  }
1507
- async function finishSyncRun(db, id, data) {
1508
- await db.update(syncRuns).set({
1509
- finishedAt: /* @__PURE__ */ new Date(),
1510
- packagesSeen: data.packagesSeen,
1511
- packagesUpdated: data.packagesUpdated,
1512
- errors: data.errors,
1513
- status: data.status
1514
- }).where(eq(syncRuns.id, id));
1764
+ async function getContributionsByOwner(db, ownerId, opts = {}) {
1765
+ const limit = opts.limit ?? 50;
1766
+ const offset = opts.offset ?? 0;
1767
+ const [rows, [countRow]] = await Promise.all([
1768
+ db.select({
1769
+ name: packages.name,
1770
+ category: packages.category,
1771
+ healthScore: packages.healthScore,
1772
+ firstRequestedAt: packages.createdAt
1773
+ }).from(packages).where(eq2(packages.firstRequestedByOwnerId, ownerId)).orderBy(desc(packages.createdAt)).limit(limit).offset(offset),
1774
+ db.select({ count: sql3`count(*)::int` }).from(packages).where(eq2(packages.firstRequestedByOwnerId, ownerId))
1775
+ ]);
1776
+ return { total: countRow?.count ?? 0, packages: rows };
1515
1777
  }
1516
1778
  var VERSION_CHUNK;
1517
1779
  var init_packages = __esm({
@@ -1523,13 +1785,72 @@ var init_packages = __esm({
1523
1785
  }
1524
1786
  });
1525
1787
 
1788
+ // src/db/apiSurfaces.ts
1789
+ import { and as and3, eq as eq3, isNotNull as isNotNull2, isNull as isNull2 } from "drizzle-orm";
1790
+ async function getStoredSurface(db, packageName, version) {
1791
+ const [row] = await db.select({ surface: apiSurfaces.surface }).from(apiSurfaces).where(and3(eq3(apiSurfaces.packageName, packageName), eq3(apiSurfaces.version, version))).limit(1);
1792
+ return row?.surface ?? null;
1793
+ }
1794
+ var init_apiSurfaces = __esm({
1795
+ "src/db/apiSurfaces.ts"() {
1796
+ "use strict";
1797
+ init_esm_shims();
1798
+ init_schema();
1799
+ }
1800
+ });
1801
+
1802
+ // src/usage/diff.ts
1803
+ function byName(surface) {
1804
+ return new Map(surface.map((s) => [s.name, s]));
1805
+ }
1806
+ function looksRenamed(a, b) {
1807
+ return a.kind === b.kind && a.signature !== null && a.signature === b.signature;
1808
+ }
1809
+ function diffSurface(oldSurface, newSurface) {
1810
+ const oldByName = byName(oldSurface);
1811
+ const newByName = byName(newSurface);
1812
+ const removed = oldSurface.filter((s) => !newByName.has(s.name));
1813
+ const added = newSurface.filter((s) => !oldByName.has(s.name));
1814
+ const changed = [];
1815
+ for (const oldSym of oldSurface) {
1816
+ const newSym = newByName.get(oldSym.name);
1817
+ if (newSym && oldSym.signature !== newSym.signature) {
1818
+ changed.push({ name: oldSym.name, before: oldSym.signature, after: newSym.signature });
1819
+ }
1820
+ }
1821
+ const renamed = [];
1822
+ const takenAdded = /* @__PURE__ */ new Set();
1823
+ const stillRemoved = [];
1824
+ for (const r of removed) {
1825
+ const match = added.find((a) => !takenAdded.has(a.name) && looksRenamed(r, a));
1826
+ if (match) {
1827
+ renamed.push({ from: r, to: match });
1828
+ takenAdded.add(match.name);
1829
+ } else {
1830
+ stillRemoved.push(r);
1831
+ }
1832
+ }
1833
+ return {
1834
+ added: added.filter((a) => !takenAdded.has(a.name)),
1835
+ removed: stillRemoved,
1836
+ renamed,
1837
+ changed
1838
+ };
1839
+ }
1840
+ var init_diff = __esm({
1841
+ "src/usage/diff.ts"() {
1842
+ "use strict";
1843
+ init_esm_shims();
1844
+ }
1845
+ });
1846
+
1526
1847
  // src/db/verification.ts
1527
- import { and as and2, desc as desc2, eq as eq2 } from "drizzle-orm";
1848
+ import { and as and4, desc as desc2, eq as eq4 } from "drizzle-orm";
1528
1849
  async function storeVerificationRun(db, run) {
1529
1850
  await db.insert(verificationRuns).values(run);
1530
1851
  }
1531
1852
  async function getLatestVerificationByName(db, packageName) {
1532
- const rows = await db.select().from(verificationRuns).where(eq2(verificationRuns.packageName, packageName)).orderBy(desc2(verificationRuns.ranAt)).limit(1);
1853
+ const rows = await db.select().from(verificationRuns).where(eq4(verificationRuns.packageName, packageName)).orderBy(desc2(verificationRuns.ranAt)).limit(1);
1533
1854
  return rows[0] ?? null;
1534
1855
  }
1535
1856
  var init_verification = __esm({
@@ -1541,9 +1862,13 @@ var init_verification = __esm({
1541
1862
  });
1542
1863
 
1543
1864
  // src/db/outcomes.ts
1865
+ import { desc as desc3, eq as eq5 } from "drizzle-orm";
1544
1866
  async function recordOutcome(db, outcome) {
1545
1867
  await db.insert(recommendationOutcomes).values(outcome);
1546
1868
  }
1869
+ async function getOutcomesByOwner(db, ownerId, opts = {}) {
1870
+ return db.select().from(recommendationOutcomes).where(eq5(recommendationOutcomes.ownerId, ownerId)).orderBy(desc3(recommendationOutcomes.createdAt)).limit(opts.limit ?? 50);
1871
+ }
1547
1872
  var init_outcomes = __esm({
1548
1873
  "src/db/outcomes.ts"() {
1549
1874
  "use strict";
@@ -1631,10 +1956,10 @@ var init_successors2 = __esm({
1631
1956
  // src/ingestion/sources/githubReadme.ts
1632
1957
  async function fetchGithubReadme(owner, repo, fetchImpl) {
1633
1958
  for (const file of CANDIDATES) {
1634
- const url = `https://${HOST7}/${owner}/${repo}/HEAD/${file}`;
1959
+ const url = `https://${HOST6}/${owner}/${repo}/HEAD/${file}`;
1635
1960
  try {
1636
1961
  const { data } = await httpRequest(url, {
1637
- host: HOST7,
1962
+ host: HOST6,
1638
1963
  ttlMs: CACHE_TTL.github,
1639
1964
  accept: "text",
1640
1965
  retries: 0,
@@ -1646,14 +1971,14 @@ async function fetchGithubReadme(owner, repo, fetchImpl) {
1646
1971
  }
1647
1972
  return null;
1648
1973
  }
1649
- var HOST7, CANDIDATES;
1974
+ var HOST6, CANDIDATES;
1650
1975
  var init_githubReadme = __esm({
1651
1976
  "src/ingestion/sources/githubReadme.ts"() {
1652
1977
  "use strict";
1653
1978
  init_esm_shims();
1654
1979
  init_constants();
1655
1980
  init_http();
1656
- HOST7 = "raw.githubusercontent.com";
1981
+ HOST6 = "raw.githubusercontent.com";
1657
1982
  CANDIDATES = ["README.md", "readme.md", "README.markdown", "README"];
1658
1983
  }
1659
1984
  });
@@ -1951,12 +2276,6 @@ function packageRoot() {
1951
2276
  cachedRoot = process.cwd();
1952
2277
  return cachedRoot;
1953
2278
  }
1954
- function migrationsDir() {
1955
- return join2(packageRoot(), "drizzle");
1956
- }
1957
- function seedJsonPath() {
1958
- return join2(packageRoot(), "src", "data", "seed.json");
1959
- }
1960
2279
  function userWeightsPath() {
1961
2280
  const base = process.env.XDG_CONFIG_HOME || join2(homedir2(), ".config");
1962
2281
  return join2(base, "lurq", "weights.json");
@@ -2034,10 +2353,10 @@ function settableKeys() {
2034
2353
  function applyOverrides(base, sets) {
2035
2354
  const next = structuredClone(base);
2036
2355
  for (const entry of sets) {
2037
- const eq9 = entry.indexOf("=");
2038
- if (eq9 < 0) throw new Error(`Invalid --set "${entry}" (expected key=value).`);
2039
- const key = entry.slice(0, eq9).trim();
2040
- const value = Number(entry.slice(eq9 + 1).trim());
2356
+ const eq11 = entry.indexOf("=");
2357
+ if (eq11 < 0) throw new Error(`Invalid --set "${entry}" (expected key=value).`);
2358
+ const key = entry.slice(0, eq11).trim();
2359
+ const value = Number(entry.slice(eq11 + 1).trim());
2041
2360
  const apply = SETTABLE[key];
2042
2361
  if (!apply) {
2043
2362
  throw new Error(`Unknown weight key "${key}". Settable: ${settableKeys().join(", ")}.`);
@@ -2065,7 +2384,7 @@ function resetWeights() {
2065
2384
  resetWeightsCache();
2066
2385
  return removed;
2067
2386
  }
2068
- var HEALTH_WEIGHTS, COMPOSITE, QUALITY_WEIGHTS, QUALITY, MAINTENANCE_WEIGHTS, MAINTENANCE, ADOPTION, RELIABILITY, EFFICIENCY, DISCOVERY, DEFAULT_WEIGHTS, cachedWeights, SETTABLE, WEIGHT_EXPLANATIONS, CONFIDENCE;
2387
+ var HEALTH_WEIGHTS, COMPOSITE, QUALITY_WEIGHTS, QUALITY, MAINTENANCE_WEIGHTS, MAINTENANCE, ADOPTION, RELIABILITY, EFFICIENCY, DEFAULT_WEIGHTS, cachedWeights, SETTABLE, WEIGHT_EXPLANATIONS, CONFIDENCE;
2069
2388
  var init_weights = __esm({
2070
2389
  "src/scoring/weights.ts"() {
2071
2390
  "use strict";
@@ -2144,16 +2463,6 @@ var init_weights = __esm({
2144
2463
  /** Bundle size at the category median maps to this score; smaller → higher. */
2145
2464
  medianScore: 50
2146
2465
  };
2147
- DISCOVERY = {
2148
- /** A queued candidate must clear this quality pre-score to graduate to ingest. */
2149
- minPreScore: 45,
2150
- /** Max candidates fully ingested per crawler run (cost bound). */
2151
- perRunCap: 25,
2152
- /** npm-search hits to pull per category keyword. */
2153
- searchSizePerCategory: 10,
2154
- /** Max dependency-graph neighbors to enqueue per tracked seed. */
2155
- graphNeighborsPerSeed: 20
2156
- };
2157
2466
  DEFAULT_WEIGHTS = {
2158
2467
  health: { ...HEALTH_WEIGHTS },
2159
2468
  composite: { ...COMPOSITE }
@@ -2369,12 +2678,6 @@ function weightedAverage(components) {
2369
2678
  if (totalWeight === 0) return 0;
2370
2679
  return Math.round(components.reduce((s, c) => s + c.value * c.weight, 0) / totalWeight);
2371
2680
  }
2372
- function median(values) {
2373
- if (values.length === 0) return null;
2374
- const sorted = [...values].sort((a, b) => a - b);
2375
- const mid = Math.floor(sorted.length / 2);
2376
- return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
2377
- }
2378
2681
  var DAY_MS, MONTH_MS, clamp, daysSince, monthsSince, RECOGNIZED_LICENSES;
2379
2682
  var init_score = __esm({
2380
2683
  "src/scoring/score.ts"() {
@@ -2385,8 +2688,8 @@ var init_score = __esm({
2385
2688
  DAY_MS = 24 * 60 * 60 * 1e3;
2386
2689
  MONTH_MS = 30 * DAY_MS;
2387
2690
  clamp = (n, lo = 0, hi = 100) => Math.max(lo, Math.min(hi, n));
2388
- daysSince = (date, now) => date ? (now.getTime() - date.getTime()) / DAY_MS : null;
2389
- monthsSince = (date, now) => date ? (now.getTime() - date.getTime()) / MONTH_MS : null;
2691
+ daysSince = (date2, now) => date2 ? (now.getTime() - date2.getTime()) / DAY_MS : null;
2692
+ monthsSince = (date2, now) => date2 ? (now.getTime() - date2.getTime()) / MONTH_MS : null;
2390
2693
  RECOGNIZED_LICENSES = [
2391
2694
  "mit",
2392
2695
  "isc",
@@ -2531,19 +2834,6 @@ var init_embeddings = __esm({
2531
2834
  });
2532
2835
 
2533
2836
  // src/core/concurrency.ts
2534
- async function pMap(items, mapper, concurrency) {
2535
- const results = new Array(items.length);
2536
- let cursor = 0;
2537
- const workerCount = Math.max(1, Math.min(concurrency, items.length));
2538
- const workers = Array.from({ length: workerCount }, async () => {
2539
- while (cursor < items.length) {
2540
- const index2 = cursor++;
2541
- results[index2] = await mapper(items[index2], index2);
2542
- }
2543
- });
2544
- await Promise.all(workers);
2545
- return results;
2546
- }
2547
2837
  var init_concurrency = __esm({
2548
2838
  "src/core/concurrency.ts"() {
2549
2839
  "use strict";
@@ -2551,172 +2841,93 @@ var init_concurrency = __esm({
2551
2841
  }
2552
2842
  });
2553
2843
 
2554
- // src/pipeline/sync.ts
2555
- async function runSync(opts = {}) {
2556
- const config = getConfig();
2557
- const now = /* @__PURE__ */ new Date();
2558
- if (opts.full) setCacheBypassRead(true);
2559
- const handle = createDb({ max: Math.max(4, config.LURQ_SYNC_CONCURRENCY) });
2560
- const provider = createSummaryProvider();
2561
- logger.info(`Summary provider: ${provider.kind}`);
2562
- if (!config.GITHUB_TOKEN) {
2563
- logger.warn(
2564
- "GITHUB_TOKEN not set \u2014 GitHub signals (stars, issues, release cadence) will be skipped, degrading maintenance/adoption scores. Set it for accurate scoring."
2565
- );
2844
+ // src/core/errors.ts
2845
+ function truncate(text2, max) {
2846
+ if (text2.length <= max) return text2;
2847
+ return `${text2.slice(0, max)}\u2026`;
2848
+ }
2849
+ function causeText(cause) {
2850
+ if (cause == null) return null;
2851
+ if (cause instanceof Error) {
2852
+ const code = "code" in cause && (typeof cause.code === "string" || typeof cause.code === "number") ? String(cause.code) : null;
2853
+ const msg = truncate(cause.message, DEFAULT_MAX);
2854
+ return code ? `${code}: ${msg}` : msg;
2855
+ }
2856
+ if (typeof cause === "object" && cause !== null && "message" in cause) {
2857
+ const msg = truncate(String(cause.message), DEFAULT_MAX);
2858
+ const code = "code" in cause ? String(cause.code) : null;
2859
+ return code ? `${code}: ${msg}` : msg;
2860
+ }
2861
+ return truncate(String(cause), DEFAULT_MAX);
2862
+ }
2863
+ function formatError(err, max = DEFAULT_MAX) {
2864
+ const message = err instanceof Error ? truncate(err.message, max) : truncate(String(err), max);
2865
+ const cause = err instanceof Error ? causeText(err.cause) : null;
2866
+ return cause ? `${message} (cause: ${cause})` : message;
2867
+ }
2868
+ var DEFAULT_MAX;
2869
+ var init_errors = __esm({
2870
+ "src/core/errors.ts"() {
2871
+ "use strict";
2872
+ init_esm_shims();
2873
+ DEFAULT_MAX = 300;
2566
2874
  }
2567
- const runId = await startSyncRun(handle.db);
2568
- const allErrors = [];
2569
- try {
2570
- const targets = await resolveTargets(handle.db, opts);
2571
- logger.info(`Syncing ${targets.length} package(s) with concurrency ${config.LURQ_SYNC_CONCURRENCY}\u2026`);
2572
- logger.info("Fetching weekly downloads in bulk\u2026");
2573
- const weeklyMap = await fetchBulkWeeklyDownloads(targets.map((t) => t.name));
2574
- let done = 0;
2575
- const computed = await pMap(
2576
- targets,
2577
- async (target) => {
2578
- try {
2579
- const signals = await collectSignals(target.name, target.category, {
2580
- githubToken: config.GITHUB_TOKEN,
2581
- prefetchedWeekly: weeklyMap.has(target.name) ? weeklyMap.get(target.name) : void 0
2582
- });
2583
- for (const e of signals.errors) allErrors.push({ package: target.name, ...e });
2584
- const summaryInput = await buildSummaryInput(signals, target.category);
2585
- const { summary, usageGuide, inferredCategory } = await provider.generate(summaryInput);
2586
- let category = target.category;
2587
- let categorySource = target.category ? "curated" : null;
2588
- if (!category) {
2589
- category = inferCategoryFromSignals(signals) ?? inferredCategory ?? null;
2590
- categorySource = category ? "inferred" : null;
2591
- }
2592
- const input = toScoringInput(signals, category);
2593
- const quality = computeQuality(input);
2594
- if (++done % 25 === 0) logger.info(` \u2026${done}/${targets.length}`);
2595
- return {
2596
- target,
2597
- category,
2598
- categorySource,
2599
- signals,
2600
- input,
2601
- maintenance: computeMaintenance(input, now),
2602
- adoption: computeAdoption(input),
2603
- reliability: computeReliability(input),
2604
- quality,
2605
- confidence: computeConfidence(input, now, quality),
2606
- summary,
2607
- usageGuide
2608
- };
2609
- } catch (err) {
2610
- allErrors.push({
2611
- package: target.name,
2612
- source: "pipeline",
2613
- message: err instanceof Error ? err.message : String(err)
2614
- });
2615
- return null;
2616
- }
2617
- },
2618
- config.LURQ_SYNC_CONCURRENCY
2619
- );
2620
- const ok = computed.filter((c) => c !== null);
2621
- const medians = computeCategoryMedians(ok);
2622
- const embProvider = createEmbeddingProvider();
2623
- logger.info(`Embedding provider: ${embProvider.kind}`);
2624
- const embeddings = await embProvider.embed(
2625
- ok.map(
2626
- (c) => buildEmbeddingText({
2627
- name: c.target.name,
2628
- category: c.category,
2629
- summary: c.summary,
2630
- description: c.signals.registry?.description ?? null
2631
- })
2632
- )
2633
- );
2634
- let updated = 0;
2635
- for (let i = 0; i < ok.length; i++) {
2636
- const c = ok[i];
2637
- const efficiency = computeEfficiency(
2638
- c.input.bundleMinGzipKb,
2639
- c.category,
2640
- c.category ? medians.get(c.category) ?? null : null
2641
- );
2642
- const breakdown = {
2643
- maintenance: c.maintenance,
2644
- adoption: c.adoption,
2645
- reliability: c.reliability,
2646
- efficiency,
2647
- quality: c.quality
2648
- };
2649
- const healthScore = computeHealthScore(breakdown);
2650
- await upsertPackage(
2651
- handle.db,
2652
- assemblePackageRow({
2653
- name: c.target.name,
2654
- category: c.category,
2655
- categorySource: c.categorySource,
2656
- signals: c.signals,
2657
- input: c.input,
2658
- summary: c.summary,
2659
- usageGuide: c.usageGuide,
2660
- confidence: c.confidence,
2661
- breakdown,
2662
- healthScore,
2663
- qualityScore: c.quality,
2664
- embedding: embeddings[i] ?? null,
2665
- embeddingProvider: embProvider.id,
2666
- now
2667
- })
2668
- );
2669
- updated++;
2875
+ });
2876
+
2877
+ // src/pipeline/mineEdges.ts
2878
+ function trackedPairs(nodes, tracked) {
2879
+ const t = nodes.filter((n) => tracked.has(n.name));
2880
+ const pairs = [];
2881
+ for (let i = 0; i < t.length; i++) {
2882
+ for (let j = i + 1; j < t.length; j++) {
2883
+ if (t[i].name === t[j].name) continue;
2884
+ pairs.push(canonicalPair(t[i], t[j]));
2670
2885
  }
2671
- const status = updated === 0 ? "failed" : allErrors.length > 0 ? "partial" : "success";
2672
- await finishSyncRun(handle.db, runId, {
2673
- packagesSeen: targets.length,
2674
- packagesUpdated: updated,
2675
- errors: allErrors,
2676
- status
2677
- });
2678
- logger.info(`Sync ${status}: ${updated}/${targets.length} updated, ${allErrors.length} source errors.`);
2679
- if (updated > 0) await invalidateCache();
2680
- return { seen: targets.length, updated, errors: allErrors.length, status };
2681
- } catch (err) {
2682
- await finishSyncRun(handle.db, runId, {
2683
- packagesSeen: 0,
2684
- packagesUpdated: 0,
2685
- errors: [{ package: "*", source: "pipeline", message: err.message }],
2686
- status: "failed"
2687
- });
2688
- throw err;
2689
- } finally {
2690
- if (opts.full) setCacheBypassRead(false);
2691
- await handle.close();
2692
2886
  }
2887
+ return pairs;
2693
2888
  }
2694
- async function resolveTargets(db, opts) {
2695
- if (opts.packageName) {
2696
- const seeds = await getSeedTargets(db);
2697
- const found = seeds.find((s) => s.name === opts.packageName);
2698
- return [{ name: opts.packageName, category: found?.category ?? null }];
2699
- }
2700
- return getSeedTargets(db);
2889
+ async function mintObservedPairs(db, nodes, tracked, now) {
2890
+ const pairs = trackedPairs(nodes, tracked);
2891
+ await upsertCompatEdgesBatch(
2892
+ db,
2893
+ pairs.map((pair) => ({
2894
+ ...pair,
2895
+ status: "compatible",
2896
+ provenance: "observed",
2897
+ witnessCount: 1,
2898
+ driver: "depsdev",
2899
+ ranAt: now
2900
+ }))
2901
+ );
2902
+ return pairs.length;
2701
2903
  }
2702
- function computeCategoryMedians(computed) {
2703
- const byCategory = /* @__PURE__ */ new Map();
2704
- for (const c of computed) {
2705
- const cat = c.category;
2706
- const kb = c.input.bundleMinGzipKb;
2707
- if (cat && isFrontendCategory(cat) && kb !== null) {
2708
- const list = byCategory.get(cat) ?? [];
2709
- list.push(kb);
2710
- byCategory.set(cat, list);
2711
- }
2712
- }
2713
- const medians = /* @__PURE__ */ new Map();
2714
- for (const [cat, list] of byCategory) {
2715
- const m = median(list);
2716
- if (m !== null) medians.set(cat, m);
2904
+ async function mineEdgesForPackage(db, name, version, tracked, now = /* @__PURE__ */ new Date()) {
2905
+ if (!version) return 0;
2906
+ try {
2907
+ const closure = await fetchResolvedGraph(name, version);
2908
+ if (closure.length === 0) return 0;
2909
+ await persistClosure(db, name, version, closure).catch(() => {
2910
+ });
2911
+ const set = tracked ?? new Set(await getAllPackageNames(db));
2912
+ return await mintObservedPairs(db, closure, set, now);
2913
+ } catch (err) {
2914
+ logger.warn(`edge mining failed for ${name}@${version}: ${formatError(err)}`);
2915
+ return 0;
2717
2916
  }
2718
- return medians;
2719
2917
  }
2918
+ var init_mineEdges = __esm({
2919
+ "src/pipeline/mineEdges.ts"() {
2920
+ "use strict";
2921
+ init_esm_shims();
2922
+ init_errors();
2923
+ init_logger();
2924
+ init_packages();
2925
+ init_compat();
2926
+ init_depsDev();
2927
+ }
2928
+ });
2929
+
2930
+ // src/pipeline/sync.ts
2720
2931
  function assemblePackageRow(p) {
2721
2932
  const r = p.signals.registry;
2722
2933
  return {
@@ -2762,6 +2973,7 @@ var init_sync = __esm({
2762
2973
  init_cache();
2763
2974
  init_config();
2764
2975
  init_concurrency();
2976
+ init_errors();
2765
2977
  init_http();
2766
2978
  init_logger();
2767
2979
  init_collect();
@@ -2772,18 +2984,20 @@ var init_sync = __esm({
2772
2984
  init_scoring();
2773
2985
  init_client();
2774
2986
  init_packages();
2987
+ init_mineEdges();
2775
2988
  init_types();
2776
2989
  }
2777
2990
  });
2778
2991
 
2779
2992
  // src/pipeline/ingestQueue.ts
2780
- function enqueueIngest(db, name) {
2993
+ function enqueueIngest(db, name, requestedByOwnerId = null) {
2781
2994
  if (queuedNames.has(name) || inFlight.has(name)) return;
2782
2995
  if (pending.length >= MAX_PENDING) {
2783
2996
  logger.warn(`ingest queue full (${MAX_PENDING}); dropping on-demand request for ${name}`);
2784
2997
  return;
2785
2998
  }
2786
2999
  queuedNames.add(name);
3000
+ owners.set(name, requestedByOwnerId);
2787
3001
  pending.push(name);
2788
3002
  pump(db);
2789
3003
  }
@@ -2791,26 +3005,31 @@ function pump(db) {
2791
3005
  while (active < MAX_CONCURRENT && pending.length > 0) {
2792
3006
  const name = pending.shift();
2793
3007
  queuedNames.delete(name);
3008
+ const owner = owners.get(name) ?? null;
3009
+ owners.delete(name);
2794
3010
  inFlight.add(name);
2795
3011
  active += 1;
2796
- void ingestOne(db, name).finally(() => {
3012
+ void runIngest(db, name, owner).finally(() => {
2797
3013
  inFlight.delete(name);
2798
3014
  active -= 1;
2799
3015
  pump(db);
2800
3016
  });
2801
3017
  }
2802
3018
  }
2803
- async function ingestOne(db, name) {
3019
+ async function runIngest(db, name, requestedByOwnerId = null) {
2804
3020
  try {
2805
- const row = await syncOnePackage(db, name);
3021
+ const row = await syncOnePackage(db, name, { requestedByOwnerId });
2806
3022
  if (row.confidence && row.confidence !== "unproven") {
2807
- await ensureSeedEntry(db, name, row.category);
3023
+ await ensureSeedEntry(db, name, row.category).catch(() => {
3024
+ });
2808
3025
  }
3026
+ return row;
2809
3027
  } catch (err) {
2810
3028
  logger.warn(`on-demand ingest failed for ${name}: ${String(err)}`);
3029
+ return null;
2811
3030
  }
2812
3031
  }
2813
- var MAX_CONCURRENT, MAX_PENDING, pending, inFlight, queuedNames, active;
3032
+ var MAX_CONCURRENT, MAX_PENDING, pending, inFlight, queuedNames, owners, active;
2814
3033
  var init_ingestQueue = __esm({
2815
3034
  "src/pipeline/ingestQueue.ts"() {
2816
3035
  "use strict";
@@ -2823,20 +3042,24 @@ var init_ingestQueue = __esm({
2823
3042
  pending = [];
2824
3043
  inFlight = /* @__PURE__ */ new Set();
2825
3044
  queuedNames = /* @__PURE__ */ new Set();
3045
+ owners = /* @__PURE__ */ new Map();
2826
3046
  active = 0;
2827
3047
  }
2828
3048
  });
2829
3049
 
2830
3050
  // src/pipeline/single.ts
2831
- import { and as and3, eq as eq3, isNotNull as isNotNull2, sql as sql2 } from "drizzle-orm";
3051
+ import { and as and5, eq as eq6, isNotNull as isNotNull3, sql as sql4 } from "drizzle-orm";
3052
+ function raceTimeout(p, ms) {
3053
+ return Promise.race([p, new Promise((resolve) => setTimeout(() => resolve(null), ms))]);
3054
+ }
2832
3055
  async function getSeedCategory(db, name) {
2833
- const [row] = await db.select({ category: seedPackages.category }).from(seedPackages).where(eq3(seedPackages.name, name)).limit(1);
3056
+ const [row] = await db.select({ category: seedPackages.category }).from(seedPackages).where(eq6(seedPackages.name, name)).limit(1);
2834
3057
  return row?.category ?? null;
2835
3058
  }
2836
3059
  async function getCategoryMedianBundle(db, category) {
2837
3060
  const [row] = await db.select({
2838
- m: sql2`percentile_cont(0.5) within group (order by ${packages.bundleMinGzipKb})`
2839
- }).from(packages).where(and3(eq3(packages.category, category), isNotNull2(packages.bundleMinGzipKb)));
3061
+ m: sql4`percentile_cont(0.5) within group (order by ${packages.bundleMinGzipKb})`
3062
+ }).from(packages).where(and5(eq6(packages.category, category), isNotNull3(packages.bundleMinGzipKb)));
2840
3063
  return row?.m ?? null;
2841
3064
  }
2842
3065
  async function syncOnePackage(db, name, opts = {}) {
@@ -2899,20 +3122,31 @@ async function syncOnePackage(db, name, opts = {}) {
2899
3122
  now
2900
3123
  })
2901
3124
  );
3125
+ if (!existing && opts.requestedByOwnerId) {
3126
+ await stampFirstRequester(db, name, opts.requestedByOwnerId).catch(() => {
3127
+ });
3128
+ }
2902
3129
  await upsertPackageVersions(db, name, signals.registry?.versionTimeline ?? []).catch(
2903
3130
  () => {
2904
3131
  }
2905
3132
  );
3133
+ await mineEdgesForPackage(db, name, signals.registry?.latestVersion ?? null, void 0, now);
2906
3134
  return await getPackageByName(db, name);
2907
3135
  }
2908
- async function getOrFetchPackage(db, name) {
3136
+ async function getOrFetchPackage(db, name, opts = {}) {
2909
3137
  const existing = await getPackageByName(db, name);
2910
3138
  if (existing) return { row: existing, wasTracked: true, existsOnNpm: true };
2911
3139
  const exists = await npmPackageExists(name);
2912
3140
  if (!exists) return { row: null, wasTracked: false, existsOnNpm: false };
2913
- enqueueIngest(db, name);
3141
+ if (opts.blockMs && opts.blockMs > 0) {
3142
+ const row = await raceTimeout(runIngest(db, name, opts.requestedByOwnerId ?? null), opts.blockMs);
3143
+ if (row) return { row, wasTracked: false, existsOnNpm: true };
3144
+ return { row: null, wasTracked: false, existsOnNpm: true, queued: true };
3145
+ }
3146
+ enqueueIngest(db, name, opts.requestedByOwnerId ?? null);
2914
3147
  return { row: null, wasTracked: false, existsOnNpm: true, queued: true };
2915
3148
  }
3149
+ var FIRST_TOUCH_BUDGET_MS;
2916
3150
  var init_single = __esm({
2917
3151
  "src/pipeline/single.ts"() {
2918
3152
  "use strict";
@@ -2927,12 +3161,14 @@ var init_single = __esm({
2927
3161
  init_packages();
2928
3162
  init_schema();
2929
3163
  init_sync();
3164
+ init_mineEdges();
2930
3165
  init_ingestQueue();
3166
+ FIRST_TOUCH_BUDGET_MS = 4e3;
2931
3167
  }
2932
3168
  });
2933
3169
 
2934
3170
  // src/search/recommend.ts
2935
- import { and as and4, cosineDistance, eq as eq4, isNotNull as isNotNull3, lte, sql as sql3 } from "drizzle-orm";
3171
+ import { and as and6, cosineDistance, eq as eq7, inArray as inArray2, isNotNull as isNotNull4, lte, sql as sql5 } from "drizzle-orm";
2936
3172
  async function recommend(db, opts, provider = createEmbeddingProvider()) {
2937
3173
  const limit = Math.min(Math.max(opts.limit ?? 3, 1), 5);
2938
3174
  const [queryVec] = await provider.embed([opts.need]);
@@ -2976,8 +3212,8 @@ function rrfFuse(lists, k = RRF_K) {
2976
3212
  }
2977
3213
  function buildConditions(constraints, category) {
2978
3214
  const conditions = [];
2979
- if (category) conditions.push(eq4(packages.category, category));
2980
- if (constraints?.license) conditions.push(eq4(packages.license, constraints.license));
3215
+ if (category) conditions.push(eq7(packages.category, category));
3216
+ if (constraints?.license) conditions.push(eq7(packages.license, constraints.license));
2981
3217
  if (constraints?.maxBundleKb !== void 0) {
2982
3218
  conditions.push(lte(packages.bundleMinGzipKb, constraints.maxBundleKb));
2983
3219
  }
@@ -2985,26 +3221,24 @@ function buildConditions(constraints, category) {
2985
3221
  const allowed = ["proven", "emerging", "promising", "unproven"].filter(
2986
3222
  (c) => CONFIDENCE_RANK[c] >= CONFIDENCE_RANK[constraints.minConfidence]
2987
3223
  );
2988
- conditions.push(
2989
- sql3`${packages.confidence} in ${sql3.raw(`(${allowed.map((c) => `'${c}'`).join(",")})`)}`
2990
- );
3224
+ conditions.push(inArray2(packages.confidence, allowed));
2991
3225
  }
2992
3226
  return conditions;
2993
3227
  }
2994
3228
  async function runVectorQuery(db, queryVec, providerId, constraints, category, pool) {
2995
3229
  const distance = cosineDistance(packages.embedding, queryVec);
2996
3230
  const conditions = [
2997
- isNotNull3(packages.embedding),
2998
- eq4(packages.embeddingProvider, providerId),
3231
+ isNotNull4(packages.embedding),
3232
+ eq7(packages.embeddingProvider, providerId),
2999
3233
  ...buildConditions(constraints, category)
3000
3234
  ];
3001
- return db.select(ROW_COLUMNS).from(packages).where(and4(...conditions)).orderBy(distance).limit(pool);
3235
+ return db.select(ROW_COLUMNS).from(packages).where(and6(...conditions)).orderBy(distance).limit(pool);
3002
3236
  }
3003
3237
  async function runLexicalQuery(db, need, constraints, category, pool) {
3004
- const tsquery = sql3`websearch_to_tsquery('english', ${need})`;
3005
- const rank = sql3`ts_rank(${packages.searchVector}, ${tsquery})`;
3006
- const conditions = [sql3`${packages.searchVector} @@ ${tsquery}`, ...buildConditions(constraints, category)];
3007
- return db.select(ROW_COLUMNS).from(packages).where(and4(...conditions)).orderBy(sql3`${rank} desc`).limit(pool);
3238
+ const tsquery = sql5`websearch_to_tsquery('english', ${need})`;
3239
+ const rank = sql5`ts_rank(${packages.searchVector}, ${tsquery})`;
3240
+ const conditions = [sql5`${packages.searchVector} @@ ${tsquery}`, ...buildConditions(constraints, category)];
3241
+ return db.select(ROW_COLUMNS).from(packages).where(and6(...conditions)).orderBy(sql5`${rank} desc`).limit(pool);
3008
3242
  }
3009
3243
  function toCandidate(row) {
3010
3244
  return {
@@ -3361,12 +3595,13 @@ __export(handlers_exports, {
3361
3595
  handleEvaluate: () => handleEvaluate,
3362
3596
  handleRecommend: () => handleRecommend,
3363
3597
  handleReportOutcome: () => handleReportOutcome,
3598
+ handleUsage: () => handleUsage,
3364
3599
  handleVerify: () => handleVerify,
3365
3600
  latestDataAsOf: () => latestDataAsOf,
3366
3601
  rowToEvaluate: () => rowToEvaluate
3367
3602
  });
3368
3603
  import { createHash as createHash3 } from "crypto";
3369
- import { sql as sql4 } from "drizzle-orm";
3604
+ import { sql as sql6 } from "drizzle-orm";
3370
3605
  function isStale(dataAsOf) {
3371
3606
  if (!dataAsOf) return true;
3372
3607
  return Date.now() - dataAsOf.getTime() > STALENESS_DAYS * DAY_MS2;
@@ -3375,8 +3610,8 @@ function refreshStale(out) {
3375
3610
  out.stale = isStale(out.dataAsOf ? new Date(out.dataAsOf) : null) || void 0;
3376
3611
  return out;
3377
3612
  }
3378
- function withinDays(date, days) {
3379
- return date ? Date.now() - date.getTime() <= days * DAY_MS2 : false;
3613
+ function withinDays(date2, days) {
3614
+ return date2 ? Date.now() - date2.getTime() <= days * DAY_MS2 : false;
3380
3615
  }
3381
3616
  function topAdvisories(advisories, max = 5) {
3382
3617
  if (!advisories?.length) return [];
@@ -3415,7 +3650,7 @@ function rowToEvaluate(row) {
3415
3650
  };
3416
3651
  }
3417
3652
  async function latestDataAsOf(db) {
3418
- const [row] = await db.select({ m: sql4`max(${packages.dataAsOf})` }).from(packages);
3653
+ const [row] = await db.select({ m: sql6`max(${packages.dataAsOf})` }).from(packages);
3419
3654
  return new Date(row?.m ?? Date.now()).toISOString();
3420
3655
  }
3421
3656
  function cacheKey(parts) {
@@ -3438,12 +3673,15 @@ async function handleRecommend(db, input) {
3438
3673
  { skipCache: (r) => r.candidates.length === 0 }
3439
3674
  );
3440
3675
  }
3441
- async function handleEvaluate(db, input) {
3676
+ async function handleEvaluate(db, input, ownerId = null) {
3442
3677
  const out = await cached2(
3443
3678
  "eval",
3444
3679
  cacheKey([input.package]),
3445
3680
  async () => {
3446
- const { row, existsOnNpm } = await getOrFetchPackage(db, input.package);
3681
+ const { row, existsOnNpm } = await getOrFetchPackage(db, input.package, {
3682
+ blockMs: FIRST_TOUCH_BUDGET_MS,
3683
+ requestedByOwnerId: ownerId
3684
+ });
3447
3685
  if (!row) {
3448
3686
  return {
3449
3687
  tracked: false,
@@ -3459,12 +3697,14 @@ async function handleEvaluate(db, input) {
3459
3697
  );
3460
3698
  return "tracked" in out ? out : refreshStale(out);
3461
3699
  }
3462
- async function handleCompare(db, input) {
3700
+ async function handleCompare(db, input, ownerId = null) {
3463
3701
  const out = await cached2(
3464
3702
  "cmp",
3465
3703
  cacheKey(input.packages),
3466
3704
  async () => {
3467
- const results = await Promise.all(input.packages.map((name) => getOrFetchPackage(db, name)));
3705
+ const results = await Promise.all(
3706
+ input.packages.map((name) => getOrFetchPackage(db, name, { requestedByOwnerId: ownerId }))
3707
+ );
3468
3708
  const rows = results.map((r) => r.row).filter((row) => row !== null).map(rowToEvaluate).sort((a, b) => b.healthScore - a.healthScore);
3469
3709
  const missing = input.packages.filter((name) => !rows.some((r) => r.name === name));
3470
3710
  return {
@@ -3492,9 +3732,12 @@ function toBuildVerified(v) {
3492
3732
  };
3493
3733
  }
3494
3734
  async function handleCompat(db, input) {
3495
- return checkCompat(db, input.packages);
3735
+ return checkCompat(db, input.packages, {
3736
+ versions: input.versions,
3737
+ node: input.node
3738
+ });
3496
3739
  }
3497
- async function handleVerify(db, input) {
3740
+ async function handleVerify(db, input, ownerId = null) {
3498
3741
  const name = input.package;
3499
3742
  const exists = await npmPackageExists(name);
3500
3743
  if (!exists) {
@@ -3515,7 +3758,9 @@ async function handleVerify(db, input) {
3515
3758
  }
3516
3759
  const [registry, { row, wasTracked }, popular] = await Promise.all([
3517
3760
  fetchNpmRegistry(name).catch(() => null),
3518
- getOrFetchPackage(db, name),
3761
+ // Block-on-first-touch (§4A): verify is single-package, so await the ingest
3762
+ // for a real confidence/tracked read on the first call.
3763
+ getOrFetchPackage(db, name, { blockMs: FIRST_TOUCH_BUDGET_MS, requestedByOwnerId: ownerId }),
3519
3764
  getTopPackageNames(db).catch(() => [])
3520
3765
  ]);
3521
3766
  const weeklyDownloads = row?.weeklyDownloads ?? await fetchWeeklyDownloads(name).catch(() => null);
@@ -3560,6 +3805,38 @@ async function handleVerify(db, input) {
3560
3805
  advisoryCount
3561
3806
  };
3562
3807
  }
3808
+ async function resolveVersion(db, name, requested) {
3809
+ if (requested) return requested;
3810
+ const row = await getPackageByName(db, name);
3811
+ if (row?.latestVersion) return row.latestVersion;
3812
+ const reg = await fetchNpmRegistry(name).catch(() => null);
3813
+ return reg?.latestVersion ?? null;
3814
+ }
3815
+ async function handleUsage(db, input) {
3816
+ const version = await resolveVersion(db, input.package, input.version);
3817
+ if (!version) {
3818
+ return {
3819
+ package: input.package,
3820
+ version: null,
3821
+ surface: null,
3822
+ available: false,
3823
+ note: `Could not resolve a version for "${input.package}" on npm.`
3824
+ };
3825
+ }
3826
+ const surface = await getStoredSurface(db, input.package, version);
3827
+ const out = {
3828
+ package: input.package,
3829
+ version,
3830
+ surface,
3831
+ available: surface !== null,
3832
+ note: surface ? void 0 : "No extracted API surface for this version yet; fall back to the README."
3833
+ };
3834
+ if (input.knownVersion && input.knownVersion !== version && surface) {
3835
+ const known = await getStoredSurface(db, input.package, input.knownVersion);
3836
+ if (known) out.delta = { ...diffSurface(known, surface), fromVersion: input.knownVersion };
3837
+ }
3838
+ return out;
3839
+ }
3563
3840
  async function handleReportOutcome(db, input, ownerId = null) {
3564
3841
  await recordOutcome(db, {
3565
3842
  ownerId,
@@ -3579,6 +3856,8 @@ var init_handlers = __esm({
3579
3856
  init_constants();
3580
3857
  init_check();
3581
3858
  init_packages();
3859
+ init_apiSurfaces();
3860
+ init_diff();
3582
3861
  init_verification();
3583
3862
  init_outcomes();
3584
3863
  init_successors2();
@@ -3602,7 +3881,7 @@ var init_handlers = __esm({
3602
3881
  });
3603
3882
 
3604
3883
  // src/mcp/diagram.ts
3605
- import { inArray as inArray2 } from "drizzle-orm";
3884
+ import { inArray as inArray3 } from "drizzle-orm";
3606
3885
  function layerFor(item) {
3607
3886
  if (!item.category) return UNCLASSIFIED;
3608
3887
  if (item.category === "framework" && BACKEND_FRAMEWORKS.has(item.label)) return "Backend";
@@ -3655,7 +3934,7 @@ async function handleDiagram(db, input) {
3655
3934
  note: "Provide a `stack` of package names to diagram. lurq labels a stack you choose; it does not infer an architecture from a description."
3656
3935
  };
3657
3936
  }
3658
- const rows = await db.select({ name: packages.name, category: packages.category }).from(packages).where(inArray2(packages.name, input.stack));
3937
+ const rows = await db.select({ name: packages.name, category: packages.category }).from(packages).where(inArray3(packages.name, input.stack));
3659
3938
  const known = new Map(rows.map((r) => [r.name, r.category]));
3660
3939
  const items = input.stack.map((name) => ({
3661
3940
  label: name,
@@ -3794,7 +4073,7 @@ __export(plan_exports, {
3794
4073
  resolvePins: () => resolvePins
3795
4074
  });
3796
4075
  import { createHash as createHash4 } from "crypto";
3797
- import { inArray as inArray3 } from "drizzle-orm";
4076
+ import { inArray as inArray4 } from "drizzle-orm";
3798
4077
  function packageToCandidate(row) {
3799
4078
  return {
3800
4079
  name: row.name,
@@ -3809,12 +4088,12 @@ function packageToCandidate(row) {
3809
4088
  repoUrl: row.repoUrl
3810
4089
  };
3811
4090
  }
3812
- async function resolvePins(db, using, recommendedNames) {
4091
+ async function resolvePins(db, using, recommendedNames, requestedByOwnerId = null) {
3813
4092
  const slots = [];
3814
4093
  const unresolved = [];
3815
4094
  for (const name of new Set(using ?? [])) {
3816
4095
  if (recommendedNames.has(name)) continue;
3817
- const { row } = await getOrFetchPackage(db, name);
4096
+ const { row } = await getOrFetchPackage(db, name, { requestedByOwnerId });
3818
4097
  if (!row) {
3819
4098
  unresolved.push(name);
3820
4099
  continue;
@@ -3831,7 +4110,7 @@ async function resolvePins(db, using, recommendedNames) {
3831
4110
  }
3832
4111
  return { slots, unresolved };
3833
4112
  }
3834
- async function handlePlan(db, input) {
4113
+ async function handlePlan(db, input, ownerId = null) {
3835
4114
  const optimize = input.optimize ?? "balanced";
3836
4115
  const decomposed = input.needs?.length ? { needs: dedupeNeeds(input.needs), source: "needs" } : input.document?.trim() ? await decompose(input.document) : null;
3837
4116
  const hasPins = Boolean(input.using?.length);
@@ -3886,7 +4165,8 @@ async function handlePlan(db, input) {
3886
4165
  const { slots: pinnedSlots, unresolved: unresolvedPins } = await resolvePins(
3887
4166
  db,
3888
4167
  input.using,
3889
- recommendedNames
4168
+ recommendedNames,
4169
+ ownerId
3890
4170
  );
3891
4171
  const slots = [...pinnedSlots, ...recSlots];
3892
4172
  const compatibility = await resolveCompat(db, slots);
@@ -4011,7 +4291,7 @@ function orderCandidates(cands, anchorFamily, optimize, bundleByName) {
4011
4291
  async function bundleSizes(db, candidates) {
4012
4292
  const names = [...new Set(candidates.map((c) => c.name))];
4013
4293
  if (names.length === 0) return /* @__PURE__ */ new Map();
4014
- const rows = await db.select({ name: packages.name, bundle: packages.bundleMinGzipKb }).from(packages).where(inArray3(packages.name, names));
4294
+ const rows = await db.select({ name: packages.name, bundle: packages.bundleMinGzipKb }).from(packages).where(inArray4(packages.name, names));
4015
4295
  return new Map(rows.filter((r) => r.bundle != null).map((r) => [r.name, r.bundle]));
4016
4296
  }
4017
4297
  async function decompose(document) {
@@ -4191,11 +4471,62 @@ var init_compact = __esm({
4191
4471
  }
4192
4472
  });
4193
4473
 
4194
- // src/mcp/server.ts
4195
- var server_exports = {};
4196
- __export(server_exports, {
4197
- buildMcpServer: () => buildMcpServer,
4198
- npmName: () => npmName,
4474
+ // src/db/usage.ts
4475
+ import { and as and7, desc as desc4, eq as eq8, gte, sql as sql7 } from "drizzle-orm";
4476
+ function windowStart(days) {
4477
+ return sql7`CURRENT_DATE - (${days}::int - 1)`;
4478
+ }
4479
+ async function recordUsage(db, ownerId, tool) {
4480
+ if (!ownerId) return;
4481
+ try {
4482
+ await db.insert(ownerUsageDaily).values({ ownerId, date: sql7`CURRENT_DATE`, tool, count: 1 }).onConflictDoUpdate({
4483
+ target: [ownerUsageDaily.ownerId, ownerUsageDaily.date, ownerUsageDaily.tool],
4484
+ set: { count: sql7`${ownerUsageDaily.count} + 1` }
4485
+ });
4486
+ } catch {
4487
+ }
4488
+ }
4489
+ async function getUsageSummary(db, ownerId, days) {
4490
+ const rows = await db.execute(sql7`
4491
+ select
4492
+ to_char(d, 'YYYY-MM-DD') as date,
4493
+ coalesce(sum(u.count), 0)::int as count
4494
+ from generate_series(${windowStart(days)}, CURRENT_DATE, interval '1 day') as d
4495
+ left join ${ownerUsageDaily} u
4496
+ on u.date = d::date and u.owner_id = ${ownerId}
4497
+ group by d
4498
+ order by d
4499
+ `);
4500
+ const series = rows.map((r) => ({ date: String(r.date), count: Number(r.count) }));
4501
+ const todayUtc = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4502
+ const today = series.find((p) => p.date === todayUtc)?.count ?? 0;
4503
+ return { today, series };
4504
+ }
4505
+ async function getUsageByTool(db, ownerId, days) {
4506
+ const rows = await db.select({
4507
+ tool: ownerUsageDaily.tool,
4508
+ count: sql7`sum(${ownerUsageDaily.count})::int`
4509
+ }).from(ownerUsageDaily).where(
4510
+ and7(
4511
+ eq8(ownerUsageDaily.ownerId, ownerId),
4512
+ gte(ownerUsageDaily.date, windowStart(days))
4513
+ )
4514
+ ).groupBy(ownerUsageDaily.tool).orderBy(desc4(sql7`sum(${ownerUsageDaily.count})`));
4515
+ return rows.map((r) => ({ tool: r.tool, count: Number(r.count) }));
4516
+ }
4517
+ var init_usage = __esm({
4518
+ "src/db/usage.ts"() {
4519
+ "use strict";
4520
+ init_esm_shims();
4521
+ init_schema();
4522
+ }
4523
+ });
4524
+
4525
+ // src/mcp/server.ts
4526
+ var server_exports = {};
4527
+ __export(server_exports, {
4528
+ buildMcpServer: () => buildMcpServer,
4529
+ npmName: () => npmName,
4199
4530
  startMcpServer: () => startMcpServer
4200
4531
  });
4201
4532
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -4206,6 +4537,13 @@ function json(obj) {
4206
4537
  }
4207
4538
  function buildMcpServer(db, ctx = {}) {
4208
4539
  const server = new McpServer({ name: SERVER_NAME, version: VERSION });
4540
+ const run = (tool, fn) => (async () => {
4541
+ try {
4542
+ return await timed(tool, fn);
4543
+ } finally {
4544
+ void recordUsage(db, ctx.ownerId ?? null, tool);
4545
+ }
4546
+ })();
4209
4547
  server.registerTool(
4210
4548
  "recommend",
4211
4549
  {
@@ -4217,7 +4555,7 @@ function buildMcpServer(db, ctx = {}) {
4217
4555
  constraints: constraintsSchema
4218
4556
  }
4219
4557
  },
4220
- async (args) => json(await timed("recommend", () => handleRecommend(db, args)))
4558
+ async (args) => json(await run("recommend", () => handleRecommend(db, args)))
4221
4559
  );
4222
4560
  server.registerTool(
4223
4561
  "evaluate",
@@ -4228,7 +4566,7 @@ function buildMcpServer(db, ctx = {}) {
4228
4566
  package: npmName.describe("npm package name")
4229
4567
  }
4230
4568
  },
4231
- async (args) => json(await timed("evaluate", () => handleEvaluate(db, args)))
4569
+ async (args) => json(await run("evaluate", () => handleEvaluate(db, args, ctx.ownerId ?? null)))
4232
4570
  );
4233
4571
  server.registerTool(
4234
4572
  "compare",
@@ -4239,7 +4577,7 @@ function buildMcpServer(db, ctx = {}) {
4239
4577
  packages: z2.array(npmName).min(2).max(5).describe("2\u20135 npm package names")
4240
4578
  }
4241
4579
  },
4242
- async (args) => json(await timed("compare", () => handleCompare(db, args)))
4580
+ async (args) => json(await run("compare", () => handleCompare(db, args, ctx.ownerId ?? null)))
4243
4581
  );
4244
4582
  server.registerTool(
4245
4583
  "compat",
@@ -4247,10 +4585,12 @@ function buildMcpServer(db, ctx = {}) {
4247
4585
  title: "Check package compatibility",
4248
4586
  description: "Check whether a set of packages forms a coherent stack: peer-dependency and engine-range compatibility across the whole set (instant, from declared metadata), plus any recorded sandbox-verified conflicts. Returns the exact clashing constraints. Read-only \u2014 does not run installs. Call before committing to a multi-package stack.",
4249
4587
  inputSchema: {
4250
- packages: z2.array(npmName).min(2).max(8).describe("2\u20138 npm package names to check together")
4588
+ packages: z2.array(npmName).min(2).max(8).describe("2\u20138 npm package names to check together"),
4589
+ versions: z2.record(z2.string()).optional().describe("Optional exact versions keyed by package name (use when not checking latest)"),
4590
+ node: z2.string().optional().describe('Optional target Node runtime (e.g. "20" or "20.20.2") for engines.node checks')
4251
4591
  }
4252
4592
  },
4253
- async (args) => json(await timed("compat", () => handleCompat(db, args)))
4593
+ async (args) => json(await run("compat", () => handleCompat(db, args)))
4254
4594
  );
4255
4595
  server.registerTool(
4256
4596
  "verify",
@@ -4261,7 +4601,20 @@ function buildMcpServer(db, ctx = {}) {
4261
4601
  package: npmName.describe("npm package name to verify")
4262
4602
  }
4263
4603
  },
4264
- async (args) => json(await timed("verify", () => handleVerify(db, args)))
4604
+ async (args) => json(await run("verify", () => handleVerify(db, args, ctx.ownerId ?? null)))
4605
+ );
4606
+ server.registerTool(
4607
+ "usage",
4608
+ {
4609
+ title: "Version-exact API surface + drift",
4610
+ description: "Get a package version's real public API \u2014 the exported symbols and signatures extracted from its shipped .d.ts, exact to the version, none of it in the model's training data. Pass knownVersion (e.g. the version you were trained on) to get the precise delta: what was added, removed, renamed, or changed. Use before writing code against a package whose API may have moved. For framework file/convention changes (not exported symbols), consult the official migration guide / Context7 instead.",
4611
+ inputSchema: {
4612
+ package: npmName.describe("npm package name"),
4613
+ version: z2.string().optional().describe("Target version (defaults to latest)"),
4614
+ knownVersion: z2.string().optional().describe("A version you already know; returns the API delta from it to the target")
4615
+ }
4616
+ },
4617
+ async (args) => json(await run("usage", () => handleUsage(db, args)))
4265
4618
  );
4266
4619
  server.registerTool(
4267
4620
  "diagram",
@@ -4272,7 +4625,7 @@ function buildMcpServer(db, ctx = {}) {
4272
4625
  stack: z2.array(npmName).optional().describe("Package names that make up the stack; omit or empty to get usage guidance")
4273
4626
  }
4274
4627
  },
4275
- async (args) => json(await timed("diagram", () => handleDiagram(db, args)))
4628
+ async (args) => json(await run("diagram", () => handleDiagram(db, args)))
4276
4629
  );
4277
4630
  server.registerTool(
4278
4631
  "plan",
@@ -4293,7 +4646,7 @@ function buildMcpServer(db, ctx = {}) {
4293
4646
  optimize: z2.enum(["speed", "balanced"]).optional().describe("'speed' prefers the lightest-bundle option per slot; default 'balanced'")
4294
4647
  }
4295
4648
  },
4296
- async (args) => json(await timed("plan", () => handlePlan(db, args)))
4649
+ async (args) => json(await run("plan", () => handlePlan(db, args, ctx.ownerId ?? null)))
4297
4650
  );
4298
4651
  server.registerTool(
4299
4652
  "report_outcome",
@@ -4309,7 +4662,7 @@ function buildMcpServer(db, ctx = {}) {
4309
4662
  },
4310
4663
  // ownerId comes from the authenticated key (ctx), NOT the tool arguments —
4311
4664
  // a caller must never be able to attribute an outcome to another org.
4312
- async (args) => json(await timed("report_outcome", () => handleReportOutcome(db, args, ctx.ownerId ?? null)))
4665
+ async (args) => json(await run("report_outcome", () => handleReportOutcome(db, args, ctx.ownerId ?? null)))
4313
4666
  );
4314
4667
  return server;
4315
4668
  }
@@ -4343,6 +4696,7 @@ var init_server = __esm({
4343
4696
  init_plan();
4344
4697
  init_metrics();
4345
4698
  init_compact();
4699
+ init_usage();
4346
4700
  categoryEnum = z2.enum(CATEGORIES);
4347
4701
  confidenceEnum = z2.enum(["proven", "emerging", "promising", "unproven"]);
4348
4702
  npmName = z2.string().trim().min(1).max(214).regex(/^(?:@[a-z0-9-][a-z0-9-._]*\/)?[a-z0-9-][a-z0-9-._]*$/i, "Invalid npm package name");
@@ -4357,7 +4711,7 @@ var init_server = __esm({
4357
4711
 
4358
4712
  // src/auth/apiKeys.ts
4359
4713
  import { createHash as createHash5, randomBytes } from "crypto";
4360
- import { and as and5, desc as desc3, eq as eq5, isNull } from "drizzle-orm";
4714
+ import { and as and8, desc as desc5, eq as eq9, isNull as isNull3 } from "drizzle-orm";
4361
4715
  function hashKey(key) {
4362
4716
  return createHash5("sha256").update(key).digest("hex");
4363
4717
  }
@@ -4380,7 +4734,7 @@ async function createKey(db, input = {}) {
4380
4734
  function stampLastUsed(db, entry, now) {
4381
4735
  if (now - entry.lastStampAt < STAMP_INTERVAL_MS) return;
4382
4736
  entry.lastStampAt = now;
4383
- db.update(apiKeys).set({ lastUsedAt: new Date(now) }).where(eq5(apiKeys.id, entry.row.id)).then(void 0, (err) => logger.debug(`lastUsedAt stamp failed: ${String(err)}`));
4737
+ db.update(apiKeys).set({ lastUsedAt: new Date(now) }).where(eq9(apiKeys.id, entry.row.id)).then(void 0, (err) => logger.debug(`lastUsedAt stamp failed: ${String(err)}`));
4384
4738
  }
4385
4739
  async function lookupActiveKey(db, key) {
4386
4740
  const hash = hashKey(key);
@@ -4390,7 +4744,7 @@ async function lookupActiveKey(db, key) {
4390
4744
  stampLastUsed(db, cached3, now);
4391
4745
  return cached3.row;
4392
4746
  }
4393
- const [row] = await db.select().from(apiKeys).where(and5(eq5(apiKeys.keyHash, hash), isNull(apiKeys.revokedAt))).limit(1);
4747
+ const [row] = await db.select().from(apiKeys).where(and8(eq9(apiKeys.keyHash, hash), isNull3(apiKeys.revokedAt))).limit(1);
4394
4748
  if (!row) {
4395
4749
  authCache.delete(hash);
4396
4750
  return null;
@@ -4400,26 +4754,31 @@ async function lookupActiveKey(db, key) {
4400
4754
  stampLastUsed(db, entry, now);
4401
4755
  return row;
4402
4756
  }
4403
- async function listKeys(db) {
4404
- return db.select().from(apiKeys).orderBy(desc3(apiKeys.createdAt));
4757
+ async function listKeysForOwner(db, ownerId) {
4758
+ return db.select().from(apiKeys).where(eq9(apiKeys.ownerId, ownerId)).orderBy(desc5(apiKeys.createdAt));
4405
4759
  }
4406
4760
  function matchByPrefixOrId(prefixOrId) {
4407
4761
  const asId = Number(prefixOrId);
4408
- return Number.isInteger(asId) && String(asId) === prefixOrId.trim() ? eq5(apiKeys.id, asId) : eq5(apiKeys.prefix, prefixOrId);
4762
+ return Number.isInteger(asId) && String(asId) === prefixOrId.trim() ? eq9(apiKeys.id, asId) : eq9(apiKeys.prefix, prefixOrId);
4763
+ }
4764
+ async function findKeyForOwner(db, args) {
4765
+ const [row] = await db.select().from(apiKeys).where(and8(matchByPrefixOrId(args.prefixOrId), isNull3(apiKeys.revokedAt))).limit(1);
4766
+ if (!row || row.ownerId !== args.ownerId) return null;
4767
+ return row;
4409
4768
  }
4410
4769
  async function revokeKey(db, prefixOrId) {
4411
- const rows = await db.update(apiKeys).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and5(matchByPrefixOrId(prefixOrId), isNull(apiKeys.revokedAt))).returning({ id: apiKeys.id });
4770
+ const rows = await db.update(apiKeys).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and8(matchByPrefixOrId(prefixOrId), isNull3(apiKeys.revokedAt))).returning({ id: apiKeys.id });
4412
4771
  return rows.length;
4413
4772
  }
4414
4773
  async function rotateKey(db, prefixOrId) {
4415
- const [previous] = await db.select().from(apiKeys).where(and5(matchByPrefixOrId(prefixOrId), isNull(apiKeys.revokedAt))).limit(1);
4774
+ const [previous] = await db.select().from(apiKeys).where(and8(matchByPrefixOrId(prefixOrId), isNull3(apiKeys.revokedAt))).limit(1);
4416
4775
  if (!previous) return null;
4417
4776
  const { key, row } = await createKey(db, {
4418
4777
  label: previous.label ?? void 0,
4419
4778
  tier: previous.tier,
4420
4779
  ownerId: previous.ownerId ?? void 0
4421
4780
  });
4422
- await db.update(apiKeys).set({ revokedAt: /* @__PURE__ */ new Date() }).where(eq5(apiKeys.id, previous.id));
4781
+ await db.update(apiKeys).set({ revokedAt: /* @__PURE__ */ new Date() }).where(eq9(apiKeys.id, previous.id));
4423
4782
  return { key, row, previous };
4424
4783
  }
4425
4784
  var DISPLAY_BODY, authCache, AUTH_TTL_MS, STAMP_INTERVAL_MS;
@@ -4543,7 +4902,7 @@ async function startHttpServer(opts = {}) {
4543
4902
  ...makeStore ? { store: makeStore("rl:key:") } : {},
4544
4903
  message: rpcError(-32029, "Rate limit exceeded.")
4545
4904
  });
4546
- app.post("/keys", async (req, res) => {
4905
+ const requireIssuerSecret = (req, res, next) => {
4547
4906
  const secret = config.LURQ_ISSUER_SECRET;
4548
4907
  if (!secret) {
4549
4908
  res.status(404).end();
@@ -4555,6 +4914,18 @@ async function startHttpServer(opts = {}) {
4555
4914
  res.status(401).json({ error: "Invalid issuer secret." });
4556
4915
  return;
4557
4916
  }
4917
+ next();
4918
+ };
4919
+ const toDashboardKey = (row) => ({
4920
+ id: row.id,
4921
+ prefix: row.prefix,
4922
+ label: row.label,
4923
+ tier: row.tier,
4924
+ createdAt: row.createdAt,
4925
+ lastUsedAt: row.lastUsedAt,
4926
+ revokedAt: row.revokedAt
4927
+ });
4928
+ app.post("/keys", requireIssuerSecret, async (req, res) => {
4558
4929
  const body = req.body ?? {};
4559
4930
  const ownerId = typeof body.ownerId === "string" ? body.ownerId.trim() : "";
4560
4931
  if (!ownerId) {
@@ -4570,6 +4941,127 @@ async function startHttpServer(opts = {}) {
4570
4941
  res.status(500).json({ error: "Could not issue key." });
4571
4942
  }
4572
4943
  });
4944
+ app.get("/keys", requireIssuerSecret, async (req, res) => {
4945
+ const ownerId = typeof req.query.ownerId === "string" ? req.query.ownerId.trim() : "";
4946
+ if (!ownerId) {
4947
+ res.status(400).json({ error: "ownerId is required." });
4948
+ return;
4949
+ }
4950
+ try {
4951
+ const rows = await listKeysForOwner(db, ownerId);
4952
+ res.status(200).json({ keys: rows.map(toDashboardKey) });
4953
+ } catch (err) {
4954
+ logger.error("key listing failed:", err instanceof Error ? err.message : String(err));
4955
+ res.status(500).json({ error: "Could not list keys." });
4956
+ }
4957
+ });
4958
+ app.post("/keys/:prefix/revoke", requireIssuerSecret, async (req, res) => {
4959
+ const prefix = req.params.prefix;
4960
+ const body = req.body ?? {};
4961
+ const ownerId = typeof body.ownerId === "string" ? body.ownerId.trim() : "";
4962
+ if (typeof prefix !== "string" || !ownerId) {
4963
+ res.status(400).json({ error: "ownerId is required." });
4964
+ return;
4965
+ }
4966
+ try {
4967
+ const row = await findKeyForOwner(db, { prefixOrId: prefix, ownerId });
4968
+ if (!row) {
4969
+ res.status(404).json({ error: "Key not found." });
4970
+ return;
4971
+ }
4972
+ await revokeKey(db, String(row.id));
4973
+ res.status(200).json({ revoked: true });
4974
+ } catch (err) {
4975
+ logger.error("key revoke failed:", err instanceof Error ? err.message : String(err));
4976
+ res.status(500).json({ error: "Could not revoke key." });
4977
+ }
4978
+ });
4979
+ app.post("/keys/:prefix/rotate", requireIssuerSecret, async (req, res) => {
4980
+ const prefix = req.params.prefix;
4981
+ const body = req.body ?? {};
4982
+ const ownerId = typeof body.ownerId === "string" ? body.ownerId.trim() : "";
4983
+ if (typeof prefix !== "string" || !ownerId) {
4984
+ res.status(400).json({ error: "ownerId is required." });
4985
+ return;
4986
+ }
4987
+ try {
4988
+ const row = await findKeyForOwner(db, { prefixOrId: prefix, ownerId });
4989
+ if (!row) {
4990
+ res.status(404).json({ error: "Key not found." });
4991
+ return;
4992
+ }
4993
+ const rotated = await rotateKey(db, String(row.id));
4994
+ if (!rotated) {
4995
+ res.status(404).json({ error: "Key not found." });
4996
+ return;
4997
+ }
4998
+ res.status(200).json({ key: rotated.key, prefix: rotated.row.prefix });
4999
+ } catch (err) {
5000
+ logger.error("key rotate failed:", err instanceof Error ? err.message : String(err));
5001
+ res.status(500).json({ error: "Could not rotate key." });
5002
+ }
5003
+ });
5004
+ app.get("/outcomes", requireIssuerSecret, async (req, res) => {
5005
+ const ownerId = typeof req.query.ownerId === "string" ? req.query.ownerId.trim() : "";
5006
+ if (!ownerId) {
5007
+ res.status(400).json({ error: "ownerId is required." });
5008
+ return;
5009
+ }
5010
+ const limitRaw = typeof req.query.limit === "string" ? Number(req.query.limit) : void 0;
5011
+ const limit = limitRaw && Number.isInteger(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : void 0;
5012
+ try {
5013
+ const rows = await getOutcomesByOwner(db, ownerId, { limit });
5014
+ res.status(200).json({
5015
+ outcomes: rows.map((row) => ({
5016
+ packageName: row.packageName,
5017
+ accepted: row.accepted,
5018
+ buildSignal: row.buildSignal,
5019
+ need: row.need,
5020
+ createdAt: row.createdAt
5021
+ }))
5022
+ });
5023
+ } catch (err) {
5024
+ logger.error("outcomes read failed:", err instanceof Error ? err.message : String(err));
5025
+ res.status(500).json({ error: "Could not read outcomes." });
5026
+ }
5027
+ });
5028
+ app.get("/usage", requireIssuerSecret, async (req, res) => {
5029
+ const ownerId = typeof req.query.ownerId === "string" ? req.query.ownerId.trim() : "";
5030
+ if (!ownerId) {
5031
+ res.status(400).json({ error: "ownerId is required." });
5032
+ return;
5033
+ }
5034
+ const daysRaw = typeof req.query.days === "string" ? Number(req.query.days) : NaN;
5035
+ const days = Number.isInteger(daysRaw) && daysRaw > 0 ? Math.min(daysRaw, 365) : 30;
5036
+ try {
5037
+ const [summary, byTool] = await Promise.all([
5038
+ getUsageSummary(db, ownerId, days),
5039
+ getUsageByTool(db, ownerId, days)
5040
+ ]);
5041
+ res.status(200).json({ today: summary.today, series: summary.series, byTool });
5042
+ } catch (err) {
5043
+ logger.error("usage read failed:", err instanceof Error ? err.message : String(err));
5044
+ res.status(500).json({ error: "Could not read usage." });
5045
+ }
5046
+ });
5047
+ app.get("/contributions", requireIssuerSecret, async (req, res) => {
5048
+ const ownerId = typeof req.query.ownerId === "string" ? req.query.ownerId.trim() : "";
5049
+ if (!ownerId) {
5050
+ res.status(400).json({ error: "ownerId is required." });
5051
+ return;
5052
+ }
5053
+ const limitRaw = typeof req.query.limit === "string" ? Number(req.query.limit) : NaN;
5054
+ const limit = Number.isInteger(limitRaw) && limitRaw > 0 ? Math.min(limitRaw, 200) : 50;
5055
+ const offsetRaw = typeof req.query.offset === "string" ? Number(req.query.offset) : NaN;
5056
+ const offset = Number.isInteger(offsetRaw) && offsetRaw > 0 ? offsetRaw : 0;
5057
+ try {
5058
+ const { total, packages: rows } = await getContributionsByOwner(db, ownerId, { limit, offset });
5059
+ res.status(200).json({ total, packages: rows });
5060
+ } catch (err) {
5061
+ logger.error("contributions read failed:", err instanceof Error ? err.message : String(err));
5062
+ res.status(500).json({ error: "Could not read contributions." });
5063
+ }
5064
+ });
4573
5065
  app.post("/mcp", ipLimiter, auth, keyLimiter, async (req, res) => {
4574
5066
  const server = buildMcpServer(db, { ownerId: req.lurqKey?.ownerId ?? null });
4575
5067
  const transport = new StreamableHTTPServerTransport({
@@ -4602,236 +5094,15 @@ var init_http2 = __esm({
4602
5094
  init_config();
4603
5095
  init_logger();
4604
5096
  init_apiKeys();
5097
+ init_outcomes();
5098
+ init_packages();
5099
+ init_usage();
4605
5100
  init_client();
4606
5101
  init_server();
4607
5102
  init_metrics();
4608
5103
  }
4609
5104
  });
4610
5105
 
4611
- // src/pipeline/rescore.ts
4612
- import { isNotNull as isNotNull4 } from "drizzle-orm";
4613
- import { eq as eq6 } from "drizzle-orm";
4614
- async function runRescore() {
4615
- const weights = loadWeights();
4616
- const handle = createDb({ max: 4 });
4617
- try {
4618
- const rows = await handle.db.select({ id: packages.id, breakdown: packages.scoreBreakdown, healthScore: packages.healthScore }).from(packages).where(isNotNull4(packages.scoreBreakdown));
4619
- let updated = 0;
4620
- for (const row of rows) {
4621
- if (!row.breakdown) continue;
4622
- const health = computeHealthScore(row.breakdown, weights.health);
4623
- if (health !== row.healthScore) {
4624
- await handle.db.update(packages).set({ healthScore: health, updatedAt: /* @__PURE__ */ new Date() }).where(eq6(packages.id, row.id));
4625
- updated++;
4626
- }
4627
- }
4628
- logger.info(`Rescored ${rows.length} package(s); ${updated} health score(s) changed.`);
4629
- if (updated > 0) await invalidateCache();
4630
- return { seen: rows.length, updated };
4631
- } finally {
4632
- await handle.close();
4633
- }
4634
- }
4635
- var init_rescore = __esm({
4636
- "src/pipeline/rescore.ts"() {
4637
- "use strict";
4638
- init_esm_shims();
4639
- init_cache();
4640
- init_logger();
4641
- init_client();
4642
- init_schema();
4643
- init_scoring();
4644
- init_weights();
4645
- }
4646
- });
4647
-
4648
- // src/db/discovery.ts
4649
- import { eq as eq7 } from "drizzle-orm";
4650
- async function getKnownNames(db) {
4651
- const [tracked, queued] = await Promise.all([
4652
- db.select({ name: packages.name }).from(packages),
4653
- db.select({ name: discoveryQueue.name }).from(discoveryQueue)
4654
- ]);
4655
- return /* @__PURE__ */ new Set([...tracked.map((r) => r.name), ...queued.map((r) => r.name)]);
4656
- }
4657
- async function enqueueCandidates(db, candidates) {
4658
- if (candidates.length === 0) return 0;
4659
- const rows = candidates.map((c) => ({ name: c.name, discoveredVia: c.via }));
4660
- const inserted = await db.insert(discoveryQueue).values(rows).onConflictDoNothing({ target: discoveryQueue.name }).returning({ id: discoveryQueue.id });
4661
- return inserted.length;
4662
- }
4663
- async function getPendingCandidates(db, limit) {
4664
- return db.select().from(discoveryQueue).where(eq7(discoveryQueue.status, "pending")).limit(limit);
4665
- }
4666
- async function setDiscoveryStatus(db, name, data) {
4667
- await db.update(discoveryQueue).set({ status: data.status, ...data.preScore !== void 0 ? { preScore: data.preScore } : {} }).where(eq7(discoveryQueue.name, name));
4668
- }
4669
- var init_discovery = __esm({
4670
- "src/db/discovery.ts"() {
4671
- "use strict";
4672
- init_esm_shims();
4673
- init_schema();
4674
- }
4675
- });
4676
-
4677
- // src/pipeline/discovery.ts
4678
- import { isNotNull as isNotNull5 } from "drizzle-orm";
4679
- function selectCandidates(raw, known) {
4680
- const seen = new Set(known);
4681
- const out = [];
4682
- for (const c of raw) {
4683
- const name = c.name?.trim();
4684
- if (!name || seen.has(name)) continue;
4685
- seen.add(name);
4686
- out.push({ name, via: c.via });
4687
- }
4688
- return out;
4689
- }
4690
- function passesGate(preScore) {
4691
- return preScore !== null && preScore >= DISCOVERY.minPreScore;
4692
- }
4693
- async function preScorePackage(name, fetchImpl) {
4694
- try {
4695
- const registry = await fetchNpmRegistry(name, fetchImpl);
4696
- const signals = {
4697
- name,
4698
- registry,
4699
- downloads: null,
4700
- github: null,
4701
- depsDev: null,
4702
- bundle: null,
4703
- errors: []
4704
- };
4705
- return computeQuality(toScoringInput(signals, null));
4706
- } catch {
4707
- return null;
4708
- }
4709
- }
4710
- async function graphChannel(db) {
4711
- const tracked = await db.select({ name: packages.name, version: packages.latestVersion }).from(packages).where(isNotNull5(packages.latestVersion));
4712
- const out = [];
4713
- for (const t of tracked) {
4714
- if (!t.version) continue;
4715
- const deps = (await fetchDependencyNames(t.name, t.version)).slice(
4716
- 0,
4717
- DISCOVERY.graphNeighborsPerSeed
4718
- );
4719
- for (const name of deps) out.push({ name, via: "dependency-graph" });
4720
- }
4721
- return out;
4722
- }
4723
- async function searchChannel() {
4724
- const out = [];
4725
- for (const category of CATEGORIES) {
4726
- if (category === "other") continue;
4727
- const hits = await searchNpm(`keywords:${category}`, DISCOVERY.searchSizePerCategory);
4728
- for (const hit of hits) {
4729
- const via = isRecent(hit.date) ? "recent" : "category-search";
4730
- out.push({ name: hit.name, via });
4731
- }
4732
- }
4733
- return out;
4734
- }
4735
- function isRecent(date) {
4736
- if (!date) return false;
4737
- const ms = Date.parse(date);
4738
- if (Number.isNaN(ms)) return false;
4739
- return Date.now() - ms <= 90 * 24 * 60 * 60 * 1e3;
4740
- }
4741
- async function runDiscovery(opts = {}) {
4742
- const cap = opts.perRunCap ?? DISCOVERY.perRunCap;
4743
- const handle = createDb({ max: 6 });
4744
- try {
4745
- logger.info("Discovery: gathering candidates from graph + search channels\u2026");
4746
- const [graph, search] = await Promise.all([graphChannel(handle.db), searchChannel()]);
4747
- const known = await getKnownNames(handle.db);
4748
- const fresh = selectCandidates([...graph, ...search], known);
4749
- const enqueued = await enqueueCandidates(handle.db, fresh);
4750
- logger.info(
4751
- `Discovery: ${graph.length} graph + ${search.length} search candidates \u2192 ${enqueued} new queued.`
4752
- );
4753
- const pending2 = await getPendingCandidates(handle.db, cap * 4);
4754
- const scored = [];
4755
- for (const cand of pending2) {
4756
- if (cand.preScore !== null) {
4757
- scored.push({ name: cand.name, preScore: cand.preScore });
4758
- continue;
4759
- }
4760
- const preScore = await preScorePackage(cand.name);
4761
- await setDiscoveryStatus(handle.db, cand.name, {
4762
- status: passesGate(preScore) ? "pending" : "rejected",
4763
- preScore: preScore ?? null
4764
- });
4765
- if (passesGate(preScore)) scored.push({ name: cand.name, preScore });
4766
- }
4767
- logger.info(`Discovery: gated ${pending2.length}; ${scored.length} cleared the quality bar.`);
4768
- scored.sort((a, b) => b.preScore - a.preScore);
4769
- const toIngest = scored.slice(0, cap);
4770
- const deferred = scored.slice(cap);
4771
- if (deferred.length > 0) {
4772
- logger.info(
4773
- `Discovery: per-run cap ${cap} reached \u2014 ${deferred.length} eligible candidate(s) deferred to the next run: ${deferred.map((d) => d.name).join(", ")}`
4774
- );
4775
- }
4776
- let ingested = 0;
4777
- if (!opts.dryRun) {
4778
- for (const cand of toIngest) {
4779
- try {
4780
- await syncOnePackage(handle.db, cand.name);
4781
- await setDiscoveryStatus(handle.db, cand.name, { status: "ingested" });
4782
- ingested++;
4783
- } catch (err) {
4784
- logger.warn(`Discovery: failed to ingest ${cand.name}: ${err.message}`);
4785
- }
4786
- }
4787
- }
4788
- logger.info(`Discovery: ingested ${ingested}/${toIngest.length} candidate(s).`);
4789
- return {
4790
- enqueued,
4791
- gated: pending2.length,
4792
- passed: scored.length,
4793
- ingested,
4794
- droppedToNextRun: deferred.length
4795
- };
4796
- } finally {
4797
- await handle.close();
4798
- }
4799
- }
4800
- var init_discovery2 = __esm({
4801
- "src/pipeline/discovery.ts"() {
4802
- "use strict";
4803
- init_esm_shims();
4804
- init_logger();
4805
- init_types();
4806
- init_client();
4807
- init_discovery();
4808
- init_schema();
4809
- init_depsDev();
4810
- init_npmRegistry();
4811
- init_npmSearch();
4812
- init_scoring();
4813
- init_weights();
4814
- init_single();
4815
- }
4816
- });
4817
-
4818
- // src/pipeline/index.ts
4819
- var pipeline_exports = {};
4820
- __export(pipeline_exports, {
4821
- runDiscovery: () => runDiscovery,
4822
- runRescore: () => runRescore,
4823
- runSync: () => runSync
4824
- });
4825
- var init_pipeline = __esm({
4826
- "src/pipeline/index.ts"() {
4827
- "use strict";
4828
- init_esm_shims();
4829
- init_sync();
4830
- init_rescore();
4831
- init_discovery2();
4832
- }
4833
- });
4834
-
4835
5106
  // src/cli/format.ts
4836
5107
  function table(headers, rows) {
4837
5108
  const widths = headers.map((h, i) => Math.max(width(h), ...rows.map((r) => width(r[i] ?? ""))));
@@ -4964,9 +5235,9 @@ var init_planView = __esm({
4964
5235
  });
4965
5236
 
4966
5237
  // src/db/watch.ts
4967
- import { eq as eq8 } from "drizzle-orm";
5238
+ import { eq as eq10 } from "drizzle-orm";
4968
5239
  async function getWatchCursor(db, id) {
4969
- const rows = await db.select({ seq: watchState.seq }).from(watchState).where(eq8(watchState.id, id)).limit(1);
5240
+ const rows = await db.select({ seq: watchState.seq }).from(watchState).where(eq10(watchState.id, id)).limit(1);
4970
5241
  return rows[0]?.seq ?? null;
4971
5242
  }
4972
5243
  async function setWatchCursor(db, id, seq) {
@@ -5131,7 +5402,7 @@ function stderrOf(err) {
5131
5402
  }
5132
5403
  return String(err);
5133
5404
  }
5134
- var execFileAsync, INSTALL_TIMEOUT_MS, SMOKE_TIMEOUT_MS, ERROR_MAX, toSpec, LocalSandbox;
5405
+ var execFileAsync, INSTALL_TIMEOUT_MS, SMOKE_TIMEOUT_MS, ERROR_MAX, EXEC_MAX_BUFFER, toSpec, LocalSandbox;
5135
5406
  var init_local = __esm({
5136
5407
  "src/sandbox/local.ts"() {
5137
5408
  "use strict";
@@ -5141,6 +5412,7 @@ var init_local = __esm({
5141
5412
  INSTALL_TIMEOUT_MS = 12e4;
5142
5413
  SMOKE_TIMEOUT_MS = 3e4;
5143
5414
  ERROR_MAX = 500;
5415
+ EXEC_MAX_BUFFER = 8 * 1024 * 1024;
5144
5416
  toSpec = (p) => p.version ? `${p.name}@${p.version}` : p.name;
5145
5417
  LocalSandbox = class {
5146
5418
  name = "local";
@@ -5162,7 +5434,8 @@ var init_local = __esm({
5162
5434
  const specs = packages2.map(toSpec);
5163
5435
  const dir = await mkdtemp(join3(tmpdir(), "lurq-sandbox-"));
5164
5436
  const started = Date.now();
5165
- const loaded = packages2.map((p) => ({ name: p.name, loaded: null }));
5437
+ const smokeTargets = opts.smokePackages ?? packages2;
5438
+ const loaded = smokeTargets.map((p) => ({ name: p.name, loaded: null }));
5166
5439
  let installed = false;
5167
5440
  let error = null;
5168
5441
  try {
@@ -5176,9 +5449,9 @@ var init_local = __esm({
5176
5449
  signal: opts.signal
5177
5450
  });
5178
5451
  installed = true;
5179
- for (let i = 0; i < packages2.length; i++) {
5452
+ for (let i = 0; i < smokeTargets.length; i++) {
5180
5453
  try {
5181
- await execFileAsync("node", smokeScript(packages2[i].name, target.moduleSystem), {
5454
+ await execFileAsync("node", smokeScript(smokeTargets[i].name, target.moduleSystem), {
5182
5455
  cwd: dir,
5183
5456
  timeout: SMOKE_TIMEOUT_MS,
5184
5457
  signal: opts.signal
@@ -5204,6 +5477,54 @@ var init_local = __esm({
5204
5477
  error
5205
5478
  };
5206
5479
  }
5480
+ async exec(command, opts = {}) {
5481
+ const dir = await mkdtemp(join3(tmpdir(), "lurq-exec-"));
5482
+ try {
5483
+ await writeFile2(
5484
+ join3(dir, "package.json"),
5485
+ JSON.stringify({ name: "lurq-sandbox", version: "0.0.0", private: true })
5486
+ );
5487
+ if (opts.install?.length) {
5488
+ await execFileAsync(
5489
+ "npm",
5490
+ npmInstallArgs(opts.install.map(toSpec), { allowScripts: opts.allowScripts ?? false }),
5491
+ { cwd: dir, timeout: opts.timeoutMs ?? INSTALL_TIMEOUT_MS, signal: opts.signal }
5492
+ );
5493
+ }
5494
+ const { stdout, stderr } = await execFileAsync("sh", ["-c", command], {
5495
+ cwd: dir,
5496
+ timeout: opts.timeoutMs ?? SMOKE_TIMEOUT_MS,
5497
+ signal: opts.signal,
5498
+ maxBuffer: EXEC_MAX_BUFFER
5499
+ });
5500
+ return { exitCode: 0, stdout, stderr };
5501
+ } catch (err) {
5502
+ const e = err;
5503
+ if (typeof e?.code === "number") {
5504
+ return {
5505
+ exitCode: e.code,
5506
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
5507
+ stderr: typeof e.stderr === "string" ? e.stderr : stderrOf(err)
5508
+ };
5509
+ }
5510
+ throw err;
5511
+ } finally {
5512
+ await rm(dir, { recursive: true, force: true }).catch(() => {
5513
+ });
5514
+ }
5515
+ }
5516
+ async getRuntimeInfo() {
5517
+ let nodeVersion = "unknown";
5518
+ let npmVersion = "unknown";
5519
+ try {
5520
+ const nodeOut = await execFileAsync("node", ["--version"]);
5521
+ nodeVersion = nodeOut.stdout.trim() || "unknown";
5522
+ const npmOut = await execFileAsync("npm", ["--version"]);
5523
+ npmVersion = npmOut.stdout.trim() || "unknown";
5524
+ } catch {
5525
+ }
5526
+ return { nodeVersion, npmVersion };
5527
+ }
5207
5528
  };
5208
5529
  }
5209
5530
  });
@@ -5216,7 +5537,6 @@ __export(e2b_exports, {
5216
5537
  shQuote: () => shQuote,
5217
5538
  smokeCommand: () => smokeCommand
5218
5539
  });
5219
- import Sandbox from "e2b";
5220
5540
  function shQuote(s) {
5221
5541
  return `'${s.replace(/'/g, `'\\''`)}'`;
5222
5542
  }
@@ -5276,10 +5596,12 @@ var init_e2b = __esm({
5276
5596
  const specs = packages2.map(toSpec2);
5277
5597
  const installTimeout = opts.timeoutMs ?? INSTALL_TIMEOUT_MS2;
5278
5598
  const started = Date.now();
5279
- const loaded = packages2.map((p) => ({ name: p.name, loaded: null }));
5599
+ const smokeTargets = opts.smokePackages ?? packages2;
5600
+ const loaded = smokeTargets.map((p) => ({ name: p.name, loaded: null }));
5280
5601
  let installed = false;
5281
5602
  let error = null;
5282
5603
  const install = installCommand(specs, allowScripts);
5604
+ const { default: Sandbox } = await import("e2b");
5283
5605
  const createOpts = {
5284
5606
  apiKey: config.E2B_API_KEY,
5285
5607
  timeoutMs: installTimeout + SMOKE_TIMEOUT_MS2 * packages2.length + 3e4
@@ -5292,9 +5614,9 @@ var init_e2b = __esm({
5292
5614
  );
5293
5615
  await sandbox.commands.run(install, { cwd: WORKDIR, timeoutMs: installTimeout });
5294
5616
  installed = true;
5295
- for (let i = 0; i < packages2.length; i++) {
5617
+ for (let i = 0; i < smokeTargets.length; i++) {
5296
5618
  try {
5297
- await sandbox.commands.run(smokeCommand(packages2[i].name, target.moduleSystem), {
5619
+ await sandbox.commands.run(smokeCommand(smokeTargets[i].name, target.moduleSystem), {
5298
5620
  cwd: WORKDIR,
5299
5621
  timeoutMs: SMOKE_TIMEOUT_MS2
5300
5622
  });
@@ -5319,6 +5641,62 @@ var init_e2b = __esm({
5319
5641
  error
5320
5642
  };
5321
5643
  }
5644
+ async exec(command, opts = {}) {
5645
+ const config = getConfig();
5646
+ const timeoutMs = opts.timeoutMs ?? SMOKE_TIMEOUT_MS2;
5647
+ const install = opts.install?.length ? installCommand(opts.install.map(toSpec2), opts.allowScripts ?? false) : null;
5648
+ const { default: Sandbox } = await import("e2b");
5649
+ const createOpts = {
5650
+ apiKey: config.E2B_API_KEY,
5651
+ timeoutMs: (install ? INSTALL_TIMEOUT_MS2 : 0) + timeoutMs + 3e4
5652
+ };
5653
+ const sandbox = config.E2B_TEMPLATE ? await Sandbox.create(config.E2B_TEMPLATE, createOpts) : await Sandbox.create(createOpts);
5654
+ try {
5655
+ await sandbox.files.write(
5656
+ `${WORKDIR}/package.json`,
5657
+ JSON.stringify({ name: "lurq-sandbox", version: "0.0.0", private: true })
5658
+ );
5659
+ if (install) {
5660
+ await sandbox.commands.run(install, { cwd: WORKDIR, timeoutMs: INSTALL_TIMEOUT_MS2 });
5661
+ }
5662
+ const out = await sandbox.commands.run(command, { cwd: WORKDIR, timeoutMs });
5663
+ return { exitCode: out.exitCode ?? 0, stdout: out.stdout ?? "", stderr: out.stderr ?? "" };
5664
+ } catch (err) {
5665
+ const e = err;
5666
+ if (typeof e?.exitCode === "number") {
5667
+ return {
5668
+ exitCode: e.exitCode,
5669
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
5670
+ stderr: typeof e.stderr === "string" ? e.stderr : errText(err)
5671
+ };
5672
+ }
5673
+ throw err;
5674
+ } finally {
5675
+ await sandbox.kill().catch(() => {
5676
+ });
5677
+ }
5678
+ }
5679
+ async getRuntimeInfo() {
5680
+ let nodeVersion = "unknown";
5681
+ let npmVersion = "unknown";
5682
+ const config = getConfig();
5683
+ try {
5684
+ const { default: Sandbox } = await import("e2b");
5685
+ const createOpts = { apiKey: config.E2B_API_KEY, timeoutMs: 3e4 };
5686
+ const sandbox = config.E2B_TEMPLATE ? await Sandbox.create(config.E2B_TEMPLATE, createOpts) : await Sandbox.create(createOpts);
5687
+ try {
5688
+ const nodeOut = await sandbox.commands.run("node --version");
5689
+ nodeVersion = (nodeOut.stdout ?? "").trim() || "unknown";
5690
+ const npmOut = await sandbox.commands.run("npm --version");
5691
+ npmVersion = (npmOut.stdout ?? "").trim() || "unknown";
5692
+ } finally {
5693
+ await sandbox.kill().catch(() => {
5694
+ });
5695
+ }
5696
+ } catch {
5697
+ }
5698
+ return { nodeVersion, npmVersion };
5699
+ }
5322
5700
  };
5323
5701
  }
5324
5702
  });
@@ -5373,16 +5751,59 @@ var init_sandbox2 = __esm({
5373
5751
  }
5374
5752
  });
5375
5753
 
5754
+ // src/pipeline/resolveCheck.ts
5755
+ import { execFile as execFile2 } from "child_process";
5756
+ import { mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile3 } from "fs/promises";
5757
+ import { tmpdir as tmpdir2 } from "os";
5758
+ import { join as join4 } from "path";
5759
+ import { promisify as promisify2 } from "util";
5760
+ async function resolveSet(specs, opts = {}) {
5761
+ const dir = await mkdtemp2(join4(tmpdir2(), "lurq-resolve-"));
5762
+ try {
5763
+ const dependencies = {};
5764
+ for (const s of specs) dependencies[s.name] = s.version ?? "latest";
5765
+ await writeFile3(
5766
+ join4(dir, "package.json"),
5767
+ JSON.stringify({ name: "lurq-resolve", private: true, dependencies })
5768
+ );
5769
+ await execFileP("npm", ["install", "--package-lock-only", "--no-audit", "--no-fund"], {
5770
+ cwd: dir,
5771
+ timeout: opts.timeoutMs ?? 6e4
5772
+ });
5773
+ return { resolved: true, reason: null };
5774
+ } catch (err) {
5775
+ const msg = err.stderr ?? err.message ?? "";
5776
+ if (/ERESOLVE/i.test(msg)) return { resolved: false, reason: "ERESOLVE" };
5777
+ throw err;
5778
+ } finally {
5779
+ await rm2(dir, { recursive: true, force: true }).catch(() => {
5780
+ });
5781
+ }
5782
+ }
5783
+ var execFileP;
5784
+ var init_resolveCheck = __esm({
5785
+ "src/pipeline/resolveCheck.ts"() {
5786
+ "use strict";
5787
+ init_esm_shims();
5788
+ execFileP = promisify2(execFile2);
5789
+ }
5790
+ });
5791
+
5376
5792
  // src/pipeline/compat.ts
5377
5793
  var compat_exports = {};
5378
5794
  __export(compat_exports, {
5795
+ backfillVerify: () => backfillVerify,
5379
5796
  deriveCompatEdges: () => deriveCompatEdges,
5797
+ drainCompatVerifyQueue: () => drainCompatVerifyQueue,
5798
+ fullyCovered: () => fullyCovered,
5799
+ pairKey: () => pairKey,
5800
+ resolveBackfill: () => resolveBackfill,
5801
+ resolveVerifyCompatibility: () => resolveVerifyCompatibility,
5380
5802
  verifyCompatibility: () => verifyCompatibility
5381
5803
  });
5382
- function deriveCompatEdges(resolved, result) {
5383
- const allLoaded = result.loaded.every((l) => l.loaded === true);
5804
+ function pairwiseEdges(resolved, success) {
5384
5805
  const edges = [];
5385
- if (result.installed && allLoaded) {
5806
+ if (success) {
5386
5807
  for (let i = 0; i < resolved.length; i++) {
5387
5808
  for (let j = i + 1; j < resolved.length; j++) {
5388
5809
  edges.push({
@@ -5405,6 +5826,9 @@ function deriveCompatEdges(resolved, result) {
5405
5826
  }
5406
5827
  return edges;
5407
5828
  }
5829
+ function deriveCompatEdges(resolved, result) {
5830
+ return pairwiseEdges(resolved, result.installed && result.loaded.every((l) => l.loaded === true));
5831
+ }
5408
5832
  async function verifyCompatibility(db, packages2, opts = {}) {
5409
5833
  const resolved = await Promise.all(
5410
5834
  packages2.map(async (name) => ({
@@ -5417,6 +5841,12 @@ async function verifyCompatibility(db, packages2, opts = {}) {
5417
5841
  { allowScripts: opts.allowScripts }
5418
5842
  );
5419
5843
  const edges = deriveCompatEdges(resolved, result);
5844
+ await persistCompatEdges(db, edges, "verified", result.driver);
5845
+ const failed = !result.installed || !result.loaded.every((l) => l.loaded === true);
5846
+ return { result, edges, unattributedConflict: failed && edges.length === 0 };
5847
+ }
5848
+ async function persistCompatEdges(db, edges, compatibleProvenance, driver) {
5849
+ const now = /* @__PURE__ */ new Date();
5420
5850
  for (const e of edges) {
5421
5851
  const pair = canonicalPair(
5422
5852
  { name: e.a, version: e.aVersion },
@@ -5425,21 +5855,106 @@ async function verifyCompatibility(db, packages2, opts = {}) {
5425
5855
  await upsertCompatEdge(db, {
5426
5856
  ...pair,
5427
5857
  status: e.status,
5428
- driver: result.driver,
5429
- ranAt: /* @__PURE__ */ new Date()
5858
+ provenance: e.status === "conflict" ? "conflict" : compatibleProvenance,
5859
+ // Witness accrues for co-resolution evidence (`observed`); ignored otherwise.
5860
+ witnessCount: e.status === "compatible" && compatibleProvenance === "observed" ? 1 : 0,
5861
+ driver,
5862
+ ranAt: now
5430
5863
  }).catch(() => {
5431
5864
  });
5432
5865
  }
5433
- const failed = !result.installed || !result.loaded.every((l) => l.loaded === true);
5434
- return { result, edges, unattributedConflict: failed && edges.length === 0 };
5435
5866
  }
5867
+ async function resolveVerifyCompatibility(db, packages2) {
5868
+ const resolved = await Promise.all(
5869
+ packages2.map(async (name) => ({
5870
+ name,
5871
+ version: (await getPackageByName(db, name))?.latestVersion ?? "latest"
5872
+ }))
5873
+ );
5874
+ const res = await resolveSet(
5875
+ resolved.map((r) => ({ name: r.name, version: r.version === "latest" ? null : r.version }))
5876
+ );
5877
+ const edges = pairwiseEdges(resolved, res.resolved);
5878
+ await persistCompatEdges(db, edges, "observed", "npm-resolve");
5879
+ return { edges, resolved: res.resolved };
5880
+ }
5881
+ async function coveredPairs(db, names) {
5882
+ const edges = await getCompatEdges(db, names);
5883
+ return new Set(edges.map((e) => pairKey(e.packageA, e.packageB)));
5884
+ }
5885
+ async function runBackfill(db, opts, runner, label) {
5886
+ const topN = opts.topN ?? 50;
5887
+ const batchSize = Math.max(2, opts.batchSize ?? 5);
5888
+ const names = await getTopPackageNames(db, topN);
5889
+ const covered = await coveredPairs(db, names);
5890
+ let verified = 0;
5891
+ let batches = 0;
5892
+ let skipped = 0;
5893
+ for (let i = 0; i < names.length; i += batchSize) {
5894
+ const batch = names.slice(i, i + batchSize);
5895
+ if (batch.length < 2) continue;
5896
+ if (fullyCovered(batch, covered)) {
5897
+ skipped++;
5898
+ continue;
5899
+ }
5900
+ logger.info(`${label}: ${batch.join(", ")}`);
5901
+ const { edges } = await runner(db, batch).catch((err) => {
5902
+ logger.warn(`${label} batch failed (${batch.join(", ")}): ${String(err)}`);
5903
+ return { edges: [] };
5904
+ });
5905
+ verified += edges.length;
5906
+ batches++;
5907
+ }
5908
+ logger.info(`${label}: ${verified} edges across ${batches} runs, ${skipped} batches skipped`);
5909
+ return { batches, verified, skipped };
5910
+ }
5911
+ async function backfillVerify(db, opts = {}) {
5912
+ return runBackfill(db, opts, verifyCompatibility, "backfill(sandbox)");
5913
+ }
5914
+ async function resolveBackfill(db, opts = {}) {
5915
+ return runBackfill(db, opts, resolveVerifyCompatibility, "backfill(resolve)");
5916
+ }
5917
+ async function drainCompatVerifyQueue(db, opts = {}) {
5918
+ const limit = Math.max(1, opts.limit ?? 10);
5919
+ const pending2 = await getPendingCompatVerify(db, limit);
5920
+ let verified = 0;
5921
+ let dropped = 0;
5922
+ for (const req of pending2) {
5923
+ const names = req.packages;
5924
+ if (fullyCovered(names, await coveredPairs(db, names))) {
5925
+ await deleteCompatVerify(db, req.id);
5926
+ continue;
5927
+ }
5928
+ try {
5929
+ const { edges } = await resolveVerifyCompatibility(db, names);
5930
+ verified += edges.length;
5931
+ await deleteCompatVerify(db, req.id);
5932
+ } catch (err) {
5933
+ logger.warn(`compat-verify drain failed for ${names.join(", ")}: ${String(err)}`);
5934
+ const attempts = await bumpCompatVerifyAttempt(db, req.id);
5935
+ if (attempts >= MAX_COMPAT_VERIFY_ATTEMPTS) {
5936
+ await deleteCompatVerify(db, req.id);
5937
+ dropped++;
5938
+ }
5939
+ }
5940
+ }
5941
+ logger.info(
5942
+ `compat-verify: ${verified} edge(s) from ${pending2.length} queued set(s), ${dropped} dropped`
5943
+ );
5944
+ return { processed: pending2.length, verified, dropped };
5945
+ }
5946
+ var MAX_COMPAT_VERIFY_ATTEMPTS;
5436
5947
  var init_compat2 = __esm({
5437
5948
  "src/pipeline/compat.ts"() {
5438
5949
  "use strict";
5439
5950
  init_esm_shims();
5951
+ init_logger();
5440
5952
  init_compat();
5441
5953
  init_packages();
5442
5954
  init_sandbox();
5955
+ init_resolveCheck();
5956
+ init_compat();
5957
+ MAX_COMPAT_VERIFY_ATTEMPTS = 3;
5443
5958
  }
5444
5959
  });
5445
5960
 
@@ -5448,11 +5963,13 @@ var commands_exports = {};
5448
5963
  __export(commands_exports, {
5449
5964
  runCompare: () => runCompare,
5450
5965
  runCompat: () => runCompat,
5966
+ runCompatBackfill: () => runCompatBackfill,
5451
5967
  runEditWeights: () => runEditWeights,
5452
5968
  runEvaluate: () => runEvaluate,
5453
5969
  runPlan: () => runPlan,
5454
5970
  runRecommend: () => runRecommend,
5455
5971
  runSandbox: () => runSandbox,
5972
+ runUsage: () => runUsage,
5456
5973
  runVerify: () => runVerify,
5457
5974
  runVersions: () => runVersions,
5458
5975
  runWatch: () => runWatch,
@@ -5651,10 +6168,10 @@ async function openInBrowser(target) {
5651
6168
  spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
5652
6169
  }
5653
6170
  async function runPlan(file, opts) {
5654
- const { readFileSync: readFileSync5 } = await import("fs");
6171
+ const { readFileSync: readFileSync4 } = await import("fs");
5655
6172
  let document;
5656
6173
  try {
5657
- document = readFileSync5(file, "utf8");
6174
+ document = readFileSync4(file, "utf8");
5658
6175
  } catch {
5659
6176
  throw new Error(`Could not read "${file}".`);
5660
6177
  }
@@ -5674,10 +6191,10 @@ async function runPlan(file, opts) {
5674
6191
  }
5675
6192
  if (opts.html || opts.open) {
5676
6193
  const { writeFileSync: writeFileSync3 } = await import("fs");
5677
- const { tmpdir: tmpdir2 } = await import("os");
5678
- const { join: join5 } = await import("path");
6194
+ const { tmpdir: tmpdir3 } = await import("os");
6195
+ const { join: join6 } = await import("path");
5679
6196
  const { renderPlanHtml: renderPlanHtml2 } = await Promise.resolve().then(() => (init_planView(), planView_exports));
5680
- const out = opts.html ?? join5(tmpdir2(), `lurq-plan-${Date.now()}.html`);
6197
+ const out = opts.html ?? join6(tmpdir3(), `lurq-plan-${Date.now()}.html`);
5681
6198
  writeFileSync3(out, renderPlanHtml2(res), "utf8");
5682
6199
  console.log(`Roadmap written to ${out}`);
5683
6200
  if (opts.open) await openInBrowser(out);
@@ -5800,6 +6317,57 @@ async function runSandbox(pkg, version, opts) {
5800
6317
  );
5801
6318
  });
5802
6319
  }
6320
+ async function runUsage(pkg, opts) {
6321
+ await withDb(async (db) => {
6322
+ const { handleUsage: handleUsage2 } = await Promise.resolve().then(() => (init_handlers(), handlers_exports));
6323
+ const res = await handleUsage2(db, {
6324
+ package: pkg,
6325
+ version: opts.version,
6326
+ knownVersion: opts.known
6327
+ });
6328
+ if (opts.json) return console.log(JSON.stringify(res, null, 2));
6329
+ console.log(`${bold(res.package)}${res.version ? `@${res.version}` : ""}`);
6330
+ if (!res.available) return console.log(dim(res.note ?? "no API surface available"));
6331
+ console.log(
6332
+ table(
6333
+ ["Export", "Kind", "Signature"],
6334
+ (res.surface ?? []).map((s) => [s.name, s.kind, s.signature ?? ""])
6335
+ )
6336
+ );
6337
+ if (res.delta) {
6338
+ const d = res.delta;
6339
+ console.log(bold(`
6340
+ \u0394 from ${res.package}@${d.fromVersion}:`));
6341
+ for (const s of d.removed) console.log(red(` - ${s.name}`));
6342
+ for (const s of d.added) console.log(green(` + ${s.name}`));
6343
+ for (const r of d.renamed) console.log(yellow(` ~ ${r.from.name} \u2192 ${r.to.name}`));
6344
+ for (const c of d.changed) console.log(yellow(` ! ${c.name}: ${c.before ?? "?"} \u2192 ${c.after ?? "?"}`));
6345
+ if (!d.removed.length && !d.added.length && !d.renamed.length && !d.changed.length) {
6346
+ console.log(dim(" no API changes"));
6347
+ }
6348
+ }
6349
+ });
6350
+ }
6351
+ async function runCompatBackfill(opts) {
6352
+ await withDb(async (db) => {
6353
+ const compat = await Promise.resolve().then(() => (init_compat2(), compat_exports));
6354
+ if (opts.resolve) {
6355
+ console.error(yellow("resolve-only backfill (npm resolution, no install/VM)"));
6356
+ const res2 = await compat.resolveBackfill(db, opts);
6357
+ console.log(
6358
+ `${green("resolve backfill done")}: ${res2.verified} edges across ${res2.batches} runs, ${res2.skipped} batches skipped`
6359
+ );
6360
+ return;
6361
+ }
6362
+ console.error(
6363
+ yellow("co-installing top packages in the sandbox (loads package code locally without isolation unless E2B_API_KEY is set)")
6364
+ );
6365
+ const res = await compat.backfillVerify(db, opts);
6366
+ console.log(
6367
+ `${green("backfill done")}: ${res.verified} edges across ${res.batches} runs, ${res.skipped} batches skipped`
6368
+ );
6369
+ });
6370
+ }
5803
6371
  async function runCompat(pkgs, opts) {
5804
6372
  await withDb(async (db) => {
5805
6373
  if (opts.run) {
@@ -5828,6 +6396,19 @@ async function runCompat(pkgs, opts) {
5828
6396
  console.log(dim(`
5829
6397
  unverified (no metadata): ${res.unverified.join(", ")}`));
5830
6398
  }
6399
+ const compatEvidence = res.evidence.filter((e) => e.status === "compatible");
6400
+ if (compatEvidence.length) {
6401
+ console.log(
6402
+ table(
6403
+ ["Pair", "Evidence", "Witnesses"],
6404
+ compatEvidence.map((e) => [
6405
+ `${e.packages[0]} + ${e.packages[1]}`,
6406
+ e.provenance,
6407
+ e.provenance === "observed" ? String(e.witnessCount) : "\u2014"
6408
+ ])
6409
+ )
6410
+ );
6411
+ }
5831
6412
  });
5832
6413
  }
5833
6414
  var init_commands = __esm({
@@ -5863,9 +6444,9 @@ __export(installSkill_exports, {
5863
6444
  import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
5864
6445
  import { copyFileSync } from "fs";
5865
6446
  import { homedir as homedir3 } from "os";
5866
- import { dirname as dirname4, join as join4 } from "path";
6447
+ import { dirname as dirname4, join as join5 } from "path";
5867
6448
  function home(...p) {
5868
- return join4(homedir3(), ...p);
6449
+ return join5(homedir3(), ...p);
5869
6450
  }
5870
6451
  function agentSpecs() {
5871
6452
  return [
@@ -5999,10 +6580,10 @@ function installAgent(spec, mode) {
5999
6580
  }
6000
6581
  }
6001
6582
  function installInstructionsFile() {
6002
- const src = join4(packageRoot(), "templates", "skill-instructions.md");
6583
+ const src = join5(packageRoot(), "templates", "skill-instructions.md");
6003
6584
  if (!existsSync4(src)) return null;
6004
6585
  const destDir = home(".lurq");
6005
- const dest = join4(destDir, "skill-instructions.md");
6586
+ const dest = join5(destDir, "skill-instructions.md");
6006
6587
  mkdirSync2(destDir, { recursive: true });
6007
6588
  copyFileSync(src, dest);
6008
6589
  return dest;
@@ -6213,248 +6794,9 @@ var init_install = __esm({
6213
6794
  }
6214
6795
  });
6215
6796
 
6216
- // src/cli/keys.ts
6217
- var keys_exports = {};
6218
- __export(keys_exports, {
6219
- runKeysCreate: () => runKeysCreate,
6220
- runKeysList: () => runKeysList,
6221
- runKeysRevoke: () => runKeysRevoke,
6222
- runKeysRotate: () => runKeysRotate
6223
- });
6224
- function waitForEnter(prompt) {
6225
- return new Promise((resolve) => {
6226
- process.stdout.write(prompt);
6227
- process.stdin.resume();
6228
- process.stdin.once("data", () => {
6229
- process.stdin.pause();
6230
- resolve();
6231
- });
6232
- });
6233
- }
6234
- async function presentNewKey(key, row, opts) {
6235
- if (opts.json) {
6236
- console.log(
6237
- JSON.stringify({ key, prefix: row.prefix, tier: row.tier, label: row.label, ...opts.extraJson })
6238
- );
6239
- return;
6240
- }
6241
- const meta = `prefix=${row.prefix} tier=${row.tier}${row.label ? ` label=${row.label}` : ""}`;
6242
- const block = [bold(opts.header ?? "API key created."), "", ` ${green(key)}`, "", dim(meta)];
6243
- console.log(block.join("\n"));
6244
- if (process.stdout.isTTY && process.stdin.isTTY) {
6245
- await waitForEnter(dim("Copy it now, then press Enter to erase it from the terminal\u2026 "));
6246
- process.stdout.write(`\x1B[${block.length + 1}F\x1B[0J\x1B[3J`);
6247
- console.log(
6248
- dim(`New key (prefix ${row.prefix}) erased from the terminal. It is stored only as a hash and cannot be recovered, so make sure you saved it.`)
6249
- );
6250
- } else {
6251
- console.log(dim("Store it now \u2014 shown only once, stored hashed, cannot be recovered."));
6252
- }
6253
- }
6254
- async function runKeysCreate(opts) {
6255
- requireConfig(["DATABASE_URL"]);
6256
- const { db, close } = createDb({ max: 1 });
6257
- try {
6258
- const { key, row } = await createKey(db, {
6259
- label: opts.label,
6260
- tier: opts.tier,
6261
- ownerId: opts.owner
6262
- });
6263
- await presentNewKey(key, row, opts);
6264
- } finally {
6265
- await close();
6266
- }
6267
- }
6268
- async function runKeysRotate(prefixOrId, opts) {
6269
- requireConfig(["DATABASE_URL"]);
6270
- const { db, close } = createDb({ max: 1 });
6271
- try {
6272
- const result = await rotateKey(db, prefixOrId);
6273
- if (!result) {
6274
- logger.warn(`No active key matched "${prefixOrId}".`);
6275
- process.exitCode = 1;
6276
- return;
6277
- }
6278
- await presentNewKey(result.key, result.row, {
6279
- json: opts.json,
6280
- header: `API key rotated \u2014 replaces ${result.previous.prefix} (now revoked).`,
6281
- extraJson: { replaced: result.previous.prefix }
6282
- });
6283
- } finally {
6284
- await close();
6285
- }
6286
- }
6287
- async function runKeysList(opts) {
6288
- requireConfig(["DATABASE_URL"]);
6289
- const { db, close } = createDb({ max: 1 });
6290
- try {
6291
- const rows = await listKeys(db);
6292
- if (opts.json) {
6293
- console.log(
6294
- JSON.stringify(
6295
- rows.map((r) => ({
6296
- id: r.id,
6297
- prefix: r.prefix,
6298
- label: r.label,
6299
- tier: r.tier,
6300
- ownerId: r.ownerId,
6301
- createdAt: r.createdAt,
6302
- lastUsedAt: r.lastUsedAt,
6303
- revokedAt: r.revokedAt
6304
- })),
6305
- null,
6306
- 2
6307
- )
6308
- );
6309
- return;
6310
- }
6311
- if (rows.length === 0) {
6312
- console.log("No API keys yet. Create one with `lurq keys create --label <name>`.");
6313
- return;
6314
- }
6315
- console.log(
6316
- table(
6317
- ["prefix", "label", "tier", "created", "last used", "status"],
6318
- rows.map((r) => [
6319
- r.prefix,
6320
- r.label ?? "\u2014",
6321
- r.tier,
6322
- isoDay(r.createdAt),
6323
- isoDay(r.lastUsedAt),
6324
- r.revokedAt ? "revoked" : "active"
6325
- ])
6326
- )
6327
- );
6328
- } finally {
6329
- await close();
6330
- }
6331
- }
6332
- async function runKeysRevoke(prefixOrId) {
6333
- requireConfig(["DATABASE_URL"]);
6334
- const { db, close } = createDb({ max: 1 });
6335
- try {
6336
- const n = await revokeKey(db, prefixOrId);
6337
- if (n === 0) {
6338
- logger.warn(`No active key matched "${prefixOrId}".`);
6339
- process.exitCode = 1;
6340
- return;
6341
- }
6342
- console.log(`Revoked ${n} key(s) matching "${prefixOrId}".`);
6343
- } finally {
6344
- await close();
6345
- }
6346
- }
6347
- var isoDay;
6348
- var init_keys = __esm({
6349
- "src/cli/keys.ts"() {
6350
- "use strict";
6351
- init_esm_shims();
6352
- init_config();
6353
- init_logger();
6354
- init_apiKeys();
6355
- init_client();
6356
- init_format();
6357
- isoDay = (d) => d ? d.toISOString().slice(0, 10) : "\u2014";
6358
- }
6359
- });
6360
-
6361
- // src/db/seed.ts
6362
- import { readFileSync as readFileSync3 } from "fs";
6363
- import { sql as sql5 } from "drizzle-orm";
6364
- import { z as z3 } from "zod";
6365
- function loadSeedFile(path2 = seedJsonPath()) {
6366
- const raw = JSON.parse(readFileSync3(path2, "utf8"));
6367
- const parsed = SeedFileSchema.parse(raw);
6368
- const byName = /* @__PURE__ */ new Map();
6369
- for (const entry of parsed) {
6370
- if (!byName.has(entry.name)) byName.set(entry.name, entry);
6371
- }
6372
- return [...byName.values()];
6373
- }
6374
- async function loadSeedPackages(db, path2) {
6375
- const entries = loadSeedFile(path2);
6376
- if (entries.length === 0) return 0;
6377
- await db.insert(seedPackages).values(entries.map((e) => ({ name: e.name, category: e.category ?? null }))).onConflictDoUpdate({
6378
- target: seedPackages.name,
6379
- // Refresh category to the incoming value on conflict (EXCLUDED.category).
6380
- set: { category: sql5`excluded.category` }
6381
- });
6382
- logger.info(`Loaded ${entries.length} packages into seed_packages.`);
6383
- return entries.length;
6384
- }
6385
- var SeedEntrySchema, SeedFileSchema;
6386
- var init_seed = __esm({
6387
- "src/db/seed.ts"() {
6388
- "use strict";
6389
- init_esm_shims();
6390
- init_types();
6391
- init_logger();
6392
- init_paths();
6393
- init_schema();
6394
- SeedEntrySchema = z3.object({
6395
- name: z3.string().min(1),
6396
- category: z3.enum(CATEGORIES).optional()
6397
- });
6398
- SeedFileSchema = z3.array(SeedEntrySchema);
6399
- }
6400
- });
6401
-
6402
- // src/db/migrate.ts
6403
- var migrate_exports = {};
6404
- __export(migrate_exports, {
6405
- runMigrate: () => runMigrate,
6406
- runReset: () => runReset
6407
- });
6408
- import { migrate } from "drizzle-orm/postgres-js/migrator";
6409
- async function ensureVectorExtension(handle) {
6410
- try {
6411
- await handle.sql`CREATE EXTENSION IF NOT EXISTS vector`;
6412
- } catch (err) {
6413
- logger.warn(
6414
- `CREATE EXTENSION vector did not run cleanly (continuing to migrations): ${err instanceof Error ? err.message : String(err)}`
6415
- );
6416
- }
6417
- }
6418
- async function runMigrate() {
6419
- const handle = createDb({ max: 1 });
6420
- try {
6421
- logger.info("Ensuring pgvector extension\u2026");
6422
- await ensureVectorExtension(handle);
6423
- logger.info("Applying migrations\u2026");
6424
- await migrate(handle.db, { migrationsFolder: migrationsDir() });
6425
- logger.info("Loading seed list\u2026");
6426
- await loadSeedPackages(handle.db);
6427
- logger.info("Migration complete.");
6428
- } finally {
6429
- await handle.close();
6430
- }
6431
- }
6432
- async function runReset() {
6433
- const handle = createDb({ max: 1 });
6434
- try {
6435
- logger.warn("Dropping schema `public` (destructive)\u2026");
6436
- await handle.sql.unsafe("DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;");
6437
- await handle.sql`DROP SCHEMA IF EXISTS drizzle CASCADE`;
6438
- } finally {
6439
- await handle.close();
6440
- }
6441
- await runMigrate();
6442
- logger.info("Reset complete.");
6443
- }
6444
- var init_migrate = __esm({
6445
- "src/db/migrate.ts"() {
6446
- "use strict";
6447
- init_esm_shims();
6448
- init_logger();
6449
- init_paths();
6450
- init_client();
6451
- init_seed();
6452
- }
6453
- });
6454
-
6455
6797
  // src/bin/lurq.ts
6456
6798
  init_esm_shims();
6457
- import { readFileSync as readFileSync4 } from "fs";
6799
+ import { readFileSync as readFileSync3 } from "fs";
6458
6800
  import updateNotifier from "update-notifier";
6459
6801
 
6460
6802
  // src/cli/index.ts
@@ -6474,12 +6816,6 @@ function buildProgram() {
6474
6816
  const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http2(), http_exports));
6475
6817
  await startHttpServer2({ port: opts.port });
6476
6818
  });
6477
- program.command("sync").description("run ingestion: refresh scores for the seed list (or one package)").option("--full", "force a full re-sync, ignoring cache TTLs").option("--package <name>", "sync a single package by name").option("--json", "output the run summary as JSON").action(async (opts) => {
6478
- const { runSync: runSync2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
6479
- const summary = await runSync2({ full: opts.full, packageName: opts.package });
6480
- if (opts.json) console.log(JSON.stringify(summary, null, 2));
6481
- if (summary.status === "failed") process.exitCode = 1;
6482
- });
6483
6819
  program.command("recommend").argument("<need>", "natural-language description of what you need").description("recommend the best current packages for a described need").option("--category <category>", "restrict to a taxonomy category").option("--min-confidence <level>", "proven | emerging | promising | unproven").option("--json", "output JSON instead of a table").action(async (need, opts) => {
6484
6820
  const { runRecommend: runRecommend2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6485
6821
  await runRecommend2(need, opts);
@@ -6496,6 +6832,10 @@ function buildProgram() {
6496
6832
  const { runVerify: runVerify2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6497
6833
  await runVerify2(pkg, opts);
6498
6834
  });
6835
+ program.command("usage").argument("<package>", "npm package name").description("version-exact API surface (exported symbols/signatures) + drift from a known version").option("--version <v>", "target version (defaults to latest)").option("--known <v>", "a version you know; shows the API delta to the target").option("--json", "output JSON").action(async (pkg, opts) => {
6836
+ const { runUsage: runUsage2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6837
+ await runUsage2(pkg, opts);
6838
+ });
6499
6839
  program.command("versions").argument("<package>", "npm package name").description("show the stored version timeline for a package").option("--json", "output JSON instead of a table").option("-n, --limit <n>", "how many versions to show (default 30)").action(async (pkg, opts) => {
6500
6840
  const { runVersions: runVersions2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6501
6841
  await runVersions2(pkg, opts);
@@ -6512,35 +6852,7 @@ function buildProgram() {
6512
6852
  const { runEditWeights: runEditWeights2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6513
6853
  await runEditWeights2(opts);
6514
6854
  });
6515
- program.command("discover").description("operator-side: proactively crawl for new packages and queue/gate them (\xA72B)").option("--cap <n>", "max candidates to fully ingest this run", (v) => parseInt(v, 10)).option("--dry-run", "discover, queue, and gate, but do not ingest survivors").option("--json", "output the discovery summary as JSON").action(async (opts) => {
6516
- const { requireConfig: requireConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6517
- requireConfig2(["DATABASE_URL"]);
6518
- const { runDiscovery: runDiscovery2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
6519
- const summary = await runDiscovery2({ perRunCap: opts.cap, dryRun: opts.dryRun });
6520
- if (opts.json) console.log(JSON.stringify(summary, null, 2));
6521
- });
6522
- program.command("rescore").description("re-derive health scores from cached breakdowns using current weights (no re-ingest)").option("--json", "output the rescore summary as JSON").action(async (opts) => {
6523
- const { requireConfig: requireConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
6524
- requireConfig2(["DATABASE_URL"]);
6525
- const { runRescore: runRescore2 } = await Promise.resolve().then(() => (init_pipeline(), pipeline_exports));
6526
- const summary = await runRescore2();
6527
- if (opts.json) console.log(JSON.stringify(summary, null, 2));
6528
- });
6529
- program.command("watch").description(
6530
- "operator-side: follow the npm changes feed, re-syncing tracked packages on new releases"
6531
- ).action(async () => {
6532
- const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6533
- await runWatch2();
6534
- });
6535
- program.command("sandbox").argument("<package>", "npm package name").argument("[version]", "specific version (default: latest)").description(
6536
- "operator-side: install + smoke-load a package in a sandbox to verify it actually works"
6537
- ).option("--esm", "load via ESM import instead of CJS require").option("--allow-scripts", "run install scripts (UNSAFE without VM isolation)").option("--json", "output JSON").action(
6538
- async (pkg, version, opts) => {
6539
- const { runSandbox: runSandbox2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6540
- await runSandbox2(pkg, version, opts);
6541
- }
6542
- );
6543
- program.command("compat").argument("<packages...>", "npm package names to check together").description("check pairwise compatibility of packages from the sandbox matrix").option("--run", "operator-side: co-install them in the sandbox first (UNSAFE without VM isolation)").option("--json", "output JSON").action(async (pkgs, opts) => {
6855
+ program.command("compat").argument("<packages...>", "npm package names to check together").description("check whether a set of packages forms a coherent stack (peer/engine + recorded evidence)").option("--json", "output JSON").action(async (pkgs, opts) => {
6544
6856
  const { runCompat: runCompat2 } = await Promise.resolve().then(() => (init_commands(), commands_exports));
6545
6857
  await runCompat2(pkgs, opts);
6546
6858
  });
@@ -6556,39 +6868,6 @@ function buildProgram() {
6556
6868
  const { runInstallSkill: runInstallSkill2 } = await Promise.resolve().then(() => (init_installSkill(), installSkill_exports));
6557
6869
  await runInstallSkill2(opts);
6558
6870
  });
6559
- const keys = program.command("keys").description("manage API keys for the hosted service (operator; needs DATABASE_URL)");
6560
- keys.command("create").description("create a new API key (shown once; erased from the terminal after you copy it)").option("--label <label>", "human label (owner / org / purpose)").option("--tier <tier>", "tier name", "free").option("--owner <id>", "org/owner id to attribute this key to (e.g. a Clerk org id)").option("--json", "print the key as JSON and skip the interactive erase (for scripts)").action(async (opts) => {
6561
- const { runKeysCreate: runKeysCreate2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
6562
- await runKeysCreate2(opts);
6563
- });
6564
- keys.command("list").description("list issued API keys (hashes are never shown)").option("--json", "output as JSON").action(async (opts) => {
6565
- const { runKeysList: runKeysList2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
6566
- await runKeysList2(opts);
6567
- });
6568
- keys.command("rotate").argument("<prefixOrId>", "key prefix (e.g. lurq_live_ab12cd) or numeric id to replace").description("issue a replacement key (same label/tier) and revoke the old one").option("--json", "print the new key as JSON and skip the interactive erase (for scripts)").action(async (prefixOrId, opts) => {
6569
- const { runKeysRotate: runKeysRotate2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
6570
- await runKeysRotate2(prefixOrId, opts);
6571
- });
6572
- keys.command("revoke").argument("<prefixOrId>", "key prefix (e.g. lurq_live_ab12cd) or numeric id").description("revoke an API key").action(async (prefixOrId) => {
6573
- const { runKeysRevoke: runKeysRevoke2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
6574
- await runKeysRevoke2(prefixOrId);
6575
- });
6576
- const db = program.command("db").description("database management");
6577
- db.command("migrate").description("apply database migrations and load the seed list").action(async () => {
6578
- const { runMigrate: runMigrate2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
6579
- await runMigrate2();
6580
- });
6581
- db.command("reset").description("drop and recreate the schema (destructive)").option("--yes", "skip the confirmation prompt").action(async (opts) => {
6582
- if (!opts.yes) {
6583
- console.error(
6584
- "Refusing to reset without confirmation. Re-run with `--yes` to drop and recreate the schema."
6585
- );
6586
- process.exitCode = 1;
6587
- return;
6588
- }
6589
- const { runReset: runReset2 } = await Promise.resolve().then(() => (init_migrate(), migrate_exports));
6590
- await runReset2();
6591
- });
6592
6871
  return program;
6593
6872
  }
6594
6873
 
@@ -6668,7 +6947,7 @@ function notifyOnUpdate() {
6668
6947
  if (quiet) return;
6669
6948
  try {
6670
6949
  const pkg = JSON.parse(
6671
- readFileSync4(new URL("../../package.json", import.meta.url), "utf8")
6950
+ readFileSync3(new URL("../../package.json", import.meta.url), "utf8")
6672
6951
  );
6673
6952
  updateNotifier({ pkg }).notify();
6674
6953
  } catch {