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/README.md +70 -13
- package/dist/bin/lurq.js +1268 -989
- package/dist/bin/lurq.js.map +1 -1
- package/dist/index.d.ts +85 -4
- package/dist/index.js +1273 -987
- package/dist/index.js.map +1 -1
- package/drizzle/0011_compat_provenance_closures.sql +11 -0
- package/drizzle/0012_api_surfaces.sql +9 -0
- package/drizzle/0013_nice_thanos.sql +1 -0
- package/drizzle/0014_luxuriant_malcolm_colcord.sql +10 -0
- package/drizzle/0015_past_fat_cobra.sql +11 -0
- package/drizzle/0016_futuristic_junta.sql +50 -0
- package/drizzle/meta/0011_snapshot.json +1049 -0
- package/drizzle/meta/0012_snapshot.json +1115 -0
- package/drizzle/meta/0013_snapshot.json +1121 -0
- package/drizzle/meta/0014_snapshot.json +1190 -0
- package/drizzle/meta/0015_snapshot.json +1280 -0
- package/drizzle/meta/0016_snapshot.json +1683 -0
- package/drizzle/meta/_journal.json +42 -0
- package/package.json +8 -3
- package/templates/skill-instructions.md +38 -16
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.
|
|
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
|
|
450
|
-
* operator-issued keys. This is what turns the flywheel from a
|
|
451
|
-
* into a per-
|
|
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
|
|
628
|
+
const sql8 = postgres(DATABASE_URL, { max: opts.max ?? 10, onnotice: () => {
|
|
476
629
|
} });
|
|
477
|
-
const db = drizzle(
|
|
478
|
-
return { db, sql:
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
|
736
|
-
return Number.isNaN(
|
|
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(
|
|
1011
|
-
return
|
|
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://${
|
|
1039
|
-
{ host:
|
|
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://${
|
|
1051
|
-
{ host:
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
1134
|
-
ENDPOINT = `https://${
|
|
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://${
|
|
1357
|
+
const url = `https://${HOST4}/v3/projects/${projectKey}`;
|
|
1181
1358
|
try {
|
|
1182
1359
|
const { data } = await httpGetJson(url, {
|
|
1183
|
-
host:
|
|
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://${
|
|
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:
|
|
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://${
|
|
1208
|
-
host:
|
|
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
|
|
1219
|
-
const url = `https://${
|
|
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:
|
|
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
|
|
1228
|
-
|
|
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
|
|
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
|
-
|
|
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://${
|
|
1447
|
+
const url = `https://${HOST5}/api/size?package=${encodeURIComponent(name)}`;
|
|
1261
1448
|
try {
|
|
1262
1449
|
const { data } = await httpGetJson(url, {
|
|
1263
|
-
host:
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1307
|
-
const row = tracked.get(name);
|
|
1308
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1430
|
-
|
|
1431
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
1503
|
-
|
|
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
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
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
|
|
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(
|
|
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://${
|
|
1964
|
+
const url = `https://${HOST6}/${owner}/${repo}/HEAD/${file}`;
|
|
1634
1965
|
try {
|
|
1635
1966
|
const { data } = await httpRequest(url, {
|
|
1636
|
-
host:
|
|
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
|
|
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
|
-
|
|
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
|
|
2037
|
-
if (
|
|
2038
|
-
const key = entry.slice(0,
|
|
2039
|
-
const value = Number(entry.slice(
|
|
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,
|
|
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 = (
|
|
2388
|
-
monthsSince = (
|
|
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/
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
let
|
|
2574
|
-
|
|
2575
|
-
|
|
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
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
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
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
const
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
2838
|
-
}).from(packages).where(
|
|
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
|
-
|
|
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
|
|
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(
|
|
2979
|
-
if (constraints?.license) conditions.push(
|
|
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
|
-
|
|
2997
|
-
|
|
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(
|
|
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 =
|
|
3004
|
-
const rank =
|
|
3005
|
-
const conditions = [
|
|
3006
|
-
return db.select(ROW_COLUMNS).from(packages).where(
|
|
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
|
|
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(
|
|
3378
|
-
return
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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/
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
4403
|
-
return db.select().from(apiKeys).orderBy(
|
|
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() ?
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 <
|
|
5457
|
+
for (let i = 0; i < smokeTargets.length; i++) {
|
|
5179
5458
|
try {
|
|
5180
|
-
await execFileAsync("node", smokeScript(
|
|
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
|
|
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 <
|
|
5622
|
+
for (let i = 0; i < smokeTargets.length; i++) {
|
|
5295
5623
|
try {
|
|
5296
|
-
await sandbox.commands.run(smokeCommand(
|
|
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
|
|
5382
|
-
const allLoaded = result.loaded.every((l) => l.loaded === true);
|
|
5809
|
+
function pairwiseEdges(resolved, success) {
|
|
5383
5810
|
const edges = [];
|
|
5384
|
-
if (
|
|
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
|
-
|
|
5428
|
-
|
|
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:
|
|
6176
|
+
const { readFileSync: readFileSync3 } = await import("fs");
|
|
5654
6177
|
let document;
|
|
5655
6178
|
try {
|
|
5656
|
-
document =
|
|
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:
|
|
5677
|
-
const { join:
|
|
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 ??
|
|
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
|
|
6452
|
+
import { dirname as dirname4, join as join5 } from "path";
|
|
5866
6453
|
function home(...p) {
|
|
5867
|
-
return
|
|
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 =
|
|
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 =
|
|
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("
|
|
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,
|