@rainy-updates/cli 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.5.7] - 2026-03-01
6
+
7
+ Final stabilization release for the `v0.5` series, focused on modularization, doctor scan quality, and maintainability.
8
+
9
+ ### Added
10
+
11
+ - **Doctor scan upgrades inspired by high-level audit CLIs**:
12
+ - normalized doctor findings with categories and severities,
13
+ - deterministic dependency health score (`0-100`),
14
+ - score labels and next-action reasoning,
15
+ - agent-oriented doctor output via `doctor --agent-report`.
16
+ - **New modular doctor core** under `src/core/doctor/`:
17
+ - findings derivation,
18
+ - score calculation,
19
+ - result assembly,
20
+ - rendering.
21
+ - **New modular analysis helpers** under `src/core/analysis/`:
22
+ - analysis option adapters,
23
+ - review item enrichment,
24
+ - silenced runner wrapper.
25
+ - **New CLI seam modules**:
26
+ - `src/bin/dispatch.ts`
27
+ - `src/bin/help.ts`
28
+ - `src/core/review-verdict.ts`
29
+ - **New help coverage** in `tests/help.test.ts`.
30
+
31
+ ### Changed
32
+
33
+ - `doctor` now behaves as a stronger high-level scan surface:
34
+ - `State`
35
+ - `Score`
36
+ - `PrimaryRisk`
37
+ - `NextAction`
38
+ - `NextActionReason`
39
+ - Summary and machine outputs now carry additive doctor metadata:
40
+ - `dependencyHealthScore`,
41
+ - `findingCountsByCategory`,
42
+ - `findingCountsBySeverity`,
43
+ - `primaryFindingCode`,
44
+ - `primaryFindingCategory`,
45
+ - `nextActionReason`.
46
+ - GitHub output, SARIF, and human-readable metrics/table output now expose the new doctor summary fields additively.
47
+ - `src/core/review-model.ts` was reduced to review aggregation responsibilities, with doctor logic extracted into focused modules.
48
+ - `src/core/analysis-bundle.ts` was reduced to a thin coordinator, with item enrichment and option adaptation moved into dedicated modules.
49
+ - `src/bin/cli.ts` was simplified by extracting command dispatch and help rendering into standalone modules.
50
+
51
+ ### Tests
52
+
53
+ - Added coverage for doctor score/findings behavior and agent report rendering.
54
+ - Added coverage for new GitHub output and SARIF fields.
55
+ - Added help rendering coverage after extracting CLI help into its own module.
56
+
5
57
  ## [0.5.6] - 2026-03-01
6
58
 
7
59
  GA readiness, shared analysis plumbing, and richer review operations.
package/dist/bin/cli.js CHANGED
@@ -4,12 +4,6 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { parseCliArgs } from "../core/options.js";
7
- import { check } from "../core/check.js";
8
- import { upgrade } from "../core/upgrade.js";
9
- import { warmCache } from "../core/warm-cache.js";
10
- import { runCi } from "../core/ci.js";
11
- import { initCiWorkflow } from "../core/init-ci.js";
12
- import { diffBaseline, saveBaseline } from "../core/baseline.js";
13
7
  import { applyFixPr } from "../core/fix-pr.js";
14
8
  import { applyFixPrBatches } from "../core/fix-pr-batch.js";
15
9
  import { createRunId, writeArtifactManifest } from "../core/artifacts.js";
@@ -20,6 +14,8 @@ import { renderPrReport } from "../output/pr-report.js";
20
14
  import { writeFileAtomic } from "../utils/io.js";
21
15
  import { resolveFailReason } from "../core/summary.js";
22
16
  import { stableStringify } from "../utils/stable-json.js";
17
+ import { handleDirectCommand, runPrimaryCommand } from "./dispatch.js";
18
+ import { renderHelp } from "./help.js";
23
19
  async function main() {
24
20
  try {
25
21
  const argv = process.argv.slice(2);
@@ -32,134 +28,15 @@ async function main() {
32
28
  return;
33
29
  }
34
30
  const parsed = await parseCliArgs(argv);
35
- if (parsed.command === "init-ci") {
36
- const workflow = await initCiWorkflow(parsed.options.cwd, parsed.options.force, {
37
- mode: parsed.options.mode,
38
- schedule: parsed.options.schedule,
39
- });
40
- process.stdout.write(workflow.created
41
- ? `Created CI workflow at ${workflow.path}\n`
42
- : `CI workflow already exists at ${workflow.path}. Use --force to overwrite.\n`);
43
- return;
44
- }
45
- if (parsed.command === "baseline") {
46
- if (parsed.options.action === "save") {
47
- const saved = await saveBaseline(parsed.options);
48
- process.stdout.write(`Saved baseline at ${saved.filePath} (${saved.entries} entries)\n`);
49
- return;
50
- }
51
- const diff = await diffBaseline(parsed.options);
52
- const changes = diff.added.length + diff.removed.length + diff.changed.length;
53
- if (changes === 0) {
54
- process.stdout.write(`No baseline drift detected (${diff.filePath}).\n`);
55
- return;
56
- }
57
- process.stdout.write(`Baseline drift detected (${diff.filePath}).\n`);
58
- if (diff.added.length > 0) {
59
- process.stdout.write(`Added: ${diff.added.length}\n`);
60
- }
61
- if (diff.removed.length > 0) {
62
- process.stdout.write(`Removed: ${diff.removed.length}\n`);
63
- }
64
- if (diff.changed.length > 0) {
65
- process.stdout.write(`Changed: ${diff.changed.length}\n`);
66
- }
67
- process.exitCode = 1;
68
- return;
69
- }
70
- // ─── v0.5.1 commands: lazy-loaded, isolated from check pipeline ──────────
71
- if (parsed.command === "bisect") {
72
- const { runBisect } = await import("../commands/bisect/runner.js");
73
- const result = await runBisect(parsed.options);
74
- process.exitCode = result.breakingVersion ? 1 : 0;
75
- return;
76
- }
77
- if (parsed.command === "audit") {
78
- const { runAudit } = await import("../commands/audit/runner.js");
79
- const result = await runAudit(parsed.options);
80
- process.exitCode = result.advisories.length > 0 ? 1 : 0;
81
- return;
82
- }
83
- if (parsed.command === "health") {
84
- const { runHealth } = await import("../commands/health/runner.js");
85
- const result = await runHealth(parsed.options);
86
- process.exitCode = result.totalFlagged > 0 ? 1 : 0;
87
- return;
88
- }
89
- // ─── v0.5.4 commands ─────────────────────────────────────────────────────
90
- if (parsed.command === "unused") {
91
- const { runUnused } = await import("../commands/unused/runner.js");
92
- const result = await runUnused(parsed.options);
93
- process.exitCode =
94
- result.totalUnused > 0 || result.totalMissing > 0 ? 1 : 0;
95
- return;
96
- }
97
- if (parsed.command === "resolve") {
98
- const { runResolve } = await import("../commands/resolve/runner.js");
99
- const result = await runResolve(parsed.options);
100
- process.exitCode = result.errorConflicts > 0 ? 1 : 0;
101
- return;
102
- }
103
- if (parsed.command === "licenses") {
104
- const { runLicenses } = await import("../commands/licenses/runner.js");
105
- const result = await runLicenses(parsed.options);
106
- process.exitCode = result.totalViolations > 0 ? 1 : 0;
107
- return;
108
- }
109
- if (parsed.command === "snapshot") {
110
- const { runSnapshot } = await import("../commands/snapshot/runner.js");
111
- const result = await runSnapshot(parsed.options);
112
- process.exitCode = result.errors.length > 0 ? 1 : 0;
113
- return;
114
- }
115
- if (parsed.command === "review") {
116
- const { runReview } = await import("../commands/review/runner.js");
117
- const result = await runReview(parsed.options);
118
- process.exitCode =
119
- result.summary.verdict === "blocked" ||
120
- result.summary.verdict === "actionable" ||
121
- result.summary.verdict === "review"
122
- ? 1
123
- : 0;
124
- return;
125
- }
126
- if (parsed.command === "doctor") {
127
- const { runDoctor } = await import("../commands/doctor/runner.js");
128
- const result = await runDoctor(parsed.options);
129
- process.exitCode = result.verdict === "safe" ? 0 : 1;
130
- return;
131
- }
132
- if (parsed.command === "dashboard") {
133
- const { runDashboard } = await import("../commands/dashboard/runner.js");
134
- const result = await runDashboard(parsed.options);
135
- process.exitCode = result.errors.length > 0 ? 1 : 0;
136
- return;
137
- }
138
- if (parsed.command === "ga") {
139
- const { runGa } = await import("../commands/ga/runner.js");
140
- const result = await runGa(parsed.options);
141
- process.exitCode = result.ready ? 0 : 1;
142
- return;
143
- }
144
- if (parsed.options.interactive &&
145
- (parsed.command === "check" ||
146
- parsed.command === "upgrade" ||
147
- parsed.command === "ci")) {
148
- const { runReview } = await import("../commands/review/runner.js");
149
- const result = await runReview({
150
- ...parsed.options,
151
- securityOnly: false,
152
- risk: undefined,
153
- diff: undefined,
154
- applySelected: parsed.command === "upgrade",
155
- });
156
- process.exitCode =
157
- result.summary.verdict === "safe" && result.updates.length === 0
158
- ? 0
159
- : 1;
31
+ if (await handleDirectCommand(parsed))
160
32
  return;
33
+ if (parsed.command !== "check" &&
34
+ parsed.command !== "upgrade" &&
35
+ parsed.command !== "warm-cache" &&
36
+ parsed.command !== "ci") {
37
+ throw new Error(`Unhandled command: ${parsed.command}`);
161
38
  }
162
- const result = await runCommand(parsed);
39
+ const result = await runPrimaryCommand(parsed);
163
40
  result.summary.runId = createRunId(parsed.command, parsed.options, result);
164
41
  if (parsed.options.fixPr &&
165
42
  (parsed.command === "check" ||
@@ -241,341 +118,6 @@ async function main() {
241
118
  }
242
119
  }
243
120
  void main();
244
- function renderHelp(command) {
245
- const isCommand = command && !command.startsWith("-");
246
- if (isCommand && command === "check") {
247
- return `rainy-updates check [options]
248
-
249
- Detect candidate dependency updates. This is the first step in the flow:
250
- check detects
251
- doctor summarizes
252
- review decides
253
- upgrade applies
254
-
255
- Options:
256
- --workspace
257
- --target patch|minor|major|latest
258
- --filter <pattern>
259
- --reject <pattern>
260
- --dep-kinds deps,dev,optional,peer
261
- --concurrency <n>
262
- --registry-timeout-ms <n>
263
- --registry-retries <n>
264
- --cache-ttl <seconds>
265
- --stream
266
- --policy-file <path>
267
- --offline
268
- --fix-pr
269
- --fix-branch <name>
270
- --fix-commit-message <text>
271
- --fix-dry-run
272
- --fix-pr-no-checkout
273
- --fix-pr-batch-size <n>
274
- --no-pr-report
275
- --json-file <path>
276
- --github-output <path>
277
- --sarif-file <path>
278
- --pr-report-file <path>
279
- --fail-on none|patch|minor|major|any
280
- --max-updates <n>
281
- --group-by none|name|scope|kind|risk
282
- --group-max <n>
283
- --cooldown-days <n>
284
- --pr-limit <n>
285
- --only-changed
286
- --interactive
287
- --show-impact
288
- --show-links
289
- --show-homepage
290
- --lockfile-mode preserve|update|error
291
- --log-level error|warn|info|debug
292
- --ci`;
293
- }
294
- if (isCommand && command === "warm-cache") {
295
- return `rainy-updates warm-cache [options]
296
-
297
- Pre-warm local metadata cache for faster CI checks.
298
-
299
- Options:
300
- --workspace
301
- --target patch|minor|major|latest
302
- --filter <pattern>
303
- --reject <pattern>
304
- --dep-kinds deps,dev,optional,peer
305
- --concurrency <n>
306
- --registry-timeout-ms <n>
307
- --registry-retries <n>
308
- --cache-ttl <seconds>
309
- --offline
310
- --stream
311
- --json-file <path>
312
- --github-output <path>
313
- --sarif-file <path>
314
- --pr-report-file <path>`;
315
- }
316
- if (isCommand && command === "upgrade") {
317
- return `rainy-updates upgrade [options]
318
-
319
- Apply an approved change set to package.json manifests.
320
-
321
- Options:
322
- --workspace
323
- --sync
324
- --install
325
- --pm auto|npm|pnpm
326
- --target patch|minor|major|latest
327
- --policy-file <path>
328
- --concurrency <n>
329
- --registry-timeout-ms <n>
330
- --registry-retries <n>
331
- --fix-pr
332
- --fix-branch <name>
333
- --fix-commit-message <text>
334
- --fix-dry-run
335
- --fix-pr-no-checkout
336
- --fix-pr-batch-size <n>
337
- --interactive
338
- --lockfile-mode preserve|update|error
339
- --no-pr-report
340
- --json-file <path>
341
- --pr-report-file <path>`;
342
- }
343
- if (isCommand && command === "ci") {
344
- return `rainy-updates ci [options]
345
-
346
- Run CI-oriented automation around the same lifecycle:
347
- check detects
348
- doctor summarizes
349
- review decides
350
- upgrade applies
351
-
352
- Options:
353
- --workspace
354
- --mode minimal|strict|enterprise
355
- --group-by none|name|scope|kind|risk
356
- --group-max <n>
357
- --cooldown-days <n>
358
- --pr-limit <n>
359
- --only-changed
360
- --offline
361
- --concurrency <n>
362
- --registry-timeout-ms <n>
363
- --registry-retries <n>
364
- --stream
365
- --fix-pr
366
- --fix-branch <name>
367
- --fix-commit-message <text>
368
- --fix-dry-run
369
- --fix-pr-no-checkout
370
- --fix-pr-batch-size <n>
371
- --no-pr-report
372
- --json-file <path>
373
- --github-output <path>
374
- --sarif-file <path>
375
- --pr-report-file <path>
376
- --fail-on none|patch|minor|major|any
377
- --max-updates <n>
378
- --lockfile-mode preserve|update|error
379
- --log-level error|warn|info|debug
380
- --ci`;
381
- }
382
- if (isCommand && command === "init-ci") {
383
- return `rainy-updates init-ci [options]
384
-
385
- Create a GitHub Actions workflow template at:
386
- .github/workflows/rainy-updates.yml
387
-
388
- Options:
389
- --force
390
- --mode minimal|strict|enterprise
391
- --schedule weekly|daily|off`;
392
- }
393
- if (isCommand && command === "baseline") {
394
- return `rainy-updates baseline [options]
395
-
396
- Save or compare dependency baseline snapshots.
397
-
398
- Options:
399
- --save
400
- --check
401
- --file <path>
402
- --workspace
403
- --dep-kinds deps,dev,optional,peer
404
- --ci`;
405
- }
406
- if (isCommand && command === "audit") {
407
- return `rainy-updates audit [options]
408
-
409
- Scan dependencies for CVEs using OSV.dev and GitHub Advisory Database.
410
-
411
- Options:
412
- --workspace
413
- --severity critical|high|medium|low
414
- --summary
415
- --report table|summary|json
416
- --source auto|osv|github|all
417
- --fix
418
- --dry-run
419
- --commit
420
- --pm auto|npm|pnpm|bun|yarn
421
- --json-file <path>
422
- --concurrency <n>
423
- --registry-timeout-ms <n>`;
424
- }
425
- if (isCommand && command === "review") {
426
- return `rainy-updates review [options]
427
-
428
- Review is the decision center of Rainy Updates.
429
- Use it to inspect risk, security, peer, license, and policy context before applying changes.
430
-
431
- Options:
432
- --workspace
433
- --interactive
434
- --security-only
435
- --risk critical|high|medium|low
436
- --diff patch|minor|major|latest
437
- --apply-selected
438
- --show-changelog
439
- --policy-file <path>
440
- --json-file <path>
441
- --concurrency <n>
442
- --registry-timeout-ms <n>
443
- --registry-retries <n>`;
444
- }
445
- if (isCommand && command === "doctor") {
446
- return `rainy-updates doctor [options]
447
-
448
- Produce a fast summary verdict and point the operator to review when action is needed.
449
-
450
- Options:
451
- --workspace
452
- --verdict-only
453
- --include-changelog
454
- --json-file <path>`;
455
- }
456
- if (isCommand && command === "ga") {
457
- return `rainy-updates ga [options]
458
-
459
- Audit release and CI readiness for Rainy Updates.
460
-
461
- Options:
462
- --workspace
463
- --json-file <path>
464
- --cwd <path>`;
465
- }
466
- return `rainy-updates (rup / rainy-up) <command> [options]
467
-
468
- Commands:
469
- check Detect candidate updates
470
- doctor Summarize what matters
471
- review Decide what to do
472
- upgrade Apply the approved change set
473
- dashboard Open the interactive DevOps dashboard (Ink TUI)
474
- ci Run CI-focused orchestration
475
- warm-cache Warm local cache for fast/offline checks
476
- init-ci Scaffold GitHub Actions workflow
477
- baseline Save/check dependency baseline snapshots
478
- audit Scan dependencies for CVEs (OSV.dev + GitHub)
479
- health Detect stale/deprecated/unmaintained packages
480
- bisect Find which version of a dep introduced a failure
481
- unused Detect unused or missing npm dependencies
482
- resolve Check peer dependency conflicts (pure-TS, no subprocess)
483
- licenses Scan dependency licenses and generate SPDX SBOM
484
- snapshot Save, list, restore, and diff dependency state snapshots
485
- ga Audit GA and CI readiness for this checkout
486
-
487
- Global options:
488
- --cwd <path>
489
- --workspace
490
- --target patch|minor|major|latest
491
- --format table|json|minimal|github|metrics
492
- --json-file <path>
493
- --github-output <path>
494
- --sarif-file <path>
495
- --pr-report-file <path>
496
- --policy-file <path>
497
- --fail-on none|patch|minor|major|any
498
- --max-updates <n>
499
- --group-by none|name|scope|kind|risk
500
- --group-max <n>
501
- --cooldown-days <n>
502
- --pr-limit <n>
503
- --only-changed
504
- --interactive
505
- --show-impact
506
- --show-links
507
- --show-homepage
508
- --mode minimal|strict|enterprise
509
- --fix-pr
510
- --fix-branch <name>
511
- --fix-commit-message <text>
512
- --fix-dry-run
513
- --fix-pr-no-checkout
514
- --fix-pr-batch-size <n>
515
- --no-pr-report
516
- --log-level error|warn|info|debug
517
- --concurrency <n>
518
- --registry-timeout-ms <n>
519
- --registry-retries <n>
520
- --cache-ttl <seconds>
521
- --offline
522
- --stream
523
- --lockfile-mode preserve|update|error
524
- --ci
525
- --help, -h
526
- --version, -v`;
527
- }
528
- async function runCommand(parsed) {
529
- if (parsed.command === "review") {
530
- const { runReview } = await import("../commands/review/runner.js");
531
- const result = await runReview(parsed.options);
532
- return {
533
- projectPath: result.projectPath,
534
- packagePaths: result.items.map((item) => item.update.packagePath),
535
- packageManager: "unknown",
536
- target: result.target,
537
- timestamp: new Date().toISOString(),
538
- summary: result.summary,
539
- updates: result.updates,
540
- errors: result.errors,
541
- warnings: result.warnings,
542
- };
543
- }
544
- if (parsed.command === "doctor") {
545
- const { runDoctor } = await import("../commands/doctor/runner.js");
546
- const result = await runDoctor(parsed.options);
547
- return {
548
- projectPath: result.review.projectPath,
549
- packagePaths: result.review.items.map((item) => item.update.packagePath),
550
- packageManager: "unknown",
551
- target: result.review.target,
552
- timestamp: new Date().toISOString(),
553
- summary: result.summary,
554
- updates: result.review.updates,
555
- errors: result.review.errors,
556
- warnings: result.review.warnings,
557
- };
558
- }
559
- if (parsed.command === "upgrade") {
560
- return await upgrade(parsed.options);
561
- }
562
- if (parsed.command === "warm-cache") {
563
- return await warmCache(parsed.options);
564
- }
565
- if (parsed.command === "ci") {
566
- return await runCi(parsed.options);
567
- }
568
- if (parsed.options.fixPr) {
569
- const upgradeOptions = {
570
- ...parsed.options,
571
- install: false,
572
- packageManager: "auto",
573
- sync: false,
574
- };
575
- return await upgrade(upgradeOptions);
576
- }
577
- return await check(parsed.options);
578
- }
579
121
  async function readPackageVersion() {
580
122
  const currentFile = fileURLToPath(import.meta.url);
581
123
  const packageJsonPath = path.resolve(path.dirname(currentFile), "../../package.json");
@@ -0,0 +1,16 @@
1
+ import type { ParsedCliArgs } from "../core/options.js";
2
+ import type { CheckOptions, CheckResult, UpgradeOptions } from "../types/index.js";
3
+ export declare function handleDirectCommand(parsed: ParsedCliArgs): Promise<boolean>;
4
+ export declare function runPrimaryCommand(parsed: {
5
+ command: "check";
6
+ options: CheckOptions;
7
+ } | {
8
+ command: "upgrade";
9
+ options: UpgradeOptions;
10
+ } | {
11
+ command: "warm-cache";
12
+ options: CheckOptions;
13
+ } | {
14
+ command: "ci";
15
+ options: CheckOptions;
16
+ }): Promise<CheckResult>;