@rainy-updates/cli 0.5.2-rc.2 → 0.5.3

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 (58) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +105 -22
  3. package/dist/bin/cli.js +124 -9
  4. package/dist/cache/cache.d.ts +1 -0
  5. package/dist/cache/cache.js +9 -2
  6. package/dist/commands/audit/runner.js +8 -1
  7. package/dist/commands/audit/sources/index.js +8 -1
  8. package/dist/commands/doctor/parser.d.ts +2 -0
  9. package/dist/commands/doctor/parser.js +92 -0
  10. package/dist/commands/doctor/runner.d.ts +2 -0
  11. package/dist/commands/doctor/runner.js +13 -0
  12. package/dist/commands/resolve/runner.js +3 -0
  13. package/dist/commands/review/parser.d.ts +2 -0
  14. package/dist/commands/review/parser.js +174 -0
  15. package/dist/commands/review/runner.d.ts +2 -0
  16. package/dist/commands/review/runner.js +30 -0
  17. package/dist/config/loader.d.ts +3 -0
  18. package/dist/core/check.js +64 -5
  19. package/dist/core/errors.d.ts +11 -0
  20. package/dist/core/errors.js +6 -0
  21. package/dist/core/options.d.ts +8 -1
  22. package/dist/core/options.js +43 -0
  23. package/dist/core/review-model.d.ts +5 -0
  24. package/dist/core/review-model.js +372 -0
  25. package/dist/core/summary.js +11 -2
  26. package/dist/core/upgrade.d.ts +1 -0
  27. package/dist/core/upgrade.js +27 -21
  28. package/dist/core/warm-cache.js +28 -4
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.js +2 -0
  31. package/dist/output/format.d.ts +4 -1
  32. package/dist/output/format.js +41 -3
  33. package/dist/output/github.js +6 -1
  34. package/dist/output/sarif.js +14 -0
  35. package/dist/registry/npm.d.ts +22 -0
  36. package/dist/registry/npm.js +33 -4
  37. package/dist/risk/index.d.ts +3 -0
  38. package/dist/risk/index.js +24 -0
  39. package/dist/risk/scorer.d.ts +3 -0
  40. package/dist/risk/scorer.js +114 -0
  41. package/dist/risk/signals/fresh-package.d.ts +3 -0
  42. package/dist/risk/signals/fresh-package.js +22 -0
  43. package/dist/risk/signals/install-scripts.d.ts +3 -0
  44. package/dist/risk/signals/install-scripts.js +10 -0
  45. package/dist/risk/signals/maintainer-churn.d.ts +3 -0
  46. package/dist/risk/signals/maintainer-churn.js +11 -0
  47. package/dist/risk/signals/metadata.d.ts +3 -0
  48. package/dist/risk/signals/metadata.js +18 -0
  49. package/dist/risk/signals/mutable-source.d.ts +3 -0
  50. package/dist/risk/signals/mutable-source.js +24 -0
  51. package/dist/risk/signals/typosquat.d.ts +3 -0
  52. package/dist/risk/signals/typosquat.js +70 -0
  53. package/dist/risk/types.d.ts +15 -0
  54. package/dist/risk/types.js +1 -0
  55. package/dist/types/index.d.ts +85 -0
  56. package/dist/ui/tui.d.ts +0 -4
  57. package/dist/ui/tui.js +103 -21
  58. package/package.json +10 -2
@@ -30,6 +30,15 @@ export function createSarifReport(result) {
30
30
  kind: update.kind,
31
31
  diffType: update.diffType,
32
32
  resolvedVersion: update.toVersionResolved,
33
+ impactRank: update.impactScore?.rank,
34
+ impactScore: update.impactScore?.score,
35
+ riskLevel: update.riskLevel,
36
+ riskScore: update.riskScore,
37
+ riskCategories: update.riskCategories ?? [],
38
+ recommendedAction: update.recommendedAction,
39
+ advisoryCount: update.advisoryCount ?? 0,
40
+ peerConflictSeverity: update.peerConflictSeverity ?? "none",
41
+ licenseStatus: update.licenseStatus ?? "allowed",
33
42
  },
34
43
  }));
35
44
  const errorResults = [...result.errors].sort((a, b) => a.localeCompare(b)).map((error) => ({
@@ -75,6 +84,11 @@ export function createSarifReport(result) {
75
84
  prLimitHit: result.summary.prLimitHit,
76
85
  fixPrBranchesCreated: result.summary.fixPrBranchesCreated,
77
86
  durationMs: result.summary.durationMs,
87
+ verdict: result.summary.verdict,
88
+ riskPackages: result.summary.riskPackages ?? 0,
89
+ securityPackages: result.summary.securityPackages ?? 0,
90
+ peerConflictPackages: result.summary.peerConflictPackages ?? 0,
91
+ licenseViolationPackages: result.summary.licenseViolationPackages ?? 0,
78
92
  },
79
93
  },
80
94
  ],
@@ -1,3 +1,13 @@
1
+ interface RegistryConfig {
2
+ defaultRegistry: string;
3
+ scopedRegistries: Map<string, string>;
4
+ authByRegistry: Map<string, RegistryAuth>;
5
+ }
6
+ interface RegistryAuth {
7
+ token?: string;
8
+ basicAuth?: string;
9
+ alwaysAuth: boolean;
10
+ }
1
11
  export interface ResolveManyOptions {
2
12
  concurrency: number;
3
13
  timeoutMs?: number;
@@ -12,6 +22,10 @@ export interface ResolveManyResult {
12
22
  latestVersion: string | null;
13
23
  versions: string[];
14
24
  publishedAtByVersion: Record<string, number>;
25
+ homepage?: string;
26
+ repository?: string;
27
+ installScriptByVersion: Record<string, boolean>;
28
+ maintainerCount: number | null;
15
29
  }>;
16
30
  errors: Map<string, string>;
17
31
  }
@@ -24,6 +38,10 @@ export declare class NpmRegistryClient {
24
38
  latestVersion: string | null;
25
39
  versions: string[];
26
40
  publishedAtByVersion: Record<string, number>;
41
+ homepage?: string;
42
+ repository?: string;
43
+ installScriptByVersion: Record<string, boolean>;
44
+ maintainerCount: number | null;
27
45
  }>;
28
46
  resolveLatestVersion(packageName: string, timeoutMs?: number): Promise<string | null>;
29
47
  resolveManyPackageMetadata(packageNames: string[], options: ResolveManyOptions): Promise<ResolveManyResult>;
@@ -32,3 +50,7 @@ export declare class NpmRegistryClient {
32
50
  errors: Map<string, string>;
33
51
  }>;
34
52
  }
53
+ export declare function loadRegistryConfig(cwd: string): Promise<RegistryConfig>;
54
+ export declare function resolveRegistryForPackage(packageName: string, config: RegistryConfig): string;
55
+ export declare function resolveAuthHeader(registry: string, config: RegistryConfig): string | undefined;
56
+ export {};
@@ -22,7 +22,13 @@ export class NpmRegistryClient {
22
22
  try {
23
23
  const response = await requester(packageName, timeoutMs);
24
24
  if (response.status === 404) {
25
- return { latestVersion: null, versions: [], publishedAtByVersion: {} };
25
+ return {
26
+ latestVersion: null,
27
+ versions: [],
28
+ publishedAtByVersion: {},
29
+ installScriptByVersion: {},
30
+ maintainerCount: null,
31
+ };
26
32
  }
27
33
  if (response.status === 429 || response.status >= 500) {
28
34
  throw new RetryableRegistryError(`Registry temporary error: ${response.status}`, response.retryAfterMs ?? computeBackoffMs(attempt));
@@ -35,6 +41,12 @@ export class NpmRegistryClient {
35
41
  latestVersion: response.data?.["dist-tags"]?.latest ?? null,
36
42
  versions,
37
43
  publishedAtByVersion: extractPublishTimes(response.data?.time),
44
+ homepage: response.data?.homepage,
45
+ repository: normalizeRepository(response.data?.repository),
46
+ installScriptByVersion: detectInstallScriptsByVersion(response.data?.versions),
47
+ maintainerCount: Array.isArray(response.data?.maintainers)
48
+ ? response.data?.maintainers.length
49
+ : null,
38
50
  };
39
51
  }
40
52
  catch (error) {
@@ -94,6 +106,23 @@ export class NpmRegistryClient {
94
106
  function sleep(ms) {
95
107
  return new Promise((resolve) => setTimeout(resolve, ms));
96
108
  }
109
+ function normalizeRepository(value) {
110
+ if (!value)
111
+ return undefined;
112
+ if (typeof value === "string")
113
+ return value;
114
+ return value.url;
115
+ }
116
+ function detectInstallScriptsByVersion(versions) {
117
+ if (!versions)
118
+ return {};
119
+ const results = {};
120
+ for (const [version, metadata] of Object.entries(versions)) {
121
+ const scripts = metadata?.scripts;
122
+ results[version] = Boolean(scripts?.preinstall || scripts?.install || scripts?.postinstall);
123
+ }
124
+ return results;
125
+ }
97
126
  function computeBackoffMs(attempt) {
98
127
  const baseMs = Math.max(120, attempt * 180);
99
128
  const jitterMs = Math.floor(Math.random() * 120);
@@ -195,7 +224,7 @@ async function tryCreateUndiciRequester(registryConfig) {
195
224
  return null;
196
225
  }
197
226
  }
198
- async function loadRegistryConfig(cwd) {
227
+ export async function loadRegistryConfig(cwd) {
199
228
  const homeNpmrc = path.join(os.homedir(), ".npmrc");
200
229
  const projectNpmrc = path.join(cwd, ".npmrc");
201
230
  const merged = new Map();
@@ -274,7 +303,7 @@ function normalizeRegistryUrl(value) {
274
303
  const normalized = value.endsWith("/") ? value : `${value}/`;
275
304
  return normalized;
276
305
  }
277
- function resolveRegistryForPackage(packageName, config) {
306
+ export function resolveRegistryForPackage(packageName, config) {
278
307
  const scope = extractScope(packageName);
279
308
  if (scope) {
280
309
  const scoped = config.scopedRegistries.get(scope);
@@ -295,7 +324,7 @@ function buildRegistryUrl(registry, packageName) {
295
324
  const base = normalizeRegistryUrl(registry);
296
325
  return new URL(encodeURIComponent(packageName), base).toString();
297
326
  }
298
- function resolveAuthHeader(registry, config) {
327
+ export function resolveAuthHeader(registry, config) {
299
328
  const registryUrl = normalizeRegistryUrl(registry);
300
329
  const auth = findRegistryAuth(registryUrl, config.authByRegistry);
301
330
  if (!auth)
@@ -0,0 +1,3 @@
1
+ import type { ReviewItem } from "../types/index.js";
2
+ import type { RiskContext } from "./types.js";
3
+ export declare function applyRiskAssessments(items: ReviewItem[], context: RiskContext): ReviewItem[];
@@ -0,0 +1,24 @@
1
+ import { assessRisk } from "./scorer.js";
2
+ export function applyRiskAssessments(items, context) {
3
+ return items.map((item) => {
4
+ const assessment = assessRisk({
5
+ update: item.update,
6
+ advisories: item.advisories,
7
+ health: item.health,
8
+ peerConflicts: item.peerConflicts,
9
+ licenseViolation: item.update.licenseStatus === "denied",
10
+ unusedIssues: item.unusedIssues,
11
+ }, context);
12
+ return {
13
+ ...item,
14
+ update: {
15
+ ...item.update,
16
+ riskLevel: assessment.level,
17
+ riskScore: assessment.score,
18
+ riskReasons: assessment.reasons,
19
+ riskCategories: assessment.categories,
20
+ recommendedAction: assessment.recommendedAction,
21
+ },
22
+ };
23
+ });
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskAssessment } from "../types/index.js";
2
+ import type { RiskContext, RiskInput } from "./types.js";
3
+ export declare function assessRisk(input: RiskInput, context: RiskContext): RiskAssessment;
@@ -0,0 +1,114 @@
1
+ import { detectInstallScriptsRisk } from "./signals/install-scripts.js";
2
+ import { detectTyposquatRisk } from "./signals/typosquat.js";
3
+ import { detectFreshPackageRisk } from "./signals/fresh-package.js";
4
+ import { detectSuspiciousMetadataRisk } from "./signals/metadata.js";
5
+ import { detectMutableSourceRisk } from "./signals/mutable-source.js";
6
+ import { detectMaintainerChurnRisk } from "./signals/maintainer-churn.js";
7
+ export function assessRisk(input, context) {
8
+ // Base factors model direct supply-chain behavior; modifiers adjust operational severity.
9
+ const baseFactors = [];
10
+ const modifierFactors = [];
11
+ if (input.advisories.length > 0) {
12
+ baseFactors.push({
13
+ code: "known-vulnerability",
14
+ weight: 35,
15
+ category: "known-vulnerability",
16
+ message: `${input.advisories.length} known vulnerability finding(s) affect this package.`,
17
+ });
18
+ }
19
+ const installScripts = detectInstallScriptsRisk(input);
20
+ if (installScripts)
21
+ baseFactors.push(installScripts);
22
+ const typosquat = detectTyposquatRisk(input, context);
23
+ if (typosquat)
24
+ baseFactors.push(typosquat);
25
+ const freshPackage = detectFreshPackageRisk(input);
26
+ if (freshPackage)
27
+ baseFactors.push(freshPackage);
28
+ const metadata = detectSuspiciousMetadataRisk(input);
29
+ if (metadata)
30
+ baseFactors.push(metadata);
31
+ const mutableSource = detectMutableSourceRisk(input);
32
+ if (mutableSource)
33
+ baseFactors.push(mutableSource);
34
+ const maintainerChurn = detectMaintainerChurnRisk(input);
35
+ if (maintainerChurn)
36
+ baseFactors.push(maintainerChurn);
37
+ if (input.peerConflicts.some((conflict) => conflict.severity === "error")) {
38
+ modifierFactors.push({
39
+ code: "peer-conflict",
40
+ weight: 20,
41
+ category: "operational-health",
42
+ message: "Peer dependency conflicts block safe application.",
43
+ });
44
+ }
45
+ if (input.licenseViolation) {
46
+ modifierFactors.push({
47
+ code: "license-violation",
48
+ weight: 20,
49
+ category: "operational-health",
50
+ message: "License policy would block or require review for this update.",
51
+ });
52
+ }
53
+ if (input.health?.flags.includes("deprecated")) {
54
+ modifierFactors.push({
55
+ code: "deprecated-package",
56
+ weight: 10,
57
+ category: "operational-health",
58
+ message: "Package is deprecated.",
59
+ });
60
+ }
61
+ else if (input.health?.flags.includes("stale") ||
62
+ input.health?.flags.includes("unmaintained")) {
63
+ modifierFactors.push({
64
+ code: "stale-package",
65
+ weight: 5,
66
+ category: "operational-health",
67
+ message: "Package has stale operational health signals.",
68
+ });
69
+ }
70
+ if (input.update.diffType === "major") {
71
+ modifierFactors.push({
72
+ code: "major-version",
73
+ weight: 10,
74
+ category: "operational-health",
75
+ message: "Update crosses a major version boundary.",
76
+ });
77
+ }
78
+ const baseScore = baseFactors.reduce((sum, factor) => sum + factor.weight, 0);
79
+ const modifierScore = modifierFactors.reduce((sum, factor) => sum + factor.weight, 0);
80
+ const factors = [...baseFactors, ...modifierFactors];
81
+ const score = Math.min(100, baseScore + modifierScore);
82
+ const level = scoreToLevel(score);
83
+ const categories = Array.from(new Set(factors.map((factor) => factor.category)));
84
+ const reasons = factors.map((factor) => factor.message);
85
+ return {
86
+ score,
87
+ level,
88
+ reasons,
89
+ categories,
90
+ recommendedAction: recommendAction(level, input),
91
+ factors,
92
+ };
93
+ }
94
+ function scoreToLevel(score) {
95
+ if (score >= 70)
96
+ return "critical";
97
+ if (score >= 45)
98
+ return "high";
99
+ if (score >= 20)
100
+ return "medium";
101
+ return "low";
102
+ }
103
+ function recommendAction(level, input) {
104
+ if (input.peerConflicts.some((conflict) => conflict.severity === "error")) {
105
+ return "Run `rup resolve --after-update` before applying this update.";
106
+ }
107
+ if (input.advisories.length > 0) {
108
+ return "Review in `rup review` and consider `rup audit --fix` for the secure minimum patch.";
109
+ }
110
+ if (level === "critical" || level === "high") {
111
+ return "Keep this update in review until the risk reasons are cleared.";
112
+ }
113
+ return "Safe to keep in the review queue and apply after normal verification.";
114
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskInput } from "../types.js";
3
+ export declare function detectFreshPackageRisk(input: RiskInput): RiskFactor | null;
@@ -0,0 +1,22 @@
1
+ export function detectFreshPackageRisk(input) {
2
+ const age = input.update.publishAgeDays;
3
+ if (typeof age !== "number")
4
+ return null;
5
+ if (age <= 7) {
6
+ return {
7
+ code: "fresh-package-7d",
8
+ weight: 20,
9
+ category: "behavioral-risk",
10
+ message: `Resolved version was published ${age} day(s) ago.`,
11
+ };
12
+ }
13
+ if (age <= 30) {
14
+ return {
15
+ code: "fresh-package-30d",
16
+ weight: 10,
17
+ category: "behavioral-risk",
18
+ message: `Resolved version was published ${age} day(s) ago.`,
19
+ };
20
+ }
21
+ return null;
22
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskInput } from "../types.js";
3
+ export declare function detectInstallScriptsRisk(input: RiskInput): RiskFactor | null;
@@ -0,0 +1,10 @@
1
+ export function detectInstallScriptsRisk(input) {
2
+ if (!input.update.hasInstallScript)
3
+ return null;
4
+ return {
5
+ code: "install-scripts",
6
+ weight: 20,
7
+ category: "behavioral-risk",
8
+ message: "Resolved package includes install lifecycle scripts.",
9
+ };
10
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskInput } from "../types.js";
3
+ export declare function detectMaintainerChurnRisk(input: RiskInput): RiskFactor | null;
@@ -0,0 +1,11 @@
1
+ export function detectMaintainerChurnRisk(input) {
2
+ if (input.update.maintainerChurn !== "elevated-change") {
3
+ return null;
4
+ }
5
+ return {
6
+ code: "maintainer-churn",
7
+ weight: 15,
8
+ category: "behavioral-risk",
9
+ message: "Maintainer profile looks unstable for a recent release based on available registry metadata.",
10
+ };
11
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskInput } from "../types.js";
3
+ export declare function detectSuspiciousMetadataRisk(input: RiskInput): RiskFactor | null;
@@ -0,0 +1,18 @@
1
+ export function detectSuspiciousMetadataRisk(input) {
2
+ const { homepage, repository } = input.update;
3
+ const homepageMissing = !homepage;
4
+ const repositoryMissing = !repository;
5
+ const repositoryMalformed = typeof repository === "string" &&
6
+ !repository.startsWith("http://") &&
7
+ !repository.startsWith("https://") &&
8
+ !repository.startsWith("git+");
9
+ if ((homepageMissing && repositoryMissing) || repositoryMalformed) {
10
+ return {
11
+ code: "suspicious-metadata",
12
+ weight: 10,
13
+ category: "behavioral-risk",
14
+ message: "Package metadata is incomplete or uses a non-canonical repository reference.",
15
+ };
16
+ }
17
+ return null;
18
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskInput } from "../types.js";
3
+ export declare function detectMutableSourceRisk(input: RiskInput): RiskFactor | null;
@@ -0,0 +1,24 @@
1
+ const MUTABLE_PATTERNS = [
2
+ "git+",
3
+ "github:",
4
+ "gitlab:",
5
+ "http://",
6
+ "https://",
7
+ "git://",
8
+ ];
9
+ export function detectMutableSourceRisk(input) {
10
+ const raw = input.update.fromRange;
11
+ if (!MUTABLE_PATTERNS.some((pattern) => raw.startsWith(pattern))) {
12
+ return null;
13
+ }
14
+ const immutableCommitPinned = /#[a-f0-9]{7,40}$/i.test(raw);
15
+ if (immutableCommitPinned) {
16
+ return null;
17
+ }
18
+ return {
19
+ code: "mutable-source",
20
+ weight: 25,
21
+ category: "behavioral-risk",
22
+ message: "Dependency uses a mutable git/http source without an immutable commit pin.",
23
+ };
24
+ }
@@ -0,0 +1,3 @@
1
+ import type { RiskFactor } from "../../types/index.js";
2
+ import type { RiskContext, RiskInput } from "../types.js";
3
+ export declare function detectTyposquatRisk(input: RiskInput, context: RiskContext): RiskFactor | null;
@@ -0,0 +1,70 @@
1
+ const HIGH_VALUE_PACKAGES = [
2
+ "react",
3
+ "react-dom",
4
+ "next",
5
+ "typescript",
6
+ "lodash",
7
+ "axios",
8
+ "zod",
9
+ "vite",
10
+ "eslint",
11
+ "express",
12
+ ];
13
+ export function detectTyposquatRisk(input, context) {
14
+ const target = normalizeName(input.update.name);
15
+ if (target.length < 4)
16
+ return null;
17
+ const candidates = new Set([
18
+ ...HIGH_VALUE_PACKAGES,
19
+ ...Array.from(context.knownPackageNames),
20
+ ]);
21
+ for (const candidate of candidates) {
22
+ const normalizedCandidate = normalizeName(candidate);
23
+ if (!normalizedCandidate || normalizedCandidate === target)
24
+ continue;
25
+ if (Math.abs(normalizedCandidate.length - target.length) > 1)
26
+ continue;
27
+ if (isTransposition(normalizedCandidate, target) || levenshtein(normalizedCandidate, target) === 1) {
28
+ return {
29
+ code: "typosquat-heuristic",
30
+ weight: 25,
31
+ category: "behavioral-risk",
32
+ message: `Package name is highly similar to "${candidate}", which may indicate typosquatting.`,
33
+ };
34
+ }
35
+ }
36
+ return null;
37
+ }
38
+ function normalizeName(value) {
39
+ const trimmed = value.startsWith("@") ? value.split("/")[1] ?? value : value;
40
+ return trimmed.replace(/[^a-z0-9]/gi, "").toLowerCase();
41
+ }
42
+ function isTransposition(left, right) {
43
+ if (left.length !== right.length || left === right)
44
+ return false;
45
+ const mismatches = [];
46
+ for (let index = 0; index < left.length; index += 1) {
47
+ if (left[index] !== right[index])
48
+ mismatches.push(index);
49
+ if (mismatches.length > 2)
50
+ return false;
51
+ }
52
+ if (mismatches.length !== 2)
53
+ return false;
54
+ const [first, second] = mismatches;
55
+ return left[first] === right[second] && left[second] === right[first];
56
+ }
57
+ function levenshtein(left, right) {
58
+ const matrix = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
59
+ for (let i = 0; i <= left.length; i += 1)
60
+ matrix[i][0] = i;
61
+ for (let j = 0; j <= right.length; j += 1)
62
+ matrix[0][j] = j;
63
+ for (let i = 1; i <= left.length; i += 1) {
64
+ for (let j = 1; j <= right.length; j += 1) {
65
+ const cost = left[i - 1] === right[j - 1] ? 0 : 1;
66
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost);
67
+ }
68
+ }
69
+ return matrix[left.length][right.length];
70
+ }
@@ -0,0 +1,15 @@
1
+ import type { HealthResult, PackageUpdate, PeerConflict, RiskAssessment, UnusedDependency, CveAdvisory } from "../types/index.js";
2
+ export interface RiskInput {
3
+ update: PackageUpdate;
4
+ advisories: CveAdvisory[];
5
+ health?: HealthResult["metrics"][number];
6
+ peerConflicts: PeerConflict[];
7
+ licenseViolation: boolean;
8
+ unusedIssues: UnusedDependency[];
9
+ }
10
+ export interface RiskContext {
11
+ knownPackageNames: ReadonlySet<string>;
12
+ }
13
+ export interface RiskSignalResult {
14
+ assessment: RiskAssessment;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,6 +3,10 @@ export type TargetLevel = "patch" | "minor" | "major" | "latest";
3
3
  export type GroupBy = "none" | "name" | "scope" | "kind" | "risk";
4
4
  export type CiProfile = "minimal" | "strict" | "enterprise";
5
5
  export type LockfileMode = "preserve" | "update" | "error";
6
+ export type Verdict = "safe" | "review" | "blocked" | "actionable";
7
+ export type RiskLevel = "critical" | "high" | "medium" | "low";
8
+ export type RiskCategory = "known-vulnerability" | "behavioral-risk" | "operational-health";
9
+ export type MaintainerChurnStatus = "unknown" | "stable" | "elevated-change";
6
10
  export type OutputFormat = "table" | "json" | "minimal" | "github" | "metrics";
7
11
  export type FailOnLevel = "none" | "patch" | "minor" | "major" | "any";
8
12
  export type LogLevel = "error" | "warn" | "info" | "debug";
@@ -44,6 +48,9 @@ export interface RunOptions {
44
48
  onlyChanged: boolean;
45
49
  ciProfile: CiProfile;
46
50
  lockfileMode: LockfileMode;
51
+ interactive: boolean;
52
+ showImpact: boolean;
53
+ showHomepage: boolean;
47
54
  }
48
55
  export interface CheckOptions extends RunOptions {
49
56
  }
@@ -86,6 +93,21 @@ export interface PackageUpdate {
86
93
  reason?: string;
87
94
  impactScore?: ImpactScore;
88
95
  homepage?: string;
96
+ repository?: string;
97
+ publishedAt?: string;
98
+ publishAgeDays?: number | null;
99
+ hasInstallScript?: boolean;
100
+ maintainerCount?: number | null;
101
+ maintainerChurn?: MaintainerChurnStatus;
102
+ riskLevel?: RiskLevel;
103
+ riskScore?: number;
104
+ riskReasons?: string[];
105
+ riskCategories?: RiskCategory[];
106
+ recommendedAction?: string;
107
+ advisoryCount?: number;
108
+ peerConflictSeverity?: "none" | PeerConflictSeverity;
109
+ licenseStatus?: "allowed" | "review" | "denied";
110
+ healthStatus?: "healthy" | HealthFlag;
89
111
  }
90
112
  export interface Summary {
91
113
  contractVersion: "2";
@@ -126,6 +148,13 @@ export interface Summary {
126
148
  prLimitHit: boolean;
127
149
  streamedEvents: number;
128
150
  policyOverridesApplied: number;
151
+ verdict?: Verdict;
152
+ interactiveSession?: boolean;
153
+ riskPackages?: number;
154
+ securityPackages?: number;
155
+ peerConflictPackages?: number;
156
+ licenseViolationPackages?: number;
157
+ privateRegistryPackages?: number;
129
158
  }
130
159
  export interface CheckResult {
131
160
  projectPath: string;
@@ -303,6 +332,62 @@ export interface ResolveResult {
303
332
  errors: string[];
304
333
  warnings: string[];
305
334
  }
335
+ export interface RiskSignal {
336
+ packageName: string;
337
+ code: string;
338
+ weight: number;
339
+ category: RiskCategory;
340
+ level: RiskLevel;
341
+ reasons: string[];
342
+ }
343
+ export interface RiskFactor {
344
+ code: string;
345
+ weight: number;
346
+ category: RiskCategory;
347
+ message: string;
348
+ }
349
+ export interface RiskAssessment {
350
+ score: number;
351
+ level: RiskLevel;
352
+ reasons: string[];
353
+ categories: RiskCategory[];
354
+ recommendedAction: string;
355
+ factors: RiskFactor[];
356
+ }
357
+ export interface ReviewItem {
358
+ update: PackageUpdate;
359
+ advisories: CveAdvisory[];
360
+ health?: PackageHealthMetric;
361
+ peerConflicts: PeerConflict[];
362
+ license?: PackageLicense;
363
+ unusedIssues: UnusedDependency[];
364
+ selected: boolean;
365
+ }
366
+ export interface ReviewResult {
367
+ projectPath: string;
368
+ target: TargetLevel;
369
+ summary: Summary;
370
+ items: ReviewItem[];
371
+ updates: PackageUpdate[];
372
+ errors: string[];
373
+ warnings: string[];
374
+ }
375
+ export interface ReviewOptions extends CheckOptions {
376
+ securityOnly: boolean;
377
+ risk?: RiskLevel;
378
+ diff?: TargetLevel;
379
+ applySelected: boolean;
380
+ }
381
+ export interface DoctorOptions extends CheckOptions {
382
+ verdictOnly: boolean;
383
+ }
384
+ export interface DoctorResult {
385
+ verdict: Verdict;
386
+ summary: Summary;
387
+ review: ReviewResult;
388
+ primaryFindings: string[];
389
+ recommendedCommand: string;
390
+ }
306
391
  export type UnusedKind = "declared-not-imported" | "imported-not-declared";
307
392
  export interface UnusedDependency {
308
393
  name: string;
package/dist/ui/tui.d.ts CHANGED
@@ -1,6 +1,2 @@
1
1
  import type { PackageUpdate } from "../types/index.js";
2
- export declare function VersionDiff({ from, to }: {
3
- from: string;
4
- to: string;
5
- }): import("react/jsx-runtime").JSX.Element;
6
2
  export declare function runTui(updates: PackageUpdate[]): Promise<PackageUpdate[]>;