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.
Files changed (206) hide show
  1. package/README.md +18 -10
  2. package/dist/auditHandler-JCOJSUYW.js +23 -0
  3. package/dist/bin/lurq.js +3090 -23306
  4. package/dist/browserAuth-B6G27USY.js +95 -0
  5. package/dist/cache-DKI34SVS.js +12 -0
  6. package/dist/check-2FDGUOV7.js +192 -0
  7. package/dist/checkUpgrade-F2WESBPM.js +103 -0
  8. package/dist/chunk-2VMOWSRY.js +300 -0
  9. package/dist/chunk-3GHJR7XR.js +2049 -0
  10. package/dist/chunk-3N3Q3DSW.js +214 -0
  11. package/dist/chunk-4ESO33J6.js +34 -0
  12. package/dist/chunk-4F7ZESPO.js +132 -0
  13. package/dist/chunk-4KCPOMG6.js +68 -0
  14. package/dist/chunk-4ZYCIMY4.js +155 -0
  15. package/dist/chunk-63MUJNZL.js +318 -0
  16. package/dist/chunk-6AAEVQ7I.js +6336 -0
  17. package/dist/chunk-6EQ3ARXA.js +304 -0
  18. package/dist/chunk-6L3U7DZ5.js +23 -0
  19. package/dist/chunk-AHBP533R.js +220 -0
  20. package/dist/chunk-B43LCXBM.js +223 -0
  21. package/dist/chunk-BCANRN5B.js +33 -0
  22. package/dist/chunk-BUALU27S.js +123 -0
  23. package/dist/chunk-BULZAX2U.js +1554 -0
  24. package/dist/chunk-BV32UNLL.js +154 -0
  25. package/dist/chunk-C6GLE626.js +160 -0
  26. package/dist/chunk-CIDWNUAP.js +145 -0
  27. package/dist/chunk-CUP36ND3.js +2126 -0
  28. package/dist/chunk-CW5HJ3LX.js +96 -0
  29. package/dist/chunk-DB2VF5UD.js +315 -0
  30. package/dist/chunk-DWAGCNEL.js +210 -0
  31. package/dist/chunk-EA3DP5HG.js +293 -0
  32. package/dist/chunk-ELGCAM7P.js +184 -0
  33. package/dist/chunk-FW6BD2NO.js +1915 -0
  34. package/dist/chunk-GE37NVLN.js +15 -0
  35. package/dist/chunk-GPTKSNR6.js +171 -0
  36. package/dist/chunk-GRJXQBQZ.js +793 -0
  37. package/dist/chunk-HWBBVLOT.js +92 -0
  38. package/dist/chunk-I2JUKN6Z.js +24 -0
  39. package/dist/chunk-I3M2KJXB.js +56 -0
  40. package/dist/chunk-IQ4SDKKY.js +733 -0
  41. package/dist/chunk-J5FZYN4A.js +116 -0
  42. package/dist/chunk-L6N4JFW2.js +263 -0
  43. package/dist/chunk-LBFGSSF2.js +126 -0
  44. package/dist/chunk-LEGPB7DO.js +81 -0
  45. package/dist/chunk-LO35HLQZ.js +219 -0
  46. package/dist/chunk-LRD4GBYE.js +73 -0
  47. package/dist/chunk-LUAKDHLZ.js +40 -0
  48. package/dist/chunk-LYMPYETA.js +965 -0
  49. package/dist/chunk-M6NYEXCW.js +38 -0
  50. package/dist/chunk-MHUEVBVO.js +35 -0
  51. package/dist/chunk-MYGSLK44.js +37 -0
  52. package/dist/chunk-NH5I4KHH.js +201 -0
  53. package/dist/chunk-OQS4GUSI.js +1391 -0
  54. package/dist/chunk-PSX3GIAN.js +176 -0
  55. package/dist/chunk-Q5VPF4WI.js +26 -0
  56. package/dist/chunk-QKGYDMWB.js +7409 -0
  57. package/dist/chunk-QSL6QN5X.js +115 -0
  58. package/dist/chunk-SHCEBP3J.js +107 -0
  59. package/dist/chunk-SJOVFF7B.js +201 -0
  60. package/dist/chunk-ST2X2GYN.js +373 -0
  61. package/dist/chunk-TNTXWSYA.js +19 -0
  62. package/dist/chunk-U5AT3TD4.js +2128 -0
  63. package/dist/chunk-UDVNA7EA.js +483 -0
  64. package/dist/chunk-VL2E5T2A.js +109 -0
  65. package/dist/chunk-VLRHFMGJ.js +58 -0
  66. package/dist/chunk-WHX2ZFCG.js +4048 -0
  67. package/dist/chunk-WQ23REYT.js +37 -0
  68. package/dist/chunk-XBIDMHSK.js +15425 -0
  69. package/dist/chunk-Y5GI7RL3.js +66 -0
  70. package/dist/client-IRKAMDYZ.js +15 -0
  71. package/dist/commands-FIL5ET6P.js +911 -0
  72. package/dist/compat-R3UTFQ5Q.js +201 -0
  73. package/dist/dashboardIssue-WYYMBFIB.js +192 -0
  74. package/dist/diff-GZA2774U.js +13 -0
  75. package/dist/dist-4XXIXHHZ.js +12945 -0
  76. package/dist/e2b-FVPHDULZ.js +172 -0
  77. package/dist/extract-NKGKLIVT.js +444 -0
  78. package/dist/fetch-QYTUJVG2.js +22 -0
  79. package/dist/firstTouch-QQTJH4QX.js +55 -0
  80. package/dist/handlers-GEJAVZDK.js +53 -0
  81. package/dist/http-ETPYMBGD.js +5360 -0
  82. package/dist/ingestQueue-VKN4KBIF.js +35 -0
  83. package/dist/install-EK3W2COX.js +20 -0
  84. package/dist/installSkill-VQJWJVCL.js +52 -0
  85. package/dist/inventory-5AWSLJYU.js +31 -0
  86. package/dist/local-XPZMRH5R.js +13 -0
  87. package/dist/mcp-3JHD5SUZ.js +472 -0
  88. package/dist/mcpHandlers-QFN4MCFL.js +22 -0
  89. package/dist/mcpScan-QZHWENIP.js +1871 -0
  90. package/dist/mcpScanWorkflow-JPYCBGC4.js +81 -0
  91. package/dist/mcpStack-BYJCZNIV.js +20 -0
  92. package/dist/packages-THVFMWFP.js +42 -0
  93. package/dist/plan-V4OEI3J4.js +463 -0
  94. package/dist/planView-JRXGCG2N.js +79 -0
  95. package/dist/policy-3P6BGRDN.js +115 -0
  96. package/dist/references-6N7VEPHT.js +14 -0
  97. package/dist/release-4U4ABNSJ.js +128 -0
  98. package/dist/remote-YGCXYI4V.js +32 -0
  99. package/dist/reportRuns-CKQQYLY4.js +64 -0
  100. package/dist/sandbox-MEZLAODF.js +39 -0
  101. package/dist/server-KFUD5X57.js +55 -0
  102. package/dist/surface-V62GE554.js +141 -0
  103. package/dist/surface-YL6O67W3.js +36 -0
  104. package/dist/surfaceHandlers-X2IZKA3E.js +20 -0
  105. package/dist/typecheck-X7U5ELMR.js +268 -0
  106. package/dist/types-M5JP2W3D.js +10 -0
  107. package/dist/uninstall-P7RGBX6G.js +174 -0
  108. package/dist/upgrade-NQ5IWFZB.js +596 -0
  109. package/dist/upgradePlan-A4LRKLEG.js +146 -0
  110. package/dist/userConfig-Z3GQE776.js +20 -0
  111. package/dist/watch-DGFMWUDL.js +214 -0
  112. package/package.json +26 -21
  113. package/templates/agent-rules.md +4 -4
  114. package/templates/skill-instructions.md +13 -14
  115. package/dist/bin/lurq.js.map +0 -1
  116. package/dist/index.d.ts +0 -805
  117. package/dist/index.js +0 -23570
  118. package/dist/index.js.map +0 -1
  119. package/drizzle/0000_confused_fabian_cortez.sql +0 -54
  120. package/drizzle/0001_aspiring_timeslip.sql +0 -15
  121. package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
  122. package/drizzle/0003_violet_giant_girl.sql +0 -14
  123. package/drizzle/0004_slim_valeria_richards.sql +0 -15
  124. package/drizzle/0005_petite_luke_cage.sql +0 -12
  125. package/drizzle/0006_calm_nighthawk.sql +0 -3
  126. package/drizzle/0007_slimy_naoko.sql +0 -1
  127. package/drizzle/0008_peaceful_tigra.sql +0 -1
  128. package/drizzle/0009_smooth_harrier.sql +0 -10
  129. package/drizzle/0010_curved_amphibian.sql +0 -2
  130. package/drizzle/0011_compat_provenance_closures.sql +0 -11
  131. package/drizzle/0012_api_surfaces.sql +0 -9
  132. package/drizzle/0013_nice_thanos.sql +0 -1
  133. package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
  134. package/drizzle/0015_past_fat_cobra.sql +0 -11
  135. package/drizzle/0016_futuristic_junta.sql +0 -50
  136. package/drizzle/0017_absent_grandmaster.sql +0 -20
  137. package/drizzle/0018_rainy_proemial_gods.sql +0 -1
  138. package/drizzle/0019_dashing_korvac.sql +0 -11
  139. package/drizzle/0020_normal_vulture.sql +0 -3
  140. package/drizzle/0021_dazzling_blue_shield.sql +0 -17
  141. package/drizzle/0022_massive_ghost_rider.sql +0 -24
  142. package/drizzle/0023_next_shatterstar.sql +0 -1
  143. package/drizzle/0024_dusty_maelstrom.sql +0 -15
  144. package/drizzle/0025_gifted_harpoon.sql +0 -5
  145. package/drizzle/0026_awesome_mordo.sql +0 -1
  146. package/drizzle/0027_charming_ulik.sql +0 -2
  147. package/drizzle/0028_romantic_loners.sql +0 -2
  148. package/drizzle/0029_busy_korvac.sql +0 -16
  149. package/drizzle/0030_steady_mathemanic.sql +0 -6
  150. package/drizzle/0031_redundant_maginty.sql +0 -2
  151. package/drizzle/0032_stack_resolutions.sql +0 -14
  152. package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
  153. package/drizzle/0034_uneven_thunderbird.sql +0 -1
  154. package/drizzle/0035_api_key_scopes.sql +0 -1
  155. package/drizzle/0036_policy_audit.sql +0 -21
  156. package/drizzle/0037_subscription_seats.sql +0 -1
  157. package/drizzle/0038_symbol_offsets.sql +0 -2
  158. package/drizzle/0039_mcp_runtime_scans.sql +0 -80
  159. package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
  160. package/drizzle/0041_account_email.sql +0 -38
  161. package/drizzle/0042_alert_channels.sql +0 -21
  162. package/drizzle/meta/0000_snapshot.json +0 -370
  163. package/drizzle/meta/0001_snapshot.json +0 -482
  164. package/drizzle/meta/0002_snapshot.json +0 -575
  165. package/drizzle/meta/0003_snapshot.json +0 -667
  166. package/drizzle/meta/0004_snapshot.json +0 -769
  167. package/drizzle/meta/0005_snapshot.json +0 -864
  168. package/drizzle/meta/0006_snapshot.json +0 -882
  169. package/drizzle/meta/0007_snapshot.json +0 -876
  170. package/drizzle/meta/0008_snapshot.json +0 -882
  171. package/drizzle/meta/0009_snapshot.json +0 -948
  172. package/drizzle/meta/0010_snapshot.json +0 -969
  173. package/drizzle/meta/0011_snapshot.json +0 -1049
  174. package/drizzle/meta/0012_snapshot.json +0 -1115
  175. package/drizzle/meta/0013_snapshot.json +0 -1121
  176. package/drizzle/meta/0014_snapshot.json +0 -1190
  177. package/drizzle/meta/0015_snapshot.json +0 -1280
  178. package/drizzle/meta/0016_snapshot.json +0 -1683
  179. package/drizzle/meta/0017_snapshot.json +0 -1834
  180. package/drizzle/meta/0018_snapshot.json +0 -1834
  181. package/drizzle/meta/0019_snapshot.json +0 -1909
  182. package/drizzle/meta/0020_snapshot.json +0 -1921
  183. package/drizzle/meta/0021_snapshot.json +0 -2045
  184. package/drizzle/meta/0022_snapshot.json +0 -2246
  185. package/drizzle/meta/0023_snapshot.json +0 -2252
  186. package/drizzle/meta/0024_snapshot.json +0 -2375
  187. package/drizzle/meta/0025_snapshot.json +0 -2407
  188. package/drizzle/meta/0026_snapshot.json +0 -2413
  189. package/drizzle/meta/0027_snapshot.json +0 -2425
  190. package/drizzle/meta/0028_snapshot.json +0 -2438
  191. package/drizzle/meta/0029_snapshot.json +0 -2546
  192. package/drizzle/meta/0030_snapshot.json +0 -2586
  193. package/drizzle/meta/0031_snapshot.json +0 -2598
  194. package/drizzle/meta/0032_snapshot.json +0 -2699
  195. package/drizzle/meta/0033_snapshot.json +0 -2712
  196. package/drizzle/meta/0034_snapshot.json +0 -2719
  197. package/drizzle/meta/0035_snapshot.json +0 -2726
  198. package/drizzle/meta/0036_snapshot.json +0 -2876
  199. package/drizzle/meta/0037_snapshot.json +0 -2883
  200. package/drizzle/meta/0038_snapshot.json +0 -2895
  201. package/drizzle/meta/0039_snapshot.json +0 -3501
  202. package/drizzle/meta/0040_snapshot.json +0 -3527
  203. package/drizzle/meta/0041_snapshot.json +0 -3824
  204. package/drizzle/meta/0042_snapshot.json +0 -3960
  205. package/drizzle/meta/_journal.json +0 -307
  206. 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
+ };