sickbay 1.15.2 → 1.15.4

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # @nebulord/sickbay
1
+ # sickbay
2
2
 
3
- The terminal interface for Sickbay. Built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals) and [Commander](https://github.com/tj/commander.js).
3
+ The terminal interface for Sickbay. Published to npm as the unscoped `sickbay` package. Built with [Ink](https://github.com/vadimdemedes/ink) (React for terminals) and [Commander](https://github.com/tj/commander.js).
4
4
 
5
5
  ## Quickstart
6
6
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Header
3
- } from "./chunk-J4C4MHXN.js";
3
+ } from "./chunk-PD5OA3SZ.js";
4
4
 
5
5
  // src/components/DiffApp.tsx
6
6
  import React, { useState, useEffect } from "react";
@@ -119,7 +119,7 @@ function DiffApp({ projectPath, branch, jsonOutput, checks, verbose }) {
119
119
  useEffect(() => {
120
120
  (async () => {
121
121
  try {
122
- const { runSickbay } = await import("./dist-Q76NLAL5.js");
122
+ const { runSickbay } = await import("./dist-G7GDVJT5.js");
123
123
  const currentReport = await runSickbay({
124
124
  projectPath,
125
125
  checks,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-QYJLQBEL.js";
3
+ } from "./chunk-MCHTDD7R.js";
4
4
  import {
5
5
  Header
6
- } from "./chunk-J4C4MHXN.js";
6
+ } from "./chunk-PD5OA3SZ.js";
7
7
  import {
8
8
  detectPackageManager,
9
9
  detectProject
10
- } from "./chunk-6GIJCWXK.js";
10
+ } from "./chunk-WEGMDILT.js";
11
11
 
12
12
  // src/components/DoctorApp.tsx
13
13
  import React, { useState, useEffect } from "react";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-QYJLQBEL.js";
3
+ } from "./chunk-MCHTDD7R.js";
4
4
  import {
5
5
  ProgressList
6
6
  } from "./chunk-MBVA75EM.js";
7
7
  import {
8
8
  Header
9
- } from "./chunk-J4C4MHXN.js";
9
+ } from "./chunk-PD5OA3SZ.js";
10
10
  import {
11
11
  runSickbay
12
- } from "./chunk-6GIJCWXK.js";
12
+ } from "./chunk-WEGMDILT.js";
13
13
 
14
14
  // src/components/FixApp.tsx
15
15
  import React, { useState, useEffect, useCallback } from "react";
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-QYJLQBEL.js";
3
+ } from "./chunk-MCHTDD7R.js";
4
4
  import {
5
5
  Header
6
- } from "./chunk-J4C4MHXN.js";
6
+ } from "./chunk-PD5OA3SZ.js";
7
7
  import {
8
8
  detectProject
9
- } from "./chunk-6GIJCWXK.js";
9
+ } from "./chunk-WEGMDILT.js";
10
10
 
11
11
  // src/components/StatsApp.tsx
12
12
  import React, { useState, useEffect } from "react";
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-SHO3ZXTH.js";
5
5
  import {
6
6
  shortName
7
- } from "./chunk-QYJLQBEL.js";
7
+ } from "./chunk-MCHTDD7R.js";
8
8
  import {
9
9
  Header
10
- } from "./chunk-J4C4MHXN.js";
11
- import "./chunk-6GIJCWXK.js";
10
+ } from "./chunk-PD5OA3SZ.js";
11
+ import "./chunk-WEGMDILT.js";
12
12
  import {
13
13
  detectRegressions,
14
14
  loadHistory
@@ -14,7 +14,7 @@ import {
14
14
  detectMonorepo,
15
15
  runSickbay,
16
16
  runSickbayMonorepo
17
- } from "./chunk-6GIJCWXK.js";
17
+ } from "./chunk-WEGMDILT.js";
18
18
  import {
19
19
  loadHistory
20
20
  } from "./chunk-3OR2GFVE.js";
@@ -689,7 +689,7 @@ function TuiApp({
689
689
  return () => timers.forEach(clearTimeout);
690
690
  }, []);
691
691
  useEffect7(() => {
692
- checkForUpdate("1.15.1").then((info) => {
692
+ checkForUpdate("1.15.3").then((info) => {
693
693
  if (info) setUpdateInfo(info);
694
694
  });
695
695
  }, []);
@@ -715,7 +715,7 @@ function TuiApp({
715
715
  } catch {
716
716
  }
717
717
  try {
718
- const { getDependencyTree } = await import("./dist-Q76NLAL5.js");
718
+ const { getDependencyTree } = await import("./dist-G7GDVJT5.js");
719
719
  const { saveDepTree } = await import("./history-XLNVZEDI.js");
720
720
  const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);
721
721
  saveDepTree(projectPath, tree);
@@ -819,7 +819,7 @@ function TuiApp({
819
819
  const webReport = monorepoReportRef.current ?? reportRef.current;
820
820
  if (!webReport) return;
821
821
  try {
822
- const { serveWeb } = await import("./web-U22VUQGS.js");
822
+ const { serveWeb } = await import("./web-EUG7IJKA.js");
823
823
  const { default: openBrowser } = await import("open");
824
824
  let aiService;
825
825
  if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  detectMonorepo
3
- } from "./chunk-6GIJCWXK.js";
3
+ } from "./chunk-WEGMDILT.js";
4
4
 
5
5
  // src/lib/resolve-package.ts
6
6
  import { readFileSync } from "fs";
@@ -11,7 +11,7 @@ var ASCII_ART = `
11
11
  A vitals health check for your app
12
12
  `.trim();
13
13
  function Header({ projectName }) {
14
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.15.1")), projectName && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " Analyzing "), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "white" }, projectName)));
14
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.15.3")), projectName && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " Analyzing "), /* @__PURE__ */ React.createElement(Text, { bold: true, color: "white" }, projectName)));
15
15
  }
16
16
 
17
17
  export {
@@ -1,5 +1,6 @@
1
1
  // ../../packages/core/dist/index.js
2
- import { relative as relative2 } from "path";
2
+ import { existsSync as existsSync27, readFileSync as readFileSync31 } from "fs";
3
+ import { basename as basename3, join as join38, relative as relative2 } from "path";
3
4
  import { readFileSync, readdirSync, statSync, existsSync } from "fs";
4
5
  import { join, extname } from "path";
5
6
  import { readFileSync as readFileSync2, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
@@ -77,20 +78,20 @@ import { existsSync as existsSync21 } from "fs";
77
78
  import { dirname as dirname4, join as join31, relative } from "path";
78
79
  import { execa as execa10 } from "execa";
79
80
  import { execa as execa11 } from "execa";
80
- import { readFileSync as readFileSync26, readdirSync as readdirSync16, statSync as statSync15 } from "fs";
81
+ import { existsSync as existsSync22, readFileSync as readFileSync26, readdirSync as readdirSync16, statSync as statSync15 } from "fs";
81
82
  import { join as join32, extname as extname7 } from "path";
82
- import { readFileSync as readFileSync27, readdirSync as readdirSync17, statSync as statSync16, existsSync as existsSync22 } from "fs";
83
+ import { readFileSync as readFileSync27, readdirSync as readdirSync17, statSync as statSync16, existsSync as existsSync23 } from "fs";
83
84
  import { join as join33, extname as extname8, basename as basename2 } from "path";
84
85
  import { statSync as statSync17, readFileSync as readFileSync28 } from "fs";
85
86
  import { join as join34 } from "path";
86
87
  import { execa as execa12 } from "execa";
87
88
  import { globby } from "globby";
88
- import { readFileSync as readFileSync29, readdirSync as readdirSync18, statSync as statSync18, existsSync as existsSync23 } from "fs";
89
+ import { readFileSync as readFileSync29, readdirSync as readdirSync18, statSync as statSync18, existsSync as existsSync24 } from "fs";
89
90
  import { join as join35, extname as extname9 } from "path";
90
- import { existsSync as existsSync24 } from "fs";
91
+ import { existsSync as existsSync25 } from "fs";
91
92
  import { join as join36 } from "path";
92
93
  import { execa as execa13 } from "execa";
93
- import { existsSync as existsSync25, readFileSync as readFileSync30 } from "fs";
94
+ import { existsSync as existsSync26, readFileSync as readFileSync30 } from "fs";
94
95
  import { join as join37, resolve as resolve2 } from "path";
95
96
  import { globby as globby2 } from "globby";
96
97
  import picomatch2 from "picomatch";
@@ -361,7 +362,7 @@ var NextBestPracticesAdvisor = class extends BaseAdvisor {
361
362
  const hasStrictMode = NEXT_CONFIG_FILES.some((f) => {
362
363
  try {
363
364
  const content = readFileSync2(join2(projectPath, f), "utf-8");
364
- return content.includes("reactStrictMode") && content.includes("true");
365
+ return /reactStrictMode\s*:\s*true/.test(content);
365
366
  } catch {
366
367
  return false;
367
368
  }
@@ -581,6 +582,8 @@ function scanComponentFiles(dir) {
581
582
  var LOCK_FILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lockb"];
582
583
  var UniversalBestPracticesAdvisor = class extends BaseAdvisor {
583
584
  name = "universal-best-practices";
585
+ // Empty array means "applies to all frameworks" (see BaseAdvisor.isApplicableToContext).
586
+ // Explicit type annotation avoids the `never[]` widening that previously required a double cast.
584
587
  frameworks = [];
585
588
  async run(projectPath, _context) {
586
589
  const recommendations = [];
@@ -2047,6 +2050,7 @@ async function detectProject(projectPath) {
2047
2050
  }
2048
2051
  function detectFramework(deps) {
2049
2052
  if ("next" in deps) return "next";
2053
+ if ("@angular/core" in deps) return "angular";
2050
2054
  if ("@vitejs/plugin-react" in deps || "vite" in deps) return "vite";
2051
2055
  if ("react-scripts" in deps) return "cra";
2052
2056
  if ("react" in deps) return "react";
@@ -2552,7 +2556,8 @@ var GitRunner = class extends BaseRunner {
2552
2556
  const commitCount = logCountResult.status === "fulfilled" ? parseInt(logCountResult.value.stdout.trim(), 10) : 0;
2553
2557
  const contributorCount = contributorsResult.status === "fulfilled" ? contributorsResult.value.stdout.trim().split("\n").filter(Boolean).length : 0;
2554
2558
  const issues = [];
2555
- const isStale = lastCommit.includes("year") || lastCommit.includes("month") && parseInt(lastCommit) > staleMonths;
2559
+ const lastCommitMonths = lastCommit.includes("year") ? parseGitRelativeCount(lastCommit) * 12 : lastCommit.includes("month") ? parseGitRelativeCount(lastCommit) : 0;
2560
+ const isStale = lastCommitMonths > staleMonths;
2556
2561
  if (isStale) {
2557
2562
  issues.push({
2558
2563
  severity: "warning",
@@ -2595,6 +2600,12 @@ var GitRunner = class extends BaseRunner {
2595
2600
  }
2596
2601
  }
2597
2602
  };
2603
+ function parseGitRelativeCount(s) {
2604
+ const trimmed = s.trim().toLowerCase();
2605
+ if (trimmed.startsWith("a ") || trimmed.startsWith("an ")) return 1;
2606
+ const match = trimmed.match(/^(\d+)\b/);
2607
+ return match ? parseInt(match[1], 10) : 0;
2608
+ }
2598
2609
  var HEAVY_DEPS = {
2599
2610
  moment: {
2600
2611
  alternative: "dayjs or date-fns",
@@ -4223,18 +4234,24 @@ function getUpdateType(current, latest) {
4223
4234
  if (curMin < latMin) return "minor";
4224
4235
  return "patch";
4225
4236
  }
4237
+ var SOURCE_DIRS2 = ["src", "app", "lib"];
4226
4238
  var COMPONENT_EXTENSIONS3 = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
4227
4239
  var ReactPerfRunner = class extends BaseRunner {
4228
4240
  name = "react-perf";
4229
4241
  category = "performance";
4230
4242
  applicableFrameworks = ["react", "next", "remix"];
4243
+ async isApplicable(projectPath) {
4244
+ return SOURCE_DIRS2.some((dir) => existsSync22(join32(projectPath, dir)));
4245
+ }
4231
4246
  async run(projectPath, options) {
4232
4247
  const elapsed = timer();
4233
4248
  const isExcluded = createExcludeFilter(options?.checkConfig?.exclude ?? []);
4234
4249
  try {
4235
4250
  const hasReactCompiler = detectReactCompiler(projectPath);
4236
4251
  const findings = [];
4237
- const files = scanDirectory2(join32(projectPath, "src"), projectPath, isExcluded);
4252
+ const files = SOURCE_DIRS2.flatMap(
4253
+ (dir) => existsSync22(join32(projectPath, dir)) ? scanDirectory2(join32(projectPath, dir), projectPath, isExcluded) : []
4254
+ );
4238
4255
  for (const file of files) {
4239
4256
  findings.push(...analyzeFile(file.path, file.fullPath, file.lines));
4240
4257
  }
@@ -4451,7 +4468,7 @@ var SecretsRunner = class extends BaseRunner {
4451
4468
  const findings = [];
4452
4469
  const envFiles = [".env", ".env.local", ".env.production"];
4453
4470
  for (const envFile of envFiles) {
4454
- if (existsSync22(join33(projectPath, envFile))) {
4471
+ if (existsSync23(join33(projectPath, envFile))) {
4455
4472
  const gitignorePath = join33(projectPath, ".gitignore");
4456
4473
  let ignored = false;
4457
4474
  try {
@@ -4468,9 +4485,14 @@ var SecretsRunner = class extends BaseRunner {
4468
4485
  }
4469
4486
  }
4470
4487
  }
4471
- if (existsSync22(join33(projectPath, "src"))) {
4472
- findings.push(...scanDirectory3(join33(projectPath, "src"), projectPath, isExcluded));
4488
+ const SCAN_DIRS = ["src", "app", "lib", "config", "scripts", ".github/workflows"];
4489
+ for (const dir of SCAN_DIRS) {
4490
+ const fullDir = join33(projectPath, dir);
4491
+ if (existsSync23(fullDir)) {
4492
+ findings.push(...scanDirectory3(fullDir, projectPath, isExcluded));
4493
+ }
4473
4494
  }
4495
+ findings.push(...scanRootFiles(projectPath, isExcluded));
4474
4496
  const issues = findings.map((f) => ({
4475
4497
  severity: "critical",
4476
4498
  message: f.line > 0 ? `${f.file}:${f.line} \u2014 ${f.pattern} detected` : `${f.file} \u2014 ${f.pattern}`,
@@ -4541,6 +4563,29 @@ function scanDirectory3(dir, projectRoot, isExcluded) {
4541
4563
  function isTestFile2(filename) {
4542
4564
  return /\.(test|spec)\.(ts|tsx|js|jsx|mts|cts)$/.test(filename);
4543
4565
  }
4566
+ function scanRootFiles(projectRoot, isExcluded) {
4567
+ const findings = [];
4568
+ try {
4569
+ for (const entry of readdirSync17(projectRoot)) {
4570
+ const fullPath = join33(projectRoot, entry);
4571
+ let stat;
4572
+ try {
4573
+ stat = statSync16(fullPath);
4574
+ } catch {
4575
+ continue;
4576
+ }
4577
+ if (!stat.isFile()) continue;
4578
+ if (!SCAN_EXTENSIONS.has(extname8(entry))) continue;
4579
+ if (SKIP_FILES.has(basename2(entry))) continue;
4580
+ if (entry === "package.json") continue;
4581
+ if (isTestFile2(entry)) continue;
4582
+ if (isExcluded(entry)) continue;
4583
+ findings.push(...scanFile(fullPath, projectRoot));
4584
+ }
4585
+ } catch {
4586
+ }
4587
+ return findings;
4588
+ }
4544
4589
  function scanFile(filePath, projectRoot) {
4545
4590
  const findings = [];
4546
4591
  try {
@@ -4760,7 +4805,7 @@ var TodoScannerRunner = class extends BaseRunner {
4760
4805
  name = "todo-scanner";
4761
4806
  category = "code-quality";
4762
4807
  async isApplicable(projectPath) {
4763
- return existsSync23(join35(projectPath, "src"));
4808
+ return existsSync24(join35(projectPath, "src"));
4764
4809
  }
4765
4810
  async run(projectPath, options) {
4766
4811
  const elapsed = timer();
@@ -4859,7 +4904,7 @@ var TypeScriptRunner = class extends BaseRunner {
4859
4904
  name = "typescript";
4860
4905
  category = "code-quality";
4861
4906
  async isApplicable(projectPath) {
4862
- return existsSync24(join36(projectPath, "tsconfig.json"));
4907
+ return existsSync25(join36(projectPath, "tsconfig.json"));
4863
4908
  }
4864
4909
  async run(projectPath, options) {
4865
4910
  const elapsed = timer();
@@ -5114,12 +5159,12 @@ async function discoverPackages(rootPath, patterns) {
5114
5159
  ignore: ["**/node_modules/**"]
5115
5160
  });
5116
5161
  return matched.filter((dir) => {
5117
- return dir !== resolve2(rootPath) && existsSync25(join37(dir, "package.json"));
5162
+ return dir !== resolve2(rootPath) && existsSync26(join37(dir, "package.json"));
5118
5163
  });
5119
5164
  }
5120
5165
  function detectSignals(rootPath) {
5121
5166
  const pnpmWs = join37(rootPath, "pnpm-workspace.yaml");
5122
- if (existsSync25(pnpmWs)) {
5167
+ if (existsSync26(pnpmWs)) {
5123
5168
  try {
5124
5169
  const content = readFileSync30(pnpmWs, "utf-8");
5125
5170
  const patterns = parseYamlPackagesArray(content);
@@ -5129,7 +5174,7 @@ function detectSignals(rootPath) {
5129
5174
  }
5130
5175
  }
5131
5176
  const lernaJson = join37(rootPath, "lerna.json");
5132
- if (existsSync25(lernaJson)) {
5177
+ if (existsSync26(lernaJson)) {
5133
5178
  try {
5134
5179
  const lerna = JSON.parse(readFileSync30(lernaJson, "utf-8"));
5135
5180
  const patterns = Array.isArray(lerna.packages) ? lerna.packages : ["packages/*"];
@@ -5139,27 +5184,27 @@ function detectSignals(rootPath) {
5139
5184
  }
5140
5185
  }
5141
5186
  const pkgJson = join37(rootPath, "package.json");
5142
- if (existsSync25(pkgJson)) {
5187
+ if (existsSync26(pkgJson)) {
5143
5188
  try {
5144
5189
  const pkg = JSON.parse(readFileSync30(pkgJson, "utf-8"));
5145
5190
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : void 0;
5146
5191
  if (workspaces && workspaces.length > 0) {
5147
- if (existsSync25(join37(rootPath, "turbo.json"))) {
5192
+ if (existsSync26(join37(rootPath, "turbo.json"))) {
5148
5193
  return { type: "turbo", patterns: workspaces };
5149
5194
  }
5150
- if (existsSync25(join37(rootPath, "nx.json"))) {
5195
+ if (existsSync26(join37(rootPath, "nx.json"))) {
5151
5196
  return { type: "nx", patterns: workspaces };
5152
5197
  }
5153
- const hasYarn = existsSync25(join37(rootPath, "yarn.lock"));
5198
+ const hasYarn = existsSync26(join37(rootPath, "yarn.lock"));
5154
5199
  return { type: hasYarn ? "yarn" : "npm", patterns: workspaces };
5155
5200
  }
5156
5201
  } catch {
5157
5202
  }
5158
5203
  }
5159
- if (existsSync25(join37(rootPath, "turbo.json"))) {
5204
+ if (existsSync26(join37(rootPath, "turbo.json"))) {
5160
5205
  return { type: "turbo", patterns: ["packages/*", "apps/*"] };
5161
5206
  }
5162
- if (existsSync25(join37(rootPath, "nx.json"))) {
5207
+ if (existsSync26(join37(rootPath, "nx.json"))) {
5163
5208
  return { type: "nx", patterns: ["packages/*", "apps/*", "libs/*"] };
5164
5209
  }
5165
5210
  return null;
@@ -5266,6 +5311,17 @@ var ALL_ADVISORS = [
5266
5311
  new NextBestPracticesAdvisor(),
5267
5312
  new UniversalBestPracticesAdvisor()
5268
5313
  ];
5314
+ function readPackageName(pkgPath) {
5315
+ const pkgJsonPath = join38(pkgPath, "package.json");
5316
+ if (existsSync27(pkgJsonPath)) {
5317
+ try {
5318
+ const pkg = JSON.parse(readFileSync31(pkgJsonPath, "utf-8"));
5319
+ if (pkg.name) return pkg.name;
5320
+ } catch {
5321
+ }
5322
+ }
5323
+ return basename3(pkgPath);
5324
+ }
5269
5325
  function getAvailableChecks(context) {
5270
5326
  const runners = context ? ALL_RUNNERS.filter((r) => r.isApplicableToContext(context)) : ALL_RUNNERS;
5271
5327
  return runners.map((r) => ({ name: r.name, category: r.category }));
@@ -5370,9 +5426,9 @@ async function runSickbayMonorepo(options = {}) {
5370
5426
  monorepoInfo.packagePaths.map(async (pkgPath) => {
5371
5427
  const pkgConfig = await loadConfig(pkgPath);
5372
5428
  const mergedConfig = mergeConfigs(rootConfig, pkgConfig);
5429
+ options.onPackageStart?.(readPackageName(pkgPath));
5373
5430
  const report = await runSickbay({ ...options, projectPath: pkgPath, _config: mergedConfig });
5374
5431
  const context = await detectContext(pkgPath);
5375
- options.onPackageStart?.(report.projectInfo.name);
5376
5432
  const packageReport = {
5377
5433
  name: report.projectInfo.name,
5378
5434
  path: pkgPath,
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineConfig
3
- } from "./chunk-6GIJCWXK.js";
3
+ } from "./chunk-WEGMDILT.js";
4
4
  export {
5
5
  defineConfig
6
6
  };
@@ -29,7 +29,7 @@ import {
29
29
  runSickbay,
30
30
  runSickbayMonorepo,
31
31
  validateConfig
32
- } from "./chunk-6GIJCWXK.js";
32
+ } from "./chunk-WEGMDILT.js";
33
33
  export {
34
34
  BaseAdvisor,
35
35
  CATEGORY_WEIGHTS,
package/dist/index.js CHANGED
@@ -8,40 +8,54 @@ import {
8
8
  } from "./chunk-TYG7ZQBP.js";
9
9
  import {
10
10
  Header
11
- } from "./chunk-J4C4MHXN.js";
11
+ } from "./chunk-PD5OA3SZ.js";
12
12
  import {
13
13
  getScoreEmoji,
14
14
  runSickbay,
15
15
  runSickbayMonorepo
16
- } from "./chunk-6GIJCWXK.js";
16
+ } from "./chunk-WEGMDILT.js";
17
17
 
18
18
  // src/index.ts
19
19
  import { existsSync } from "fs";
20
20
  import { homedir } from "os";
21
21
  import { join } from "path";
22
- import React6 from "react";
22
+ import React7 from "react";
23
23
  import { Command } from "commander";
24
24
  import { config } from "dotenv";
25
25
  import { render } from "ink";
26
26
 
27
27
  // src/components/App.tsx
28
- import React5, { useState, useEffect, useRef } from "react";
29
- import { Box as Box5, Text as Text5, useApp } from "ink";
28
+ import React6, { useState, useEffect, useRef } from "react";
29
+ import { Box as Box6, Text as Text6, useApp } from "ink";
30
30
  import Gradient from "ink-gradient";
31
31
  import Spinner from "ink-spinner";
32
32
 
33
+ // src/components/BestPractices.tsx
34
+ import React from "react";
35
+ import { Box, Text } from "ink";
36
+ function BestPractices({ report }) {
37
+ const recs = report.recommendations;
38
+ if (!recs || recs.length === 0) return null;
39
+ const sorted = [...recs].sort((a, b) => {
40
+ const order = { recommend: 0, suggest: 1 };
41
+ return order[a.severity] - order[b.severity];
42
+ });
43
+ const display = sorted.slice(0, 5);
44
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "\u{1F4A1} Advisor:"), display.map((rec) => /* @__PURE__ */ React.createElement(Box, { key: rec.id, marginLeft: 2 }, /* @__PURE__ */ React.createElement(Text, { color: rec.severity === "recommend" ? "yellow" : "gray" }, rec.severity === "recommend" ? "\u25CF " : "\u25CB "), /* @__PURE__ */ React.createElement(Text, { bold: true }, rec.title), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " \u2014 ", rec.message))));
45
+ }
46
+
33
47
  // src/components/CheckResult.tsx
34
- import React2 from "react";
35
- import { Box as Box2, Text as Text2 } from "ink";
48
+ import React3 from "react";
49
+ import { Box as Box3, Text as Text3 } from "ink";
36
50
 
37
51
  // src/components/ScoreBar.tsx
38
- import React from "react";
39
- import { Box, Text } from "ink";
52
+ import React2 from "react";
53
+ import { Box as Box2, Text as Text2 } from "ink";
40
54
  function ScoreBar({ score, width = 20 }) {
41
55
  const filled = Math.round(score / 100 * width);
42
56
  const empty = width - filled;
43
57
  const color = score >= 80 ? "green" : score >= 60 ? "yellow" : "red";
44
- return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color }, "\u2588".repeat(filled)), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "\u2591".repeat(empty)), /* @__PURE__ */ React.createElement(Text, { color }, " ", score, "/100"));
58
+ return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { color }, "\u2588".repeat(filled)), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "\u2591".repeat(empty)), /* @__PURE__ */ React2.createElement(Text2, { color }, " ", score, "/100"));
45
59
  }
46
60
 
47
61
  // src/components/CheckResult.tsx
@@ -61,20 +75,20 @@ var CATEGORY_ICONS = {
61
75
  function CheckResultRow({ result }) {
62
76
  const statusColor = result.status === "pass" ? "green" : result.status === "fail" ? "red" : result.status === "skipped" ? "gray" : "yellow";
63
77
  const icon = CATEGORY_ICONS[result.category] ?? "\u2022";
64
- return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, icon, " "), /* @__PURE__ */ React2.createElement(Text2, { bold: true }, result.name), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " via ", result.toolsUsed.join(", "))), /* @__PURE__ */ React2.createElement(Box2, { marginLeft: 2 }, /* @__PURE__ */ React2.createElement(ScoreBar, { score: result.score, width: 16 }), /* @__PURE__ */ React2.createElement(Text2, { color: statusColor }, " ", STATUS_ICONS[result.status], " ", result.status)), result.issues.slice(0, 3).map((issue) => /* @__PURE__ */ React2.createElement(Box2, { key: `${issue.severity}-${issue.message}`, marginLeft: 2 }, /* @__PURE__ */ React2.createElement(
65
- Text2,
78
+ return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginBottom: 1 }, /* @__PURE__ */ React3.createElement(Box3, null, /* @__PURE__ */ React3.createElement(Text3, null, icon, " "), /* @__PURE__ */ React3.createElement(Text3, { bold: true }, result.name), /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, " via ", result.toolsUsed.join(", "))), /* @__PURE__ */ React3.createElement(Box3, { marginLeft: 2 }, /* @__PURE__ */ React3.createElement(ScoreBar, { score: result.score, width: 16 }), /* @__PURE__ */ React3.createElement(Text3, { color: statusColor }, " ", STATUS_ICONS[result.status], " ", result.status)), result.issues.slice(0, 3).map((issue) => /* @__PURE__ */ React3.createElement(Box3, { key: `${issue.severity}-${issue.message}`, marginLeft: 2 }, /* @__PURE__ */ React3.createElement(
79
+ Text3,
66
80
  {
67
81
  color: issue.severity === "critical" ? "red" : issue.severity === "warning" ? "yellow" : "gray"
68
82
  },
69
83
  issue.severity === "critical" ? " \u2717" : issue.severity === "warning" ? " \u26A0" : " \u2139",
70
84
  " ",
71
85
  issue.message
72
- ))), result.issues.length > 3 && /* @__PURE__ */ React2.createElement(Box2, { marginLeft: 4 }, /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "... and ", result.issues.length - 3, " more")));
86
+ ))), result.issues.length > 3 && /* @__PURE__ */ React3.createElement(Box3, { marginLeft: 4 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "... and ", result.issues.length - 3, " more")));
73
87
  }
74
88
 
75
89
  // src/components/QuickWins.tsx
76
- import React3 from "react";
77
- import { Box as Box3, Text as Text3 } from "ink";
90
+ import React4 from "react";
91
+ import { Box as Box4, Text as Text4 } from "ink";
78
92
  function replacePackageManager(cmd, pm) {
79
93
  if (pm === "npm") return cmd;
80
94
  const install = pm === "pnpm" ? "pnpm add" : pm === "yarn" ? "yarn add" : "bun add";
@@ -90,12 +104,12 @@ function QuickWins({ report }) {
90
104
  return order[a.severity] - order[b.severity];
91
105
  }).slice(0, 5);
92
106
  if (fixes.length === 0) return null;
93
- return /* @__PURE__ */ React3.createElement(Box3, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true }, "\u{1F525} Quick Wins:"), fixes.map((fix) => /* @__PURE__ */ React3.createElement(Box3, { key: `${fix.severity}-${fix.message}`, marginLeft: 2 }, /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, "\u2192 "), /* @__PURE__ */ React3.createElement(Text3, null, fix.fix.description), fix.fix.command && /* @__PURE__ */ React3.createElement(Text3, { dimColor: true }, ": ", replacePackageManager(fix.fix.command, pm)))));
107
+ return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "\u{1F525} Quick Wins:"), fixes.map((fix) => /* @__PURE__ */ React4.createElement(Box4, { key: `${fix.severity}-${fix.message}`, marginLeft: 2 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "\u2192 "), /* @__PURE__ */ React4.createElement(Text4, null, fix.fix.description), fix.fix.command && /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, ": ", replacePackageManager(fix.fix.command, pm)))));
94
108
  }
95
109
 
96
110
  // src/components/Summary.tsx
97
- import React4 from "react";
98
- import { Box as Box4, Text as Text4 } from "ink";
111
+ import React5 from "react";
112
+ import { Box as Box5, Text as Text5 } from "ink";
99
113
  function formatDuration(ms) {
100
114
  if (ms < 1e3) return `${ms}ms`;
101
115
  const seconds = ms / 1e3;
@@ -105,7 +119,7 @@ function formatDuration(ms) {
105
119
  return `${minutes}m ${remainingSeconds}s`;
106
120
  }
107
121
  function Summary({ report, scanDuration }) {
108
- return /* @__PURE__ */ React4.createElement(Box4, { flexDirection: "column" }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "\u2501".repeat(52)), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { bold: true }, "Overall Health Score: "), /* @__PURE__ */ React4.createElement(ScoreBar, { score: report.overallScore, width: 12 }), /* @__PURE__ */ React4.createElement(Text4, null, " ", getScoreEmoji(report.overallScore)), scanDuration != null && /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " ", formatDuration(scanDuration))), /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { color: "red" }, " \u2717 ", report.summary.critical, " critical"), /* @__PURE__ */ React4.createElement(Text4, { color: "yellow" }, " \u26A0 ", report.summary.warnings, " warnings"), /* @__PURE__ */ React4.createElement(Text4, { color: "gray" }, " i ", report.summary.info, " info")), report.quote && /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { italic: true, dimColor: true }, '"', report.quote.text, '"'), /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, " \u2014 ", report.quote.source)), report.config?.hasCustomConfig && /* @__PURE__ */ React4.createElement(Box4, { marginTop: 1 }, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true, italic: true }, "Custom config active", report.config.disabledChecks.length > 0 && ` (${report.config.disabledChecks.length} check${report.config.disabledChecks.length === 1 ? "" : "s"} disabled)`)));
122
+ return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2501".repeat(52)), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Overall Health Score: "), /* @__PURE__ */ React5.createElement(ScoreBar, { score: report.overallScore, width: 12 }), /* @__PURE__ */ React5.createElement(Text5, null, " ", getScoreEmoji(report.overallScore)), scanDuration != null && /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " ", formatDuration(scanDuration))), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, " \u2717 ", report.summary.critical, " critical"), /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, " \u26A0 ", report.summary.warnings, " warnings"), /* @__PURE__ */ React5.createElement(Text5, { color: "gray" }, " i ", report.summary.info, " info")), report.quote && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { italic: true, dimColor: true }, '"', report.quote.text, '"'), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2014 ", report.quote.source)), report.config?.hasCustomConfig && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true, italic: true }, "Custom config active", report.config.disabledChecks.length > 0 && ` (${report.config.disabledChecks.length} check${report.config.disabledChecks.length === 1 ? "" : "s"} disabled)`)));
109
123
  }
110
124
 
111
125
  // src/components/App.tsx
@@ -176,7 +190,7 @@ function App({
176
190
  setMonorepoReport(r);
177
191
  setProjectName(`monorepo (${r.packages.length} packages)`);
178
192
  try {
179
- const { getDependencyTree } = await import("./dist-Q76NLAL5.js");
193
+ const { getDependencyTree } = await import("./dist-G7GDVJT5.js");
180
194
  const { saveDepTree } = await import("./history-XLNVZEDI.js");
181
195
  const packages = {};
182
196
  for (const pkg of r.packages) {
@@ -188,7 +202,7 @@ function App({
188
202
  if (openWeb) {
189
203
  setPhase("opening-web");
190
204
  try {
191
- const { serveWeb } = await import("./web-U22VUQGS.js");
205
+ const { serveWeb } = await import("./web-EUG7IJKA.js");
192
206
  const { default: openBrowser } = await import("open");
193
207
  let aiService;
194
208
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -245,7 +259,7 @@ function App({
245
259
  } catch {
246
260
  }
247
261
  try {
248
- const { getDependencyTree } = await import("./dist-Q76NLAL5.js");
262
+ const { getDependencyTree } = await import("./dist-G7GDVJT5.js");
249
263
  const { saveDepTree } = await import("./history-XLNVZEDI.js");
250
264
  const tree = await getDependencyTree(projectPath, r.projectInfo.packageManager);
251
265
  saveDepTree(projectPath, tree);
@@ -254,7 +268,7 @@ function App({
254
268
  if (openWeb) {
255
269
  setPhase("opening-web");
256
270
  try {
257
- const { serveWeb } = await import("./web-U22VUQGS.js");
271
+ const { serveWeb } = await import("./web-EUG7IJKA.js");
258
272
  const { default: openBrowser } = await import("open");
259
273
  let aiService;
260
274
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -280,20 +294,20 @@ function App({
280
294
  setTimeout(() => exit(err), 100);
281
295
  });
282
296
  }, []);
283
- return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React5.createElement(Header, { projectName }), updateInfo && /* @__PURE__ */ React5.createElement(
297
+ return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React6.createElement(Header, { projectName }), updateInfo && /* @__PURE__ */ React6.createElement(
284
298
  UpdateNotice,
285
299
  {
286
300
  currentVersion: updateInfo.currentVersion,
287
301
  latestVersion: updateInfo.latestVersion
288
302
  }
289
- ), phase === "loading" && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, isMonorepo ? /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " ", LOADING_MESSAGES[loadingMsgIdx])), scanningPackage && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2192 "), /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, scanningPackage))) : /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Running health checks..."), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React5.createElement(ProgressList, { items: progress })))), phase === "error" && /* @__PURE__ */ React5.createElement(Box5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, "\u2717 Error: ", error)), phase === "results" && monorepoReport && /* @__PURE__ */ React5.createElement(MonorepoSummaryTable, { report: monorepoReport, scanDuration }), phase === "results" && report && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, report.checks.filter((c) => c.status !== "skipped").map((check) => /* @__PURE__ */ React5.createElement(CheckResultRow, { key: check.id, result: check })), /* @__PURE__ */ React5.createElement(Summary, { report, scanDuration }), /* @__PURE__ */ React5.createElement(QuickWins, { report }), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "View detailed report: "), /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "sickbay --web"))), phase === "opening-web" && (monorepoReport ?? report) && /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, monorepoReport ? /* @__PURE__ */ React5.createElement(MonorepoSummaryTable, { report: monorepoReport, scanDuration }) : report ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, report.checks.filter((c) => c.status !== "skipped").map((check) => /* @__PURE__ */ React5.createElement(CheckResultRow, { key: check.id, result: check })), /* @__PURE__ */ React5.createElement(Summary, { report, scanDuration })) : null, /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, webUrl ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, "\u2713 Dashboard running at "), /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, webUrl), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " (Ctrl+C to stop)")) : /* @__PURE__ */ React5.createElement(Text5, null, /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" })), " ", /* @__PURE__ */ React5.createElement(Gradient, { name: "retro" }, "Launching dashboard with AI insights...")))));
303
+ ), phase === "loading" && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, isMonorepo ? /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " ", LOADING_MESSAGES[loadingMsgIdx])), scanningPackage && /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\u2192 "), /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, scanningPackage))) : /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Running health checks..."), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, marginLeft: 2 }, /* @__PURE__ */ React6.createElement(ProgressList, { items: progress })))), phase === "error" && /* @__PURE__ */ React6.createElement(Box6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, "\u2717 Error: ", error)), phase === "results" && monorepoReport && /* @__PURE__ */ React6.createElement(MonorepoSummaryTable, { report: monorepoReport, scanDuration }), phase === "results" && report && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, report.checks.filter((c) => c.status !== "skipped").map((check) => /* @__PURE__ */ React6.createElement(CheckResultRow, { key: check.id, result: check })), /* @__PURE__ */ React6.createElement(Summary, { report, scanDuration }), /* @__PURE__ */ React6.createElement(QuickWins, { report }), /* @__PURE__ */ React6.createElement(BestPractices, { report }), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "View detailed report: "), /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, "sickbay --web"))), phase === "opening-web" && (monorepoReport ?? report) && /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, monorepoReport ? /* @__PURE__ */ React6.createElement(MonorepoSummaryTable, { report: monorepoReport, scanDuration }) : report ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, report.checks.filter((c) => c.status !== "skipped").map((check) => /* @__PURE__ */ React6.createElement(CheckResultRow, { key: check.id, result: check })), /* @__PURE__ */ React6.createElement(Summary, { report, scanDuration })) : null, /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, webUrl ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { color: "green" }, "\u2713 Dashboard running at "), /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, webUrl), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " (Ctrl+C to stop)")) : /* @__PURE__ */ React6.createElement(Text6, null, /* @__PURE__ */ React6.createElement(Text6, { color: "magenta" }, /* @__PURE__ */ React6.createElement(Spinner, { type: "dots" })), " ", /* @__PURE__ */ React6.createElement(Gradient, { name: "retro" }, "Launching dashboard with AI insights...")))));
290
304
  }
291
305
  function MonorepoSummaryTable({
292
306
  report,
293
307
  scanDuration
294
308
  }) {
295
309
  const scoreColor = (score) => score >= 80 ? "green" : score >= 60 ? "yellow" : "red";
296
- return /* @__PURE__ */ React5.createElement(Box5, { flexDirection: "column" }, /* @__PURE__ */ React5.createElement(Box5, { marginBottom: 1 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Monorepo \xB7 "), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, report.monorepoType, " workspaces \xB7 "), /* @__PURE__ */ React5.createElement(Text5, null, report.packages.length, " packages")), report.packages.map((pkg) => /* @__PURE__ */ React5.createElement(Box5, { key: pkg.path, marginLeft: 2, gap: 1 }, /* @__PURE__ */ React5.createElement(Text5, { color: scoreColor(pkg.score) }, scoreBar(pkg.score)), /* @__PURE__ */ React5.createElement(Text5, { bold: true, color: scoreColor(pkg.score) }, String(pkg.score).padStart(3)), /* @__PURE__ */ React5.createElement(Text5, null, pkg.name), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, pkg.framework), pkg.summary.critical > 0 && /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, " ", pkg.summary.critical, " critical"))), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1, gap: 2 }, /* @__PURE__ */ React5.createElement(Text5, { bold: true }, "Overall: "), /* @__PURE__ */ React5.createElement(Text5, { color: scoreColor(report.overallScore), bold: true }, report.overallScore), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React5.createElement(Text5, { color: "red" }, report.summary.critical, " critical"), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, report.summary.warnings, " warnings"), scanDuration !== null && /* @__PURE__ */ React5.createElement(React5.Fragment, null, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, formatDuration2(scanDuration)))), report.quote && /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { italic: true, dimColor: true }, '"', report.quote.text, '"'), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " \u2014 ", report.quote.source)), /* @__PURE__ */ React5.createElement(Box5, { marginTop: 1 }, /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "Per-package details: "), /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "sickbay --web")));
310
+ return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column" }, /* @__PURE__ */ React6.createElement(Box6, { marginBottom: 1 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Monorepo \xB7 "), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, report.monorepoType, " workspaces \xB7 "), /* @__PURE__ */ React6.createElement(Text6, null, report.packages.length, " packages")), report.packages.map((pkg) => /* @__PURE__ */ React6.createElement(Box6, { key: pkg.path, marginLeft: 2, gap: 1 }, /* @__PURE__ */ React6.createElement(Text6, { color: scoreColor(pkg.score) }, scoreBar(pkg.score)), /* @__PURE__ */ React6.createElement(Text6, { bold: true, color: scoreColor(pkg.score) }, String(pkg.score).padStart(3)), /* @__PURE__ */ React6.createElement(Text6, null, pkg.name), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, pkg.framework), pkg.summary.critical > 0 && /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, " ", pkg.summary.critical, " critical"))), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1, gap: 2 }, /* @__PURE__ */ React6.createElement(Text6, { bold: true }, "Overall: "), /* @__PURE__ */ React6.createElement(Text6, { color: scoreColor(report.overallScore), bold: true }, report.overallScore), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "red" }, report.summary.critical, " critical"), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React6.createElement(Text6, { color: "yellow" }, report.summary.warnings, " warnings"), scanDuration !== null && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "\xB7 "), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, formatDuration2(scanDuration)))), report.quote && /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { italic: true, dimColor: true }, '"', report.quote.text, '"'), /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, " \u2014 ", report.quote.source)), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text6, { dimColor: true }, "Per-package details: "), /* @__PURE__ */ React6.createElement(Text6, { color: "cyan" }, "sickbay --web")));
297
311
  }
298
312
 
299
313
  // src/index.ts
@@ -303,7 +317,7 @@ if (existsSync(globalConfigPath)) {
303
317
  }
304
318
  config({ debug: false, quiet: true });
305
319
  var program = new Command();
306
- program.name("sickbay").description("React project health check CLI").version("1.15.1", "-v, --version").enablePositionalOptions().passThroughOptions().option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single named package (monorepo only)").option("--json", "output raw JSON report").option("--web", "open web dashboard after scan").option("--no-ai", "disable AI features").option("--no-quotes", "suppress personality quotes in output").option("--verbose", "show verbose output").action(async (options) => {
320
+ program.name("sickbay").description("React project health check CLI").version("1.15.3", "-v, --version").enablePositionalOptions().passThroughOptions().option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single named package (monorepo only)").option("--json", "output raw JSON report").option("--web", "open web dashboard after scan").option("--no-ai", "disable AI features").option("--no-quotes", "suppress personality quotes in output").option("--verbose", "show verbose output").action(async (options) => {
307
321
  if (options.path && options.path !== process.cwd()) {
308
322
  const projectEnvPath = join(options.path, ".env");
309
323
  if (existsSync(projectEnvPath)) {
@@ -313,13 +327,13 @@ program.name("sickbay").description("React project health check CLI").version("1
313
327
  const updatePromise = (async () => {
314
328
  try {
315
329
  const { checkForUpdate } = await import("./update-check-M7IFOMLJ.js");
316
- return await checkForUpdate("1.15.1");
330
+ return await checkForUpdate("1.15.3");
317
331
  } catch {
318
332
  return null;
319
333
  }
320
334
  })();
321
335
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
322
- const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-Q76NLAL5.js");
336
+ const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-G7GDVJT5.js");
323
337
  const monorepoInfo = await detectMonorepo(options.path);
324
338
  if (options.package && monorepoInfo.isMonorepo) {
325
339
  const { readFileSync } = await import("fs");
@@ -347,7 +361,7 @@ program.name("sickbay").description("React project health check CLI").version("1
347
361
  process.exit(0);
348
362
  }
349
363
  render(
350
- React6.createElement(App, {
364
+ React7.createElement(App, {
351
365
  projectPath: targetPath,
352
366
  checks,
353
367
  openWeb: options.web,
@@ -383,7 +397,7 @@ program.name("sickbay").description("React project health check CLI").version("1
383
397
  } catch {
384
398
  }
385
399
  try {
386
- const { getDependencyTree } = await import("./dist-Q76NLAL5.js");
400
+ const { getDependencyTree } = await import("./dist-G7GDVJT5.js");
387
401
  const { saveDepTree } = await import("./history-XLNVZEDI.js");
388
402
  const tree = await getDependencyTree(options.path, report.projectInfo.packageManager);
389
403
  saveDepTree(options.path, tree);
@@ -393,7 +407,7 @@ program.name("sickbay").description("React project health check CLI").version("1
393
407
  process.exit(0);
394
408
  }
395
409
  render(
396
- React6.createElement(App, {
410
+ React7.createElement(App, {
397
411
  projectPath: options.path,
398
412
  checks,
399
413
  openWeb: options.web,
@@ -410,16 +424,16 @@ program.command("init").description("Initialize .sickbay/ folder and run an init
410
424
  "add newly available checks to existing config without touching existing entries"
411
425
  ).option("--reset-config", "regenerate config from scratch (overwrites existing)").action(async (options) => {
412
426
  if (options.resetConfig) {
413
- const { generateConfigFile } = await import("./init-XUMTXUVV.js");
427
+ const { generateConfigFile } = await import("./init-BBKWOHRV.js");
414
428
  await generateConfigFile(options.path, { force: true });
415
429
  return;
416
430
  }
417
431
  if (options.sync) {
418
- const { syncConfigFile } = await import("./init-XUMTXUVV.js");
432
+ const { syncConfigFile } = await import("./init-BBKWOHRV.js");
419
433
  await syncConfigFile(options.path);
420
434
  return;
421
435
  }
422
- const { initSickbay } = await import("./init-XUMTXUVV.js");
436
+ const { initSickbay } = await import("./init-BBKWOHRV.js");
423
437
  await initSickbay(options.path);
424
438
  });
425
439
  program.command("fix").description("Interactively fix issues found by sickbay scan").option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single package (monorepo only)").option("--all", "apply all available fixes without prompting").option("--dry-run", "show what would be fixed without executing").option("--verbose", "show verbose output").action(async (options) => {
@@ -429,13 +443,13 @@ program.command("fix").description("Interactively fix issues found by sickbay sc
429
443
  config({ path: projectEnvPath, override: true });
430
444
  }
431
445
  }
432
- const { resolveProject } = await import("./resolve-package-RN2JUNIX.js");
446
+ const { resolveProject } = await import("./resolve-package-CB3KDANT.js");
433
447
  const resolution = await resolveProject(options.path, options.package);
434
- const { FixApp } = await import("./FixApp-OFGNY2UR.js");
448
+ const { FixApp } = await import("./FixApp-6JU4B3EJ.js");
435
449
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
436
450
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
437
451
  render(
438
- React6.createElement(FixApp, {
452
+ React7.createElement(FixApp, {
439
453
  projectPath,
440
454
  checks,
441
455
  applyAll: options.all ?? false,
@@ -454,12 +468,12 @@ program.command("trend").description("Show score history and trends over time").
454
468
  config({ path: projectEnvPath, override: true });
455
469
  }
456
470
  }
457
- const { resolveProject } = await import("./resolve-package-RN2JUNIX.js");
471
+ const { resolveProject } = await import("./resolve-package-CB3KDANT.js");
458
472
  const resolution = await resolveProject(options.path, options.package);
459
473
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
460
- const { TrendApp } = await import("./TrendApp-IBHR4R5D.js");
474
+ const { TrendApp } = await import("./TrendApp-TB4E7ZUU.js");
461
475
  render(
462
- React6.createElement(TrendApp, {
476
+ React7.createElement(TrendApp, {
463
477
  projectPath,
464
478
  last: parseInt(options.last, 10),
465
479
  jsonOutput: options.json ?? false,
@@ -476,12 +490,12 @@ program.command("stats").description("Show a quick codebase overview and project
476
490
  config({ path: projectEnvPath, override: true });
477
491
  }
478
492
  }
479
- const { resolveProject } = await import("./resolve-package-RN2JUNIX.js");
493
+ const { resolveProject } = await import("./resolve-package-CB3KDANT.js");
480
494
  const resolution = await resolveProject(options.path, options.package);
481
495
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
482
- const { StatsApp } = await import("./StatsApp-YQJOEQIW.js");
496
+ const { StatsApp } = await import("./StatsApp-EYYWVSGZ.js");
483
497
  render(
484
- React6.createElement(StatsApp, {
498
+ React7.createElement(StatsApp, {
485
499
  projectPath,
486
500
  jsonOutput: options.json ?? false,
487
501
  isMonorepo: resolution.isMonorepo && !resolution.targetPath,
@@ -491,10 +505,10 @@ program.command("stats").description("Show a quick codebase overview and project
491
505
  );
492
506
  });
493
507
  program.command("tui").description("Launch the persistent developer dashboard").option("-p, --path <path>", "project path to monitor", process.cwd()).option("--no-watch", "disable file-watching auto-refresh").option("--no-quotes", "suppress personality quotes in output").option("--refresh <seconds>", "auto-refresh interval in seconds", "300").option("-c, --checks <checks>", "comma-separated list of checks to run").action(async (options) => {
494
- const { TuiApp } = await import("./TuiApp-APJXORT5.js");
508
+ const { TuiApp } = await import("./TuiApp-4JRQCD7M.js");
495
509
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
496
510
  render(
497
- React6.createElement(TuiApp, {
511
+ React7.createElement(TuiApp, {
498
512
  projectPath: options.path,
499
513
  checks,
500
514
  watchEnabled: options.watch !== false,
@@ -511,12 +525,12 @@ program.command("doctor").description("Diagnose project setup and configuration
511
525
  config({ path: projectEnvPath, override: true });
512
526
  }
513
527
  }
514
- const { resolveProject } = await import("./resolve-package-RN2JUNIX.js");
528
+ const { resolveProject } = await import("./resolve-package-CB3KDANT.js");
515
529
  const resolution = await resolveProject(options.path, options.package);
516
530
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
517
- const { DoctorApp } = await import("./DoctorApp-AT2VCFDQ.js");
531
+ const { DoctorApp } = await import("./DoctorApp-VKJCRS2I.js");
518
532
  render(
519
- React6.createElement(DoctorApp, {
533
+ React7.createElement(DoctorApp, {
520
534
  projectPath,
521
535
  autoFix: options.fix ?? false,
522
536
  jsonOutput: options.json ?? false,
@@ -527,13 +541,13 @@ program.command("doctor").description("Diagnose project setup and configuration
527
541
  );
528
542
  });
529
543
  program.command("badge").description("Generate a health score badge for your README").option("-p, --path <path>", "project path", process.cwd()).option("--package <name>", "scope to a single package (monorepo only)").option("--html", "output HTML <img> tag instead of markdown").option("--url", "output bare badge URL only").option("--label <text>", "custom badge label", "sickbay").option("--scan", "run a fresh scan instead of using last report").action(async (options) => {
530
- const { resolveProject } = await import("./resolve-package-RN2JUNIX.js");
544
+ const { resolveProject } = await import("./resolve-package-CB3KDANT.js");
531
545
  const resolution = await resolveProject(options.path, options.package);
532
546
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
533
547
  const { loadScoreFromLastReport, badgeUrl, badgeMarkdown, badgeHtml } = await import("./badge-KQ73KEIN.js");
534
548
  let score = options.scan ? null : loadScoreFromLastReport(projectPath);
535
549
  if (score === null) {
536
- const { runSickbay: runSickbay2 } = await import("./dist-Q76NLAL5.js");
550
+ const { runSickbay: runSickbay2 } = await import("./dist-G7GDVJT5.js");
537
551
  const report = await runSickbay2({ projectPath, quotes: false });
538
552
  try {
539
553
  const { saveEntry, saveLastReport } = await import("./history-XLNVZEDI.js");
@@ -566,9 +580,9 @@ program.command("diff <branch>").description("Compare health score against anoth
566
580
  }
567
581
  }
568
582
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
569
- const { DiffApp } = await import("./DiffApp-ACTRBPEJ.js");
583
+ const { DiffApp } = await import("./DiffApp-KR745TCV.js");
570
584
  render(
571
- React6.createElement(DiffApp, {
585
+ React7.createElement(DiffApp, {
572
586
  projectPath: options.path,
573
587
  branch,
574
588
  jsonOutput: options.json ?? false,
@@ -4,7 +4,7 @@ import {
4
4
  getAvailableChecks,
5
5
  runSickbay,
6
6
  runSickbayMonorepo
7
- } from "./chunk-6GIJCWXK.js";
7
+ } from "./chunk-WEGMDILT.js";
8
8
  import {
9
9
  saveEntry
10
10
  } from "./chunk-3OR2GFVE.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  resolveProject,
3
3
  shortName
4
- } from "./chunk-QYJLQBEL.js";
5
- import "./chunk-6GIJCWXK.js";
4
+ } from "./chunk-MCHTDD7R.js";
5
+ import "./chunk-WEGMDILT.js";
6
6
  export {
7
7
  resolveProject,
8
8
  shortName
@@ -106,7 +106,7 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
106
106
  if (url === "/sickbay-config.json") {
107
107
  const basePath = "isMonorepo" in report ? report.rootPath : report.projectPath;
108
108
  try {
109
- const { loadConfig } = await import("./dist-Q76NLAL5.js");
109
+ const { loadConfig } = await import("./dist-G7GDVJT5.js");
110
110
  const config = await loadConfig(basePath);
111
111
  if (config) {
112
112
  res.writeHead(200, { "Content-Type": "application/json" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sickbay",
3
- "version": "1.15.2",
3
+ "version": "1.15.4",
4
4
  "description": "Zero-config health check CLI for JavaScript and TypeScript projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -63,5 +63,6 @@
63
63
  "tsup": "catalog:",
64
64
  "typescript": "catalog:",
65
65
  "vitest": "catalog:"
66
- }
66
+ },
67
+ "_bundleNote": "tsup bundles @nebulord/sickbay-core inline (see tsup.config.ts noExternal). Core's runtime deps are mirrored in `dependencies` below so the bundled require() calls resolve at runtime. Do NOT delete entries that look unused from cli source — they are needed by core's bundled code. Drift is enforced by `pnpm check:bundled-deps` (scripts/check-bundled-deps.mjs)."
67
68
  }