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