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