@preventive/triage 1.0.0-alpha.4 → 1.0.0-alpha.41

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 (312) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/README.md +16 -0
  3. package/api/reap.ts +17 -0
  4. package/cli.js +6 -0
  5. package/client/dependency-paths.d.ts +2 -0
  6. package/client/dependency-paths.js +24 -0
  7. package/client/finding-link.js +305 -0
  8. package/client/linked-findings.d.ts +1 -0
  9. package/client/linked-findings.js +112 -0
  10. package/common/bundle-commit.d.ts +1 -0
  11. package/common/bundle-commit.js +4 -0
  12. package/common/bundle-compare-diff.d.ts +3 -0
  13. package/common/bundle-compare-diff.js +522 -0
  14. package/common/bundle-compare-inputs.d.ts +2 -0
  15. package/common/bundle-compare-inputs.js +65 -0
  16. package/common/bundle-metadata.d.ts +14 -0
  17. package/common/bundle-metadata.js +226 -0
  18. package/common/bundle-pkg-of.d.ts +1 -0
  19. package/common/bundle-pkg-of.js +90 -0
  20. package/common/bundle-reasons.d.ts +2 -0
  21. package/common/bundle-reasons.js +21 -0
  22. package/common/bundle-source-links.d.ts +6 -0
  23. package/common/bundle-source-links.js +216 -0
  24. package/common/bundle-sourcemap.d.ts +10 -0
  25. package/common/bundle-sourcemap.js +100 -0
  26. package/common/bundle-sources.d.ts +7 -0
  27. package/common/bundle-sources.js +310 -0
  28. package/common/bundle-stats.js +41 -0
  29. package/common/bundle-tabs.js +1 -0
  30. package/common/code-language.js +65 -0
  31. package/common/default-scan-models.ts +31 -0
  32. package/common/dependency-reviews.d.ts +23 -0
  33. package/common/dependency-reviews.js +81 -0
  34. package/common/finding-duplicates.js +58 -0
  35. package/common/finding-filters.js +274 -0
  36. package/common/finding-id.js +47 -0
  37. package/common/github-issue-labels.d.ts +1 -0
  38. package/common/github-issue-labels.js +11 -0
  39. package/common/github-pr.ts +56 -0
  40. package/common/gzip.js +59 -0
  41. package/common/line-diff.d.ts +8 -0
  42. package/common/line-diff.js +56 -0
  43. package/common/linked-workspace-groups.js +63 -0
  44. package/common/managed/comments.ts +34 -0
  45. package/common/managed/npm-packages.d.ts +5 -0
  46. package/common/managed/npm-packages.js +34 -0
  47. package/common/managed/permissions.ts +31 -0
  48. package/common/managed/report-content.ts +72 -0
  49. package/common/managed/report-filter.ts +108 -0
  50. package/common/managed/repository-alias.ts +65 -0
  51. package/common/managed/roles.ts +28 -0
  52. package/common/managed/routes.d.ts +2 -0
  53. package/common/managed/routes.js +254 -0
  54. package/common/managed/scan-models.ts +6 -0
  55. package/common/managed/team-app.d.ts +2 -0
  56. package/common/managed/team-app.js +31 -0
  57. package/common/managed/triage.ts +83 -0
  58. package/common/managed/upload-seal.ts +126 -0
  59. package/common/pretty-print.d.ts +5 -0
  60. package/common/pretty-print.js +24 -0
  61. package/common/revalidation-conflicts.js +48 -0
  62. package/common/revalidation-input-groups.js +56 -0
  63. package/common/save-error-reason.ts +20 -7
  64. package/common/scan-server.ts +13 -0
  65. package/common/server-info.ts +35 -0
  66. package/common/utf8.d.ts +3 -0
  67. package/common/utf8.js +45 -0
  68. package/common/workspace-app.js +23 -0
  69. package/common/workspace-groups.js +176 -0
  70. package/out/brotli-fallback.js +3 -3
  71. package/out/chunk-2AFQKRAM.js +51 -0
  72. package/out/chunk-2KBF5TZ7.js +2 -0
  73. package/out/chunk-2XUEZHIP.js +28 -0
  74. package/out/chunk-35VE6CDH.js +2 -0
  75. package/out/chunk-3BZ7JERB.js +1 -0
  76. package/out/chunk-4F6JSF57.js +3 -0
  77. package/out/chunk-4XCDFGDP.js +10 -0
  78. package/out/chunk-C4Z6ZBXV.js +10 -0
  79. package/out/chunk-CMQA4DM6.js +1 -0
  80. package/out/chunk-CVVJAXVY.js +2 -0
  81. package/out/chunk-DIJJNTRH.js +10 -0
  82. package/out/chunk-GJ4IUCNS.js +1 -0
  83. package/out/chunk-GNXOKIGP.js +80 -0
  84. package/out/chunk-GZ4MS3TE.js +13 -0
  85. package/out/chunk-IU3CCINR.js +3 -0
  86. package/out/chunk-K3X5EJBQ.js +2 -0
  87. package/out/chunk-KEX2ATEW.js +2 -0
  88. package/out/chunk-KVEUXIEU.js +1 -0
  89. package/out/chunk-KYCNJVB7.js +1 -0
  90. package/out/chunk-NRPC74AM.js +1 -0
  91. package/out/chunk-OOJM4CTU.js +1 -0
  92. package/out/chunk-PNHBZGDD.js +1 -0
  93. package/out/chunk-QZ3A376W.js +1 -0
  94. package/out/chunk-R5O6FAW4.js +2 -0
  95. package/out/chunk-RFZQQXSG.js +17 -0
  96. package/out/chunk-RSHMVYNS.js +1 -0
  97. package/out/chunk-TUII34K2.js +1 -0
  98. package/out/chunk-TY4RZD3Z.js +226 -0
  99. package/out/chunk-TZ5KB2Y5.js +10 -0
  100. package/out/chunk-U6OPEXIA.js +1 -0
  101. package/out/chunk-UNTRGE26.js +2 -0
  102. package/out/chunk-UXNUAIIC.js +33 -0
  103. package/out/chunk-UYAXVRVL.js +1 -0
  104. package/out/chunk-UZAZM2SX.js +2 -0
  105. package/out/chunk-V3ME6FI5.js +2 -0
  106. package/out/chunk-V5O7M2YT.js +1 -0
  107. package/out/chunk-VIHGO77Y.js +1 -0
  108. package/out/chunk-VQJCXGIY.js +7 -0
  109. package/out/chunk-W4J3MGLB.js +1 -0
  110. package/out/chunk-WGG7KQUH.js +1 -0
  111. package/out/chunk-WZDTZE64.js +1 -0
  112. package/out/chunk-XL35ZV6R.js +1 -0
  113. package/out/chunk-XYFXXB2G.js +10 -0
  114. package/out/chunk-YVZH5SHP.js +1 -0
  115. package/out/chunk-ZBBOBQ43.js +1 -0
  116. package/out/chunk-ZMQQCGF6.js +2 -0
  117. package/out/client-managed-import.js +4 -0
  118. package/out/client-managed.js +37 -0
  119. package/out/client-sync.js +10 -13
  120. package/out/github-issue-authorized.html +3 -0
  121. package/out/github-issue-authorized.js +1 -0
  122. package/out/graph.js +17 -4
  123. package/out/index.html +90 -8
  124. package/out/prism.js +6 -2
  125. package/out/stasis.svg +20 -0
  126. package/out/terminal.js +365 -39
  127. package/out/view.css +1 -1
  128. package/out/view.js +83 -117
  129. package/package.json +298 -59
  130. package/risk/files/characters.js +196 -0
  131. package/risk/files/hosts.js +205 -0
  132. package/risk/files/readability.js +171 -0
  133. package/risk/files/runs-code.js +595 -0
  134. package/risk/files/script.js +114 -0
  135. package/risk/files/stamps.js +46 -0
  136. package/risk/index.d.ts +156 -0
  137. package/risk/index.js +7 -0
  138. package/risk/npm/assess.js +223 -0
  139. package/risk/npm/combined.js +65 -0
  140. package/risk/npm/compare.js +228 -0
  141. package/risk/npm/dependencies.js +153 -0
  142. package/risk/npm/diff.js +43 -0
  143. package/risk/npm/downloads.js +84 -0
  144. package/risk/npm/files.js +34 -0
  145. package/risk/npm/held.js +60 -0
  146. package/risk/npm/install-scripts.js +34 -0
  147. package/risk/npm/malware.js +36 -0
  148. package/risk/npm/manifest.js +65 -0
  149. package/risk/npm/new-dependency.js +30 -0
  150. package/risk/npm/old-version.js +108 -0
  151. package/risk/npm/registry.js +282 -0
  152. package/risk/npm/release.js +85 -0
  153. package/risk/npm/score.js +59 -0
  154. package/risk/npm/specs.js +124 -0
  155. package/risk/npm/tarball.js +113 -0
  156. package/server-common/database-config.ts +16 -0
  157. package/server-common/initialize.ts +18 -0
  158. package/server-common/npm-advisories.ts +101 -0
  159. package/{server → server-common}/origin.ts +5 -5
  160. package/server-common/reap.ts +48 -0
  161. package/server-common/scan-config.ts +19 -0
  162. package/server-common/standalone.ts +29 -0
  163. package/server-common/storage-crypto.ts +182 -0
  164. package/server-common/storage-log.ts +38 -0
  165. package/server-common/vercel-blob.ts +103 -0
  166. package/server-e2e/app.ts +486 -0
  167. package/{server → server-e2e}/auth.ts +5 -1
  168. package/{server → server-e2e}/bus-receiver.ts +2 -1
  169. package/{server → server-e2e}/cli.js +9 -4
  170. package/{server → server-e2e}/config.ts +56 -35
  171. package/{server → server-e2e}/db-neon.ts +2 -2
  172. package/{server → server-e2e}/db-revision-sql.ts +1 -1
  173. package/{server → server-e2e}/db-stmt.ts +2 -2
  174. package/{server → server-e2e}/db.ts +7 -7
  175. package/{server → server-e2e}/http.ts +116 -18
  176. package/{server → server-e2e}/hub.ts +40 -9
  177. package/server-e2e/index.ts +17 -0
  178. package/server-e2e/lifecycle.ts +95 -0
  179. package/{server → server-e2e}/neon-driver.ts +2 -2
  180. package/{server → server-e2e}/npm-proxy.ts +12 -145
  181. package/{server → server-e2e}/objstore/blob-vercel.ts +10 -114
  182. package/{server → server-e2e}/objstore/blob.ts +1 -1
  183. package/server-e2e/objstore/fetch-mint-guard.ts +74 -0
  184. package/{server → server-e2e}/objstore/handlers.ts +9 -8
  185. package/{server → server-e2e}/objstore/init.ts +35 -18
  186. package/{server → server-e2e}/objstore/reaper.ts +1 -1
  187. package/server-e2e/objstore/rest-deny.ts +28 -0
  188. package/server-e2e/objstore/rest-mint.ts +224 -0
  189. package/{server → server-e2e}/objstore/rest.ts +27 -26
  190. package/{server → server-e2e}/objstore/sign.ts +105 -0
  191. package/{server → server-e2e}/objstore/store-neon.ts +6 -10
  192. package/{server → server-e2e}/objstore/store.ts +7 -7
  193. package/{server → server-e2e}/objstore/tokens.ts +2 -2
  194. package/{server → server-e2e}/pubsub.ts +12 -9
  195. package/{server → server-e2e}/sign.ts +4 -4
  196. package/{server → server-e2e}/sse-server.ts +102 -68
  197. package/{server → server-e2e}/sse-session.ts +28 -8
  198. package/{server → server-e2e}/static.ts +31 -21
  199. package/server-e2e/sync-handlers.ts +408 -0
  200. package/{server → server-e2e}/ws-server.ts +20 -9
  201. package/server-managed/activity.ts +245 -0
  202. package/server-managed/avatar-store.ts +12 -0
  203. package/server-managed/blob-store.ts +17 -0
  204. package/server-managed/brotli.ts +10 -0
  205. package/server-managed/bundle-advisories.ts +120 -0
  206. package/server-managed/bundle-advisory-inventory.ts +77 -0
  207. package/server-managed/bundle-build-diagnostics.d.ts +10 -0
  208. package/server-managed/bundle-build-diagnostics.js +23 -0
  209. package/server-managed/bundle-build-leases.ts +41 -0
  210. package/server-managed/bundle-build-worker.js +111 -0
  211. package/server-managed/bundle-build.ts +198 -0
  212. package/server-managed/bundle-cache.ts +199 -0
  213. package/server-managed/bundle-catalog.ts +29 -0
  214. package/server-managed/bundle-commits.ts +140 -0
  215. package/server-managed/bundle-store.ts +24 -0
  216. package/server-managed/bundle-summary-cache.ts +130 -0
  217. package/server-managed/bundle.ts +120 -0
  218. package/server-managed/cache-storage.ts +19 -0
  219. package/server-managed/cli.js +21 -0
  220. package/server-managed/combined.ts +46 -0
  221. package/server-managed/comments.ts +151 -0
  222. package/server-managed/config.ts +158 -0
  223. package/server-managed/content-access.ts +15 -0
  224. package/server-managed/crypto.ts +25 -0
  225. package/server-managed/db-methods.ts +1725 -0
  226. package/server-managed/db-neon.ts +367 -0
  227. package/server-managed/db-schema.ts +227 -0
  228. package/server-managed/db-table-names.ts +22 -0
  229. package/server-managed/db.ts +132 -0
  230. package/server-managed/dependency-reviews.ts +248 -0
  231. package/server-managed/github-app.ts +391 -0
  232. package/server-managed/github-errors.ts +68 -0
  233. package/server-managed/github-issue-links.ts +127 -0
  234. package/server-managed/github-issue-oauth.ts +47 -0
  235. package/server-managed/github-issues.ts +152 -0
  236. package/server-managed/github-metadata.ts +213 -0
  237. package/server-managed/github-oauth.ts +235 -0
  238. package/server-managed/github-pulls.ts +227 -0
  239. package/server-managed/http-response.ts +50 -0
  240. package/server-managed/http.ts +3472 -0
  241. package/server-managed/import-triage.ts +51 -0
  242. package/server-managed/index.ts +187 -0
  243. package/server-managed/kept.ts +28 -0
  244. package/server-managed/link-reports.ts +158 -0
  245. package/server-managed/locked-packages.ts +85 -0
  246. package/server-managed/lru.ts +30 -0
  247. package/server-managed/managed-issues.ts +75 -0
  248. package/server-managed/management-catalog.ts +45 -0
  249. package/server-managed/management.ts +99 -0
  250. package/server-managed/npm-insights.ts +440 -0
  251. package/server-managed/npm-kept.ts +65 -0
  252. package/server-managed/npm-loads.ts +167 -0
  253. package/server-managed/npm-manifest.ts +115 -0
  254. package/server-managed/npm-packages.ts +378 -0
  255. package/server-managed/npm-provenance.ts +123 -0
  256. package/server-managed/npm-risk.ts +177 -0
  257. package/server-managed/object-storage-disk.ts +148 -0
  258. package/server-managed/object-storage-vercel.ts +67 -0
  259. package/server-managed/object-storage.ts +74 -0
  260. package/server-managed/package-entry-points.ts +51 -0
  261. package/server-managed/pretty-print.ts +352 -0
  262. package/server-managed/public-workspace.ts +220 -0
  263. package/server-managed/rename-diff-worker.js +26 -0
  264. package/server-managed/rename-diff.ts +270 -0
  265. package/server-managed/repo-path.ts +21 -0
  266. package/server-managed/report-migration.ts +35 -0
  267. package/server-managed/report-query.ts +10 -0
  268. package/server-managed/report-response.ts +16 -0
  269. package/server-managed/report-sources.ts +178 -0
  270. package/server-managed/report-store.ts +47 -0
  271. package/server-managed/repository-aliases.ts +91 -0
  272. package/server-managed/repository-browser.ts +153 -0
  273. package/server-managed/repository-discovery.ts +82 -0
  274. package/server-managed/repository-policy.ts +27 -0
  275. package/server-managed/repository-risk.ts +420 -0
  276. package/server-managed/revisions.ts +35 -0
  277. package/server-managed/session.ts +92 -0
  278. package/server-managed/slugs.ts +38 -0
  279. package/server-managed/solidity-entry-points.ts +69 -0
  280. package/server-managed/sql-postgres.ts +31 -0
  281. package/server-managed/sql.ts +76 -0
  282. package/server-managed/static.ts +28 -0
  283. package/server-managed/storage-command.ts +16 -0
  284. package/server-managed/storage-db.ts +175 -0
  285. package/server-managed/storage-encryption.ts +279 -0
  286. package/server-managed/storage-maintenance.ts +256 -0
  287. package/server-managed/storage-payload.ts +103 -0
  288. package/server-managed/storage-stores.ts +85 -0
  289. package/server-managed/storage.ts +78 -0
  290. package/server-managed/team-app.ts +94 -0
  291. package/server-managed/team-catalog.ts +7 -0
  292. package/server-managed/team-feed.ts +173 -0
  293. package/server-managed/team-npm-scopes.ts +77 -0
  294. package/server-managed/team-reports.ts +228 -0
  295. package/server-managed/triage-response.ts +16 -0
  296. package/server-managed/type-only-worker.js +19 -0
  297. package/server-managed/type-only.ts +103 -0
  298. package/server-managed/uploads.ts +86 -0
  299. package/server-managed/upstream-cache.ts +215 -0
  300. package/server-managed/view-as.ts +58 -0
  301. package/server-managed/workspace-shares.ts +166 -0
  302. package/server.ts +50 -0
  303. package/server/index.ts +0 -477
  304. package/server/lifecycle.ts +0 -204
  305. package/server/sync-handlers.ts +0 -320
  306. /package/{LICENSE → LICENSE-MIT} +0 -0
  307. /package/{server → server-e2e}/config.example.json +0 -0
  308. /package/{server → server-e2e}/objstore/blob-fs.ts +0 -0
  309. /package/{server → server-e2e}/objstore/fs.ts +0 -0
  310. /package/{server → server-e2e}/peer.ts +0 -0
  311. /package/{server → server-e2e}/util.ts +0 -0
  312. /package/{server → server-e2e}/validation.ts +0 -0
@@ -0,0 +1,10 @@
1
+ import type { SourceMap } from '@preventive/sourcemap'
2
+ import type { Edge } from '@preventive/sourcemap/edges.js'
3
+ import type { BundleDetails } from './bundle-metadata.js'
4
+ export function parseSourcemap(text: string): { json: { version: unknown; file: unknown; sourceRoot: unknown }; map: SourceMap; namesCount: number | null }
5
+ export function sourcemapEntries(details: BundleDetails): [string, string | null][]
6
+ export type SourcemapEdge = [from: string, to: string] | [from: string, to: string, specifier: string]
7
+ export function sourcemapEdges(map: SourceMap, read: (map: SourceMap) => { edges: Edge[] }): SourcemapEdge[]
8
+ export function bundleSourcemapEdges(details: BundleDetails): readonly SourcemapEdge[]
9
+ export function bundleSourcemapImports(details: BundleDetails): Map<string, Set<string>>
10
+ export function bundleSourcemapSpecifiers(details: BundleDetails): Map<string, Map<string, string>>
@@ -0,0 +1,100 @@
1
+ // A sourcemap bundle as @preventive/sourcemap reads it: plain or indexed,
2
+ // each file once, with the edges between its files, which the graph draws
3
+ // and Code links follow by the specifier of each import. A file keeps the key
4
+ // it had before the package read it, the spelling the map lists it under
5
+ // (`sourceRoot` in front), so Code links, hashes and reports that name one
6
+ // still find it.
7
+ //
8
+ // Edges take a parser for most maps. The client goes without one: it reads
9
+ // them with edges-lite.js, which knows Metro's maps alone. The server reads
10
+ // every map's with oxc-parser (server-managed/bundle-cache.ts) and keeps
11
+ // them in the bundle's metadata, which a client takes over its own.
12
+ import { readSourceMap } from '@preventive/sourcemap'
13
+ import { bundleEdges as metroEdges } from '@preventive/sourcemap/edges-lite.js'
14
+
15
+ // The map as read, and what the Overview shows of the JSON: its header,
16
+ // and how many names it maps. The JSON itself is not kept, its mappings
17
+ // read and its sources the map's files.
18
+ export function parseSourcemap(text) {
19
+ // The `)]}'` line that guards a map against being run, which readSourceMap
20
+ // skips in text it parses itself.
21
+ const json = JSON.parse(text.replace(/^\)\]\}'[^\n]*\n/u, ''))
22
+ const map = readSourceMap(json)
23
+ const namesCount = json.sections
24
+ ? json.sections.reduce((count, section) => count + (section.map.names?.length ?? 0), 0)
25
+ : json.names?.length ?? null
26
+ return { json: { version: json.version, file: json.file, sourceRoot: json.sourceRoot }, map, namesCount }
27
+ }
28
+
29
+ // The map's files as `[key, content or null]`, in its order. Details with
30
+ // no map read (the npm viewer's, a map's arrays as written) list what their
31
+ // `sources` does, a repeated path repeated.
32
+ export function sourcemapEntries(details) {
33
+ if (details.map) return details.map.files.flatMap((file) => file.source === null ? [] : [[file.source, file.content]])
34
+ const contents = details.json?.sourcesContent ?? []
35
+ return (details.json?.sources ?? []).map((path, i) => [path, contents[i] ?? null])
36
+ }
37
+
38
+ // `read`'s edges between files of the map, as rows by key: `[from, to]`,
39
+ // and `[from, to, specifier]` for an import its specifier names. An edge to
40
+ // no file of it (a package left out, a builtin) leads nowhere here, and one
41
+ // to its own file is none, which metadata refuses to hold.
42
+ export function sourcemapEdges(map, read) {
43
+ const rows = new Map()
44
+ for (const { from, to, specifier } of read(map).edges) {
45
+ if (from.source === null || to?.source == null || to.source === from.source) continue
46
+ const row = typeof specifier === 'string' ? [from.source, to.source, specifier] : [from.source, to.source]
47
+ rows.set(row.join('\0'), row)
48
+ }
49
+ return [...rows.values()]
50
+ }
51
+
52
+ // edges-lite.js throws for a map that is not Metro's, or one with no
53
+ // sourcesContent to read: one with no edges the client can tell.
54
+ function liteEdges(map) {
55
+ try { return metroEdges(map) } catch { return { edges: [] } }
56
+ }
57
+
58
+ const NO_EDGES = Object.freeze([])
59
+
60
+ // The edges a view reads: those the metadata carries, else what the client
61
+ // reads of its own map. None without a map.
62
+ export function bundleSourcemapEdges(details) {
63
+ if (details?.edges) return details.edges
64
+ if (!details?.map) return NO_EDGES
65
+ details.edges = sourcemapEdges(details.map, liteEdges)
66
+ return details.edges
67
+ }
68
+
69
+ const importsCache = new WeakMap()
70
+ const specifiersCache = new WeakMap()
71
+
72
+ // The graph's: `Map<from, Set<to>>`.
73
+ export function bundleSourcemapImports(details) {
74
+ const rows = bundleSourcemapEdges(details)
75
+ if (!importsCache.has(rows)) {
76
+ const imports = new Map()
77
+ for (const [from, to] of rows) {
78
+ if (!imports.has(from)) imports.set(from, new Set())
79
+ imports.get(from).add(to)
80
+ }
81
+ importsCache.set(rows, imports)
82
+ }
83
+ return importsCache.get(rows)
84
+ }
85
+
86
+ // Code links': the file each import's specifier names, `Map<from,
87
+ // Map<specifier, to>>`.
88
+ export function bundleSourcemapSpecifiers(details) {
89
+ const rows = bundleSourcemapEdges(details)
90
+ if (!specifiersCache.has(rows)) {
91
+ const specifiers = new Map()
92
+ for (const [from, to, specifier] of rows) {
93
+ if (specifier === undefined) continue
94
+ if (!specifiers.has(from)) specifiers.set(from, new Map())
95
+ specifiers.get(from).set(specifier, to)
96
+ }
97
+ specifiersCache.set(rows, specifiers)
98
+ }
99
+ return specifiersCache.get(rows)
100
+ }
@@ -0,0 +1,7 @@
1
+ import type { BundleDetails } from './bundle-metadata.js'
2
+ export function bundleSourcesAsMap(details: BundleDetails): Map<string, string>
3
+ export function bundleSourceLines(content: string): number
4
+ export function bundleSourceOrder(sources: Map<string, string>): { paths: string[]; numbers: Map<string, number> }
5
+ export function bundleFilesAsMap(details: BundleDetails): Map<string, string | { format: 'base64'; data: string }>
6
+ export function bundlePackageDirs(details: BundleDetails): Map<string, string> | null
7
+ export function bundlePackageVersions(details: BundleDetails, paths?: Iterable<string> | null, keyOf?: (dir: string, info: { name: string; version: string }) => string): Map<string, Set<string>>
@@ -0,0 +1,310 @@
1
+ // Shared helper: given a parsed bundle `details`, return a
2
+ // Map<file, content-string> of its source files. Stasis bundles
3
+ // expose sources via `@exodus/stasis-core`'s `Bundle.sources` getter
4
+ // (flat Map<projectRelPath, content> from `.modules`); sourcemaps list
5
+ // their files as bundle-sourcemap.js reads them. Sourcemap sources whose
6
+ // `sourcesContent[i]` was omitted are skipped.
7
+ //
8
+ // What a stasis bundle records is not all source: `Bundle.formats`
9
+ // marks each entry, and three of those formats are files only in the
10
+ // sense that they occupy a path. `Bundle.sources` hands back the raw
11
+ // stored content for every one of them, so the format is what has to
12
+ // be asked — the content's JS type cannot tell them apart:
13
+ //
14
+ // directory a readdir capture, content `["a.js","b.js"]`
15
+ // resource:base64 an image or font, content its base64 text
16
+ // resource raw bytes, the one case not a string already
17
+ //
18
+ // A directory capture is the one that does real damage, because it
19
+ // claims a path a directory also occupies: hand it to the terminal
20
+ // and `ls` lists the name twice and stops listing what is under it.
21
+ // The base64 resources are quieter but equally wrong — a PNG read as
22
+ // a wall of text, counted toward the language shares that
23
+ // render-bundle.js says resources are kept out of.
24
+ //
25
+ // Own module so `render-bundle.js` (Code tab, finding tree, graph
26
+ // data) and `terminal-attach.js` (in-shell FS) read one definition —
27
+ // a new bundle kind or field handled here is visible to both.
28
+
29
+ import { Bundle } from '@exodus/stasis-core/bundle'
30
+ import { sourcemapEntries } from './bundle-sourcemap.js'
31
+ import { utf8ByteLength } from './utf8.js'
32
+
33
+ const sourcesCache = new WeakMap()
34
+ const filesCache = new WeakMap()
35
+ const sizesCache = new WeakMap()
36
+ const sourceSizesCache = new WeakMap()
37
+ const kindsCache = new WeakMap()
38
+ const unsizedCache = new WeakMap()
39
+
40
+ export function bundleSourcesAsMap(details) {
41
+ if (details?.metadataOnly) return new Map()
42
+ const key = details?.bundle ?? details?.json
43
+ if (key && sourcesCache.has(key)) return sourcesCache.get(key)
44
+ const result = new Map()
45
+ if (!details) return result
46
+ if (details.kind === 'stasis') {
47
+ if (!details.bundle) return result
48
+ const formats = details.bundle.formats
49
+ for (const [file, content] of details.bundle.sources) {
50
+ if (typeof content !== 'string') continue
51
+ // `isResourceFormat` is the package's own list, so a format it
52
+ // adds later is excluded here without this needing to know. A v0
53
+ // bundle records no formats at all: `get` returns undefined,
54
+ // which is not a resource format, and every entry is kept as
55
+ // before.
56
+ if (Bundle.isResourceFormat(formats?.get(file))) continue
57
+ result.set(file, content)
58
+ }
59
+ } else if (details.kind === 'sourcemap') {
60
+ for (const [path, content] of sourcemapEntries(details)) {
61
+ if (typeof content === 'string') result.set(path, content)
62
+ }
63
+ }
64
+ if (key) sourcesCache.set(key, result)
65
+ return result
66
+ }
67
+
68
+ // A source's lines as the viewer numbers them: one more than its newlines.
69
+ export function bundleSourceLines(content) {
70
+ let lines = 1
71
+ for (let at = content.indexOf('\n'); at !== -1; at = content.indexOf('\n', at + 1)) lines++
72
+ return lines
73
+ }
74
+
75
+ // A bundle's sources in path order, and each one's number in it from 1: how a
76
+ // managed Code link names a file. The content hash that names a bundle fixes
77
+ // its sources, so the numbers stay put.
78
+ const sourceOrders = new WeakMap()
79
+ export function bundleSourceOrder(sources) {
80
+ let order = sourceOrders.get(sources)
81
+ if (!order) {
82
+ const paths = [...sources.keys()].toSorted()
83
+ order = { paths, numbers: new Map(paths.map((path, index) => [path, index + 1])) }
84
+ sourceOrders.set(sources, order)
85
+ }
86
+ return order
87
+ }
88
+
89
+ // Every file the bundle carries, for a reader that wants the filesystem
90
+ // rather than the source: what `bundleSourcesAsMap` returns, plus the
91
+ // resources it leaves out, each in the form that is true to it.
92
+ //
93
+ // The two resource formats are not two encodings of the same thing.
94
+ // Stasis picks between them by whether the bytes are valid UTF-8:
95
+ // `resource` is that text, stored verbatim, so it stays a string and a
96
+ // reader sees the SVG or the licence file it is. `resource:base64` is
97
+ // the other case — bytes no string spells — and it is handed over
98
+ // still spelt that way, as `{ format: 'base64', data }`. The terminal
99
+ // takes that form since 2.0 and decodes it the first time the file is
100
+ // read, so a bundle carrying a hundred images costs a hundred strings
101
+ // rather than a hundred decoded buffers, and pays for the one that is
102
+ // opened. Either way it knows what it is holding: `ls -l` and `wc -c`
103
+ // report the real byte count rather than the length of the base64, and
104
+ // `cat` declines instead of printing mojibake.
105
+ //
106
+ // Decoding there also puts the diagnostic where the file is named: a
107
+ // spelling that does not decode is reported by the command that read
108
+ // it, under the detail `base64 source`. Decoding here could only drop
109
+ // the file and say nothing.
110
+ //
111
+ // Directory captures stay out. They are the one entry that is not a
112
+ // file in any encoding, and mounting one puts a second `lib` beside
113
+ // the directory it names.
114
+ export function bundleFilesAsMap(details) {
115
+ const key = details?.bundle ?? details?.json
116
+ if (key && filesCache.has(key)) return filesCache.get(key)
117
+ const files = new Map(bundleSourcesAsMap(details))
118
+ // An npm package's files that are not text (view/npm-package.js) carry
119
+ // their size and digest alone: enough for Compare to tell a changed one
120
+ // from an unchanged one, as `{ format: 'digest', size, digest }`.
121
+ for (const [path, file] of details?.npmBinaries ?? []) files.set(path, { format: 'digest', ...file })
122
+ if (details?.kind === 'stasis' && details.bundle) {
123
+ const formats = details.bundle.formats
124
+ for (const [file, content] of details.bundle.sources) {
125
+ if (typeof content !== 'string') continue
126
+ const format = formats?.get(file)
127
+ if (format === 'resource') files.set(file, content)
128
+ else if (format === 'resource:base64') files.set(file, { format: 'base64', data: content })
129
+ }
130
+ }
131
+ if (key) filesCache.set(key, files)
132
+ return files
133
+ }
134
+
135
+ const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
136
+
137
+ // The length of the bytes a base64 spelling decodes to — three for every
138
+ // four characters, less what the padding stands for — or null when it
139
+ // decodes to none. The terminal decodes strictly (`@exodus/bytes`): RFC
140
+ // 4648's alphabet, no whitespace, padding present or left off but never
141
+ // wrong, and no stray bits in the last character. A spelling it refuses is
142
+ // a file `wc -c` cannot read, so it gets no size here rather than an
143
+ // invented one. Checked without decoding: a bundle's images are not worth
144
+ // a buffer each just to be weighed.
145
+ function base64ByteLength(text) {
146
+ const padded = text.endsWith('=')
147
+ if (padded ? text.length % 4 !== 0 || text.at(-3) === '=' : text.length % 4 === 1) return null
148
+ const data = padded ? text.slice(0, text.endsWith('==') ? -2 : -1) : text
149
+ if (/[^A-Za-z0-9+/]/u.test(data)) return null
150
+ // A trailing group of 2 or 3 characters spells 1 or 2 bytes; the bits of
151
+ // its last character past those bytes must be zero.
152
+ const tail = data.length % 4
153
+ if (tail > 1 && BASE64_ALPHABET.indexOf(data.at(-1)) & (tail === 2 ? 0x0f : 0x03)) return null
154
+ return Math.floor(data.length * 3 / 4)
155
+ }
156
+
157
+ // The byte size of one entry of `bundleFilesAsMap`, as `wc -c` reports it
158
+ // in the terminal: text weighs the UTF-8 it encodes to, and a base64
159
+ // declaration the bytes it decodes to — null when it decodes to none.
160
+ // Anything else is no file, and null too. The Compare tab sizes both
161
+ // bundles' files by this, so its totals are the Overview's.
162
+ export function bundleFileByteLength(content) {
163
+ if (typeof content === 'string') return utf8ByteLength(content)
164
+ if (content?.format === 'digest') return content.size
165
+ return content?.format === 'base64' ? base64ByteLength(content.data) : null
166
+ }
167
+
168
+ // Every path the bundle records, keyed to the byte size of the file it
169
+ // holds — the size `wc -c` reports for it in the terminal, which is to
170
+ // say the size of the entry in `bundleFilesAsMap`: a source or `resource`
171
+ // weighs the UTF-8 its text encodes to, a `resource:base64` the bytes its
172
+ // base64 decodes to. Null is a path with no size to give: a directory
173
+ // capture, which is no file; a sourcemap source whose body was left out;
174
+ // or a `resource:base64` whose spelling does not decode.
175
+ //
176
+ // The Overview and the Treemap weigh a bundle by this, so what they show
177
+ // adds up to what `du` does. Neither the source-only view nor the base64
178
+ // text will do: the first drops every image and font, the second
179
+ // inflates them by a third.
180
+ //
181
+ // Metadata views need byte sizes and paths, never the bodies. The
182
+ // persistent index supplies this map directly; full parses compute it once.
183
+ export function bundleFileSizes(details) {
184
+ if (details?.fileSizes) return details.fileSizes
185
+ const key = details?.bundle ?? details?.json
186
+ if (key && sizesCache.has(key)) return sizesCache.get(key)
187
+ const sizes = new Map()
188
+ const files = bundleFilesAsMap(details)
189
+ const paths = details?.kind === 'stasis' ? details.bundle?.sources.keys() : details && sourcemapEntries(details).map(([path]) => path)
190
+ for (const path of paths ?? []) sizes.set(path, bundleFileByteLength(files.get(path)))
191
+ if (key) sizesCache.set(key, sizes)
192
+ return sizes
193
+ }
194
+
195
+ // The files the terminal mounts that have no size: a `resource:base64`
196
+ // whose spelling does not decode. The terminal lists such a file, and says
197
+ // why it cannot read it when asked, so the views list it too — but a null
198
+ // size alone cannot tell it from an entry that is no file at all, so it is
199
+ // named here. A metadata-only open reads the set from the index, which is
200
+ // the one thing a size and a format cannot say about a path.
201
+ export function bundleUnsizedFiles(details) {
202
+ if (details?.metadataOnly) return details.unsizedFiles ?? new Set()
203
+ const sizes = bundleFileSizes(details)
204
+ if (unsizedCache.has(sizes)) return unsizedCache.get(sizes)
205
+ const unsized = new Set()
206
+ for (const path of bundleFilesAsMap(details).keys()) if (sizes.get(path) === null) unsized.add(path)
207
+ unsizedCache.set(sizes, unsized)
208
+ return unsized
209
+ }
210
+
211
+ // What each file the terminal mounts is, for a view that lists files:
212
+ // 'source', or 'resource' for an image, font or other asset — a file, sized
213
+ // like any other, but none the source viewer can show. Its keys are
214
+ // `bundleFilesAsMap`'s, and nothing else: a directory capture, or an entry
215
+ // with no body to mount (a sourcemap source whose `sourcesContent` was left
216
+ // out, a body that is no string), is no file and is absent. It reads sizes
217
+ // and formats, not bodies, so it answers for a metadata-only open as it
218
+ // does for a parsed one: a sized path is a mounted file, and so is one
219
+ // `bundleUnsizedFiles` names.
220
+ export function bundleFileKinds(details) {
221
+ const sizes = bundleFileSizes(details)
222
+ if (kindsCache.has(sizes)) return kindsCache.get(sizes)
223
+ const unsized = bundleUnsizedFiles(details)
224
+ const formats = details?.kind === 'stasis' ? details.bundle?.formats : null
225
+ const kinds = new Map()
226
+ for (const [path, size] of sizes) {
227
+ if (size === null && !unsized.has(path)) continue
228
+ // An npm package's file that is not text is a resource too: no source to view.
229
+ kinds.set(path, Bundle.isResourceFormat(formats?.get(path)) || details?.npmBinaries?.has(path) ? 'resource' : 'source')
230
+ }
231
+ kindsCache.set(sizes, kinds)
232
+ return kinds
233
+ }
234
+
235
+ // Source paths and their recorded byte sizes. Keep null for a source whose
236
+ // body is missing, but omit resources and directory captures entirely so
237
+ // graph consumers can distinguish unknown source bytes from non-source.
238
+ export function bundleSourceSizes(details) {
239
+ const sizes = bundleFileSizes(details)
240
+ const formats = details?.kind === 'stasis' ? details.bundle?.formats : null
241
+ if (!formats || formats.size === 0) return sizes
242
+ if (sourceSizesCache.has(sizes)) return sourceSizesCache.get(sizes)
243
+ const result = new Map()
244
+ for (const [path, size] of sizes) if (!Bundle.isResourceFormat(formats.get(path))) result.set(path, size)
245
+ sourceSizesCache.set(sizes, result)
246
+ return result
247
+ }
248
+
249
+ // Map each stasis bundle source path to the package directory that
250
+ // owns it. A stasis `Bundle` already records authoritative package
251
+ // boundaries in `.modules` (a `Map<dir, { name, version, files }>`
252
+ // covering both `node_modules` deps and workspace packages — the
253
+ // PHP `vendor/<vendor>/<pkg>` case, monorepo workspaces, etc.); this
254
+ // mirrors the `Bundle.sources` getter's path construction (`dir/rel`,
255
+ // or just `rel` for the `.` root) so the returned keys line up exactly
256
+ // with `bundleSourcesAsMap`'s.
257
+ //
258
+ // The package views (overview, treemap, graph) otherwise classify
259
+ // paths with `bundlePkgOf`'s string heuristic, which only knows
260
+ // `node_modules/`/`dependencies/` and treats everything else as own
261
+ // source. Feeding each path's recorded module directory into
262
+ // `bundlePkgOf` (`packageDir` option) keeps those packages separate.
263
+ //
264
+ // Returns null for sourcemap bundles (and anything without parsed
265
+ // modules) — they carry no package metadata, so callers fall back to
266
+ // the path heuristic alone.
267
+ export function bundlePackageDirs(details) {
268
+ if (details?.kind !== 'stasis' || !details.bundle?.modules) return null
269
+ const map = new Map()
270
+ for (const [dir, info] of details.bundle.modules) {
271
+ for (const rel of Object.keys(info.files)) {
272
+ map.set(dir === '.' ? rel : `${dir}/${rel}`, dir)
273
+ }
274
+ }
275
+ return map
276
+ }
277
+
278
+ // Map each named npm dependency a (stasis) bundle carries to the set of
279
+ // concrete versions present for it: `Map<name, Set<version>>`. Only
280
+ // `node_modules/...` modules that carry both a name and a non-empty
281
+ // version string count — stasis v1 `scope: 'full'` bundles merge
282
+ // workspace / own-source entries into the same `Bundle.modules` Map
283
+ // (often `version: '0.0.0'` or null), and those aren't upstream
284
+ // dependencies, so they're filtered out by the directory key. A package
285
+ // can map to more than one version (pnpm keeps duplicate majors side by
286
+ // side), hence the Set.
287
+ //
288
+ // Shared by the Advisories tab (a bulk registry lookup keyed on these
289
+ // pairs) and the Compare slide (the dependency version-update diff).
290
+ // Returns an empty Map for sourcemaps and v0 stasis bundles — neither
291
+ // carries per-module version metadata. `keyOf(dir, info)` names each
292
+ // entry, the recorded name by default; Compare keys by install directory,
293
+ // as its sizes are, so an npm alias (`node_modules/alias` recording
294
+ // `actual-package`) joins its own sizes.
295
+ export function bundlePackageVersions(details, paths = null, keyOf = (_dir, info) => info.name) {
296
+ const versions = new Map()
297
+ if (details?.kind !== 'stasis' || !details.bundle?.modules) return versions
298
+ const packageDirs = paths === null ? null : bundlePackageDirs(details)
299
+ const selectedDirs = paths === null ? null : new Set([...paths].map(path => packageDirs?.get(path)))
300
+ for (const [dir, info] of details.bundle.modules) {
301
+ if (selectedDirs && !selectedDirs.has(dir)) continue
302
+ if (!dir.includes('node_modules')) continue
303
+ if (!info?.name || typeof info.version !== 'string' || !info.version) continue
304
+ const key = keyOf(dir, info)
305
+ let set = versions.get(key)
306
+ if (!set) { set = new Set(); versions.set(key, set) }
307
+ set.add(info.version)
308
+ }
309
+ return versions
310
+ }
@@ -0,0 +1,41 @@
1
+ import { langForPath } from './code-language.js'
2
+
3
+ const BUNDLE_LANGUAGE_LABELS = Object.freeze({
4
+ javascript: 'JavaScript', jsx: 'JSX', typescript: 'TypeScript', tsx: 'TSX',
5
+ json: 'JSON', css: 'CSS', markup: 'HTML', yaml: 'YAML', bash: 'Shell',
6
+ markdown: 'Markdown', solidity: 'Solidity', php: 'PHP', rust: 'Rust',
7
+ ruby: 'Ruby', java: 'Java', cpp: 'C++', c: 'C', objectivec: 'Objective-C',
8
+ python: 'Python', go: 'Go', kotlin: 'Kotlin', swift: 'Swift', dart: 'Dart',
9
+ sql: 'SQL', lua: 'Lua', csharp: 'C#', scala: 'Scala', vue: 'Vue', svelte: 'Svelte',
10
+ })
11
+ const BUNDLE_EXTENSION_LANGUAGES = Object.freeze({
12
+ py: 'python', pyw: 'python', go: 'go', kt: 'kotlin', kts: 'kotlin', swift: 'swift',
13
+ dart: 'dart', sql: 'sql', lua: 'lua', cs: 'csharp', scala: 'scala', sc: 'scala',
14
+ vue: 'vue', svelte: 'svelte',
15
+ })
16
+
17
+ function bundleLanguageOf(path) {
18
+ const basename = typeof path === 'string' ? path.slice(path.lastIndexOf('/') + 1) : ''
19
+ const dot = basename.lastIndexOf('.')
20
+ const ext = dot > 0 ? basename.slice(dot + 1).trim().toLowerCase() : ''
21
+ const lang = langForPath(path) ?? BUNDLE_EXTENSION_LANGUAGES[ext]
22
+ if (lang) return { key: lang, label: BUNDLE_LANGUAGE_LABELS[lang] ?? lang }
23
+ return ext ? { key: `extension:${ext}`, label: `.${ext}` } : { key: 'other', label: 'Other' }
24
+ }
25
+
26
+ export function bundleCodeStats(lineCounts, fileSizes) {
27
+ const languages = new Map()
28
+ let bytes = 0, lines = 0
29
+ for (const [path, count] of lineCounts) {
30
+ const language = bundleLanguageOf(path)
31
+ const stat = languages.get(language.key) ?? { ...language, files: 0, lines: 0, bytes: 0 }
32
+ stat.files++
33
+ stat.lines += count
34
+ stat.bytes += fileSizes.get(path) ?? 0
35
+ languages.set(language.key, stat)
36
+ lines += count
37
+ bytes += fileSizes.get(path) ?? 0
38
+ }
39
+ return { files: lineCounts.size, lines, bytes, languages: [...languages.values()]
40
+ .toSorted((a, b) => b.lines - a.lines || a.label.localeCompare(b.label)) }
41
+ }
@@ -0,0 +1 @@
1
+ export const BUNDLE_TABS = new Set(['overview', 'graph', 'treemap', 'compare', 'advisories', 'issues', 'code', 'search', 'terminal'])
@@ -0,0 +1,65 @@
1
+ const EXT_TO_LANG = {
2
+ __proto__: null,
3
+ js: 'javascript', mjs: 'javascript', cjs: 'javascript',
4
+ jsx: 'jsx',
5
+ ts: 'typescript', mts: 'typescript', cts: 'typescript',
6
+ tsx: 'tsx',
7
+ json: 'json',
8
+ css: 'css',
9
+ html: 'markup', htm: 'markup', xml: 'markup', svg: 'markup',
10
+ yml: 'yaml', yaml: 'yaml',
11
+ sh: 'bash', bash: 'bash',
12
+ md: 'markdown', markdown: 'markdown',
13
+ sol: 'solidity',
14
+ php: 'php', phtml: 'php',
15
+ rs: 'rust',
16
+ rb: 'ruby',
17
+ py: 'python', pyw: 'python', pyi: 'python',
18
+ java: 'java',
19
+ // .h is claimed by C, C++ and Objective-C alike. The C++ grammar
20
+ // extends the C one, so it is the superset that colours a header
21
+ // from any of the three; the C grammar would leave a C++ header's
22
+ // class/template/namespace keywords plain.
23
+ cpp: 'cpp', cc: 'cpp', cxx: 'cpp', 'c++': 'cpp',
24
+ hpp: 'cpp', hh: 'cpp', hxx: 'cpp', 'h++': 'cpp', h: 'cpp',
25
+ c: 'c',
26
+ m: 'objectivec', mm: 'objectivec',
27
+ }
28
+
29
+ // Stasis records loader formats as well as language names. Resolve those to
30
+ // the grammars we ship before consulting the filename.
31
+ const FORMAT_TO_LANG = {
32
+ __proto__: null,
33
+ module: 'javascript', commonjs: 'javascript',
34
+ 'module-typescript': 'typescript', 'commonjs-typescript': 'typescript',
35
+ json: 'json', solidity: 'solidity', php: 'php', shell: 'bash', rust: 'rust',
36
+ java: 'java', objc: 'objectivec', objcpp: 'objectivec',
37
+ c: 'c', 'c-header': 'c', cpp: 'cpp', 'cpp-header': 'cpp',
38
+ ruby: 'ruby', podspec: 'ruby', podfile: 'ruby', fastlane: 'ruby',
39
+ 'podfile-lock': 'yaml', xml: 'markup',
40
+ }
41
+
42
+ // Whether a text is a JSON object by its ends: `{` first and `}` last,
43
+ // whitespace aside. Read from both ends, never the whole of a large file.
44
+ function objectText(content) {
45
+ if (typeof content !== 'string') return false
46
+ let end = content.length - 1, start = 0
47
+ while (start < end && /\s/u.test(content[start])) start++
48
+ while (end > start && /\s/u.test(content[end])) end--
49
+ return start < end && content[start] === '{' && content[end] === '}'
50
+ }
51
+
52
+ // `content`, where the caller has it, settles what the name alone cannot: a
53
+ // `.map` file is a source map, JSON, when its text is an object.
54
+ export function langForPath(path, format, content) {
55
+ if (typeof format === 'string' && FORMAT_TO_LANG[format]) return FORMAT_TO_LANG[format]
56
+ if (typeof path !== 'string') return null
57
+ // Only the filename can contribute an extension. A dotted directory such
58
+ // as `.abc/edf` is a path segment, not an extension on `edf`.
59
+ const basename = path.slice(path.lastIndexOf('/') + 1)
60
+ const dot = basename.lastIndexOf('.')
61
+ if (dot <= 0) return null
62
+ const ext = basename.slice(dot + 1).toLowerCase()
63
+ if (ext === 'map') return objectText(content) ? 'json' : null
64
+ return EXT_TO_LANG[ext] ?? null
65
+ }
@@ -0,0 +1,31 @@
1
+ // Shared starting catalogue for the UI and managed server until scan-service
2
+ // discovery supplies supported models and effort limits. Use undated aliases.
3
+ export function defaultScanModels(provider: string | null = null) {
4
+ const ids = [
5
+ 'anthropic/claude-opus-5.5',
6
+ 'anthropic/claude-fable-5.1',
7
+ 'anthropic/claude-sonnet-5.5',
8
+ 'anthropic/claude-haiku-5.5',
9
+ 'openai/gpt-6-astra',
10
+ 'openai/gpt-6-astra-pro',
11
+ 'openai/gpt-6.1-sol',
12
+ 'openai/gpt-6.1-sol-pro',
13
+ 'openai/gpt-6-luna',
14
+ 'openai/gpt-6-luna-pro',
15
+ 'moonshotai/kimi-k3',
16
+ 'google/gemini-3.8-flash',
17
+ 'google/gemma-4-31b-it',
18
+ 'x-ai/grok-4.7',
19
+ 'z-ai/glm-5.3',
20
+ 'qwen/qwen3.8-max',
21
+ 'deepseek/deepseek-v4.1-flash',
22
+ 'deepseek/deepseek-v4-pro',
23
+ 'mistralai/mistral-large-4-0',
24
+ ]
25
+ // OpenRouter routes all listed providers. Direct-provider credentials only
26
+ // expose that provider's models; no selection leaves the catalogue intact.
27
+ const prefix = provider === 'moonshot' ? 'moonshotai' : provider
28
+ const models = ids.filter(id => !['anthropic', 'openai', 'moonshot'].includes(provider ?? '') || id.startsWith(`${prefix}/`))
29
+ .map(id => ({ id, efforts: ['low', 'medium', 'high', 'xhigh', 'max'] }))
30
+ return { models, defaultModel: models[0]!.id }
31
+ }
@@ -0,0 +1,23 @@
1
+ export type ReviewVerdict = 'accept' | 'accept-with-nits' | 'request-changes' | 'reject'
2
+ export interface ReviewSummary {
3
+ coverage: 'full' | 'partial' | null
4
+ checkedFiles: number | null
5
+ totalFiles: number | null
6
+ verdictLimit: ReviewVerdict
7
+ }
8
+ export const REVIEW_VERDICTS: Record<ReviewVerdict, string>
9
+ export function isReviewableFile(before: unknown, after: unknown): boolean
10
+ export function summarizeReview(entries: { file: string; status: ReviewVerdict | null }[], files: string[] | null, nonTextFiles?: string[] | null): ReviewSummary
11
+ export function allowedReviewVerdicts(entries: { file: string; status: ReviewVerdict | null }[], limit?: ReviewVerdict): ReviewVerdict[]
12
+ interface ReviewNpmVersion { version: string; private?: boolean; manifest?: Record<string, unknown> }
13
+ export function npmTakenDown(entry: { npm: ReviewNpmVersion }): boolean
14
+ export function npmReviewVerdictLimit(npm: ReviewNpmVersion | null | undefined,
15
+ advisoryList?: { versions: string[]; advisories: { id?: string; title?: string; severity?: string; cwe?: string[]; affected: number[] }[] } | null,
16
+ socket?: { alerts: { type: string }[] } | null, risk?: { findings: { id: string }[] } | null): ReviewVerdict
17
+ export function needsPackageJsonReview(entries: { file: string; status: ReviewVerdict | null }[], files: string[], nonTextFiles?: string[] | null): boolean
18
+ export function parseReviewNotes(comment: string): { text: string; bullet: boolean }[]
19
+ export function auditMarkdown(review: ReviewSummary & {
20
+ package: string; version: string; integrity: string; basePackage: string | null; baseVersion: string | null
21
+ publishedAt: number | null; revokedAt: number | null; entries: { file: string; comment: string }[]
22
+ status: ReviewVerdict | null; comment: string
23
+ }): string | null