lurqrun 0.1.7 → 0.1.9

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 (122) hide show
  1. package/README.md +312 -148
  2. package/dist/{auditHandler-JCOJSUYW.js → auditHandler-G7PHKMZR.js} +12 -11
  3. package/dist/autopilotInit-JO2SH5K3.js +266 -0
  4. package/dist/bin/lurq.js +238 -83
  5. package/dist/{browserAuth-B6G27USY.js → browserAuth-PVYODBNR.js} +4 -3
  6. package/dist/{cache-DKI34SVS.js → cache-ND4VOUVY.js} +2 -2
  7. package/dist/check-A3FJE62G.js +2613 -0
  8. package/dist/{check-2FDGUOV7.js → check-PQSQ2MMA.js} +1 -1
  9. package/dist/checkUpgrade-Q4CV7GXG.js +24 -0
  10. package/dist/{chunk-BCANRN5B.js → chunk-2GAYNIHG.js} +8 -1
  11. package/dist/chunk-2LXYRDUN.js +92 -0
  12. package/dist/chunk-2XMD4H2I.js +243 -0
  13. package/dist/chunk-3D2MPGOQ.js +181 -0
  14. package/dist/chunk-53SG6XWA.js +141 -0
  15. package/dist/chunk-5ABNDB3I.js +37 -0
  16. package/dist/chunk-5ALIJFDA.js +114 -0
  17. package/dist/{chunk-LYMPYETA.js → chunk-6353IJJ5.js} +270 -6
  18. package/dist/{chunk-M6NYEXCW.js → chunk-64DPQSXV.js} +1 -1
  19. package/dist/chunk-64YOP5BQ.js +320 -0
  20. package/dist/{chunk-2VMOWSRY.js → chunk-6DM6UQFD.js} +19 -1
  21. package/dist/{chunk-NH5I4KHH.js → chunk-75LMEMJ3.js} +1 -5
  22. package/dist/{chunk-FW6BD2NO.js → chunk-ABH4BTK4.js} +458 -121
  23. package/dist/chunk-AHRDDKNO.js +90 -0
  24. package/dist/chunk-AQ6IVSFB.js +119 -0
  25. package/dist/chunk-BM3LF5T6.js +132 -0
  26. package/dist/chunk-D3K6IZVR.js +12845 -0
  27. package/dist/chunk-DONTW3NE.js +171 -0
  28. package/dist/{chunk-UDVNA7EA.js → chunk-ETY4FSOW.js} +161 -8
  29. package/dist/{chunk-VL2E5T2A.js → chunk-FHRT32JH.js} +6 -3
  30. package/dist/{chunk-DWAGCNEL.js → chunk-GM5Y555O.js} +36 -103
  31. package/dist/{chunk-6L3U7DZ5.js → chunk-GNNGXVCG.js} +1 -1
  32. package/dist/{chunk-PSX3GIAN.js → chunk-HWFX4BC4.js} +2 -1
  33. package/dist/{checkUpgrade-F2WESBPM.js → chunk-ILKYIVYV.js} +72 -10
  34. package/dist/{chunk-HWBBVLOT.js → chunk-J3NUERU7.js} +1 -1
  35. package/dist/chunk-J3U2CPSK.js +136 -0
  36. package/dist/{chunk-Y5GI7RL3.js → chunk-J6XQ7OPZ.js} +9 -1
  37. package/dist/{chunk-3N3Q3DSW.js → chunk-JGIJZJ3X.js} +8 -8
  38. package/dist/{chunk-DB2VF5UD.js → chunk-JJCQVOC6.js} +16 -11
  39. package/dist/{chunk-LBFGSSF2.js → chunk-KMV4Y6R3.js} +2 -1
  40. package/dist/{chunk-L6N4JFW2.js → chunk-KP5ZDDPE.js} +81 -4
  41. package/dist/{chunk-CW5HJ3LX.js → chunk-KT7PWDNV.js} +1 -1
  42. package/dist/{chunk-SJOVFF7B.js → chunk-KYKSZ3XZ.js} +3 -2
  43. package/dist/{chunk-GPTKSNR6.js → chunk-L3V7AVM6.js} +2 -1
  44. package/dist/{chunk-CUP36ND3.js → chunk-M2CYYBAH.js} +145 -36
  45. package/dist/{chunk-IQ4SDKKY.js → chunk-N2NYPTRU.js} +31 -228
  46. package/dist/chunk-N4TJO44E.js +603 -0
  47. package/dist/chunk-NEZNAPPN.js +41 -0
  48. package/dist/{chunk-WQ23REYT.js → chunk-NGMJQBOV.js} +1 -1
  49. package/dist/{chunk-BULZAX2U.js → chunk-POHTPT37.js} +55 -38
  50. package/dist/{chunk-EA3DP5HG.js → chunk-PZZU3FQY.js} +155 -25
  51. package/dist/{chunk-4F7ZESPO.js → chunk-Q7CMOU5C.js} +3 -5
  52. package/dist/{chunk-LRD4GBYE.js → chunk-QUIMQ7YL.js} +1 -1
  53. package/dist/chunk-RG3YLU6E.js +49 -0
  54. package/dist/{chunk-4ZYCIMY4.js → chunk-SFY5MPJE.js} +25 -4
  55. package/dist/{chunk-ST2X2GYN.js → chunk-SHZFOSXC.js} +109 -20
  56. package/dist/{chunk-CIDWNUAP.js → chunk-SOXFMMXC.js} +8 -2
  57. package/dist/{chunk-B43LCXBM.js → chunk-SWMWVK2R.js} +81 -16
  58. package/dist/{chunk-J5FZYN4A.js → chunk-TVOCVIM7.js} +1 -1
  59. package/dist/{chunk-U5AT3TD4.js → chunk-X4W64MHU.js} +1735 -1858
  60. package/dist/{chunk-BV32UNLL.js → chunk-XA2SLNBR.js} +2 -2
  61. package/dist/{chunk-AHBP533R.js → chunk-XBTYJQEK.js} +7 -5
  62. package/dist/chunk-XGOV6KV4.js +413 -0
  63. package/dist/{chunk-OQS4GUSI.js → chunk-XIFQIFSZ.js} +210 -661
  64. package/dist/{chunk-XBIDMHSK.js → chunk-XLXPVN3Y.js} +2 -34
  65. package/dist/{chunk-LO35HLQZ.js → chunk-ZJ5XHA77.js} +25 -4
  66. package/dist/{client-IRKAMDYZ.js → client-SJIWK4CK.js} +3 -3
  67. package/dist/{commands-FIL5ET6P.js → commands-6BBLN5N5.js} +145 -33
  68. package/dist/{compat-R3UTFQ5Q.js → compat-6B3CRT5F.js} +6 -6
  69. package/dist/constants-HX3RTD6Q.js +24 -0
  70. package/dist/{dashboardIssue-WYYMBFIB.js → dashboardIssue-KBKLQMGC.js} +105 -23
  71. package/dist/{diff-GZA2774U.js → diff-I4SNUHU3.js} +1 -1
  72. package/dist/domains-RKRPVP5A.js +14 -0
  73. package/dist/env-UN6KGZ7S.js +15 -0
  74. package/dist/envCheck-IL22OTSC.js +71 -0
  75. package/dist/{extract-NKGKLIVT.js → extract-6WG265BL.js} +14 -3
  76. package/dist/{fetch-QYTUJVG2.js → fetch-GHIP3VHN.js} +4 -4
  77. package/dist/{firstTouch-QQTJH4QX.js → firstTouch-M64T2MWF.js} +1 -1
  78. package/dist/fix-Z7ZY55HB.js +220 -0
  79. package/dist/{handlers-GEJAVZDK.js → handlers-GOXW2GWZ.js} +19 -17
  80. package/dist/hook-TWFLIDK3.js +412 -0
  81. package/dist/{http-ETPYMBGD.js → http-7VF2RE2D.js} +2097 -509
  82. package/dist/{ingestQueue-VKN4KBIF.js → ingestQueue-RSEPMORK.js} +14 -12
  83. package/dist/{install-EK3W2COX.js → install-55EPX34C.js} +6 -6
  84. package/dist/{installSkill-VQJWJVCL.js → installSkill-A7KKPDMS.js} +21 -3
  85. package/dist/manifest-ENMIC54B.js +13 -0
  86. package/dist/{mcp-3JHD5SUZ.js → mcp-CM6CDLHM.js} +60 -32
  87. package/dist/mcpConfig-FENV6NI7.js +80 -0
  88. package/dist/{mcpHandlers-QFN4MCFL.js → mcpHandlers-2UCLKMWN.js} +7 -7
  89. package/dist/{mcpScan-QZHWENIP.js → mcpScan-CKBQNK5H.js} +306 -442
  90. package/dist/{mcpScanWorkflow-JPYCBGC4.js → mcpScanWorkflow-NFUD3S7U.js} +29 -12
  91. package/dist/{mcpStack-BYJCZNIV.js → mcpStack-FLQ6XY2P.js} +1 -1
  92. package/dist/model-ASMZWLM7.js +214 -0
  93. package/dist/{packages-THVFMWFP.js → packages-2XRHNBJ7.js} +3 -5
  94. package/dist/{plan-V4OEI3J4.js → plan-XRZS6C7Z.js} +23 -21
  95. package/dist/{planView-JRXGCG2N.js → planView-ZAKRTGVS.js} +4 -1
  96. package/dist/{policy-3P6BGRDN.js → policy-EQT43P7S.js} +5 -5
  97. package/dist/probe-ZEZFNIQZ.js +610 -0
  98. package/dist/publicUpgrades-JZEOV24A.js +42 -0
  99. package/dist/{references-6N7VEPHT.js → references-I6Y4BSRR.js} +3 -1
  100. package/dist/{release-4U4ABNSJ.js → release-EUOM5I3R.js} +5 -5
  101. package/dist/{remote-YGCXYI4V.js → remote-I5O43WAY.js} +13 -3
  102. package/dist/rename-OS6JYBZQ.js +204 -0
  103. package/dist/{reportRuns-CKQQYLY4.js → reportRuns-O4X7ZFTX.js} +15 -2
  104. package/dist/safeFetch-COERXGFN.js +19 -0
  105. package/dist/{sandbox-MEZLAODF.js → sandbox-46TZNYZT.js} +4 -4
  106. package/dist/sarif-WLVJFJ7U.js +125 -0
  107. package/dist/server-LROMHS62.js +59 -0
  108. package/dist/{surface-YL6O67W3.js → surface-CZMHQLV3.js} +5 -3
  109. package/dist/{surface-V62GE554.js → surface-OH4TZR32.js} +18 -16
  110. package/dist/{surfaceHandlers-X2IZKA3E.js → surfaceHandlers-BDYRZMAC.js} +5 -5
  111. package/dist/{typecheck-X7U5ELMR.js → typecheck-NGOKNAJY.js} +23 -9
  112. package/dist/{uninstall-P7RGBX6G.js → uninstall-4E5YUDQO.js} +30 -4
  113. package/dist/{upgrade-NQ5IWFZB.js → upgrade-S72W7JVC.js} +86 -32
  114. package/dist/{upgradePlan-A4LRKLEG.js → upgradePlan-F3OUNL3E.js} +46 -11
  115. package/dist/upkeepHandler-7Q56A4UA.js +13 -0
  116. package/dist/{userConfig-Z3GQE776.js → userConfig-ICASHI47.js} +1 -1
  117. package/dist/{watch-DGFMWUDL.js → watch-LZPOKWCL.js} +24 -34
  118. package/package.json +3 -2
  119. package/templates/skill-instructions.md +22 -3
  120. package/dist/chunk-6AAEVQ7I.js +0 -6336
  121. package/dist/chunk-I2JUKN6Z.js +0 -24
  122. package/dist/server-KFUD5X57.js +0 -55
@@ -5,16 +5,12 @@ import {
5
5
  import {
6
6
  installKey,
7
7
  queryVulnerableInstalls
8
- } from "./chunk-LRD4GBYE.js";
9
- import {
10
- DEFAULT_REPO_POLICY,
11
- REPO_DRIFT_DETAIL_CAP,
12
- TRANSITIVE_DETAIL_CAP
13
- } from "./chunk-BCANRN5B.js";
8
+ } from "./chunk-QUIMQ7YL.js";
14
9
  import {
15
10
  ADOPTION,
16
11
  COMPOSITE,
17
12
  CONFIDENCE,
13
+ DISCOVERY,
18
14
  EFFICIENCY,
19
15
  FIELD,
20
16
  HEALTH_WEIGHTS,
@@ -23,30 +19,24 @@ import {
23
19
  QUALITY,
24
20
  QUALITY_WEIGHTS,
25
21
  RELIABILITY
26
- } from "./chunk-2VMOWSRY.js";
27
- import {
28
- withBudget
29
- } from "./chunk-4ESO33J6.js";
22
+ } from "./chunk-6DM6UQFD.js";
30
23
  import {
31
24
  getCompatMetadata
32
- } from "./chunk-CW5HJ3LX.js";
25
+ } from "./chunk-KT7PWDNV.js";
33
26
  import {
34
27
  fetchNpmCompatAtVersion,
35
28
  fetchNpmRegistry,
36
29
  npmPackageExists
37
- } from "./chunk-AHBP533R.js";
30
+ } from "./chunk-XBTYJQEK.js";
38
31
  import {
39
- HttpError,
40
- httpGetJson,
41
- httpRequest
42
- } from "./chunk-SJOVFF7B.js";
32
+ withBudget
33
+ } from "./chunk-4ESO33J6.js";
43
34
  import {
44
- ensureSeedEntry,
45
35
  getPackageByName,
46
36
  stampFirstRequester,
47
37
  upsertPackage,
48
38
  upsertPackageVersions
49
- } from "./chunk-NH5I4KHH.js";
39
+ } from "./chunk-75LMEMJ3.js";
50
40
  import {
51
41
  CATEGORIES,
52
42
  DEFAULT_ECOSYSTEM,
@@ -54,13 +44,33 @@ import {
54
44
  isFrontendCategory
55
45
  } from "./chunk-VLRHFMGJ.js";
56
46
  import {
47
+ discoveryQueue,
57
48
  packageVersions,
58
49
  packages,
59
50
  recommendationOutcomes,
60
51
  repoAlerts,
52
+ repoPolicyDefaults,
61
53
  repos,
62
- seedPackages
63
- } from "./chunk-LYMPYETA.js";
54
+ seedPackages,
55
+ upgradeRuns
56
+ } from "./chunk-6353IJJ5.js";
57
+ import {
58
+ GithubAppError,
59
+ installationPost
60
+ } from "./chunk-GM5Y555O.js";
61
+ import {
62
+ DEFAULT_REPO_POLICY,
63
+ REPO_DRIFT_DETAIL_CAP,
64
+ TRANSITIVE_DETAIL_CAP
65
+ } from "./chunk-2GAYNIHG.js";
66
+ import {
67
+ HttpError,
68
+ httpGetJson,
69
+ httpRequest
70
+ } from "./chunk-KYKSZ3XZ.js";
71
+ import {
72
+ WORKFLOW_PATH
73
+ } from "./chunk-SWMWVK2R.js";
64
74
  import {
65
75
  require_semver
66
76
  } from "./chunk-3GHJR7XR.js";
@@ -73,7 +83,7 @@ import {
73
83
  import {
74
84
  CACHE_TTL,
75
85
  EMBEDDING_DIM
76
- } from "./chunk-GPTKSNR6.js";
86
+ } from "./chunk-L3V7AVM6.js";
77
87
  import {
78
88
  __toESM,
79
89
  init_esm_shims
@@ -82,9 +92,46 @@ import {
82
92
  // src/pipeline/ingestQueue.ts
83
93
  init_esm_shims();
84
94
 
95
+ // src/db/discovery.ts
96
+ init_esm_shims();
97
+ import { eq, sql } from "drizzle-orm";
98
+ async function getQueuedNames(db) {
99
+ const rows = await db.select({ name: discoveryQueue.name }).from(discoveryQueue);
100
+ return new Set(rows.map((r) => r.name));
101
+ }
102
+ async function enqueueCandidates(db, candidates) {
103
+ if (candidates.length === 0) return 0;
104
+ const rows = candidates.map((c) => ({ name: c.name, discoveredVia: c.via }));
105
+ const inserted = await db.insert(discoveryQueue).values(rows).onConflictDoNothing({ target: discoveryQueue.name }).returning({ id: discoveryQueue.id });
106
+ return inserted.length;
107
+ }
108
+ async function enqueueDemand(db, name, requestedByOwnerId) {
109
+ await db.insert(discoveryQueue).values({ name, discoveredVia: "reactive", requestedByOwnerId }).onConflictDoUpdate({
110
+ target: discoveryQueue.name,
111
+ set: {
112
+ discoveredVia: "reactive",
113
+ status: sql`case when ${discoveryQueue.status} = 'failed' then 'failed' else 'pending' end`,
114
+ requestedByOwnerId: sql`coalesce(${discoveryQueue.requestedByOwnerId}, excluded.requested_by_owner_id)`
115
+ }
116
+ });
117
+ }
118
+ async function recordIngestFailure(db, name, maxAttempts) {
119
+ const [row] = await db.update(discoveryQueue).set({
120
+ attempts: sql`${discoveryQueue.attempts} + 1`,
121
+ status: sql`case when ${discoveryQueue.attempts} + 1 >= ${maxAttempts} then 'failed' else ${discoveryQueue.status} end`
122
+ }).where(eq(discoveryQueue.name, name)).returning({ attempts: discoveryQueue.attempts });
123
+ return row?.attempts ?? 0;
124
+ }
125
+ async function setDiscoveryStatus(db, name, data) {
126
+ await db.update(discoveryQueue).set({
127
+ status: data.status,
128
+ ...data.preScore !== void 0 ? { preScore: data.preScore } : {}
129
+ }).where(eq(discoveryQueue.name, name));
130
+ }
131
+
85
132
  // src/pipeline/single.ts
86
133
  init_esm_shims();
87
- import { and as and5, eq as eq5, isNotNull, sql as sql4 } from "drizzle-orm";
134
+ import { and as and5, eq as eq6, isNotNull, sql as sql6 } from "drizzle-orm";
88
135
 
89
136
  // src/ingestion/collect.ts
90
137
  init_esm_shims();
@@ -181,9 +228,7 @@ async function fetchAdvisories(name, version, fetchImpl) {
181
228
  })
182
229
  )
183
230
  );
184
- return details.filter(
185
- (d) => d.status === "fulfilled"
186
- ).map((d) => parseAdvisoryDetail(d.value.data));
231
+ return details.filter((d) => d.status === "fulfilled").map((d) => parseAdvisoryDetail(d.value.data));
187
232
  }
188
233
  async function fetchDependents(name, version, fetchImpl) {
189
234
  const url = `https://${HOST2}/v3alpha/systems/npm/packages/${encodeName(name)}/versions/${encodeURIComponent(version)}:dependents`;
@@ -292,10 +337,12 @@ function last90DayRange(now = /* @__PURE__ */ new Date()) {
292
337
  }
293
338
  async function fetchWeeklyDownloads(name, fetchImpl) {
294
339
  try {
295
- const { data } = await httpGetJson(
296
- `https://${HOST4}/downloads/point/last-week/${name}`,
297
- { host: HOST4, ttlMs: CACHE_TTL.npmDownloads, retries: 5, fetchImpl }
298
- );
340
+ const { data } = await httpGetJson(`https://${HOST4}/downloads/point/last-week/${name}`, {
341
+ host: HOST4,
342
+ ttlMs: CACHE_TTL.npmDownloads,
343
+ retries: 5,
344
+ fetchImpl
345
+ });
299
346
  return parseWeeklyDownloads(data);
300
347
  } catch (err) {
301
348
  if (err instanceof HttpError && err.status === 404) return null;
@@ -328,7 +375,11 @@ async function attempt(source, errors, fn) {
328
375
  async function collectSignals(name, category, deps = {}) {
329
376
  const { githubToken, fetchImpl } = deps;
330
377
  const errors = [];
331
- const registry = await attempt("npm-registry", errors, () => fetchNpmRegistry(name, fetchImpl));
378
+ const registry = await attempt(
379
+ "npm-registry",
380
+ errors,
381
+ () => fetchNpmRegistry(name, fetchImpl, { fresh: deps.freshRegistry })
382
+ );
332
383
  const repo = registry?.repo ?? null;
333
384
  const version = registry?.latestVersion ?? null;
334
385
  const [downloads, github, depsDev, bundle] = await Promise.all([
@@ -337,7 +388,11 @@ async function collectSignals(name, category, deps = {}) {
337
388
  const downloadGrowth90d = await fetchDownloadGrowth(name, fetchImpl);
338
389
  return { weeklyDownloads, downloadGrowth90d };
339
390
  }),
340
- repo && githubToken ? attempt("github", errors, () => fetchGithubRepo(repo.owner, repo.repo, githubToken, fetchImpl)) : Promise.resolve(null),
391
+ repo && githubToken ? attempt(
392
+ "github",
393
+ errors,
394
+ () => fetchGithubRepo(repo.owner, repo.repo, githubToken, fetchImpl)
395
+ ) : Promise.resolve(null),
341
396
  attempt("deps-dev", errors, () => fetchDepsDev(name, version, repo, fetchImpl)),
342
397
  attempt("bundlephobia", errors, () => fetchBundlephobia(name, category, fetchImpl))
343
398
  ]);
@@ -502,7 +557,9 @@ var OpenAISummaryProvider = class {
502
557
  inferredCategory
503
558
  };
504
559
  } catch (err) {
505
- logger.warn(`summary LLM failed for ${input.name}, using fallback: ${err.message}`);
560
+ logger.warn(
561
+ `summary LLM failed for ${input.name}, using fallback: ${err.message}`
562
+ );
506
563
  return new FallbackSummaryProvider().generate(input);
507
564
  }
508
565
  }
@@ -542,30 +599,84 @@ async function buildSummaryInput(signals, category, fetchImpl) {
542
599
  // src/search/categoryInference.ts
543
600
  init_esm_shims();
544
601
  var RULES = [
545
- { category: "meta-framework", patterns: /\b(meta-?framework|next\.?js|nuxt|remix|astro|gatsby|sveltekit|full-?stack framework)\b/ },
546
- { category: "state-management", patterns: /\b(state management|global state|store|redux|zustand|jotai|mobx|atoms?)\b/ },
602
+ {
603
+ category: "meta-framework",
604
+ patterns: /\b(meta-?framework|next\.?js|nuxt|remix|astro|gatsby|sveltekit|full-?stack framework)\b/
605
+ },
606
+ {
607
+ category: "state-management",
608
+ patterns: /\b(state management|global state|store|redux|zustand|jotai|mobx|atoms?)\b/
609
+ },
547
610
  { category: "routing", patterns: /\b(rout(e|er|ing)|navigation|url matching)\b/ },
548
- { category: "orm", patterns: /\b(orm|object-?relational|query builder|prisma|drizzle|sequelize|typeorm)\b/ },
549
- { category: "database-client", patterns: /\b(database (driver|client)|postgres|mysql|mongo(db)?|redis|sqlite|db driver)\b/ },
550
- { category: "ui-component-library", patterns: /\b(component library|ui kit|ui components?|design system|buttons?|modal|dialog)\b/ },
611
+ {
612
+ category: "orm",
613
+ patterns: /\b(orm|object-?relational|query builder|prisma|drizzle|sequelize|typeorm)\b/
614
+ },
615
+ {
616
+ category: "database-client",
617
+ patterns: /\b(database (driver|client)|postgres|mysql|mongo(db)?|redis|sqlite|db driver)\b/
618
+ },
619
+ {
620
+ category: "ui-component-library",
621
+ patterns: /\b(component library|ui kit|ui components?|design system|buttons?|modal|dialog)\b/
622
+ },
551
623
  { category: "styling", patterns: /\b(styl(e|ing)|css|tailwind|sass|scss|class ?names?|theme)\b/ },
552
624
  { category: "forms", patterns: /\b(forms?|form (state|library|handling)|input handling)\b/ },
553
- { category: "validation", patterns: /\b(validat(e|ion|or)|schema|parse input|type ?safe parsing)\b/ },
554
- { category: "data-fetching", patterns: /\b(data fetching|server state|react query|swr|graphql client|caching queries)\b/ },
555
- { category: "http-client", patterns: /\b(http client|fetch wrapper|rest client|make (a )?request|ajax|api calls?)\b/ },
556
- { category: "auth", patterns: /\b(auth(entication|orization)?|login|session|jwt|oauth|password hashing)\b/ },
557
- { category: "testing", patterns: /\b(test(ing|s)?|unit test|e2e|assertion|mocking|test runner)\b/ },
558
- { category: "bundler", patterns: /\b(bundler|bundle (modules?|code)|webpack|rollup|esbuild|parcel)\b/ },
559
- { category: "build-tool", patterns: /\b(build tool|dev server|monorepo|task runner|compile|transpile)\b/ },
625
+ {
626
+ category: "validation",
627
+ patterns: /\b(validat(e|ion|or)|schema|parse input|type ?safe parsing)\b/
628
+ },
629
+ {
630
+ category: "data-fetching",
631
+ patterns: /\b(data fetching|server state|react query|swr|graphql client|caching queries)\b/
632
+ },
633
+ {
634
+ category: "http-client",
635
+ patterns: /\b(http client|fetch wrapper|rest client|make (a )?request|ajax|api calls?)\b/
636
+ },
637
+ {
638
+ category: "auth",
639
+ patterns: /\b(auth(entication|orization)?|login|session|jwt|oauth|password hashing)\b/
640
+ },
641
+ {
642
+ category: "testing",
643
+ patterns: /\b(test(ing|s)?|unit test|e2e|assertion|mocking|test runner)\b/
644
+ },
645
+ {
646
+ category: "bundler",
647
+ patterns: /\b(bundler|bundle (modules?|code)|webpack|rollup|esbuild|parcel)\b/
648
+ },
649
+ {
650
+ category: "build-tool",
651
+ patterns: /\b(build tool|dev server|monorepo|task runner|compile|transpile)\b/
652
+ },
560
653
  // date-time precedes linting: "date formatting" must not be captured by linting's
561
654
  // bare "formatting" token before the date rule is reached (first-match-wins).
562
- { category: "date-time", patterns: /\b(dates?|date ?time|time(zone)?s?|calendar|parse dates?|format dates?)\b/ },
563
- { category: "linting", patterns: /\b(lint(er|ing)?|formatter|formatting|code (quality|style|format)|prettier|eslint)\b/ },
655
+ {
656
+ category: "date-time",
657
+ patterns: /\b(dates?|date ?time|time(zone)?s?|calendar|parse dates?|format dates?)\b/
658
+ },
659
+ {
660
+ category: "linting",
661
+ patterns: /\b(lint(er|ing)?|formatter|formatting|code (quality|style|format)|prettier|eslint)\b/
662
+ },
564
663
  { category: "animation", patterns: /\b(animat(e|ion)|motion|transition|spring|gsap|3d)\b/ },
565
- { category: "charts", patterns: /\b(charts?|graphs?|data ?vis(ualization)?|plots?|dashboards?)\b/ },
566
- { category: "i18n", patterns: /\b(i18n|internationali[sz]ation|localization|translat(e|ion)|locale)\b/ },
567
- { category: "framework", patterns: /\b(framework|react|vue|svelte|angular|web server|backend framework)\b/ },
568
- { category: "utility", patterns: /\b(debounce|throttle|deep ?clone|slugify|uuid|util(ity|ities)?|helper|lodash|retry)\b/ }
664
+ {
665
+ category: "charts",
666
+ patterns: /\b(charts?|graphs?|data ?vis(ualization)?|plots?|dashboards?)\b/
667
+ },
668
+ {
669
+ category: "i18n",
670
+ patterns: /\b(i18n|internationali[sz]ation|localization|translat(e|ion)|locale)\b/
671
+ },
672
+ {
673
+ category: "framework",
674
+ patterns: /\b(framework|react|vue|svelte|angular|web server|backend framework)\b/
675
+ },
676
+ {
677
+ category: "utility",
678
+ patterns: /\b(debounce|throttle|deep ?clone|slugify|uuid|util(ity|ities)?|helper|lodash|retry)\b/
679
+ }
569
680
  ];
570
681
  function inferCategory(need) {
571
682
  const text = need.toLowerCase();
@@ -636,17 +747,24 @@ function computeMaintenance(input, now) {
636
747
  const components = [];
637
748
  const days = daysSince(input.lastReleaseAt, now);
638
749
  if (days !== null) {
639
- const recency = days <= MAINTENANCE.freshDays ? 100 : clamp(100 * (MAINTENANCE.staleDays - days) / (MAINTENANCE.staleDays - MAINTENANCE.freshDays));
750
+ const recency = days <= MAINTENANCE.freshDays ? 100 : clamp(
751
+ 100 * (MAINTENANCE.staleDays - days) / (MAINTENANCE.staleDays - MAINTENANCE.freshDays)
752
+ );
640
753
  components.push({ value: recency, weight: MAINTENANCE_WEIGHTS.recency });
641
754
  }
642
755
  if (input.releasesLast12mo !== null) {
643
- const cadence = clamp(Math.min(input.releasesLast12mo, MAINTENANCE.cadenceCap) / MAINTENANCE.cadenceCap * 100);
756
+ const cadence = clamp(
757
+ Math.min(input.releasesLast12mo, MAINTENANCE.cadenceCap) / MAINTENANCE.cadenceCap * 100
758
+ );
644
759
  components.push({ value: cadence, weight: MAINTENANCE_WEIGHTS.cadence });
645
760
  }
646
761
  if (input.openIssues !== null && input.closedIssues !== null) {
647
762
  const total = input.openIssues + input.closedIssues;
648
763
  if (total > 0) {
649
- components.push({ value: input.closedIssues / total * 100, weight: MAINTENANCE_WEIGHTS.closeRatio });
764
+ components.push({
765
+ value: input.closedIssues / total * 100,
766
+ weight: MAINTENANCE_WEIGHTS.closeRatio
767
+ });
650
768
  }
651
769
  }
652
770
  return weightedAverage(components);
@@ -905,7 +1023,7 @@ function createEmbeddingProvider(fetchImpl) {
905
1023
 
906
1024
  // src/db/outcomes.ts
907
1025
  init_esm_shims();
908
- import { and, count, desc, eq, inArray, sql } from "drizzle-orm";
1026
+ import { and, count, desc, eq as eq2, inArray, sql as sql2 } from "drizzle-orm";
909
1027
  import { alias } from "drizzle-orm/pg-core";
910
1028
 
911
1029
  // src/core/successors.ts
@@ -999,13 +1117,13 @@ function buildLearnedSuccessors(observed, isSuperseded) {
999
1117
  async function recordOutcome(db, outcome) {
1000
1118
  await db.insert(recommendationOutcomes).values(outcome);
1001
1119
  }
1002
- var SUCCESS_PREDICATE = sql`${recommendationOutcomes.accepted} AND (${recommendationOutcomes.buildSignal} IS NULL OR ${recommendationOutcomes.buildSignal} <> 'failed')`;
1120
+ var SUCCESS_PREDICATE = sql2`${recommendationOutcomes.accepted} AND (${recommendationOutcomes.buildSignal} IS NULL OR ${recommendationOutcomes.buildSignal} <> 'failed')`;
1003
1121
  async function getFieldEvidence(db, names) {
1004
1122
  if (names && names.length === 0) return /* @__PURE__ */ new Map();
1005
1123
  const rows = await db.select({
1006
1124
  packageName: recommendationOutcomes.packageName,
1007
1125
  reports: count(),
1008
- successes: sql`count(*) FILTER (WHERE ${SUCCESS_PREDICATE})`.mapWith(Number)
1126
+ successes: sql2`count(*) FILTER (WHERE ${SUCCESS_PREDICATE})`.mapWith(Number)
1009
1127
  }).from(recommendationOutcomes).where(names ? inArray(recommendationOutcomes.packageName, names) : void 0).groupBy(recommendationOutcomes.packageName);
1010
1128
  return new Map(
1011
1129
  rows.map((r) => [r.packageName, { reports: Number(r.reports), successes: r.successes }])
@@ -1018,20 +1136,20 @@ async function learnSuccessions(db) {
1018
1136
  const rows = await db.select({
1019
1137
  from: failed.packageName,
1020
1138
  to: worked.packageName,
1021
- owners: sql`count(DISTINCT ${failed.ownerId})`.mapWith(Number),
1022
- observations: sql`count(*)`.mapWith(Number)
1139
+ owners: sql2`count(DISTINCT ${failed.ownerId})`.mapWith(Number),
1140
+ observations: sql2`count(*)`.mapWith(Number)
1023
1141
  }).from(failed).innerJoin(
1024
1142
  worked,
1025
- sql`${worked.ownerId} = ${failed.ownerId}
1143
+ sql2`${worked.ownerId} = ${failed.ownerId}
1026
1144
  AND ${worked.need} = ${failed.need}
1027
1145
  AND ${worked.packageName} <> ${failed.packageName}
1028
1146
  AND ${worked.createdAt} > ${failed.createdAt}`
1029
1147
  ).where(
1030
- sql`${failed.need} IS NOT NULL
1148
+ sql2`${failed.need} IS NOT NULL
1031
1149
  AND ${failed.ownerId} IS NOT NULL
1032
1150
  AND NOT (${failed.accepted} AND (${failed.buildSignal} IS NULL OR ${failed.buildSignal} <> 'failed'))
1033
1151
  AND (${worked.accepted} AND (${worked.buildSignal} IS NULL OR ${worked.buildSignal} <> 'failed'))`
1034
- ).groupBy(failed.packageName, worked.packageName).having(sql`count(DISTINCT ${failed.ownerId}) >= ${MIN_SUCCESSION_OWNERS}`);
1152
+ ).groupBy(failed.packageName, worked.packageName).having(sql2`count(DISTINCT ${failed.ownerId}) >= ${MIN_SUCCESSION_OWNERS}`);
1035
1153
  return rows.map((r) => ({
1036
1154
  from: r.from,
1037
1155
  to: r.to,
@@ -1049,14 +1167,14 @@ async function loadLearnedSuccessors(db, now = Date.now()) {
1049
1167
  return learnedCache.value;
1050
1168
  }
1051
1169
  const names = [...new Set(observed.map((o) => o.from))];
1052
- const rows = await db.select({ name: packages.name, deprecated: packages.deprecated, archived: packages.archived }).from(packages).where(and(inArray(packages.name, names), eq(packages.ecosystem, DEFAULT_ECOSYSTEM)));
1170
+ const rows = await db.select({ name: packages.name, deprecated: packages.deprecated, archived: packages.archived }).from(packages).where(and(inArray(packages.name, names), eq2(packages.ecosystem, DEFAULT_ECOSYSTEM)));
1053
1171
  const dead = new Set(rows.filter((r) => r.deprecated || r.archived).map((r) => r.name));
1054
1172
  const value = buildLearnedSuccessors(observed, (name) => dead.has(name));
1055
1173
  learnedCache = { at: now, value };
1056
1174
  return value;
1057
1175
  }
1058
1176
  async function getOutcomesByOwner(db, ownerId, opts = {}) {
1059
- return db.select().from(recommendationOutcomes).where(eq(recommendationOutcomes.ownerId, ownerId)).orderBy(desc(recommendationOutcomes.createdAt)).limit(opts.limit ?? 50);
1177
+ return db.select().from(recommendationOutcomes).where(eq2(recommendationOutcomes.ownerId, ownerId)).orderBy(desc(recommendationOutcomes.createdAt)).limit(opts.limit ?? 50);
1060
1178
  }
1061
1179
 
1062
1180
  // src/github/alerts.ts
@@ -1065,26 +1183,49 @@ var import_semver2 = __toESM(require_semver(), 1);
1065
1183
 
1066
1184
  // src/db/alerts.ts
1067
1185
  init_esm_shims();
1068
- import { and as and2, desc as desc2, eq as eq2 } from "drizzle-orm";
1186
+ import { and as and2, desc as desc2, eq as eq3, gte, isNull, sql as sql3 } from "drizzle-orm";
1069
1187
  var ALERT_FEED_LIMIT = 50;
1188
+ var CLI_WATCH_DAYS = 90;
1070
1189
  async function insertAlerts(db, rows) {
1071
- if (rows.length === 0) return 0;
1072
- const inserted = await db.insert(repoAlerts).values(rows).onConflictDoNothing({
1073
- target: [repoAlerts.repoId, repoAlerts.packageName, repoAlerts.toVersion]
1074
- }).returning({ id: repoAlerts.id });
1075
- return inserted.length;
1190
+ if (rows.length === 0) return [];
1191
+ return db.insert(repoAlerts).values(rows).onConflictDoNothing().returning({ id: repoAlerts.id, repoId: repoAlerts.repoId });
1192
+ }
1193
+ async function cliUpgradeWatchers(db, packageName, sinceDays = CLI_WATCH_DAYS) {
1194
+ const rows = await db.select({
1195
+ ownerId: upgradeRuns.ownerId,
1196
+ repoFullName: upgradeRuns.repoFullName,
1197
+ toVersion: upgradeRuns.toVersion
1198
+ }).from(upgradeRuns).where(
1199
+ and2(
1200
+ eq3(upgradeRuns.packageName, packageName),
1201
+ isNull(upgradeRuns.repoId),
1202
+ gte(upgradeRuns.createdAt, sql3`now() - make_interval(days => ${sinceDays})`)
1203
+ )
1204
+ ).orderBy(desc2(upgradeRuns.createdAt)).limit(5e3);
1205
+ const newest = /* @__PURE__ */ new Map();
1206
+ for (const r of rows) {
1207
+ const key = `${r.ownerId}\0${r.repoFullName}`;
1208
+ if (!newest.has(key)) {
1209
+ newest.set(key, {
1210
+ ownerId: r.ownerId,
1211
+ repoFullName: r.repoFullName,
1212
+ lastToVersion: r.toVersion
1213
+ });
1214
+ }
1215
+ }
1216
+ return [...newest.values()];
1076
1217
  }
1077
1218
  async function listAlerts(db, ownerId, limit = ALERT_FEED_LIMIT) {
1078
- return db.select().from(repoAlerts).where(eq2(repoAlerts.ownerId, ownerId)).orderBy(desc2(repoAlerts.createdAt)).limit(Math.min(limit, ALERT_FEED_LIMIT));
1219
+ return db.select().from(repoAlerts).where(eq3(repoAlerts.ownerId, ownerId)).orderBy(desc2(repoAlerts.createdAt)).limit(Math.min(limit, ALERT_FEED_LIMIT));
1079
1220
  }
1080
1221
  async function deleteAlertsForRepo(db, ownerId, repoId) {
1081
- await db.delete(repoAlerts).where(and2(eq2(repoAlerts.ownerId, ownerId), eq2(repoAlerts.repoId, repoId)));
1222
+ await db.delete(repoAlerts).where(and2(eq3(repoAlerts.ownerId, ownerId), eq3(repoAlerts.repoId, repoId)));
1082
1223
  }
1083
1224
 
1084
1225
  // src/db/repos.ts
1085
1226
  init_esm_shims();
1086
- import { and as and3, eq as eq3, sql as sql2 } from "drizzle-orm";
1087
- async function upsertRepos(db, rows) {
1227
+ import { and as and3, eq as eq4, inArray as inArray2, sql as sql4 } from "drizzle-orm";
1228
+ async function upsertRepos(db, rows, policy = DEFAULT_REPO_POLICY) {
1088
1229
  if (rows.length === 0) return 0;
1089
1230
  await db.insert(repos).values(
1090
1231
  rows.map((row) => ({
@@ -1093,36 +1234,36 @@ async function upsertRepos(db, rows) {
1093
1234
  fullName: row.fullName,
1094
1235
  defaultBranch: row.defaultBranch,
1095
1236
  isPrivate: row.isPrivate,
1096
- policy: DEFAULT_REPO_POLICY
1237
+ policy
1097
1238
  }))
1098
1239
  ).onConflictDoUpdate({
1099
1240
  target: [repos.ownerId, repos.fullName],
1100
1241
  set: {
1101
- installationId: sql2`excluded.installation_id`,
1102
- defaultBranch: sql2`excluded.default_branch`,
1103
- isPrivate: sql2`excluded.is_private`
1242
+ installationId: sql4`excluded.installation_id`,
1243
+ defaultBranch: sql4`excluded.default_branch`,
1244
+ isPrivate: sql4`excluded.is_private`
1104
1245
  }
1105
1246
  });
1106
1247
  return rows.length;
1107
1248
  }
1108
1249
  async function listRepos(db, ownerId) {
1109
- return db.select().from(repos).where(eq3(repos.ownerId, ownerId)).orderBy(repos.fullName);
1250
+ return db.select().from(repos).where(eq4(repos.ownerId, ownerId)).orderBy(repos.fullName);
1110
1251
  }
1111
1252
  async function reposDeclaring(db, name) {
1112
1253
  return db.select().from(repos).where(
1113
- sql2`exists (
1254
+ sql4`exists (
1114
1255
  select 1 from jsonb_array_elements(${repos.manifests}) as m
1115
1256
  where jsonb_exists(m -> 'deps', ${name})
1116
1257
  )`
1117
1258
  );
1118
1259
  }
1119
1260
  async function ownerForInstallation(db, installationId) {
1120
- const rows = await db.select({ ownerId: repos.ownerId }).from(repos).where(eq3(repos.installationId, installationId)).limit(1);
1261
+ const rows = await db.select({ ownerId: repos.ownerId }).from(repos).where(eq4(repos.installationId, installationId)).limit(1);
1121
1262
  return rows[0]?.ownerId ?? null;
1122
1263
  }
1123
1264
  async function deleteReposByInstallation(db, installationId, fullNames) {
1124
1265
  const only = fullNames ? new Set(fullNames) : null;
1125
- const rows = await db.select({ id: repos.id, ownerId: repos.ownerId, fullName: repos.fullName }).from(repos).where(eq3(repos.installationId, installationId));
1266
+ const rows = await db.select({ id: repos.id, ownerId: repos.ownerId, fullName: repos.fullName }).from(repos).where(eq4(repos.installationId, installationId));
1126
1267
  let deleted = 0;
1127
1268
  for (const row of rows) {
1128
1269
  if (only && !only.has(row.fullName)) continue;
@@ -1131,11 +1272,11 @@ async function deleteReposByInstallation(db, installationId, fullNames) {
1131
1272
  return deleted;
1132
1273
  }
1133
1274
  async function getRepo(db, ownerId, id) {
1134
- const rows = await db.select().from(repos).where(and3(eq3(repos.ownerId, ownerId), eq3(repos.id, id))).limit(1);
1275
+ const rows = await db.select().from(repos).where(and3(eq4(repos.ownerId, ownerId), eq4(repos.id, id))).limit(1);
1135
1276
  return rows[0] ?? null;
1136
1277
  }
1137
1278
  async function findPolicyByFullName(db, ownerId, fullName) {
1138
- const rows = await db.select({ policy: repos.policy }).from(repos).where(and3(eq3(repos.ownerId, ownerId), eq3(repos.fullName, fullName))).limit(1);
1279
+ const rows = await db.select({ policy: repos.policy }).from(repos).where(and3(eq4(repos.ownerId, ownerId), eq4(repos.fullName, fullName))).limit(1);
1139
1280
  return rows[0]?.policy ?? null;
1140
1281
  }
1141
1282
  async function saveScan(db, id, scan) {
@@ -1145,25 +1286,40 @@ async function saveScan(db, id, scan) {
1145
1286
  installCommand: scan.installCommand,
1146
1287
  lastScanAt: /* @__PURE__ */ new Date(),
1147
1288
  lastScanError: null
1148
- }).where(eq3(repos.id, id));
1289
+ }).where(eq4(repos.id, id));
1149
1290
  }
1150
1291
  async function saveScanError(db, id, message) {
1151
- await db.update(repos).set({ lastScanAt: /* @__PURE__ */ new Date(), lastScanError: message.slice(0, 500) }).where(eq3(repos.id, id));
1292
+ await db.update(repos).set({ lastScanAt: /* @__PURE__ */ new Date(), lastScanError: message.slice(0, 500) }).where(eq4(repos.id, id));
1152
1293
  }
1153
1294
  async function setRepoPolicy(db, ownerId, id, policy) {
1154
- const updated = await db.update(repos).set({ policy }).where(and3(eq3(repos.ownerId, ownerId), eq3(repos.id, id))).returning({ id: repos.id });
1295
+ const updated = await db.update(repos).set({ policy }).where(and3(eq4(repos.ownerId, ownerId), eq4(repos.id, id))).returning({ id: repos.id });
1155
1296
  return updated.length > 0;
1156
1297
  }
1157
1298
  async function deleteRepo(db, ownerId, id) {
1158
1299
  await deleteAlertsForRepo(db, ownerId, id);
1159
- const deleted = await db.delete(repos).where(and3(eq3(repos.ownerId, ownerId), eq3(repos.id, id))).returning({ id: repos.id });
1300
+ const deleted = await db.delete(repos).where(and3(eq4(repos.ownerId, ownerId), eq4(repos.id, id))).returning({ id: repos.id });
1160
1301
  return deleted.length > 0;
1161
1302
  }
1303
+ async function getRepoPolicyDefault(db, ownerId) {
1304
+ const [row] = await db.select({ policy: repoPolicyDefaults.policy }).from(repoPolicyDefaults).where(eq4(repoPolicyDefaults.ownerId, ownerId)).limit(1);
1305
+ return row?.policy ?? null;
1306
+ }
1307
+ async function setRepoPolicyDefault(db, ownerId, policy) {
1308
+ await db.insert(repoPolicyDefaults).values({ ownerId, policy }).onConflictDoUpdate({
1309
+ target: repoPolicyDefaults.ownerId,
1310
+ set: { policy, updatedAt: /* @__PURE__ */ new Date() }
1311
+ });
1312
+ }
1313
+ async function applyPolicyToRepos(db, ownerId, ids, policy) {
1314
+ if (ids.length === 0) return 0;
1315
+ const updated = await db.update(repos).set({ policy }).where(and3(eq4(repos.ownerId, ownerId), inArray2(repos.id, ids))).returning({ id: repos.id });
1316
+ return updated.length;
1317
+ }
1162
1318
 
1163
1319
  // src/github/drift.ts
1164
1320
  init_esm_shims();
1165
1321
  var import_semver = __toESM(require_semver(), 1);
1166
- import { and as and4, eq as eq4, inArray as inArray2, sql as sql3 } from "drizzle-orm";
1322
+ import { and as and4, eq as eq5, inArray as inArray3, sql as sql5 } from "drizzle-orm";
1167
1323
 
1168
1324
  // src/github/attribute.ts
1169
1325
  init_esm_shims();
@@ -1180,9 +1336,7 @@ function buildAttribution(deps, edges, directNames) {
1180
1336
  if (list) list.push(edge.parent);
1181
1337
  else parentIds.set(edge.child, [edge.parent]);
1182
1338
  }
1183
- const informative = edges.some(
1184
- (edge) => nameOf.has(edge.parent) && nameOf.has(edge.child)
1185
- );
1339
+ const informative = edges.some((edge) => nameOf.has(edge.parent) && nameOf.has(edge.child));
1186
1340
  if (!informative) return { available: false, parentsOf: /* @__PURE__ */ new Map() };
1187
1341
  const parentsOf = /* @__PURE__ */ new Map();
1188
1342
  for (const dep of deps) {
@@ -1313,8 +1467,8 @@ async function loadIndexed(db, names, ecosystem = DEFAULT_ECOSYSTEM) {
1313
1467
  advisories: packages.advisories
1314
1468
  }).from(packages).where(
1315
1469
  and4(
1316
- inArray2(packages.name, names.slice(i, i + NAME_CHUNK)),
1317
- eq4(packages.ecosystem, ecosystem)
1470
+ inArray3(packages.name, names.slice(i, i + NAME_CHUNK)),
1471
+ eq5(packages.ecosystem, ecosystem)
1318
1472
  )
1319
1473
  );
1320
1474
  for (const row of rows) {
@@ -1330,7 +1484,7 @@ async function loadIndexed(db, names, ecosystem = DEFAULT_ECOSYSTEM) {
1330
1484
  async function loadVersions(db, names) {
1331
1485
  const out = /* @__PURE__ */ new Map();
1332
1486
  for (let i = 0; i < names.length; i += NAME_CHUNK) {
1333
- const rows = await db.select({ name: packageVersions.packageName, version: packageVersions.version }).from(packageVersions).where(inArray2(packageVersions.packageName, names.slice(i, i + NAME_CHUNK)));
1487
+ const rows = await db.select({ name: packageVersions.packageName, version: packageVersions.version }).from(packageVersions).where(inArray3(packageVersions.packageName, names.slice(i, i + NAME_CHUNK)));
1334
1488
  for (const row of rows) {
1335
1489
  const list = out.get(row.name);
1336
1490
  if (list) list.push(row.version);
@@ -1342,7 +1496,8 @@ async function loadVersions(db, names) {
1342
1496
  function depDrift(name, declared, indexed, knownVersions) {
1343
1497
  const { range } = declared;
1344
1498
  const latest = indexed.latestVersion;
1345
- const resolved = bestSatisfying(knownVersions, range) ?? rangeFloor(range);
1499
+ const fromIndex = bestSatisfying(knownVersions, range);
1500
+ const resolved = fromIndex ?? rangeFloor(range);
1346
1501
  let majorsBehind = 0;
1347
1502
  if (resolved && latest && import_semver.default.valid(resolved) && import_semver.default.valid(latest)) {
1348
1503
  majorsBehind = Math.max(0, import_semver.default.major(latest) - import_semver.default.major(resolved));
@@ -1355,9 +1510,20 @@ function depDrift(name, declared, indexed, knownVersions) {
1355
1510
  latest,
1356
1511
  majorsBehind,
1357
1512
  deprecated: indexed.deprecated,
1358
- advisories: indexed.advisories
1513
+ advisories: indexed.advisories,
1514
+ status: driftStatus(resolved, latest),
1515
+ resolvedFrom: fromIndex ? "index" : "range-floor",
1516
+ // Until computeDrift checks the resolved version against OSV, this is the
1517
+ // package-level count, and says so.
1518
+ advisoriesAt: "package"
1359
1519
  };
1360
1520
  }
1521
+ function driftStatus(resolved, latest) {
1522
+ if (!resolved || !latest || !import_semver.default.valid(resolved) || !import_semver.default.valid(latest)) return "unknown";
1523
+ if (!import_semver.default.lt(resolved, latest)) return "current";
1524
+ const breaking = import_semver.default.major(latest) > import_semver.default.major(resolved) || import_semver.default.major(resolved) === 0 && import_semver.default.major(latest) === 0 && import_semver.default.minor(latest) > import_semver.default.minor(resolved);
1525
+ return breaking ? "major" : "behind";
1526
+ }
1361
1527
  function isBehind(dep) {
1362
1528
  if (!dep.resolved || !dep.latest) return false;
1363
1529
  if (!import_semver.default.valid(dep.resolved) || !import_semver.default.valid(dep.latest)) return false;
@@ -1436,9 +1602,9 @@ async function conflictsAtCurrent(db, deps) {
1436
1602
  peerDependencies: packageVersions.peerDependencies,
1437
1603
  engines: packageVersions.engines
1438
1604
  }).from(packageVersions).where(
1439
- inArray2(
1440
- sql3`(${packageVersions.packageName}, ${packageVersions.version})`,
1441
- pinned.map((d) => sql3`(${d.name}, ${d.resolved})`)
1605
+ inArray3(
1606
+ sql5`(${packageVersions.packageName}, ${packageVersions.version})`,
1607
+ pinned.map((d) => sql5`(${d.name}, ${d.resolved})`)
1442
1608
  )
1443
1609
  );
1444
1610
  const members = rows.map((r) => ({
@@ -1452,7 +1618,7 @@ async function conflictsAtCurrent(db, deps) {
1452
1618
  }
1453
1619
  function queueUntracked(db, names, ownerId) {
1454
1620
  if (names.length === 0) return;
1455
- void import("./ingestQueue-VKN4KBIF.js").then(({ enqueueIngest: enqueueIngest2 }) => {
1621
+ void import("./ingestQueue-RSEPMORK.js").then(({ enqueueIngest: enqueueIngest2 }) => {
1456
1622
  for (const name of names) enqueueIngest2(db, name, ownerId);
1457
1623
  }).catch((err) => {
1458
1624
  logger.debug(`untracked-dep enqueue failed: ${String(err)}`);
@@ -1477,16 +1643,14 @@ async function computeDrift(db, manifests, resolvedTree = null, requestedByOwner
1477
1643
  anyDrift: 0,
1478
1644
  deprecated: 0,
1479
1645
  advisories: 0,
1646
+ advisoriesExact: true,
1480
1647
  deps: [],
1481
1648
  transitive,
1482
1649
  conflictsAtLatest: [],
1483
1650
  conflictsAtCurrent: []
1484
1651
  };
1485
1652
  }
1486
- const [indexed, versions] = await Promise.all([
1487
- loadIndexed(db, names),
1488
- loadVersions(db, names)
1489
- ]);
1653
+ const [indexed, versions] = await Promise.all([loadIndexed(db, names), loadVersions(db, names)]);
1490
1654
  queueUntracked(
1491
1655
  db,
1492
1656
  names.filter((name) => !indexed.has(name)),
@@ -1498,9 +1662,22 @@ async function computeDrift(db, manifests, resolvedTree = null, requestedByOwner
1498
1662
  if (!row) continue;
1499
1663
  deps.push(depDrift(name, entry, row, versions.get(name) ?? []));
1500
1664
  }
1665
+ const checkable = deps.filter((d) => d.resolved && import_semver.default.valid(d.resolved));
1666
+ const exact = await queryVulnerableInstalls(
1667
+ checkable.map((d) => ({ name: d.name, version: d.resolved }))
1668
+ );
1669
+ if (exact.complete) {
1670
+ for (const d of checkable) {
1671
+ d.advisories = exact.affected.get(installKey(d.name, d.resolved))?.length ?? 0;
1672
+ d.advisoriesAt = "resolved";
1673
+ }
1674
+ }
1501
1675
  deps.sort((a, b) => severity(b) - severity(a) || a.name.localeCompare(b.name));
1502
1676
  const [conflicts, currentConflicts] = await Promise.all([
1503
- conflictsAtLatest(db, deps.map((d) => d.name)),
1677
+ conflictsAtLatest(
1678
+ db,
1679
+ deps.map((d) => d.name)
1680
+ ),
1504
1681
  conflictsAtCurrent(db, deps)
1505
1682
  ]);
1506
1683
  return {
@@ -1510,6 +1687,7 @@ async function computeDrift(db, manifests, resolvedTree = null, requestedByOwner
1510
1687
  anyDrift: deps.filter(isBehind).length,
1511
1688
  deprecated: deps.filter((d) => d.deprecated).length,
1512
1689
  advisories: deps.reduce((sum, d) => sum + d.advisories, 0),
1690
+ advisoriesExact: deps.every((d) => d.advisoriesAt === "resolved"),
1513
1691
  deps: deps.slice(0, REPO_DRIFT_DETAIL_CAP),
1514
1692
  transitive,
1515
1693
  conflictsAtLatest: conflicts,
@@ -1517,6 +1695,116 @@ async function computeDrift(db, manifests, resolvedTree = null, requestedByOwner
1517
1695
  };
1518
1696
  }
1519
1697
 
1698
+ // src/github/dispatch.ts
1699
+ init_esm_shims();
1700
+
1701
+ // src/github/scope.ts
1702
+ init_esm_shims();
1703
+ function permits(policy, check) {
1704
+ return policy?.checks?.[check] !== false;
1705
+ }
1706
+ function isUnassessed(u) {
1707
+ return u.verdict === "unknown";
1708
+ }
1709
+ function breaksSomething(u) {
1710
+ return u.verdict === "removes-exports" || u.verdict === "arity-changed";
1711
+ }
1712
+ function scopeVerdict(u, scope) {
1713
+ if (scope === "all") return { inScope: true };
1714
+ if (u.advisories > 0) return { inScope: true };
1715
+ if (scope === "security") {
1716
+ return { inScope: false, reason: "no advisory \u2014 scope is security-only" };
1717
+ }
1718
+ if (isUnassessed(u)) {
1719
+ return {
1720
+ inScope: false,
1721
+ reason: "surface not yet analysed \u2014 nothing for the agent to rewrite"
1722
+ };
1723
+ }
1724
+ if (breaksSomething(u)) return { inScope: true };
1725
+ return { inScope: false, reason: "nothing referenced breaks \u2014 scope is security + blocking" };
1726
+ }
1727
+ function repoMode(policy) {
1728
+ if (!policy.enabled) return "comment";
1729
+ return policy.mode ?? "pr";
1730
+ }
1731
+ function applyScope(upgrades, policy) {
1732
+ if (!policy) {
1733
+ return {
1734
+ upgrades: upgrades.map((u) => ({ ...u, inScope: true })),
1735
+ scope: "all",
1736
+ scopeSource: "unconnected",
1737
+ outOfScope: 0
1738
+ };
1739
+ }
1740
+ const scoped = upgrades.map((u) => {
1741
+ const verdict = scopeVerdict(u, policy.scope);
1742
+ return verdict.inScope ? { ...u, inScope: true } : { ...u, inScope: false, scopeReason: verdict.reason };
1743
+ });
1744
+ return {
1745
+ upgrades: scoped,
1746
+ scope: policy.scope,
1747
+ scopeSource: "repo-policy",
1748
+ outOfScope: scoped.filter((u) => !u.inScope).length,
1749
+ // Only on this branch. The unconnected branch above deliberately omits it:
1750
+ // "no policy" is not "policy says comment", and conflating them would turn
1751
+ // an unconnected checkout into a silent disarm.
1752
+ mode: repoMode(policy)
1753
+ };
1754
+ }
1755
+
1756
+ // src/github/dispatch.ts
1757
+ var WORKFLOW_FILE = WORKFLOW_PATH.split("/").pop();
1758
+ function shouldDispatch(repo) {
1759
+ return repoMode(repo.policy) !== "comment";
1760
+ }
1761
+ async function dispatchUpgradeWorkflow(repo) {
1762
+ if (!shouldDispatch(repo)) return "not-armed";
1763
+ try {
1764
+ await installationPost(
1765
+ repo.installationId,
1766
+ `/repos/${repo.fullName}/actions/workflows/${WORKFLOW_FILE}/dispatches`,
1767
+ { ref: repo.defaultBranch ?? "main" }
1768
+ );
1769
+ return "dispatched";
1770
+ } catch (err) {
1771
+ if (err instanceof GithubAppError && err.status === 403) return "permission-denied";
1772
+ if (err instanceof GithubAppError && err.status === 404) return "no-workflow";
1773
+ logger.warn(
1774
+ `dispatch: could not start a run for ${repo.fullName}: ${err instanceof Error ? err.message : String(err)}`
1775
+ );
1776
+ return "failed";
1777
+ }
1778
+ }
1779
+ async function dispatchForAlerts(repos2) {
1780
+ const tally = {
1781
+ dispatched: 0,
1782
+ "not-armed": 0,
1783
+ "permission-denied": 0,
1784
+ "no-workflow": 0,
1785
+ failed: 0
1786
+ };
1787
+ for (const repo of repos2) {
1788
+ try {
1789
+ tally[await dispatchUpgradeWorkflow(repo)] += 1;
1790
+ } catch (err) {
1791
+ tally.failed += 1;
1792
+ logger.warn(
1793
+ `dispatch: skipped ${repo.fullName}: ${err instanceof Error ? err.message : String(err)}`
1794
+ );
1795
+ }
1796
+ }
1797
+ if (tally.dispatched > 0) {
1798
+ logger.info(`dispatch: started ${tally.dispatched} upgrade run(s) on a new major`);
1799
+ }
1800
+ if (tally["permission-denied"] > 0) {
1801
+ logger.warn(
1802
+ `dispatch: ${tally["permission-denied"]} run(s) not started \u2014 the lurq GitHub App needs Actions: write. The weekly schedule is unaffected.`
1803
+ );
1804
+ }
1805
+ return tally;
1806
+ }
1807
+
1520
1808
  // src/github/alerts.ts
1521
1809
  function newMajorRelease(before, after) {
1522
1810
  const from = before?.latestVersion;
@@ -1548,18 +1836,44 @@ function draftAlert(repo, packageName, toVersion) {
1548
1836
  inRange: import_semver2.default.validRange(declared.range) ? import_semver2.default.satisfies(toVersion, declared.range, { includePrerelease: false }) : false
1549
1837
  };
1550
1838
  }
1839
+ function draftCliAlert(watcher, packageName, toVersion) {
1840
+ const last = import_semver2.default.valid(watcher.lastToVersion) ?? import_semver2.default.coerce(watcher.lastToVersion)?.version ?? null;
1841
+ if (last && import_semver2.default.major(last) >= import_semver2.default.major(toVersion)) return null;
1842
+ return {
1843
+ ownerId: watcher.ownerId,
1844
+ repoId: null,
1845
+ repoFullName: watcher.repoFullName,
1846
+ packageName,
1847
+ range: watcher.lastToVersion,
1848
+ fromVersion: watcher.lastToVersion,
1849
+ toVersion,
1850
+ inRange: false
1851
+ };
1852
+ }
1551
1853
  async function emitPublishAlerts(db, name, before, after) {
1552
1854
  const toVersion = newMajorRelease(before, after);
1553
1855
  if (!toVersion) return 0;
1554
1856
  try {
1555
- const affected = await reposDeclaring(db, name);
1556
- if (affected.length === 0) return 0;
1557
- const rows = affected.map((repo) => draftAlert(repo, name, toVersion)).filter((row) => row !== null);
1558
- const written = await insertAlerts(db, rows);
1559
- if (written > 0) {
1560
- logger.info(`alerts: ${name}@${toVersion} is a new major, notified ${written} repo(s)`);
1857
+ const [affected, watchers] = await Promise.all([
1858
+ reposDeclaring(db, name),
1859
+ cliUpgradeWatchers(db, name)
1860
+ ]);
1861
+ const connected = new Set(affected.map((r) => `${r.ownerId}\0${r.fullName}`));
1862
+ const rows = [
1863
+ ...affected.map((repo) => draftAlert(repo, name, toVersion)),
1864
+ ...watchers.filter((w) => !connected.has(`${w.ownerId}\0${w.repoFullName}`)).map((w) => draftCliAlert(w, name, toVersion))
1865
+ ].filter((row) => row !== null);
1866
+ if (rows.length === 0) return 0;
1867
+ const inserted = await insertAlerts(db, rows);
1868
+ if (inserted.length > 0) {
1869
+ logger.info(
1870
+ `alerts: ${name}@${toVersion} is a new major, notified ${inserted.length} repo(s)`
1871
+ );
1561
1872
  }
1562
- return written;
1873
+ const byId = new Map(affected.map((repo) => [repo.id, repo]));
1874
+ const freshlyAlerted = inserted.map((row) => row.repoId === null ? void 0 : byId.get(row.repoId)).filter((repo) => repo !== void 0);
1875
+ if (freshlyAlerted.length > 0) await dispatchForAlerts(freshlyAlerted);
1876
+ return inserted.length;
1563
1877
  } catch (err) {
1564
1878
  logger.warn(`alerts: could not fan out ${name}@${toVersion}: ${err.message}`);
1565
1879
  return 0;
@@ -1698,13 +2012,13 @@ function assemblePackageRow(p) {
1698
2012
  // src/pipeline/single.ts
1699
2013
  var FIRST_TOUCH_BUDGET_MS = 4e3;
1700
2014
  async function getSeedCategory(db, name) {
1701
- const [row] = await db.select({ category: seedPackages.category }).from(seedPackages).where(eq5(seedPackages.name, name)).limit(1);
2015
+ const [row] = await db.select({ category: seedPackages.category }).from(seedPackages).where(eq6(seedPackages.name, name)).limit(1);
1702
2016
  return row?.category ?? null;
1703
2017
  }
1704
2018
  async function getCategoryMedianBundle(db, category) {
1705
2019
  const [row] = await db.select({
1706
- m: sql4`percentile_cont(0.5) within group (order by ${packages.bundleMinGzipKb})`
1707
- }).from(packages).where(and5(eq5(packages.category, category), isNotNull(packages.bundleMinGzipKb)));
2020
+ m: sql6`percentile_cont(0.5) within group (order by ${packages.bundleMinGzipKb})`
2021
+ }).from(packages).where(and5(eq6(packages.category, category), isNotNull(packages.bundleMinGzipKb)));
1708
2022
  return row?.m ?? null;
1709
2023
  }
1710
2024
  async function syncOnePackage(db, name, opts = {}) {
@@ -1716,7 +2030,8 @@ async function syncOnePackage(db, name, opts = {}) {
1716
2030
  const prefetchedWeekly = await fetchWeeklyDownloads(name).catch(() => null);
1717
2031
  const signals = await collectSignals(name, initialCategory, {
1718
2032
  githubToken: config.GITHUB_TOKEN,
1719
- prefetchedWeekly
2033
+ prefetchedWeekly,
2034
+ freshRegistry: opts.fresh
1720
2035
  });
1721
2036
  const embProvider = createEmbeddingProvider();
1722
2037
  const reusable = canReuse(existing, signals.registry?.latestVersion ?? null, embProvider.id);
@@ -1805,7 +2120,10 @@ async function getOrFetchPackage(db, name, opts = {}) {
1805
2120
  const exists = await npmPackageExists(name);
1806
2121
  if (!exists) return { row: null, wasTracked: false, existsOnNpm: false };
1807
2122
  if (opts.blockMs && opts.blockMs > 0) {
1808
- const row = await withBudget(runIngest(db, name, opts.requestedByOwnerId ?? null), opts.blockMs);
2123
+ const row = await withBudget(
2124
+ runIngest(db, name, opts.requestedByOwnerId ?? null),
2125
+ opts.blockMs
2126
+ );
1809
2127
  if (row) return { row, wasTracked: false, existsOnNpm: true };
1810
2128
  return { row: null, wasTracked: false, existsOnNpm: true, queued: true };
1811
2129
  }
@@ -1814,6 +2132,11 @@ async function getOrFetchPackage(db, name, opts = {}) {
1814
2132
  }
1815
2133
 
1816
2134
  // src/pipeline/ingestQueue.ts
2135
+ async function persistDemand(db, name, owner) {
2136
+ await enqueueDemand(db, name, owner).catch(
2137
+ (err) => logger.warn(`ingest queue: could not persist request for ${name}: ${String(err)}`)
2138
+ );
2139
+ }
1817
2140
  var MAX_CONCURRENT = 3;
1818
2141
  var MAX_PENDING = 500;
1819
2142
  var pending = [];
@@ -1847,6 +2170,7 @@ function enqueueIngest(db, name, requestedByOwnerId = null) {
1847
2170
  queuedNames.add(name);
1848
2171
  owners.set(name, requestedByOwnerId);
1849
2172
  pending.push(name);
2173
+ void persistDemand(db, name, requestedByOwnerId);
1850
2174
  pump(db);
1851
2175
  }
1852
2176
  function pump(db) {
@@ -1865,15 +2189,18 @@ function pump(db) {
1865
2189
  }
1866
2190
  }
1867
2191
  async function runIngest(db, name, requestedByOwnerId = null) {
2192
+ const persisted = persistDemand(db, name, requestedByOwnerId);
1868
2193
  try {
1869
2194
  const row = await syncOnePackage(db, name, { requestedByOwnerId });
1870
- if (row.confidence && row.confidence !== "unproven") {
1871
- await ensureSeedEntry(db, name, row.category).catch(() => {
1872
- });
1873
- }
2195
+ await persisted;
2196
+ await setDiscoveryStatus(db, name, { status: "ingested" }).catch(() => {
2197
+ });
1874
2198
  return row;
1875
2199
  } catch (err) {
1876
2200
  logger.warn(`on-demand ingest failed for ${name}: ${String(err)}`);
2201
+ await persisted;
2202
+ await recordIngestFailure(db, name, DISCOVERY.maxIngestAttempts).catch(() => {
2203
+ });
1877
2204
  return null;
1878
2205
  }
1879
2206
  }
@@ -1900,6 +2227,11 @@ export {
1900
2227
  saveScanError,
1901
2228
  setRepoPolicy,
1902
2229
  deleteRepo,
2230
+ getRepoPolicyDefault,
2231
+ setRepoPolicyDefault,
2232
+ applyPolicyToRepos,
2233
+ getQueuedNames,
2234
+ enqueueCandidates,
1903
2235
  ingestQueueDepth,
1904
2236
  drainIngestQueue,
1905
2237
  resetIngestQueue,
@@ -1908,6 +2240,11 @@ export {
1908
2240
  declaredDeps,
1909
2241
  loadVersions,
1910
2242
  computeDrift,
2243
+ permits,
2244
+ breaksSomething,
2245
+ repoMode,
2246
+ applyScope,
2247
+ dispatchUpgradeWorkflow,
1911
2248
  classifyRuntimeTarget,
1912
2249
  FIRST_TOUCH_BUDGET_MS,
1913
2250
  syncOnePackage,