lurqrun 0.1.6 → 0.1.7
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 +18 -10
- package/dist/auditHandler-JCOJSUYW.js +23 -0
- package/dist/bin/lurq.js +3090 -23306
- package/dist/browserAuth-B6G27USY.js +95 -0
- package/dist/cache-DKI34SVS.js +12 -0
- package/dist/check-2FDGUOV7.js +192 -0
- package/dist/checkUpgrade-F2WESBPM.js +103 -0
- package/dist/chunk-2VMOWSRY.js +300 -0
- package/dist/chunk-3GHJR7XR.js +2049 -0
- package/dist/chunk-3N3Q3DSW.js +214 -0
- package/dist/chunk-4ESO33J6.js +34 -0
- package/dist/chunk-4F7ZESPO.js +132 -0
- package/dist/chunk-4KCPOMG6.js +68 -0
- package/dist/chunk-4ZYCIMY4.js +155 -0
- package/dist/chunk-63MUJNZL.js +318 -0
- package/dist/chunk-6AAEVQ7I.js +6336 -0
- package/dist/chunk-6EQ3ARXA.js +304 -0
- package/dist/chunk-6L3U7DZ5.js +23 -0
- package/dist/chunk-AHBP533R.js +220 -0
- package/dist/chunk-B43LCXBM.js +223 -0
- package/dist/chunk-BCANRN5B.js +33 -0
- package/dist/chunk-BUALU27S.js +123 -0
- package/dist/chunk-BULZAX2U.js +1554 -0
- package/dist/chunk-BV32UNLL.js +154 -0
- package/dist/chunk-C6GLE626.js +160 -0
- package/dist/chunk-CIDWNUAP.js +145 -0
- package/dist/chunk-CUP36ND3.js +2126 -0
- package/dist/chunk-CW5HJ3LX.js +96 -0
- package/dist/chunk-DB2VF5UD.js +315 -0
- package/dist/chunk-DWAGCNEL.js +210 -0
- package/dist/chunk-EA3DP5HG.js +293 -0
- package/dist/chunk-ELGCAM7P.js +184 -0
- package/dist/chunk-FW6BD2NO.js +1915 -0
- package/dist/chunk-GE37NVLN.js +15 -0
- package/dist/chunk-GPTKSNR6.js +171 -0
- package/dist/chunk-GRJXQBQZ.js +793 -0
- package/dist/chunk-HWBBVLOT.js +92 -0
- package/dist/chunk-I2JUKN6Z.js +24 -0
- package/dist/chunk-I3M2KJXB.js +56 -0
- package/dist/chunk-IQ4SDKKY.js +733 -0
- package/dist/chunk-J5FZYN4A.js +116 -0
- package/dist/chunk-L6N4JFW2.js +263 -0
- package/dist/chunk-LBFGSSF2.js +126 -0
- package/dist/chunk-LEGPB7DO.js +81 -0
- package/dist/chunk-LO35HLQZ.js +219 -0
- package/dist/chunk-LRD4GBYE.js +73 -0
- package/dist/chunk-LUAKDHLZ.js +40 -0
- package/dist/chunk-LYMPYETA.js +965 -0
- package/dist/chunk-M6NYEXCW.js +38 -0
- package/dist/chunk-MHUEVBVO.js +35 -0
- package/dist/chunk-MYGSLK44.js +37 -0
- package/dist/chunk-NH5I4KHH.js +201 -0
- package/dist/chunk-OQS4GUSI.js +1391 -0
- package/dist/chunk-PSX3GIAN.js +176 -0
- package/dist/chunk-Q5VPF4WI.js +26 -0
- package/dist/chunk-QKGYDMWB.js +7409 -0
- package/dist/chunk-QSL6QN5X.js +115 -0
- package/dist/chunk-SHCEBP3J.js +107 -0
- package/dist/chunk-SJOVFF7B.js +201 -0
- package/dist/chunk-ST2X2GYN.js +373 -0
- package/dist/chunk-TNTXWSYA.js +19 -0
- package/dist/chunk-U5AT3TD4.js +2128 -0
- package/dist/chunk-UDVNA7EA.js +483 -0
- package/dist/chunk-VL2E5T2A.js +109 -0
- package/dist/chunk-VLRHFMGJ.js +58 -0
- package/dist/chunk-WHX2ZFCG.js +4048 -0
- package/dist/chunk-WQ23REYT.js +37 -0
- package/dist/chunk-XBIDMHSK.js +15425 -0
- package/dist/chunk-Y5GI7RL3.js +66 -0
- package/dist/client-IRKAMDYZ.js +15 -0
- package/dist/commands-FIL5ET6P.js +911 -0
- package/dist/compat-R3UTFQ5Q.js +201 -0
- package/dist/dashboardIssue-WYYMBFIB.js +192 -0
- package/dist/diff-GZA2774U.js +13 -0
- package/dist/dist-4XXIXHHZ.js +12945 -0
- package/dist/e2b-FVPHDULZ.js +172 -0
- package/dist/extract-NKGKLIVT.js +444 -0
- package/dist/fetch-QYTUJVG2.js +22 -0
- package/dist/firstTouch-QQTJH4QX.js +55 -0
- package/dist/handlers-GEJAVZDK.js +53 -0
- package/dist/http-ETPYMBGD.js +5360 -0
- package/dist/ingestQueue-VKN4KBIF.js +35 -0
- package/dist/install-EK3W2COX.js +20 -0
- package/dist/installSkill-VQJWJVCL.js +52 -0
- package/dist/inventory-5AWSLJYU.js +31 -0
- package/dist/local-XPZMRH5R.js +13 -0
- package/dist/mcp-3JHD5SUZ.js +472 -0
- package/dist/mcpHandlers-QFN4MCFL.js +22 -0
- package/dist/mcpScan-QZHWENIP.js +1871 -0
- package/dist/mcpScanWorkflow-JPYCBGC4.js +81 -0
- package/dist/mcpStack-BYJCZNIV.js +20 -0
- package/dist/packages-THVFMWFP.js +42 -0
- package/dist/plan-V4OEI3J4.js +463 -0
- package/dist/planView-JRXGCG2N.js +79 -0
- package/dist/policy-3P6BGRDN.js +115 -0
- package/dist/references-6N7VEPHT.js +14 -0
- package/dist/release-4U4ABNSJ.js +128 -0
- package/dist/remote-YGCXYI4V.js +32 -0
- package/dist/reportRuns-CKQQYLY4.js +64 -0
- package/dist/sandbox-MEZLAODF.js +39 -0
- package/dist/server-KFUD5X57.js +55 -0
- package/dist/surface-V62GE554.js +141 -0
- package/dist/surface-YL6O67W3.js +36 -0
- package/dist/surfaceHandlers-X2IZKA3E.js +20 -0
- package/dist/typecheck-X7U5ELMR.js +268 -0
- package/dist/types-M5JP2W3D.js +10 -0
- package/dist/uninstall-P7RGBX6G.js +174 -0
- package/dist/upgrade-NQ5IWFZB.js +596 -0
- package/dist/upgradePlan-A4LRKLEG.js +146 -0
- package/dist/userConfig-Z3GQE776.js +20 -0
- package/dist/watch-DGFMWUDL.js +214 -0
- package/package.json +26 -21
- package/templates/agent-rules.md +4 -4
- package/templates/skill-instructions.md +13 -14
- package/dist/bin/lurq.js.map +0 -1
- package/dist/index.d.ts +0 -805
- package/dist/index.js +0 -23570
- package/dist/index.js.map +0 -1
- package/drizzle/0000_confused_fabian_cortez.sql +0 -54
- package/drizzle/0001_aspiring_timeslip.sql +0 -15
- package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
- package/drizzle/0003_violet_giant_girl.sql +0 -14
- package/drizzle/0004_slim_valeria_richards.sql +0 -15
- package/drizzle/0005_petite_luke_cage.sql +0 -12
- package/drizzle/0006_calm_nighthawk.sql +0 -3
- package/drizzle/0007_slimy_naoko.sql +0 -1
- package/drizzle/0008_peaceful_tigra.sql +0 -1
- package/drizzle/0009_smooth_harrier.sql +0 -10
- package/drizzle/0010_curved_amphibian.sql +0 -2
- package/drizzle/0011_compat_provenance_closures.sql +0 -11
- package/drizzle/0012_api_surfaces.sql +0 -9
- package/drizzle/0013_nice_thanos.sql +0 -1
- package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
- package/drizzle/0015_past_fat_cobra.sql +0 -11
- package/drizzle/0016_futuristic_junta.sql +0 -50
- package/drizzle/0017_absent_grandmaster.sql +0 -20
- package/drizzle/0018_rainy_proemial_gods.sql +0 -1
- package/drizzle/0019_dashing_korvac.sql +0 -11
- package/drizzle/0020_normal_vulture.sql +0 -3
- package/drizzle/0021_dazzling_blue_shield.sql +0 -17
- package/drizzle/0022_massive_ghost_rider.sql +0 -24
- package/drizzle/0023_next_shatterstar.sql +0 -1
- package/drizzle/0024_dusty_maelstrom.sql +0 -15
- package/drizzle/0025_gifted_harpoon.sql +0 -5
- package/drizzle/0026_awesome_mordo.sql +0 -1
- package/drizzle/0027_charming_ulik.sql +0 -2
- package/drizzle/0028_romantic_loners.sql +0 -2
- package/drizzle/0029_busy_korvac.sql +0 -16
- package/drizzle/0030_steady_mathemanic.sql +0 -6
- package/drizzle/0031_redundant_maginty.sql +0 -2
- package/drizzle/0032_stack_resolutions.sql +0 -14
- package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
- package/drizzle/0034_uneven_thunderbird.sql +0 -1
- package/drizzle/0035_api_key_scopes.sql +0 -1
- package/drizzle/0036_policy_audit.sql +0 -21
- package/drizzle/0037_subscription_seats.sql +0 -1
- package/drizzle/0038_symbol_offsets.sql +0 -2
- package/drizzle/0039_mcp_runtime_scans.sql +0 -80
- package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
- package/drizzle/0041_account_email.sql +0 -38
- package/drizzle/0042_alert_channels.sql +0 -21
- package/drizzle/meta/0000_snapshot.json +0 -370
- package/drizzle/meta/0001_snapshot.json +0 -482
- package/drizzle/meta/0002_snapshot.json +0 -575
- package/drizzle/meta/0003_snapshot.json +0 -667
- package/drizzle/meta/0004_snapshot.json +0 -769
- package/drizzle/meta/0005_snapshot.json +0 -864
- package/drizzle/meta/0006_snapshot.json +0 -882
- package/drizzle/meta/0007_snapshot.json +0 -876
- package/drizzle/meta/0008_snapshot.json +0 -882
- package/drizzle/meta/0009_snapshot.json +0 -948
- package/drizzle/meta/0010_snapshot.json +0 -969
- package/drizzle/meta/0011_snapshot.json +0 -1049
- package/drizzle/meta/0012_snapshot.json +0 -1115
- package/drizzle/meta/0013_snapshot.json +0 -1121
- package/drizzle/meta/0014_snapshot.json +0 -1190
- package/drizzle/meta/0015_snapshot.json +0 -1280
- package/drizzle/meta/0016_snapshot.json +0 -1683
- package/drizzle/meta/0017_snapshot.json +0 -1834
- package/drizzle/meta/0018_snapshot.json +0 -1834
- package/drizzle/meta/0019_snapshot.json +0 -1909
- package/drizzle/meta/0020_snapshot.json +0 -1921
- package/drizzle/meta/0021_snapshot.json +0 -2045
- package/drizzle/meta/0022_snapshot.json +0 -2246
- package/drizzle/meta/0023_snapshot.json +0 -2252
- package/drizzle/meta/0024_snapshot.json +0 -2375
- package/drizzle/meta/0025_snapshot.json +0 -2407
- package/drizzle/meta/0026_snapshot.json +0 -2413
- package/drizzle/meta/0027_snapshot.json +0 -2425
- package/drizzle/meta/0028_snapshot.json +0 -2438
- package/drizzle/meta/0029_snapshot.json +0 -2546
- package/drizzle/meta/0030_snapshot.json +0 -2586
- package/drizzle/meta/0031_snapshot.json +0 -2598
- package/drizzle/meta/0032_snapshot.json +0 -2699
- package/drizzle/meta/0033_snapshot.json +0 -2712
- package/drizzle/meta/0034_snapshot.json +0 -2719
- package/drizzle/meta/0035_snapshot.json +0 -2726
- package/drizzle/meta/0036_snapshot.json +0 -2876
- package/drizzle/meta/0037_snapshot.json +0 -2883
- package/drizzle/meta/0038_snapshot.json +0 -2895
- package/drizzle/meta/0039_snapshot.json +0 -3501
- package/drizzle/meta/0040_snapshot.json +0 -3527
- package/drizzle/meta/0041_snapshot.json +0 -3824
- package/drizzle/meta/0042_snapshot.json +0 -3960
- package/drizzle/meta/_journal.json +0 -307
- package/src/data/seed.json +0 -253
|
@@ -0,0 +1,1554 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
assessVerdict
|
|
4
|
+
} from "./chunk-QSL6QN5X.js";
|
|
5
|
+
import {
|
|
6
|
+
getStoredSurface,
|
|
7
|
+
upsertSurface
|
|
8
|
+
} from "./chunk-M6NYEXCW.js";
|
|
9
|
+
import {
|
|
10
|
+
getLatestVerificationByName
|
|
11
|
+
} from "./chunk-6L3U7DZ5.js";
|
|
12
|
+
import {
|
|
13
|
+
getStackResolution,
|
|
14
|
+
recordStackResolution,
|
|
15
|
+
resolveSet,
|
|
16
|
+
stackKey
|
|
17
|
+
} from "./chunk-J5FZYN4A.js";
|
|
18
|
+
import {
|
|
19
|
+
applyPolicy,
|
|
20
|
+
check,
|
|
21
|
+
describeRules,
|
|
22
|
+
diffPolicies,
|
|
23
|
+
hasRules,
|
|
24
|
+
withoutExpired
|
|
25
|
+
} from "./chunk-PSX3GIAN.js";
|
|
26
|
+
import {
|
|
27
|
+
FIRST_TOUCH_BUDGET_MS,
|
|
28
|
+
assembleMembers,
|
|
29
|
+
computeComposite,
|
|
30
|
+
createEmbeddingProvider,
|
|
31
|
+
fetchWeeklyDownloads,
|
|
32
|
+
getOrFetchPackage,
|
|
33
|
+
inferCategory,
|
|
34
|
+
loadLearnedSuccessors,
|
|
35
|
+
lookupSuccessor,
|
|
36
|
+
recordOutcome,
|
|
37
|
+
truncateSentences
|
|
38
|
+
} from "./chunk-FW6BD2NO.js";
|
|
39
|
+
import {
|
|
40
|
+
resolveArchitectureCompat,
|
|
41
|
+
resolveRuntimeEngineConflicts
|
|
42
|
+
} from "./chunk-BUALU27S.js";
|
|
43
|
+
import {
|
|
44
|
+
CONFIDENCE_WEIGHT,
|
|
45
|
+
RANKING,
|
|
46
|
+
loadWeights
|
|
47
|
+
} from "./chunk-2VMOWSRY.js";
|
|
48
|
+
import {
|
|
49
|
+
withBudget
|
|
50
|
+
} from "./chunk-4ESO33J6.js";
|
|
51
|
+
import {
|
|
52
|
+
enqueueCompatVerify,
|
|
53
|
+
getSandboxEdges,
|
|
54
|
+
pairKey
|
|
55
|
+
} from "./chunk-CW5HJ3LX.js";
|
|
56
|
+
import {
|
|
57
|
+
fetchNpmCompatAtVersion,
|
|
58
|
+
fetchNpmRegistry,
|
|
59
|
+
npmPackageExists,
|
|
60
|
+
npmVersionExists
|
|
61
|
+
} from "./chunk-AHBP533R.js";
|
|
62
|
+
import {
|
|
63
|
+
HttpError,
|
|
64
|
+
httpRequest
|
|
65
|
+
} from "./chunk-SJOVFF7B.js";
|
|
66
|
+
import {
|
|
67
|
+
cached
|
|
68
|
+
} from "./chunk-HWBBVLOT.js";
|
|
69
|
+
import {
|
|
70
|
+
getPackageByName,
|
|
71
|
+
getTopPackageNames
|
|
72
|
+
} from "./chunk-NH5I4KHH.js";
|
|
73
|
+
import {
|
|
74
|
+
DEFAULT_ECOSYSTEM
|
|
75
|
+
} from "./chunk-VLRHFMGJ.js";
|
|
76
|
+
import {
|
|
77
|
+
packages,
|
|
78
|
+
policyDecisions,
|
|
79
|
+
selectionPolicies,
|
|
80
|
+
selectionPolicyChanges
|
|
81
|
+
} from "./chunk-LYMPYETA.js";
|
|
82
|
+
import {
|
|
83
|
+
logger
|
|
84
|
+
} from "./chunk-MYGSLK44.js";
|
|
85
|
+
import {
|
|
86
|
+
CACHE_TTL,
|
|
87
|
+
STALENESS_DAYS
|
|
88
|
+
} from "./chunk-GPTKSNR6.js";
|
|
89
|
+
import {
|
|
90
|
+
init_esm_shims
|
|
91
|
+
} from "./chunk-I3M2KJXB.js";
|
|
92
|
+
|
|
93
|
+
// src/mcp/handlers.ts
|
|
94
|
+
init_esm_shims();
|
|
95
|
+
import { createHash } from "crypto";
|
|
96
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
97
|
+
|
|
98
|
+
// src/compat/check.ts
|
|
99
|
+
init_esm_shims();
|
|
100
|
+
function gradeOverall(args) {
|
|
101
|
+
if (args.hasConflict || args.resolution === "conflict") return "conflict";
|
|
102
|
+
if (args.hasUnverifiedMember || args.resolution === "inconclusive") return "unknown";
|
|
103
|
+
return "compatible";
|
|
104
|
+
}
|
|
105
|
+
async function checkCompat(db, packages2, opts = {}) {
|
|
106
|
+
const names = [...new Set(packages2)];
|
|
107
|
+
const refs = names.map((name) => ({
|
|
108
|
+
name,
|
|
109
|
+
version: opts.versions?.[name] ?? null
|
|
110
|
+
}));
|
|
111
|
+
const { members, unverified } = await assembleMembers(db, refs);
|
|
112
|
+
const conflicts = [
|
|
113
|
+
...resolveArchitectureCompat(members),
|
|
114
|
+
...opts.node ? resolveRuntimeEngineConflicts(members, opts.node) : []
|
|
115
|
+
];
|
|
116
|
+
const edges = await getSandboxEdges(db, names);
|
|
117
|
+
const versionOf = new Map(members.map((m) => [m.name, m.version]));
|
|
118
|
+
const evidence = edges.map((edge) => ({
|
|
119
|
+
packages: [edge.packageA, edge.packageB],
|
|
120
|
+
versions: [edge.versionA, edge.versionB],
|
|
121
|
+
status: edge.status,
|
|
122
|
+
provenance: edge.provenance,
|
|
123
|
+
witnessCount: edge.witnessCount
|
|
124
|
+
}));
|
|
125
|
+
for (const edge of edges) {
|
|
126
|
+
if (edge.status !== "conflict") continue;
|
|
127
|
+
const at = edgeMatchesVersions(edge, versionOf) ? "" : ` (recorded at those versions; you are checking ${edge.packageA}@${versionOf.get(edge.packageA) ?? "?"} and ${edge.packageB}@${versionOf.get(edge.packageB) ?? "?"})`;
|
|
128
|
+
conflicts.push({
|
|
129
|
+
source: "sandbox",
|
|
130
|
+
packages: [edge.packageA, edge.packageB],
|
|
131
|
+
detail: `${edge.packageA}@${edge.versionA} and ${edge.packageB}@${edge.versionB} failed to co-install in the sandbox${at}`
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const checkedNames = members.map((m) => m.name);
|
|
135
|
+
const { resolution, verdict } = await settle(db, members, {
|
|
136
|
+
// A declared conflict is already proof. Paying npm to re-confirm it would be
|
|
137
|
+
// seconds spent learning nothing.
|
|
138
|
+
skip: conflicts.length > 0,
|
|
139
|
+
cachedOnly: opts.cachedOnly === true,
|
|
140
|
+
hasUnverified: unverified.length > 0
|
|
141
|
+
});
|
|
142
|
+
if (resolution?.reason === "ERESOLVE") {
|
|
143
|
+
conflicts.push({
|
|
144
|
+
source: "resolve",
|
|
145
|
+
packages: checkedNames,
|
|
146
|
+
detail: resolution.detail ?? "npm could not resolve this set together"
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
packages: names,
|
|
151
|
+
overall: gradeOverall({
|
|
152
|
+
hasConflict: conflicts.length > 0,
|
|
153
|
+
hasUnverifiedMember: unverified.length > 0,
|
|
154
|
+
resolution: verdict
|
|
155
|
+
}),
|
|
156
|
+
conflicts,
|
|
157
|
+
unverified,
|
|
158
|
+
checked: members.map((m) => ({ name: m.name, version: m.version })),
|
|
159
|
+
evidence,
|
|
160
|
+
...resolution ? { resolution } : {},
|
|
161
|
+
pairs: enumeratePairs(checkedNames, conflicts)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function settle(db, members, opts) {
|
|
165
|
+
if (opts.skip) return { resolution: null, verdict: "inconclusive" };
|
|
166
|
+
const pinned = [];
|
|
167
|
+
for (const m of members) {
|
|
168
|
+
if (!m.version) return { resolution: null, verdict: "inconclusive" };
|
|
169
|
+
pinned.push({ name: m.name, version: m.version });
|
|
170
|
+
}
|
|
171
|
+
if (pinned.length < 2) return { resolution: null, verdict: "resolved" };
|
|
172
|
+
const key = stackKey(pinned);
|
|
173
|
+
const cached2 = await getStackResolution(db, key).catch(() => null);
|
|
174
|
+
if (cached2) {
|
|
175
|
+
return {
|
|
176
|
+
resolution: {
|
|
177
|
+
source: "cache",
|
|
178
|
+
resolved: cached2.resolved,
|
|
179
|
+
reason: cached2.reason === "ERESOLVE" ? "ERESOLVE" : null,
|
|
180
|
+
...cached2.detail ? { detail: cached2.detail } : {},
|
|
181
|
+
at: cached2.resolvedAt.toISOString()
|
|
182
|
+
},
|
|
183
|
+
verdict: cached2.resolved ? "resolved" : "conflict"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
if (opts.cachedOnly) return { resolution: null, verdict: "inconclusive" };
|
|
187
|
+
let result;
|
|
188
|
+
try {
|
|
189
|
+
result = await resolveSet(pinned);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
logger.warn(`compat: resolve inconclusive for ${key}: ${String(err)}`);
|
|
192
|
+
return { resolution: null, verdict: "inconclusive" };
|
|
193
|
+
}
|
|
194
|
+
await recordStackResolution(db, {
|
|
195
|
+
members: pinned,
|
|
196
|
+
resolved: result.resolved,
|
|
197
|
+
reason: result.reason,
|
|
198
|
+
detail: result.detail ?? null
|
|
199
|
+
}).catch((err) => logger.warn(`compat: caching resolution failed: ${String(err)}`));
|
|
200
|
+
if (result.resolved) {
|
|
201
|
+
await enqueueCompatVerify(
|
|
202
|
+
db,
|
|
203
|
+
pinned.map((m) => m.name)
|
|
204
|
+
).catch(() => {
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
resolution: {
|
|
209
|
+
source: "resolved",
|
|
210
|
+
resolved: result.resolved,
|
|
211
|
+
reason: result.reason,
|
|
212
|
+
...result.detail ? { detail: result.detail } : {},
|
|
213
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
214
|
+
},
|
|
215
|
+
verdict: result.resolved ? "resolved" : "conflict"
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function edgeMatchesVersions(edge, versionOf) {
|
|
219
|
+
const a = versionOf.get(edge.packageA);
|
|
220
|
+
const b = versionOf.get(edge.packageB);
|
|
221
|
+
return a != null && b != null && a === edge.versionA && b === edge.versionB;
|
|
222
|
+
}
|
|
223
|
+
function enumeratePairs(names, conflicts) {
|
|
224
|
+
const byPair = /* @__PURE__ */ new Map();
|
|
225
|
+
for (const c of conflicts) {
|
|
226
|
+
if (c.source === "resolve") continue;
|
|
227
|
+
const [a, b] = c.packages;
|
|
228
|
+
if (!a || !b) continue;
|
|
229
|
+
const key = pairKey(a, b);
|
|
230
|
+
if (!byPair.has(key)) byPair.set(key, c);
|
|
231
|
+
}
|
|
232
|
+
const pairs = [];
|
|
233
|
+
for (let i = 0; i < names.length; i++) {
|
|
234
|
+
for (let j = 0; j < i; j++) {
|
|
235
|
+
const a = names[i];
|
|
236
|
+
const b = names[j];
|
|
237
|
+
const hit = byPair.get(pairKey(a, b));
|
|
238
|
+
pairs.push(
|
|
239
|
+
hit ? {
|
|
240
|
+
// Orient the pair the way the conflict states it, so `a` is the
|
|
241
|
+
// package making the demand rather than whichever came first in
|
|
242
|
+
// the argument list.
|
|
243
|
+
a: hit.packages[0] ?? a,
|
|
244
|
+
b: hit.packages[1] ?? b,
|
|
245
|
+
status: "conflict",
|
|
246
|
+
detail: hit.detail,
|
|
247
|
+
...hit.requirement ? { requirement: hit.requirement } : {}
|
|
248
|
+
} : { a, b, status: "held" }
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return pairs;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/usage/diff.ts
|
|
256
|
+
init_esm_shims();
|
|
257
|
+
function byName(surface) {
|
|
258
|
+
return new Map(surface.map((s) => [s.name, s]));
|
|
259
|
+
}
|
|
260
|
+
function looksRenamed(a, b) {
|
|
261
|
+
return a.kind === b.kind && a.signature !== null && a.signature === b.signature;
|
|
262
|
+
}
|
|
263
|
+
function diffSurface(oldSurface, newSurface) {
|
|
264
|
+
const oldByName = byName(oldSurface);
|
|
265
|
+
const newByName = byName(newSurface);
|
|
266
|
+
const removed = oldSurface.filter((s) => !newByName.has(s.name));
|
|
267
|
+
const added = newSurface.filter((s) => !oldByName.has(s.name));
|
|
268
|
+
const changed = [];
|
|
269
|
+
for (const oldSym of oldSurface) {
|
|
270
|
+
const newSym = newByName.get(oldSym.name);
|
|
271
|
+
if (newSym && oldSym.signature !== newSym.signature) {
|
|
272
|
+
changed.push({ name: oldSym.name, before: oldSym.signature, after: newSym.signature });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const renamed = [];
|
|
276
|
+
const takenAdded = /* @__PURE__ */ new Set();
|
|
277
|
+
const stillRemoved = [];
|
|
278
|
+
for (const r of removed) {
|
|
279
|
+
const match = added.find((a) => !takenAdded.has(a.name) && looksRenamed(r, a));
|
|
280
|
+
if (match) {
|
|
281
|
+
renamed.push({ from: r, to: match });
|
|
282
|
+
takenAdded.add(match.name);
|
|
283
|
+
} else {
|
|
284
|
+
stillRemoved.push(r);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
added: added.filter((a) => !takenAdded.has(a.name)),
|
|
289
|
+
removed: stillRemoved,
|
|
290
|
+
renamed,
|
|
291
|
+
changed
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/usage/service.ts
|
|
296
|
+
init_esm_shims();
|
|
297
|
+
|
|
298
|
+
// src/usage/extract.ts
|
|
299
|
+
init_esm_shims();
|
|
300
|
+
import { posix } from "path";
|
|
301
|
+
var CDN = "cdn.jsdelivr.net";
|
|
302
|
+
function startWalk(opts) {
|
|
303
|
+
return {
|
|
304
|
+
...opts,
|
|
305
|
+
expiresAt: opts.deadlineMs === void 0 ? Infinity : Date.now() + opts.deadlineMs
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
var compiler;
|
|
309
|
+
function loadCompiler() {
|
|
310
|
+
compiler ??= import("typescript").then((m) => m.default ?? m).catch(() => null);
|
|
311
|
+
return compiler;
|
|
312
|
+
}
|
|
313
|
+
async function fetchFile(url, opts, ttlMs = CACHE_TTL.depsDev) {
|
|
314
|
+
if (Date.now() >= opts.expiresAt) return { text: null, unknown: true };
|
|
315
|
+
try {
|
|
316
|
+
const { data } = await httpRequest(url, {
|
|
317
|
+
host: CDN,
|
|
318
|
+
ttlMs,
|
|
319
|
+
accept: "text",
|
|
320
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
321
|
+
...opts.retries !== void 0 ? { retries: opts.retries } : {}
|
|
322
|
+
});
|
|
323
|
+
return { text: typeof data === "string" ? data : null, unknown: false };
|
|
324
|
+
} catch (err) {
|
|
325
|
+
const missing = err instanceof HttpError && err.status === 404;
|
|
326
|
+
return { text: null, unknown: !missing };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
async function fetchText(url, opts) {
|
|
330
|
+
return (await fetchFile(url, opts)).text;
|
|
331
|
+
}
|
|
332
|
+
var DTS_EXT = /\.d\.[cm]?ts$/;
|
|
333
|
+
var JS_EXT = /\.(m|c)?js$/;
|
|
334
|
+
function declarationFor(path) {
|
|
335
|
+
if (DTS_EXT.test(path)) return path;
|
|
336
|
+
const js = JS_EXT.exec(path);
|
|
337
|
+
return js ? path.replace(JS_EXT, `.d.${js[1] ?? ""}ts`) : null;
|
|
338
|
+
}
|
|
339
|
+
function relativeToRoot(path) {
|
|
340
|
+
return path.replace(/^\.?\//, "");
|
|
341
|
+
}
|
|
342
|
+
var EXPORT_CONDITIONS = ["types", "typings", "node", "import", "require", "default"];
|
|
343
|
+
function typesFromExports(exports, depth = 0) {
|
|
344
|
+
if (depth > 4) return null;
|
|
345
|
+
if (typeof exports === "string") return declarationFor(exports);
|
|
346
|
+
if (!exports || typeof exports !== "object") return null;
|
|
347
|
+
if (Array.isArray(exports)) {
|
|
348
|
+
for (const alt of exports) {
|
|
349
|
+
const found = typesFromExports(alt, depth + 1);
|
|
350
|
+
if (found) return found;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
const map = exports;
|
|
355
|
+
if ("." in map) return typesFromExports(map["."], depth + 1);
|
|
356
|
+
if (Object.keys(map).some((k) => k.startsWith("."))) return null;
|
|
357
|
+
for (const condition of EXPORT_CONDITIONS) {
|
|
358
|
+
if (!(condition in map)) continue;
|
|
359
|
+
const found = typesFromExports(map[condition], depth + 1);
|
|
360
|
+
if (found) return found;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
function typesEntries(pkgJson) {
|
|
365
|
+
const entries = [];
|
|
366
|
+
try {
|
|
367
|
+
const manifest = JSON.parse(pkgJson);
|
|
368
|
+
const declared = manifest.types ?? manifest.typings;
|
|
369
|
+
if (typeof declared === "string") entries.push(relativeToRoot(declared));
|
|
370
|
+
const fromExports = typesFromExports(manifest.exports);
|
|
371
|
+
if (fromExports) entries.push(relativeToRoot(fromExports));
|
|
372
|
+
} catch {
|
|
373
|
+
}
|
|
374
|
+
entries.push("index.d.ts");
|
|
375
|
+
return [...new Set(entries)];
|
|
376
|
+
}
|
|
377
|
+
function kindOf(ts, node) {
|
|
378
|
+
if (ts.isFunctionDeclaration(node)) return "function";
|
|
379
|
+
if (ts.isClassDeclaration(node)) return "class";
|
|
380
|
+
if (ts.isInterfaceDeclaration(node)) return "interface";
|
|
381
|
+
if (ts.isTypeAliasDeclaration(node)) return "type";
|
|
382
|
+
if (ts.isEnumDeclaration(node)) return "enum";
|
|
383
|
+
if (ts.isModuleDeclaration(node)) return "namespace";
|
|
384
|
+
if (ts.isVariableStatement(node)) return "variable";
|
|
385
|
+
return "unknown";
|
|
386
|
+
}
|
|
387
|
+
function normalize(text) {
|
|
388
|
+
return text.replace(/\s+/g, " ").trim();
|
|
389
|
+
}
|
|
390
|
+
function signatureOf(ts, node, src) {
|
|
391
|
+
if (ts.isFunctionDeclaration(node)) {
|
|
392
|
+
const params = node.parameters.map((p) => normalize(p.getText(src))).join(", ");
|
|
393
|
+
const ret = node.type ? `: ${normalize(node.type.getText(src))}` : "";
|
|
394
|
+
return `(${params})${ret}`;
|
|
395
|
+
}
|
|
396
|
+
if (ts.isTypeAliasDeclaration(node)) return normalize(node.type.getText(src));
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
function hasExportModifier(ts, node) {
|
|
400
|
+
return ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false);
|
|
401
|
+
}
|
|
402
|
+
function declarationsIn(ts, node, src) {
|
|
403
|
+
if (ts.isVariableStatement(node)) {
|
|
404
|
+
const out = [];
|
|
405
|
+
for (const decl of node.declarationList.declarations) {
|
|
406
|
+
if (ts.isIdentifier(decl.name)) {
|
|
407
|
+
out.push([
|
|
408
|
+
decl.name.text,
|
|
409
|
+
"variable",
|
|
410
|
+
decl.type ? normalize(decl.type.getText(src)) : null
|
|
411
|
+
]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return out;
|
|
415
|
+
}
|
|
416
|
+
const named = node;
|
|
417
|
+
if (named.name && ts.isIdentifier(named.name)) {
|
|
418
|
+
return [[named.name.text, kindOf(ts, node), signatureOf(ts, node, src)]];
|
|
419
|
+
}
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
function parseModule(source, ts) {
|
|
423
|
+
const src = ts.createSourceFile("surface.d.ts", source, ts.ScriptTarget.Latest, true);
|
|
424
|
+
const out = /* @__PURE__ */ new Map();
|
|
425
|
+
const stars = [];
|
|
426
|
+
const add = (name, kind, signature) => {
|
|
427
|
+
if (!out.has(name)) out.set(name, { name, kind, signature });
|
|
428
|
+
};
|
|
429
|
+
const local = /* @__PURE__ */ new Map();
|
|
430
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const node of src.statements) {
|
|
432
|
+
for (const [name, kind, signature] of declarationsIn(ts, node, src)) {
|
|
433
|
+
if (!local.has(name)) local.set(name, { kind, signature });
|
|
434
|
+
}
|
|
435
|
+
if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name) && !namespaces.has(node.name.text)) {
|
|
436
|
+
namespaces.set(node.name.text, node);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const liftNamespace = (decl) => {
|
|
440
|
+
if (!decl.body || !ts.isModuleBlock(decl.body)) return;
|
|
441
|
+
for (const stmt of decl.body.statements) {
|
|
442
|
+
for (const [name, kind, signature] of declarationsIn(ts, stmt, src))
|
|
443
|
+
add(name, kind, signature);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
for (const node of src.statements) {
|
|
447
|
+
if (ts.isExportDeclaration(node)) {
|
|
448
|
+
const clause = node.exportClause;
|
|
449
|
+
if (!clause) {
|
|
450
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
451
|
+
stars.push(node.moduleSpecifier.text);
|
|
452
|
+
}
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (ts.isNamespaceExport(clause)) {
|
|
456
|
+
add(clause.name.text, "namespace", null);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const reExport = node.moduleSpecifier !== void 0;
|
|
460
|
+
for (const el of clause.elements) {
|
|
461
|
+
const decl = reExport ? void 0 : local.get((el.propertyName ?? el.name).text);
|
|
462
|
+
const typeOnly = node.isTypeOnly || el.isTypeOnly;
|
|
463
|
+
add(el.name.text, decl?.kind ?? (typeOnly ? "type" : "unknown"), decl?.signature ?? null);
|
|
464
|
+
}
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (ts.isExportAssignment(node) && ts.isIdentifier(node.expression)) {
|
|
468
|
+
const target = node.expression.text;
|
|
469
|
+
const decl = local.get(target);
|
|
470
|
+
if (decl) add("default", decl.kind, decl.signature);
|
|
471
|
+
const ns = node.isExportEquals ? namespaces.get(target) : void 0;
|
|
472
|
+
if (ns) liftNamespace(ns);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (!hasExportModifier(ts, node)) continue;
|
|
476
|
+
for (const [name, kind, signature] of declarationsIn(ts, node, src)) add(name, kind, signature);
|
|
477
|
+
}
|
|
478
|
+
return { symbols: [...out.values()].sort((a, b) => a.name.localeCompare(b.name)), stars };
|
|
479
|
+
}
|
|
480
|
+
function dtsCandidates(fromFile, specifier) {
|
|
481
|
+
if (!specifier.startsWith("./") && !specifier.startsWith("../")) return [];
|
|
482
|
+
const path = posix.normalize(posix.join(posix.dirname(fromFile), specifier));
|
|
483
|
+
if (path.startsWith("..")) return [];
|
|
484
|
+
const paired = declarationFor(path);
|
|
485
|
+
if (paired) return [paired];
|
|
486
|
+
return [`${path}.d.ts`, `${path}/index.d.ts`];
|
|
487
|
+
}
|
|
488
|
+
var MAX_HOPS = 3;
|
|
489
|
+
var MAX_FILES = 24;
|
|
490
|
+
async function collectSurface(ts, root, entry, source, opts) {
|
|
491
|
+
const merged = /* @__PURE__ */ new Map();
|
|
492
|
+
const attempted = /* @__PURE__ */ new Set([entry]);
|
|
493
|
+
let budget = MAX_FILES;
|
|
494
|
+
let sound = true;
|
|
495
|
+
const absorb = (file, text) => {
|
|
496
|
+
const { symbols, stars } = parseModule(text, ts);
|
|
497
|
+
for (const s of symbols) if (!merged.has(s.name)) merged.set(s.name, s);
|
|
498
|
+
return stars.map((spec) => dtsCandidates(file, spec)).filter((c) => c.length > 0);
|
|
499
|
+
};
|
|
500
|
+
const fetchFirst = async (candidates) => {
|
|
501
|
+
for (const file of candidates) {
|
|
502
|
+
if (attempted.has(file) || budget <= 0) continue;
|
|
503
|
+
attempted.add(file);
|
|
504
|
+
budget--;
|
|
505
|
+
const { text, unknown } = await fetchFile(`${root}/${file}`, opts);
|
|
506
|
+
if (text !== null) return [file, text];
|
|
507
|
+
if (unknown) {
|
|
508
|
+
sound = false;
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
};
|
|
514
|
+
let frontier = absorb(entry, source);
|
|
515
|
+
for (let hop = 0; hop < MAX_HOPS && frontier.length > 0 && budget > 0; hop++) {
|
|
516
|
+
const fetched = await Promise.all(frontier.map(fetchFirst));
|
|
517
|
+
frontier = fetched.flatMap((hit) => hit ? absorb(hit[0], hit[1]) : []);
|
|
518
|
+
}
|
|
519
|
+
return { symbols: [...merged.values()].sort((a, b) => a.name.localeCompare(b.name)), sound };
|
|
520
|
+
}
|
|
521
|
+
async function surfaceFrom(ts, root, pkgJson, opts) {
|
|
522
|
+
for (const entry of typesEntries(pkgJson)) {
|
|
523
|
+
const dts = await fetchText(`${root}/${entry}`, opts);
|
|
524
|
+
if (!dts) continue;
|
|
525
|
+
const { symbols, sound } = await collectSurface(ts, root, entry, dts, opts);
|
|
526
|
+
if (sound && symbols.length > 0) return symbols;
|
|
527
|
+
}
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
function typesPackageName(name) {
|
|
531
|
+
return name.startsWith("@") ? `@types/${name.slice(1).replace("/", "__")}` : `@types/${name}`;
|
|
532
|
+
}
|
|
533
|
+
async function typesPackageSurface(ts, name, version, opts) {
|
|
534
|
+
const major = /^\d+/.exec(version)?.[0];
|
|
535
|
+
if (!major) return null;
|
|
536
|
+
const dt = typesPackageName(name);
|
|
537
|
+
const { text: pkgJson } = await fetchFile(
|
|
538
|
+
`https://${CDN}/npm/${dt}@${major}/package.json`,
|
|
539
|
+
opts,
|
|
540
|
+
CACHE_TTL.npmRegistry
|
|
541
|
+
// A floating range: don't hold it for a day.
|
|
542
|
+
);
|
|
543
|
+
if (!pkgJson) return null;
|
|
544
|
+
let resolved;
|
|
545
|
+
try {
|
|
546
|
+
resolved = JSON.parse(pkgJson).version;
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
if (typeof resolved !== "string") return null;
|
|
551
|
+
return surfaceFrom(ts, `https://${CDN}/npm/${dt}@${resolved}`, pkgJson, opts);
|
|
552
|
+
}
|
|
553
|
+
async function extractSurface(name, version, opts = {}) {
|
|
554
|
+
const ts = await loadCompiler();
|
|
555
|
+
if (!ts) return null;
|
|
556
|
+
const walk = startWalk(opts);
|
|
557
|
+
const root = `https://${CDN}/npm/${name}@${version}`;
|
|
558
|
+
const pkgJson = await fetchText(`${root}/package.json`, walk);
|
|
559
|
+
if (!pkgJson) return null;
|
|
560
|
+
return await surfaceFrom(ts, root, pkgJson, walk) ?? typesPackageSurface(ts, name, version, walk);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// src/usage/service.ts
|
|
564
|
+
var USAGE_EXTRACT_BUDGET_MS = 4e3;
|
|
565
|
+
var READ_PATH_FETCH = { timeoutMs: 3e3, retries: 0, deadlineMs: 15e3 };
|
|
566
|
+
var MAX_INFLIGHT = 64;
|
|
567
|
+
var inFlight = /* @__PURE__ */ new Map();
|
|
568
|
+
function extractOnce(db, name, version, fetchOpts) {
|
|
569
|
+
const key = `${name}@${version}`;
|
|
570
|
+
const existing = inFlight.get(key);
|
|
571
|
+
if (existing) return existing;
|
|
572
|
+
if (inFlight.size >= MAX_INFLIGHT) return Promise.resolve(null);
|
|
573
|
+
const task = extractSurface(name, version, fetchOpts).then(async (extracted) => {
|
|
574
|
+
if (extracted) await upsertSurface(db, name, version, extracted).catch(() => {
|
|
575
|
+
});
|
|
576
|
+
return extracted;
|
|
577
|
+
}).catch(() => null).finally(() => inFlight.delete(key));
|
|
578
|
+
inFlight.set(key, task);
|
|
579
|
+
return task;
|
|
580
|
+
}
|
|
581
|
+
async function getOrExtractSurface(db, name, version, opts = {}) {
|
|
582
|
+
const stored = await getStoredSurface(db, name, version);
|
|
583
|
+
if (stored) return stored;
|
|
584
|
+
if (opts.budgetMs === 0) return null;
|
|
585
|
+
const fetchOpts = opts.fetch ?? (opts.budgetMs === void 0 ? {} : READ_PATH_FETCH);
|
|
586
|
+
const task = extractOnce(db, name, version, fetchOpts);
|
|
587
|
+
return opts.budgetMs === void 0 ? task : withBudget(task, opts.budgetMs);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/search/recommend.ts
|
|
591
|
+
init_esm_shims();
|
|
592
|
+
import { and, cosineDistance, eq, inArray, isNotNull, lte, sql } from "drizzle-orm";
|
|
593
|
+
var CONFIDENCE_ORDER = {
|
|
594
|
+
unproven: 0,
|
|
595
|
+
promising: 1,
|
|
596
|
+
emerging: 2,
|
|
597
|
+
proven: 3
|
|
598
|
+
};
|
|
599
|
+
var RRF_K = 60;
|
|
600
|
+
function rankScore(row, relevance, lambda) {
|
|
601
|
+
const composite = computeComposite(row.healthScore ?? 0, row.qualityScore, lambda) / 100;
|
|
602
|
+
const confidence = CONFIDENCE_WEIGHT[row.confidence ?? "unproven"] ?? 0;
|
|
603
|
+
return RANKING.relevance * relevance + RANKING.composite * composite + RANKING.confidence * confidence;
|
|
604
|
+
}
|
|
605
|
+
async function recommend(db, opts, provider = createEmbeddingProvider()) {
|
|
606
|
+
const limit = Math.min(Math.max(opts.limit ?? 3, 1), 5);
|
|
607
|
+
let queryVec = null;
|
|
608
|
+
try {
|
|
609
|
+
queryVec = (await provider.embed([opts.need]))[0] ?? null;
|
|
610
|
+
} catch (err) {
|
|
611
|
+
queryVec = null;
|
|
612
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
613
|
+
logger.warn(`recommend: vector retrieval unavailable (${reason}); using lexical only.`);
|
|
614
|
+
opts.onDegraded?.(`vector retrieval unavailable: ${reason}`);
|
|
615
|
+
}
|
|
616
|
+
if (!queryVec) opts.onDegraded?.("vector retrieval unavailable");
|
|
617
|
+
const category = opts.category ?? inferCategory(opts.need);
|
|
618
|
+
const pool = Math.max(limit * 5, 25);
|
|
619
|
+
let fused = await hybridSearch(db, queryVec, provider.id, opts.need, opts.constraints, category, pool);
|
|
620
|
+
if (category && fused.length < limit) {
|
|
621
|
+
const broad = await hybridSearch(db, queryVec, provider.id, opts.need, opts.constraints, null, pool);
|
|
622
|
+
const seen = new Set(fused.map((f) => f.row.name));
|
|
623
|
+
fused = fused.concat(broad.filter((f) => !seen.has(f.row.name)));
|
|
624
|
+
}
|
|
625
|
+
if (fused.length === 0) return [];
|
|
626
|
+
const lambda = loadWeights().composite.lambda;
|
|
627
|
+
const maxRrf = Math.max(...fused.map((f) => f.rrf));
|
|
628
|
+
const ranked = fused.map((f) => ({
|
|
629
|
+
row: f.row,
|
|
630
|
+
score: rankScore(f.row, maxRrf > 0 ? f.rrf / maxRrf : 0, lambda)
|
|
631
|
+
})).sort((a, b) => b.score - a.score).slice(0, limit);
|
|
632
|
+
return ranked.map(({ row }) => toCandidate(row));
|
|
633
|
+
}
|
|
634
|
+
async function hybridSearch(db, queryVec, providerId, need, constraints, category, pool) {
|
|
635
|
+
const [vectorRows, lexicalRows] = await Promise.all([
|
|
636
|
+
queryVec ? runVectorQuery(db, queryVec, providerId, constraints, category, pool) : [],
|
|
637
|
+
runLexicalQuery(db, need, constraints, category, pool)
|
|
638
|
+
]);
|
|
639
|
+
return rrfFuse([vectorRows, lexicalRows]);
|
|
640
|
+
}
|
|
641
|
+
function rrfFuse(lists, k = RRF_K) {
|
|
642
|
+
const fused = /* @__PURE__ */ new Map();
|
|
643
|
+
for (const list of lists) {
|
|
644
|
+
list.forEach((row, idx) => {
|
|
645
|
+
const contribution = 1 / (k + idx + 1);
|
|
646
|
+
const prev = fused.get(row.name);
|
|
647
|
+
if (prev) prev.rrf += contribution;
|
|
648
|
+
else fused.set(row.name, { row, rrf: contribution });
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
return [...fused.values()].sort((a, b) => b.rrf - a.rrf);
|
|
652
|
+
}
|
|
653
|
+
var ROW_COLUMNS = {
|
|
654
|
+
name: packages.name,
|
|
655
|
+
category: packages.category,
|
|
656
|
+
healthScore: packages.healthScore,
|
|
657
|
+
qualityScore: packages.qualityScore,
|
|
658
|
+
confidence: packages.confidence,
|
|
659
|
+
latestVersion: packages.latestVersion,
|
|
660
|
+
weeklyDownloads: packages.weeklyDownloads,
|
|
661
|
+
lastReleaseAt: packages.lastReleaseAt,
|
|
662
|
+
repoUrl: packages.repoUrl,
|
|
663
|
+
deprecated: packages.deprecated,
|
|
664
|
+
archived: packages.archived,
|
|
665
|
+
// The count, not the array: an agent shortlisting five packages needs to know
|
|
666
|
+
// that one carries advisories, and `evaluate` is where it reads which. Pulling
|
|
667
|
+
// the jsonb payload for every candidate would cost tokens on every call for
|
|
668
|
+
// detail the next call already serves.
|
|
669
|
+
advisories: sql`coalesce(jsonb_array_length(${packages.advisories}), 0)`
|
|
670
|
+
};
|
|
671
|
+
function buildConditions(constraints, category) {
|
|
672
|
+
const conditions = [];
|
|
673
|
+
if (category) conditions.push(eq(packages.category, category));
|
|
674
|
+
if (constraints?.license) conditions.push(eq(packages.license, constraints.license));
|
|
675
|
+
if (constraints?.maxBundleKb !== void 0) {
|
|
676
|
+
conditions.push(lte(packages.bundleMinGzipKb, constraints.maxBundleKb));
|
|
677
|
+
}
|
|
678
|
+
if (constraints?.minConfidence) {
|
|
679
|
+
const allowed = ["proven", "emerging", "promising", "unproven"].filter(
|
|
680
|
+
(c) => CONFIDENCE_ORDER[c] >= CONFIDENCE_ORDER[constraints.minConfidence]
|
|
681
|
+
);
|
|
682
|
+
conditions.push(inArray(packages.confidence, allowed));
|
|
683
|
+
}
|
|
684
|
+
return conditions;
|
|
685
|
+
}
|
|
686
|
+
async function runVectorQuery(db, queryVec, providerId, constraints, category, pool) {
|
|
687
|
+
const distance = cosineDistance(packages.embedding, queryVec);
|
|
688
|
+
const conditions = [
|
|
689
|
+
isNotNull(packages.embedding),
|
|
690
|
+
eq(packages.embeddingProvider, providerId),
|
|
691
|
+
...buildConditions(constraints, category)
|
|
692
|
+
];
|
|
693
|
+
return db.select(ROW_COLUMNS).from(packages).where(and(...conditions)).orderBy(distance).limit(pool);
|
|
694
|
+
}
|
|
695
|
+
async function runLexicalQuery(db, need, constraints, category, pool) {
|
|
696
|
+
const tsquery = sql`websearch_to_tsquery('english', ${need})`;
|
|
697
|
+
const rank = sql`ts_rank(${packages.searchVector}, ${tsquery})`;
|
|
698
|
+
const conditions = [sql`${packages.searchVector} @@ ${tsquery}`, ...buildConditions(constraints, category)];
|
|
699
|
+
return db.select(ROW_COLUMNS).from(packages).where(and(...conditions)).orderBy(sql`${rank} desc`).limit(pool);
|
|
700
|
+
}
|
|
701
|
+
function toCandidate(row) {
|
|
702
|
+
return {
|
|
703
|
+
name: row.name,
|
|
704
|
+
category: row.category,
|
|
705
|
+
healthScore: row.healthScore ?? 0,
|
|
706
|
+
qualityScore: row.qualityScore,
|
|
707
|
+
confidence: row.confidence ?? "unproven",
|
|
708
|
+
why: buildWhy(row),
|
|
709
|
+
latestVersion: row.latestVersion,
|
|
710
|
+
weeklyDownloads: row.weeklyDownloads,
|
|
711
|
+
lastReleaseAt: row.lastReleaseAt ? row.lastReleaseAt.toISOString() : null,
|
|
712
|
+
repoUrl: row.repoUrl,
|
|
713
|
+
deprecated: row.deprecated ?? false,
|
|
714
|
+
archived: row.archived ?? false,
|
|
715
|
+
advisories: Number(row.advisories ?? 0)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function buildWhy(row) {
|
|
719
|
+
const warnings = [];
|
|
720
|
+
if (row.deprecated) warnings.push("DEPRECATED");
|
|
721
|
+
if (row.archived) warnings.push("repo archived");
|
|
722
|
+
if (row.advisories) {
|
|
723
|
+
warnings.push(`${row.advisories} advisor${row.advisories === 1 ? "y" : "ies"}`);
|
|
724
|
+
}
|
|
725
|
+
const parts = [];
|
|
726
|
+
if (row.confidence) parts.push(row.confidence);
|
|
727
|
+
if (row.weeklyDownloads) parts.push(`${formatDownloads(row.weeklyDownloads)} weekly downloads`);
|
|
728
|
+
if (row.healthScore !== null) parts.push(`health ${row.healthScore}`);
|
|
729
|
+
const rationale = parts.join(", ") || "tracked package";
|
|
730
|
+
return warnings.length ? `${warnings.join(" + ")} \u2014 ${rationale}` : rationale;
|
|
731
|
+
}
|
|
732
|
+
function formatDownloads(n) {
|
|
733
|
+
if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
|
|
734
|
+
if (n >= 1e3) return `${Math.round(n / 1e3)}k`;
|
|
735
|
+
return String(n);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// src/db/selectionPolicy.ts
|
|
739
|
+
init_esm_shims();
|
|
740
|
+
import { and as and2, desc, eq as eq2, gte, inArray as inArray2, sql as sql2 } from "drizzle-orm";
|
|
741
|
+
|
|
742
|
+
// src/policy/types.ts
|
|
743
|
+
init_esm_shims();
|
|
744
|
+
var DEFAULT_SELECTION_POLICY = {
|
|
745
|
+
mode: "enforce",
|
|
746
|
+
allow: [],
|
|
747
|
+
deny: [],
|
|
748
|
+
minConfidence: null,
|
|
749
|
+
licenses: null,
|
|
750
|
+
blockDeprecated: false,
|
|
751
|
+
blockArchived: false,
|
|
752
|
+
maxAdvisorySeverity: null,
|
|
753
|
+
minWeeklyDownloads: null,
|
|
754
|
+
maxStaleMonths: null,
|
|
755
|
+
maxBundleKb: null,
|
|
756
|
+
minPackageAgeDays: null
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
// src/db/selectionPolicy.ts
|
|
760
|
+
async function getSelectionPolicy(db, ownerId) {
|
|
761
|
+
if (!ownerId) return DEFAULT_SELECTION_POLICY;
|
|
762
|
+
const [row] = await db.select({ policy: selectionPolicies.policy }).from(selectionPolicies).where(eq2(selectionPolicies.ownerId, ownerId)).limit(1);
|
|
763
|
+
if (!row) return DEFAULT_SELECTION_POLICY;
|
|
764
|
+
const stored = { ...DEFAULT_SELECTION_POLICY, ...row.policy };
|
|
765
|
+
const allow = stored.allow.map(
|
|
766
|
+
(a) => typeof a === "string" ? { name: a } : a
|
|
767
|
+
);
|
|
768
|
+
return { ...stored, allow };
|
|
769
|
+
}
|
|
770
|
+
async function getEnforcedPolicy(db, ownerId) {
|
|
771
|
+
return withoutExpired(await getSelectionPolicy(db, ownerId), /* @__PURE__ */ new Date());
|
|
772
|
+
}
|
|
773
|
+
async function setSelectionPolicy(db, ownerId, policy, actor) {
|
|
774
|
+
const previous = await getSelectionPolicy(db, ownerId);
|
|
775
|
+
await db.transaction(async (tx) => {
|
|
776
|
+
await tx.insert(selectionPolicies).values({ ownerId, policy, updatedAt: /* @__PURE__ */ new Date() }).onConflictDoUpdate({
|
|
777
|
+
target: selectionPolicies.ownerId,
|
|
778
|
+
set: { policy, updatedAt: /* @__PURE__ */ new Date() }
|
|
779
|
+
});
|
|
780
|
+
await tx.insert(selectionPolicyChanges).values({ ownerId, actor, before: previous, after: policy });
|
|
781
|
+
});
|
|
782
|
+
return previous;
|
|
783
|
+
}
|
|
784
|
+
async function listPolicyChanges(db, ownerId, limit = 20) {
|
|
785
|
+
const rows = await db.select().from(selectionPolicyChanges).where(eq2(selectionPolicyChanges.ownerId, ownerId)).orderBy(desc(selectionPolicyChanges.createdAt), desc(selectionPolicyChanges.id)).limit(limit);
|
|
786
|
+
return rows.map((row) => ({
|
|
787
|
+
actor: row.actor,
|
|
788
|
+
at: row.createdAt,
|
|
789
|
+
changes: diffPolicies(row.before, row.after),
|
|
790
|
+
before: row.before,
|
|
791
|
+
after: row.after
|
|
792
|
+
}));
|
|
793
|
+
}
|
|
794
|
+
async function recordDecisions(db, ownerId, tool, exclusions, action) {
|
|
795
|
+
if (!ownerId || exclusions.length === 0) return;
|
|
796
|
+
try {
|
|
797
|
+
await db.insert(policyDecisions).values(
|
|
798
|
+
exclusions.map((e) => ({ ownerId, packageName: e.name, rule: e.rule, action, tool }))
|
|
799
|
+
);
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async function summarizeDecisions(db, ownerId, days) {
|
|
804
|
+
const count = sql2`count(*)::int`;
|
|
805
|
+
return db.select({
|
|
806
|
+
packageName: policyDecisions.packageName,
|
|
807
|
+
rule: policyDecisions.rule,
|
|
808
|
+
action: policyDecisions.action,
|
|
809
|
+
count,
|
|
810
|
+
lastDay: sql2`to_char(max(${policyDecisions.createdAt}) at time zone 'UTC', 'YYYY-MM-DD')`
|
|
811
|
+
}).from(policyDecisions).where(
|
|
812
|
+
and2(
|
|
813
|
+
eq2(policyDecisions.ownerId, ownerId),
|
|
814
|
+
// `::int` for the same reason usage.ts's windowStart casts: an uncast JS
|
|
815
|
+
// number leaves Postgres to guess the operand type.
|
|
816
|
+
gte(policyDecisions.createdAt, sql2`now() - make_interval(days => ${days}::int)`)
|
|
817
|
+
)
|
|
818
|
+
).groupBy(policyDecisions.packageName, policyDecisions.rule, policyDecisions.action).orderBy(desc(count)).limit(100);
|
|
819
|
+
}
|
|
820
|
+
function daysSince(date) {
|
|
821
|
+
if (!date) return null;
|
|
822
|
+
return Math.max(0, Math.floor((Date.now() - date.getTime()) / (1e3 * 60 * 60 * 24)));
|
|
823
|
+
}
|
|
824
|
+
function monthsSince(date) {
|
|
825
|
+
if (!date) return null;
|
|
826
|
+
const ms = Date.now() - date.getTime();
|
|
827
|
+
if (ms < 0) return 0;
|
|
828
|
+
return Math.floor(ms / (1e3 * 60 * 60 * 24 * 30.44));
|
|
829
|
+
}
|
|
830
|
+
async function loadPolicyFacts(db, names, ecosystem = DEFAULT_ECOSYSTEM) {
|
|
831
|
+
const out = /* @__PURE__ */ new Map();
|
|
832
|
+
if (names.length === 0) return out;
|
|
833
|
+
const rows = await db.select({
|
|
834
|
+
name: packages.name,
|
|
835
|
+
license: packages.license,
|
|
836
|
+
deprecated: packages.deprecated,
|
|
837
|
+
archived: packages.archived,
|
|
838
|
+
advisories: packages.advisories,
|
|
839
|
+
weeklyDownloads: packages.weeklyDownloads,
|
|
840
|
+
lastReleaseAt: packages.lastReleaseAt,
|
|
841
|
+
bundleMinGzipKb: packages.bundleMinGzipKb,
|
|
842
|
+
firstPublishedAt: packages.firstPublishedAt
|
|
843
|
+
}).from(packages).where(and2(inArray2(packages.name, names), eq2(packages.ecosystem, ecosystem)));
|
|
844
|
+
for (const row of rows) {
|
|
845
|
+
out.set(row.name, {
|
|
846
|
+
license: row.license,
|
|
847
|
+
deprecated: row.deprecated,
|
|
848
|
+
archived: row.archived,
|
|
849
|
+
advisories: row.advisories,
|
|
850
|
+
weeklyDownloads: row.weeklyDownloads,
|
|
851
|
+
// Resolved here, not in `check`: the rules stay a pure function with no
|
|
852
|
+
// clock, which is what lets every one of them be tested exhaustively.
|
|
853
|
+
monthsSinceRelease: monthsSince(row.lastReleaseAt),
|
|
854
|
+
bundleKb: row.bundleMinGzipKb,
|
|
855
|
+
daysSincePublished: daysSince(row.firstPublishedAt)
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/security/typosquat.ts
|
|
862
|
+
init_esm_shims();
|
|
863
|
+
|
|
864
|
+
// src/data/popular-packages.json
|
|
865
|
+
var popular_packages_default = [
|
|
866
|
+
"react",
|
|
867
|
+
"react-dom",
|
|
868
|
+
"react-router",
|
|
869
|
+
"react-router-dom",
|
|
870
|
+
"next",
|
|
871
|
+
"vue",
|
|
872
|
+
"vue-router",
|
|
873
|
+
"pinia",
|
|
874
|
+
"vuex",
|
|
875
|
+
"svelte",
|
|
876
|
+
"solid-js",
|
|
877
|
+
"preact",
|
|
878
|
+
"angular",
|
|
879
|
+
"@angular/core",
|
|
880
|
+
"jquery",
|
|
881
|
+
"lodash",
|
|
882
|
+
"underscore",
|
|
883
|
+
"ramda",
|
|
884
|
+
"immer",
|
|
885
|
+
"rxjs",
|
|
886
|
+
"express",
|
|
887
|
+
"koa",
|
|
888
|
+
"fastify",
|
|
889
|
+
"@nestjs/core",
|
|
890
|
+
"hapi",
|
|
891
|
+
"connect",
|
|
892
|
+
"body-parser",
|
|
893
|
+
"cookie-parser",
|
|
894
|
+
"cors",
|
|
895
|
+
"helmet",
|
|
896
|
+
"morgan",
|
|
897
|
+
"compression",
|
|
898
|
+
"express-session",
|
|
899
|
+
"express-rate-limit",
|
|
900
|
+
"multer",
|
|
901
|
+
"passport",
|
|
902
|
+
"jsonwebtoken",
|
|
903
|
+
"bcrypt",
|
|
904
|
+
"bcryptjs",
|
|
905
|
+
"axios",
|
|
906
|
+
"node-fetch",
|
|
907
|
+
"got",
|
|
908
|
+
"undici",
|
|
909
|
+
"superagent",
|
|
910
|
+
"request",
|
|
911
|
+
"cheerio",
|
|
912
|
+
"puppeteer",
|
|
913
|
+
"playwright",
|
|
914
|
+
"cypress",
|
|
915
|
+
"jsdom",
|
|
916
|
+
"ws",
|
|
917
|
+
"socket.io",
|
|
918
|
+
"socket.io-client",
|
|
919
|
+
"graphql",
|
|
920
|
+
"@apollo/client",
|
|
921
|
+
"apollo-server",
|
|
922
|
+
"dataloader",
|
|
923
|
+
"mongoose",
|
|
924
|
+
"mongodb",
|
|
925
|
+
"pg",
|
|
926
|
+
"mysql2",
|
|
927
|
+
"sequelize",
|
|
928
|
+
"prisma",
|
|
929
|
+
"knex",
|
|
930
|
+
"drizzle-orm",
|
|
931
|
+
"typeorm",
|
|
932
|
+
"redis",
|
|
933
|
+
"ioredis",
|
|
934
|
+
"sqlite3",
|
|
935
|
+
"better-sqlite3",
|
|
936
|
+
"typescript",
|
|
937
|
+
"eslint",
|
|
938
|
+
"prettier",
|
|
939
|
+
"tslint",
|
|
940
|
+
"webpack",
|
|
941
|
+
"vite",
|
|
942
|
+
"rollup",
|
|
943
|
+
"esbuild",
|
|
944
|
+
"parcel",
|
|
945
|
+
"@babel/core",
|
|
946
|
+
"babel-core",
|
|
947
|
+
"babel-loader",
|
|
948
|
+
"ts-node",
|
|
949
|
+
"tsx",
|
|
950
|
+
"tsup",
|
|
951
|
+
"nodemon",
|
|
952
|
+
"concurrently",
|
|
953
|
+
"npm-run-all",
|
|
954
|
+
"jest",
|
|
955
|
+
"vitest",
|
|
956
|
+
"mocha",
|
|
957
|
+
"chai",
|
|
958
|
+
"sinon",
|
|
959
|
+
"ava",
|
|
960
|
+
"tape",
|
|
961
|
+
"supertest",
|
|
962
|
+
"@testing-library/react",
|
|
963
|
+
"enzyme",
|
|
964
|
+
"chalk",
|
|
965
|
+
"colors",
|
|
966
|
+
"kleur",
|
|
967
|
+
"picocolors",
|
|
968
|
+
"ansi-colors",
|
|
969
|
+
"commander",
|
|
970
|
+
"yargs",
|
|
971
|
+
"meow",
|
|
972
|
+
"minimist",
|
|
973
|
+
"inquirer",
|
|
974
|
+
"prompts",
|
|
975
|
+
"ora",
|
|
976
|
+
"boxen",
|
|
977
|
+
"figlet",
|
|
978
|
+
"cli-progress",
|
|
979
|
+
"debug",
|
|
980
|
+
"dotenv",
|
|
981
|
+
"cross-env",
|
|
982
|
+
"rimraf",
|
|
983
|
+
"glob",
|
|
984
|
+
"fast-glob",
|
|
985
|
+
"chokidar",
|
|
986
|
+
"fs-extra",
|
|
987
|
+
"execa",
|
|
988
|
+
"cross-spawn",
|
|
989
|
+
"shelljs",
|
|
990
|
+
"which",
|
|
991
|
+
"semver",
|
|
992
|
+
"uuid",
|
|
993
|
+
"nanoid",
|
|
994
|
+
"ulid",
|
|
995
|
+
"moment",
|
|
996
|
+
"dayjs",
|
|
997
|
+
"date-fns",
|
|
998
|
+
"luxon",
|
|
999
|
+
"zod",
|
|
1000
|
+
"yup",
|
|
1001
|
+
"joi",
|
|
1002
|
+
"ajv",
|
|
1003
|
+
"validator",
|
|
1004
|
+
"class-validator",
|
|
1005
|
+
"winston",
|
|
1006
|
+
"pino",
|
|
1007
|
+
"bunyan",
|
|
1008
|
+
"nodemailer",
|
|
1009
|
+
"node-cron",
|
|
1010
|
+
"bull",
|
|
1011
|
+
"bullmq",
|
|
1012
|
+
"kafkajs",
|
|
1013
|
+
"amqplib",
|
|
1014
|
+
"sharp",
|
|
1015
|
+
"jimp",
|
|
1016
|
+
"archiver",
|
|
1017
|
+
"tar",
|
|
1018
|
+
"adm-zip",
|
|
1019
|
+
"qs",
|
|
1020
|
+
"query-string",
|
|
1021
|
+
"form-data",
|
|
1022
|
+
"mime-types",
|
|
1023
|
+
"http-errors",
|
|
1024
|
+
"classnames",
|
|
1025
|
+
"clsx",
|
|
1026
|
+
"styled-components",
|
|
1027
|
+
"@emotion/react",
|
|
1028
|
+
"tailwindcss",
|
|
1029
|
+
"postcss",
|
|
1030
|
+
"autoprefixer",
|
|
1031
|
+
"sass",
|
|
1032
|
+
"less",
|
|
1033
|
+
"framer-motion",
|
|
1034
|
+
"three",
|
|
1035
|
+
"d3",
|
|
1036
|
+
"chart.js",
|
|
1037
|
+
"recharts",
|
|
1038
|
+
"gsap",
|
|
1039
|
+
"swiper",
|
|
1040
|
+
"leaflet",
|
|
1041
|
+
"mapbox-gl",
|
|
1042
|
+
"formik",
|
|
1043
|
+
"react-hook-form",
|
|
1044
|
+
"redux",
|
|
1045
|
+
"@reduxjs/toolkit",
|
|
1046
|
+
"zustand",
|
|
1047
|
+
"jotai",
|
|
1048
|
+
"mobx",
|
|
1049
|
+
"bootstrap",
|
|
1050
|
+
"@popperjs/core",
|
|
1051
|
+
"aws-sdk",
|
|
1052
|
+
"@aws-sdk/client-s3",
|
|
1053
|
+
"firebase",
|
|
1054
|
+
"firebase-admin",
|
|
1055
|
+
"stripe",
|
|
1056
|
+
"twilio",
|
|
1057
|
+
"openai",
|
|
1058
|
+
"@anthropic-ai/sdk",
|
|
1059
|
+
"googleapis",
|
|
1060
|
+
"bignumber.js",
|
|
1061
|
+
"decimal.js",
|
|
1062
|
+
"mathjs",
|
|
1063
|
+
"slugify",
|
|
1064
|
+
"faker",
|
|
1065
|
+
"@faker-js/faker",
|
|
1066
|
+
"puppeteer-core"
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
// src/security/typosquat.ts
|
|
1070
|
+
var POPULAR_BASELINE = popular_packages_default;
|
|
1071
|
+
function typosquatCorpus(trackedTopNames) {
|
|
1072
|
+
return [.../* @__PURE__ */ new Set([...POPULAR_BASELINE, ...trackedTopNames])];
|
|
1073
|
+
}
|
|
1074
|
+
function bareName(name) {
|
|
1075
|
+
const slash = name.indexOf("/");
|
|
1076
|
+
return (slash >= 0 ? name.slice(slash + 1) : name).toLowerCase();
|
|
1077
|
+
}
|
|
1078
|
+
function editDistance(a, b) {
|
|
1079
|
+
const m = a.length;
|
|
1080
|
+
const n = b.length;
|
|
1081
|
+
if (m === 0) return n;
|
|
1082
|
+
if (n === 0) return m;
|
|
1083
|
+
let prevPrev = new Array(n + 1).fill(0);
|
|
1084
|
+
let prev = Array.from({ length: n + 1 }, (_, j) => j);
|
|
1085
|
+
for (let i = 1; i <= m; i++) {
|
|
1086
|
+
const cur = new Array(n + 1).fill(0);
|
|
1087
|
+
cur[0] = i;
|
|
1088
|
+
for (let j = 1; j <= n; j++) {
|
|
1089
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1090
|
+
let val = Math.min(
|
|
1091
|
+
(prev[j] ?? 0) + 1,
|
|
1092
|
+
(cur[j - 1] ?? 0) + 1,
|
|
1093
|
+
(prev[j - 1] ?? 0) + cost
|
|
1094
|
+
);
|
|
1095
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
1096
|
+
val = Math.min(val, (prevPrev[j - 2] ?? 0) + 1);
|
|
1097
|
+
}
|
|
1098
|
+
cur[j] = val;
|
|
1099
|
+
}
|
|
1100
|
+
prevPrev = prev;
|
|
1101
|
+
prev = cur;
|
|
1102
|
+
}
|
|
1103
|
+
return prev[n] ?? 0;
|
|
1104
|
+
}
|
|
1105
|
+
function detectTyposquat(name, popular, maxDistance = 2) {
|
|
1106
|
+
const target = bareName(name);
|
|
1107
|
+
if (target.length < 4) return null;
|
|
1108
|
+
if (popular.some((p) => p.toLowerCase() === name.toLowerCase())) return null;
|
|
1109
|
+
let best = null;
|
|
1110
|
+
for (const p of popular) {
|
|
1111
|
+
const cand = bareName(p);
|
|
1112
|
+
if (cand === target) continue;
|
|
1113
|
+
if (Math.abs(cand.length - target.length) > maxDistance) continue;
|
|
1114
|
+
const dist = editDistance(target, cand);
|
|
1115
|
+
if (dist >= 1 && dist <= maxDistance && (!best || dist < best.distance)) {
|
|
1116
|
+
best = { target: p, distance: dist };
|
|
1117
|
+
if (dist === 1) break;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return best;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/mcp/handlers.ts
|
|
1124
|
+
var SEVERITY_RANK = {
|
|
1125
|
+
critical: 4,
|
|
1126
|
+
high: 3,
|
|
1127
|
+
moderate: 2,
|
|
1128
|
+
low: 1,
|
|
1129
|
+
info: 0
|
|
1130
|
+
};
|
|
1131
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
1132
|
+
function isStale(dataAsOf) {
|
|
1133
|
+
if (!dataAsOf) return true;
|
|
1134
|
+
return Date.now() - dataAsOf.getTime() > STALENESS_DAYS * DAY_MS;
|
|
1135
|
+
}
|
|
1136
|
+
function refreshStale(out) {
|
|
1137
|
+
out.stale = isStale(out.dataAsOf ? new Date(out.dataAsOf) : null) || void 0;
|
|
1138
|
+
return out;
|
|
1139
|
+
}
|
|
1140
|
+
function withinDays(date, days) {
|
|
1141
|
+
return date ? Date.now() - date.getTime() <= days * DAY_MS : false;
|
|
1142
|
+
}
|
|
1143
|
+
function topAdvisories(advisories, max = 5) {
|
|
1144
|
+
if (!advisories) return null;
|
|
1145
|
+
return [...advisories].sort((a, b) => SEVERITY_RANK[b.severity] - SEVERITY_RANK[a.severity]).slice(0, max);
|
|
1146
|
+
}
|
|
1147
|
+
function rowToEvaluate(row, learned) {
|
|
1148
|
+
const breakdown = row.scoreBreakdown ?? {
|
|
1149
|
+
maintenance: 0,
|
|
1150
|
+
adoption: 0,
|
|
1151
|
+
reliability: 0,
|
|
1152
|
+
efficiency: null,
|
|
1153
|
+
quality: null
|
|
1154
|
+
};
|
|
1155
|
+
return {
|
|
1156
|
+
dataAsOf: (row.dataAsOf ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1157
|
+
stale: isStale(row.dataAsOf) || void 0,
|
|
1158
|
+
name: row.name,
|
|
1159
|
+
category: row.category,
|
|
1160
|
+
healthScore: row.healthScore ?? 0,
|
|
1161
|
+
qualityScore: row.qualityScore ?? null,
|
|
1162
|
+
confidence: row.confidence ?? "unproven",
|
|
1163
|
+
scoreBreakdown: breakdown,
|
|
1164
|
+
latestVersion: row.latestVersion,
|
|
1165
|
+
lastReleaseAt: row.lastReleaseAt ? row.lastReleaseAt.toISOString() : null,
|
|
1166
|
+
weeklyDownloads: row.weeklyDownloads,
|
|
1167
|
+
downloadGrowth90d: row.downloadGrowth90d,
|
|
1168
|
+
scorecard: row.scorecard,
|
|
1169
|
+
bundleMinGzipKb: row.bundleMinGzipKb,
|
|
1170
|
+
deprecated: row.deprecated,
|
|
1171
|
+
archived: row.archived,
|
|
1172
|
+
advisories: topAdvisories(row.advisories),
|
|
1173
|
+
// The SAME call `verify` and `audit` make. A health score is a quality
|
|
1174
|
+
// measure and must never be read as a safety clearance, so the verdict
|
|
1175
|
+
// travels beside it — and because all three surfaces derive it here, they
|
|
1176
|
+
// can no longer disagree about the same package.
|
|
1177
|
+
verdict: assessVerdict({
|
|
1178
|
+
exists: true,
|
|
1179
|
+
advisories: row.advisories ?? null,
|
|
1180
|
+
deprecated: row.deprecated,
|
|
1181
|
+
archived: row.archived,
|
|
1182
|
+
lowTrust: (row.weeklyDownloads ?? 0) < 1e3,
|
|
1183
|
+
subjectVersion: row.latestVersion
|
|
1184
|
+
}),
|
|
1185
|
+
summary: row.summary ? truncateSentences(row.summary, 3) : null,
|
|
1186
|
+
usageGuide: row.usageGuide ?? null,
|
|
1187
|
+
repoUrl: row.repoUrl,
|
|
1188
|
+
replacedBy: lookupSuccessor(row.name, learned)
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
async function latestDataAsOf(db) {
|
|
1192
|
+
const [row] = await db.select({ m: sql3`max(${packages.dataAsOf})` }).from(packages);
|
|
1193
|
+
return new Date(row?.m ?? Date.now()).toISOString();
|
|
1194
|
+
}
|
|
1195
|
+
function cacheKey(parts) {
|
|
1196
|
+
return createHash("sha1").update(JSON.stringify(parts)).digest("hex").slice(0, 24);
|
|
1197
|
+
}
|
|
1198
|
+
async function handleRecommend(db, input, ownerId = null) {
|
|
1199
|
+
const base = await cached(
|
|
1200
|
+
"rec",
|
|
1201
|
+
cacheKey([input.need, input.category ?? null, input.constraints ?? null]),
|
|
1202
|
+
async () => {
|
|
1203
|
+
let degraded = null;
|
|
1204
|
+
const candidates = await recommend(db, {
|
|
1205
|
+
need: input.need,
|
|
1206
|
+
category: input.category,
|
|
1207
|
+
constraints: input.constraints,
|
|
1208
|
+
limit: 5,
|
|
1209
|
+
onDegraded: (reason) => {
|
|
1210
|
+
degraded = reason;
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
return { dataAsOf: await latestDataAsOf(db), candidates, degraded };
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
// Don't cache empty results — the index may still be populating. Nor
|
|
1217
|
+
// degraded ones: a lexical-only ranking written into the shared cache
|
|
1218
|
+
// outlives the outage that produced it and gets served back long after
|
|
1219
|
+
// the vector leg recovers.
|
|
1220
|
+
skipCache: (r) => r.candidates.length === 0 || r.degraded !== null
|
|
1221
|
+
}
|
|
1222
|
+
);
|
|
1223
|
+
const policy = await getEnforcedPolicy(db, ownerId);
|
|
1224
|
+
if (!hasRules(policy)) return base;
|
|
1225
|
+
const facts = await loadPolicyFacts(
|
|
1226
|
+
db,
|
|
1227
|
+
base.candidates.map((c) => c.name)
|
|
1228
|
+
);
|
|
1229
|
+
const { allowed, excluded, warned } = applyPolicy(policy, base.candidates, facts);
|
|
1230
|
+
void (policy.mode === "warn" ? recordDecisions(db, ownerId, "recommend", warned, "warned") : recordDecisions(db, ownerId, "recommend", excluded, "blocked"));
|
|
1231
|
+
return policy.mode === "warn" ? { ...base, candidates: allowed, policyWarnings: warned } : { ...base, candidates: allowed, excluded };
|
|
1232
|
+
}
|
|
1233
|
+
async function handlePolicy(db, ownerId) {
|
|
1234
|
+
if (!ownerId) {
|
|
1235
|
+
return {
|
|
1236
|
+
enforced: false,
|
|
1237
|
+
rules: [],
|
|
1238
|
+
note: "No lurq account on this connection, so no policy applies. Connect with `lurq setup`."
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const rules = describeRules(await getEnforcedPolicy(db, ownerId));
|
|
1242
|
+
return { enforced: rules.length > 0, rules };
|
|
1243
|
+
}
|
|
1244
|
+
async function handleEvaluate(db, input, ownerId = null) {
|
|
1245
|
+
const out = await cached(
|
|
1246
|
+
"eval",
|
|
1247
|
+
cacheKey([input.package]),
|
|
1248
|
+
async () => {
|
|
1249
|
+
const { row, existsOnNpm } = await getOrFetchPackage(db, input.package, {
|
|
1250
|
+
blockMs: FIRST_TOUCH_BUDGET_MS,
|
|
1251
|
+
requestedByOwnerId: ownerId
|
|
1252
|
+
});
|
|
1253
|
+
if (!row) {
|
|
1254
|
+
return {
|
|
1255
|
+
tracked: false,
|
|
1256
|
+
suggestion: existsOnNpm ? `\u{1F389} Congrats, you're the first to add "${input.package}" to lurq's registry! It's being fetched and scored now; retry in a few seconds for the full evidence read.` : `"${input.package}" was not found on the npm registry. Check the package name.`
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
const learned = row.deprecated || row.archived ? await loadLearnedSuccessors(db) : void 0;
|
|
1260
|
+
const evaluated2 = rowToEvaluate(row, learned);
|
|
1261
|
+
const verification = await getLatestVerificationByName(db, row.name);
|
|
1262
|
+
return verification ? { ...evaluated2, buildVerified: toBuildVerified(verification) } : evaluated2;
|
|
1263
|
+
},
|
|
1264
|
+
// Don't cache "not found / not scored yet" — it may resolve on a later fetch.
|
|
1265
|
+
{ skipCache: (r) => "tracked" in r }
|
|
1266
|
+
);
|
|
1267
|
+
if ("tracked" in out) return out;
|
|
1268
|
+
const evaluated = refreshStale(out);
|
|
1269
|
+
const policy = await getEnforcedPolicy(db, ownerId);
|
|
1270
|
+
if (!hasRules(policy)) return evaluated;
|
|
1271
|
+
const facts = await loadPolicyFacts(db, [evaluated.name]);
|
|
1272
|
+
const exclusion = check(
|
|
1273
|
+
policy,
|
|
1274
|
+
{ name: evaluated.name, confidence: evaluated.confidence },
|
|
1275
|
+
facts.get(evaluated.name)
|
|
1276
|
+
);
|
|
1277
|
+
if (!exclusion) return { ...evaluated, policy: { allowed: true } };
|
|
1278
|
+
void recordDecisions(
|
|
1279
|
+
db,
|
|
1280
|
+
ownerId,
|
|
1281
|
+
"evaluate",
|
|
1282
|
+
[exclusion],
|
|
1283
|
+
policy.mode === "warn" ? "warned" : "blocked"
|
|
1284
|
+
);
|
|
1285
|
+
return {
|
|
1286
|
+
...evaluated,
|
|
1287
|
+
policy: policy.mode === "warn" ? { allowed: true, warning: exclusion } : { allowed: false, ...exclusion }
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function comparisonNote(notFound, pending) {
|
|
1291
|
+
return [
|
|
1292
|
+
notFound.length ? `Not found on the npm registry: ${notFound.join(", ")}. Check the name(s) before installing \u2014 these do not exist.` : null,
|
|
1293
|
+
pending.length ? `\u{1F389} You're the first to add ${pending.join(", ")} to lurq's registry! They're being scored now; retry shortly for the full comparison.` : null
|
|
1294
|
+
].filter(Boolean).join(" ");
|
|
1295
|
+
}
|
|
1296
|
+
async function handleCompare(db, input, ownerId = null) {
|
|
1297
|
+
const out = await cached(
|
|
1298
|
+
"cmp",
|
|
1299
|
+
cacheKey(input.packages),
|
|
1300
|
+
async () => {
|
|
1301
|
+
const results = await Promise.all(
|
|
1302
|
+
input.packages.map((name) => getOrFetchPackage(db, name, { requestedByOwnerId: ownerId }))
|
|
1303
|
+
);
|
|
1304
|
+
const rows = results.map((r) => r.row).filter((row) => row !== null).map((row) => rowToEvaluate(row)).sort((a, b) => b.healthScore - a.healthScore);
|
|
1305
|
+
const missing = [];
|
|
1306
|
+
const notFound = [];
|
|
1307
|
+
const pending = [];
|
|
1308
|
+
input.packages.forEach((name, i) => {
|
|
1309
|
+
const result = results[i];
|
|
1310
|
+
if (result.row) return;
|
|
1311
|
+
missing.push(name);
|
|
1312
|
+
(result.existsOnNpm ? pending : notFound).push(name);
|
|
1313
|
+
});
|
|
1314
|
+
return {
|
|
1315
|
+
dataAsOf: await latestDataAsOf(db),
|
|
1316
|
+
rows,
|
|
1317
|
+
// `missing` stays the union, in the caller's own order, so a client
|
|
1318
|
+
// pinned to an older lurq keeps printing its "not found" line.
|
|
1319
|
+
...missing.length ? { missing } : {},
|
|
1320
|
+
...notFound.length ? { notFound } : {},
|
|
1321
|
+
...pending.length ? { pending } : {},
|
|
1322
|
+
...missing.length ? { note: comparisonNote(notFound, pending) } : {}
|
|
1323
|
+
};
|
|
1324
|
+
},
|
|
1325
|
+
// Don't cache a miss: a pending ingest lands within seconds, and a name that
|
|
1326
|
+
// is not on npm today may be published tomorrow.
|
|
1327
|
+
{ skipCache: (r) => Boolean(r.missing?.length) }
|
|
1328
|
+
);
|
|
1329
|
+
out.rows = out.rows.map(refreshStale);
|
|
1330
|
+
return out;
|
|
1331
|
+
}
|
|
1332
|
+
function toBuildVerified(v) {
|
|
1333
|
+
return {
|
|
1334
|
+
version: v.version,
|
|
1335
|
+
installed: v.installed,
|
|
1336
|
+
loaded: v.imported,
|
|
1337
|
+
driver: v.driver,
|
|
1338
|
+
ranAt: v.ranAt ? v.ranAt.toISOString() : ""
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
async function handleCompat(db, input) {
|
|
1342
|
+
return checkCompat(db, input.packages, {
|
|
1343
|
+
versions: input.versions,
|
|
1344
|
+
node: input.node
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
async function handleVerify(db, input, ownerId = null) {
|
|
1348
|
+
const name = input.package;
|
|
1349
|
+
const exists = await npmPackageExists(name);
|
|
1350
|
+
if (!exists) {
|
|
1351
|
+
const typo2 = detectTyposquat(
|
|
1352
|
+
name,
|
|
1353
|
+
typosquatCorpus(await getTopPackageNames(db).catch(() => []))
|
|
1354
|
+
);
|
|
1355
|
+
return {
|
|
1356
|
+
exists: false,
|
|
1357
|
+
tracked: false,
|
|
1358
|
+
deprecated: false,
|
|
1359
|
+
archived: false,
|
|
1360
|
+
latestVersion: null,
|
|
1361
|
+
weeklyDownloads: null,
|
|
1362
|
+
riskFlags: typo2 ? ["not-found-on-registry", `possible-typosquat-of:${typo2.target}`] : ["not-found-on-registry"],
|
|
1363
|
+
risk: "high",
|
|
1364
|
+
// `invalid`, not `high`: this is a statement about the INPUT. Calling it
|
|
1365
|
+
// a risk level invites the reader to weigh it against a benefit, and
|
|
1366
|
+
// there is no package here to weigh.
|
|
1367
|
+
verdict: assessVerdict({
|
|
1368
|
+
exists: false,
|
|
1369
|
+
advisories: null,
|
|
1370
|
+
typosquatOf: typo2?.target ?? null
|
|
1371
|
+
}),
|
|
1372
|
+
typosquatOf: typo2?.target ?? null,
|
|
1373
|
+
confidence: null,
|
|
1374
|
+
advisoryCount: null
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
const [registry, { row, wasTracked }, popular] = await Promise.all([
|
|
1378
|
+
fetchNpmRegistry(name).catch(() => null),
|
|
1379
|
+
// Block-on-first-touch (§4A): verify is single-package, so await the ingest
|
|
1380
|
+
// for a real confidence/tracked read on the first call.
|
|
1381
|
+
getOrFetchPackage(db, name, { blockMs: FIRST_TOUCH_BUDGET_MS, requestedByOwnerId: ownerId }),
|
|
1382
|
+
getTopPackageNames(db).catch(() => [])
|
|
1383
|
+
]);
|
|
1384
|
+
const weeklyDownloads = row?.weeklyDownloads ?? await fetchWeeklyDownloads(name).catch(() => null);
|
|
1385
|
+
const advisories = row?.advisories ?? null;
|
|
1386
|
+
const advisoryCount = advisories?.length ?? null;
|
|
1387
|
+
const deprecated = Boolean(row?.deprecated || registry?.deprecated);
|
|
1388
|
+
const archived = Boolean(row?.archived);
|
|
1389
|
+
const brandNew = withinDays(registry?.firstPublishedAt ?? null, 7);
|
|
1390
|
+
const lowTrust = weeklyDownloads === null || weeklyDownloads < 1e3;
|
|
1391
|
+
const installScripts = registry?.hasInstallScripts ?? false;
|
|
1392
|
+
const typo = detectTyposquat(name, typosquatCorpus(popular));
|
|
1393
|
+
const riskFlags = [];
|
|
1394
|
+
if (typo) riskFlags.push(`possible-typosquat-of:${typo.target}`);
|
|
1395
|
+
if (weeklyDownloads === null || weeklyDownloads === 0) riskFlags.push("zero-downloads");
|
|
1396
|
+
else if (weeklyDownloads < 1e3) riskFlags.push("low-downloads");
|
|
1397
|
+
if (brandNew) riskFlags.push("published-within-7-days");
|
|
1398
|
+
if (registry?.maintainersCount === 1) riskFlags.push("single-maintainer");
|
|
1399
|
+
if (installScripts) riskFlags.push("runs-install-scripts");
|
|
1400
|
+
if (advisoryCount === null) riskFlags.push("advisories-not-yet-checked");
|
|
1401
|
+
else if (advisoryCount > 0) riskFlags.push("has-known-advisory");
|
|
1402
|
+
if (deprecated) riskFlags.push("deprecated");
|
|
1403
|
+
if (archived) riskFlags.push("archived");
|
|
1404
|
+
const verdict = assessVerdict({
|
|
1405
|
+
exists: true,
|
|
1406
|
+
advisories,
|
|
1407
|
+
typosquatOf: typo?.target ?? null,
|
|
1408
|
+
installScripts,
|
|
1409
|
+
brandNew,
|
|
1410
|
+
lowTrust,
|
|
1411
|
+
deprecated,
|
|
1412
|
+
archived,
|
|
1413
|
+
singleMaintainer: registry?.maintainersCount === 1,
|
|
1414
|
+
subjectVersion: registry?.latestVersion ?? row?.latestVersion ?? null
|
|
1415
|
+
});
|
|
1416
|
+
return {
|
|
1417
|
+
exists: true,
|
|
1418
|
+
tracked: wasTracked,
|
|
1419
|
+
deprecated,
|
|
1420
|
+
archived,
|
|
1421
|
+
latestVersion: registry?.latestVersion ?? row?.latestVersion ?? null,
|
|
1422
|
+
weeklyDownloads,
|
|
1423
|
+
riskFlags,
|
|
1424
|
+
// `invalid` cannot occur here (exists is true), so the cast is total.
|
|
1425
|
+
risk: verdict.level,
|
|
1426
|
+
verdict,
|
|
1427
|
+
typosquatOf: typo?.target ?? null,
|
|
1428
|
+
confidence: row?.confidence ?? null,
|
|
1429
|
+
advisoryCount
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
async function resolveVersion(db, name, requested) {
|
|
1433
|
+
if (requested) {
|
|
1434
|
+
const exists = await npmVersionExists(name, requested);
|
|
1435
|
+
return exists === false ? { version: null, unpublished: true } : { version: requested };
|
|
1436
|
+
}
|
|
1437
|
+
const row = await getPackageByName(db, name);
|
|
1438
|
+
if (row?.latestVersion) return { version: row.latestVersion };
|
|
1439
|
+
const reg = await fetchNpmRegistry(name).catch(() => null);
|
|
1440
|
+
return { version: reg?.latestVersion ?? null };
|
|
1441
|
+
}
|
|
1442
|
+
var USAGE_PAGE_SIZE = 80;
|
|
1443
|
+
function pageSurface(surface, input) {
|
|
1444
|
+
const q = input.query?.trim().toLowerCase();
|
|
1445
|
+
const matched = q ? surface.filter((s) => s.name.toLowerCase().includes(q)) : surface;
|
|
1446
|
+
const offset = Math.max(0, Math.floor(input.offset ?? 0));
|
|
1447
|
+
const symbols = matched.slice(offset, offset + USAGE_PAGE_SIZE);
|
|
1448
|
+
const total = matched.length;
|
|
1449
|
+
const what = q ? `symbols matching "${input.query}"` : "exported symbols";
|
|
1450
|
+
if (q && total === 0) {
|
|
1451
|
+
return {
|
|
1452
|
+
symbols,
|
|
1453
|
+
total,
|
|
1454
|
+
note: `No exported symbol name contains "${input.query}" (${surface.length} in total). Try a shorter fragment, or omit query to page through all of them.`
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
if (offset >= total && total > 0) {
|
|
1458
|
+
return { symbols, total, note: `offset ${offset} is past the last of ${total} ${what}.` };
|
|
1459
|
+
}
|
|
1460
|
+
const end = offset + symbols.length;
|
|
1461
|
+
if (offset === 0 && end === total) return { symbols, total };
|
|
1462
|
+
return {
|
|
1463
|
+
symbols,
|
|
1464
|
+
total,
|
|
1465
|
+
note: `Showing ${offset + 1}\u2013${end} of ${total} ${what}, sorted by name. ` + (end < total ? `Pass offset: ${end} for the next page, or query with part of a name to jump to it.` : "This is the last page.")
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
function isShallowSurface(surface) {
|
|
1469
|
+
const main = surface.find((s) => s.name === "default");
|
|
1470
|
+
const rest = surface.filter((s) => s !== main);
|
|
1471
|
+
return main?.kind === "variable" && rest.length > 0 && rest.length <= 3 && rest.every((s) => s.kind === "interface" || s.kind === "type" || s.kind === "namespace");
|
|
1472
|
+
}
|
|
1473
|
+
async function handleUsage(db, input) {
|
|
1474
|
+
const resolved = await resolveVersion(db, input.package, input.version);
|
|
1475
|
+
const version = resolved.version;
|
|
1476
|
+
if (!version) {
|
|
1477
|
+
return {
|
|
1478
|
+
package: input.package,
|
|
1479
|
+
version: null,
|
|
1480
|
+
surface: null,
|
|
1481
|
+
available: false,
|
|
1482
|
+
engines: null,
|
|
1483
|
+
note: resolved.unpublished ? `"${input.package}" has no published version ${input.version}. That release does not exist on the npm registry \u2014 check the version before writing code against it.` : `Could not resolve a version for "${input.package}" on npm.`,
|
|
1484
|
+
...resolved.unpublished ? { unpublishedVersion: input.version } : {}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
const deadline = Date.now() + USAGE_EXTRACT_BUDGET_MS;
|
|
1488
|
+
const [surface, compat] = await Promise.all([
|
|
1489
|
+
getOrExtractSurface(db, input.package, version, {
|
|
1490
|
+
budgetMs: USAGE_EXTRACT_BUDGET_MS
|
|
1491
|
+
}),
|
|
1492
|
+
fetchNpmCompatAtVersion(input.package, version).catch(() => null)
|
|
1493
|
+
]);
|
|
1494
|
+
const page = surface ? pageSurface(surface, input) : null;
|
|
1495
|
+
const shallow = surface !== null && isShallowSurface(surface);
|
|
1496
|
+
const types = typesPackageName(input.package);
|
|
1497
|
+
const out = {
|
|
1498
|
+
package: input.package,
|
|
1499
|
+
version,
|
|
1500
|
+
surface: page?.symbols ?? null,
|
|
1501
|
+
available: surface !== null,
|
|
1502
|
+
engines: compat?.engines ?? null,
|
|
1503
|
+
...page ? { totalSymbols: page.total } : {},
|
|
1504
|
+
...shallow ? { shallow: true } : {},
|
|
1505
|
+
note: !surface ? (
|
|
1506
|
+
// The README is not in this response, so "fall back to the README"
|
|
1507
|
+
// left the agent with nothing to act on. Name the two things it can do.
|
|
1508
|
+
`No type-derived API surface for ${input.package}@${version}: it ships no .d.ts and ${types} has no matching major, or extraction is still running (retry in a few seconds). For the names it exports at runtime call resolve_surface; for usage, read https://www.npmjs.com/package/${input.package}/v/${version}.`
|
|
1509
|
+
) : shallow ? `Shallow surface: ${input.package} exports one value typed by ${surface.filter((s) => s.name !== "default").map((s) => s.name).join(", ")}, so its methods are that interface's members and are NOT listed here. Read them in its type declarations (${types} if the package bundles none), or call resolve_surface for the names it exports at runtime.` : page?.note
|
|
1510
|
+
};
|
|
1511
|
+
if (input.knownVersion && input.knownVersion !== version && surface) {
|
|
1512
|
+
const known = await getOrExtractSurface(db, input.package, input.knownVersion, {
|
|
1513
|
+
budgetMs: Math.max(0, deadline - Date.now())
|
|
1514
|
+
});
|
|
1515
|
+
if (known) out.delta = { ...diffSurface(known, surface), fromVersion: input.knownVersion };
|
|
1516
|
+
else {
|
|
1517
|
+
const knownExists = await npmVersionExists(input.package, input.knownVersion);
|
|
1518
|
+
out.deltaNote = knownExists === false ? `No delta: "${input.package}" has no published version ${input.knownVersion}. That release does not exist on the npm registry.` : `No delta: no API surface extracted for ${input.package}@${input.knownVersion} yet. Retry shortly, or compare against a version that has been indexed.`;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
return out;
|
|
1522
|
+
}
|
|
1523
|
+
async function handleReportOutcome(db, input, ownerId = null) {
|
|
1524
|
+
await recordOutcome(db, {
|
|
1525
|
+
ownerId,
|
|
1526
|
+
packageName: input.package,
|
|
1527
|
+
accepted: input.accepted,
|
|
1528
|
+
buildSignal: input.buildSignal ?? null,
|
|
1529
|
+
need: input.need ?? null
|
|
1530
|
+
});
|
|
1531
|
+
return { recorded: true };
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
export {
|
|
1535
|
+
checkCompat,
|
|
1536
|
+
recommend,
|
|
1537
|
+
getSelectionPolicy,
|
|
1538
|
+
getEnforcedPolicy,
|
|
1539
|
+
setSelectionPolicy,
|
|
1540
|
+
listPolicyChanges,
|
|
1541
|
+
summarizeDecisions,
|
|
1542
|
+
daysSince,
|
|
1543
|
+
rowToEvaluate,
|
|
1544
|
+
latestDataAsOf,
|
|
1545
|
+
handleRecommend,
|
|
1546
|
+
handlePolicy,
|
|
1547
|
+
handleEvaluate,
|
|
1548
|
+
handleCompare,
|
|
1549
|
+
handleCompat,
|
|
1550
|
+
handleVerify,
|
|
1551
|
+
USAGE_PAGE_SIZE,
|
|
1552
|
+
handleUsage,
|
|
1553
|
+
handleReportOutcome
|
|
1554
|
+
};
|