sickbay 1.10.2 → 1.10.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  Header
3
- } from "./chunk-C7GKIF4V.js";
3
+ } from "./chunk-IPZEEXKZ.js";
4
4
  import "./chunk-JSBRDJBE.js";
5
5
 
6
6
  // src/components/DiffApp.tsx
@@ -120,7 +120,7 @@ function DiffApp({ projectPath, branch, jsonOutput, checks, verbose }) {
120
120
  useEffect(() => {
121
121
  (async () => {
122
122
  try {
123
- const { runSickbay } = await import("./dist-FKT3GNGE.js");
123
+ const { runSickbay } = await import("./dist-RKTIGZ5N.js");
124
124
  const currentReport = await runSickbay({
125
125
  projectPath,
126
126
  checks,
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-4C42NVK3.js";
3
+ } from "./chunk-DUFOKDEH.js";
4
4
  import {
5
5
  Header
6
- } from "./chunk-C7GKIF4V.js";
6
+ } from "./chunk-IPZEEXKZ.js";
7
7
  import {
8
8
  detectPackageManager,
9
9
  detectProject
10
- } from "./chunk-BVDAUF7Y.js";
10
+ } from "./chunk-IQGBMBSC.js";
11
11
  import "./chunk-JSBRDJBE.js";
12
12
 
13
13
  // src/components/DoctorApp.tsx
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-4C42NVK3.js";
3
+ } from "./chunk-DUFOKDEH.js";
4
4
  import {
5
5
  ProgressList
6
6
  } from "./chunk-MBVA75EM.js";
7
7
  import {
8
8
  Header
9
- } from "./chunk-C7GKIF4V.js";
9
+ } from "./chunk-IPZEEXKZ.js";
10
10
  import {
11
11
  runSickbay
12
- } from "./chunk-BVDAUF7Y.js";
12
+ } from "./chunk-IQGBMBSC.js";
13
13
  import "./chunk-JSBRDJBE.js";
14
14
 
15
15
  // src/components/FixApp.tsx
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  shortName
3
- } from "./chunk-4C42NVK3.js";
3
+ } from "./chunk-DUFOKDEH.js";
4
4
  import {
5
5
  Header
6
- } from "./chunk-C7GKIF4V.js";
6
+ } from "./chunk-IPZEEXKZ.js";
7
7
  import {
8
8
  detectProject
9
- } from "./chunk-BVDAUF7Y.js";
9
+ } from "./chunk-IQGBMBSC.js";
10
10
  import "./chunk-JSBRDJBE.js";
11
11
 
12
12
  // src/components/StatsApp.tsx
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-SHO3ZXTH.js";
5
5
  import {
6
6
  shortName
7
- } from "./chunk-4C42NVK3.js";
7
+ } from "./chunk-DUFOKDEH.js";
8
8
  import {
9
9
  Header
10
- } from "./chunk-C7GKIF4V.js";
11
- import "./chunk-BVDAUF7Y.js";
10
+ } from "./chunk-IPZEEXKZ.js";
11
+ import "./chunk-IQGBMBSC.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-BVDAUF7Y.js";
17
+ } from "./chunk-IQGBMBSC.js";
18
18
  import {
19
19
  loadHistory
20
20
  } from "./chunk-3OR2GFVE.js";
@@ -690,7 +690,7 @@ function TuiApp({
690
690
  return () => timers.forEach(clearTimeout);
691
691
  }, []);
692
692
  useEffect7(() => {
693
- checkForUpdate("1.10.1").then((info) => {
693
+ checkForUpdate("1.10.2").then((info) => {
694
694
  if (info) setUpdateInfo(info);
695
695
  });
696
696
  }, []);
@@ -716,7 +716,7 @@ function TuiApp({
716
716
  } catch {
717
717
  }
718
718
  try {
719
- const { getDependencyTree } = await import("./dist-FKT3GNGE.js");
719
+ const { getDependencyTree } = await import("./dist-RKTIGZ5N.js");
720
720
  const { saveDepTree } = await import("./history-KXLC45VS.js");
721
721
  const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);
722
722
  saveDepTree(projectPath, tree);
@@ -820,7 +820,7 @@ function TuiApp({
820
820
  const webReport = monorepoReportRef.current ?? reportRef.current;
821
821
  if (!webReport) return;
822
822
  try {
823
- const { serveWeb } = await import("./web-63XYUGGV.js");
823
+ const { serveWeb } = await import("./web-3HNI75TF.js");
824
824
  const { default: openBrowser } = await import("open");
825
825
  let aiService;
826
826
  if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  detectMonorepo
3
- } from "./chunk-BVDAUF7Y.js";
3
+ } from "./chunk-IQGBMBSC.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.10.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.10.2")), 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 {
@@ -1756,7 +1756,7 @@ var require_picomatch2 = __commonJS({
1756
1756
  // ../../packages/core/dist/index.js
1757
1757
  var import_picomatch = __toESM(require_picomatch2(), 1);
1758
1758
  var import_picomatch2 = __toESM(require_picomatch2(), 1);
1759
- import { relative } from "path";
1759
+ import { relative as relative2 } from "path";
1760
1760
  import { existsSync } from "fs";
1761
1761
  import { join, resolve } from "path";
1762
1762
  import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
@@ -1821,23 +1821,25 @@ import { readFileSync as readFileSync20, existsSync as existsSync15 } from "fs";
1821
1821
  import { join as join25 } from "path";
1822
1822
  import { readFileSync as readFileSync21, existsSync as existsSync16 } from "fs";
1823
1823
  import { join as join26 } from "path";
1824
+ import { existsSync as existsSync17 } from "fs";
1825
+ import { dirname as dirname4, join as join27, relative } from "path";
1824
1826
  import { execa as execa10 } from "execa";
1825
1827
  import { execa as execa11 } from "execa";
1826
1828
  import { readFileSync as readFileSync22, readdirSync as readdirSync13, statSync as statSync13 } from "fs";
1827
- import { join as join27, extname as extname5 } from "path";
1828
- import { readFileSync as readFileSync23, readdirSync as readdirSync14, statSync as statSync14, existsSync as existsSync17 } from "fs";
1829
- import { join as join28, extname as extname6, basename as basename2 } from "path";
1829
+ import { join as join28, extname as extname5 } from "path";
1830
+ import { readFileSync as readFileSync23, readdirSync as readdirSync14, statSync as statSync14, existsSync as existsSync18 } from "fs";
1831
+ import { join as join29, extname as extname6, basename as basename2 } from "path";
1830
1832
  import { statSync as statSync15, readFileSync as readFileSync24 } from "fs";
1831
- import { join as join29 } from "path";
1833
+ import { join as join30 } from "path";
1832
1834
  import { execa as execa12 } from "execa";
1833
1835
  import { globby } from "globby";
1834
- import { readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync16, existsSync as existsSync18 } from "fs";
1835
- import { join as join30, extname as extname7 } from "path";
1836
- import { existsSync as existsSync19 } from "fs";
1837
- import { join as join31 } from "path";
1836
+ import { readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync16, existsSync as existsSync19 } from "fs";
1837
+ import { join as join31, extname as extname7 } from "path";
1838
+ import { existsSync as existsSync20 } from "fs";
1839
+ import { join as join32 } from "path";
1838
1840
  import { execa as execa13 } from "execa";
1839
- import { existsSync as existsSync20, readFileSync as readFileSync26 } from "fs";
1840
- import { join as join32, resolve as resolve2 } from "path";
1841
+ import { existsSync as existsSync21, readFileSync as readFileSync26 } from "fs";
1842
+ import { join as join33, resolve as resolve2 } from "path";
1841
1843
  import { globby as globby2 } from "globby";
1842
1844
  import { execa as execa14 } from "execa";
1843
1845
  function defineConfig(config) {
@@ -5110,41 +5112,35 @@ var NpmAuditRunner = class extends BaseRunner {
5110
5112
  category = "security";
5111
5113
  async run(projectPath) {
5112
5114
  const elapsed = timer();
5115
+ const pm = detectPackageManager(projectPath);
5116
+ if (pm === "yarn" || pm === "bun") {
5117
+ return this.skipped(`${pm} audit not supported \u2014 run: ${pm} audit`);
5118
+ }
5113
5119
  try {
5114
- const { stdout } = await execa10("npm", ["audit", "--json"], {
5120
+ const { stdout } = await execa10(pm, ["audit", "--json"], {
5115
5121
  cwd: projectPath,
5116
- reject: false
5122
+ reject: false,
5123
+ timeout: 6e4
5117
5124
  });
5118
- const data = parseJsonOutput(stdout, "{}");
5119
- const issues = [];
5120
- const meta = data.metadata?.vulnerabilities;
5121
- const vulnerablePackages = {};
5122
- for (const [pkgName, vuln] of Object.entries(data.vulnerabilities ?? {})) {
5123
- const advisoryCount = Array.isArray(vuln.via) ? vuln.via.filter((v) => typeof v === "object" && v !== null && "title" in v).length : 0;
5124
- vulnerablePackages[pkgName] = Math.max(advisoryCount, 1);
5125
- }
5126
- for (const [, vuln] of Object.entries(data.vulnerabilities ?? {})) {
5127
- const via = Array.isArray(vuln.via) ? vuln.via[0] : null;
5128
- const title = typeof via === "object" && via?.title ? via.title : `Vulnerability in ${vuln.name}`;
5129
- const url = typeof via === "object" && via?.url ? via.url : void 0;
5130
- issues.push({
5131
- severity: vuln.severity === "critical" || vuln.severity === "high" ? "critical" : "warning",
5132
- message: title,
5133
- fix: typeof vuln.fixAvailable === "object" ? { description: `Upgrade to ${vuln.fixAvailable.name}@${vuln.fixAvailable.version}` } : { description: "No automatic fix available" },
5134
- reportedBy: ["npm-audit"],
5135
- ...url && { file: url }
5136
- });
5137
- }
5125
+ const raw = parseJsonOutput(stdout, "{}");
5126
+ const { issues, vulnerablePackages, meta } = pm === "pnpm" ? parsePnpmAudit(raw, projectPath) : parseNpmAudit(raw);
5138
5127
  const critical = (meta?.critical ?? 0) + (meta?.high ?? 0);
5139
- const score = critical > 0 ? Math.max(0, 60 - critical * 15) : Math.max(0, 100 - (meta?.moderate ?? 0) * 10 - (meta?.low ?? 0) * 2);
5128
+ const moderate = meta?.moderate ?? 0;
5129
+ const low = meta?.low ?? 0;
5130
+ const filteredCritical = issues.filter((i) => i.severity === "critical").length;
5131
+ const filteredWarning = issues.filter((i) => i.severity === "warning").length;
5132
+ const hasScopeFiltering = pm === "pnpm" && filteredCritical + filteredWarning < critical + moderate + low;
5133
+ const effectiveCritical = hasScopeFiltering ? filteredCritical : critical;
5134
+ const effectiveModerate = hasScopeFiltering ? filteredWarning : moderate;
5135
+ const score = effectiveCritical > 0 ? Math.max(0, 60 - effectiveCritical * 15) : Math.max(0, 100 - effectiveModerate * 10 - (hasScopeFiltering ? 0 : low) * 2);
5140
5136
  return {
5141
5137
  id: "npm-audit",
5142
5138
  category: this.category,
5143
5139
  name: "Security Vulnerabilities",
5144
5140
  score,
5145
- status: critical > 0 ? "fail" : issues.length > 0 ? "warning" : "pass",
5141
+ status: effectiveCritical > 0 ? "fail" : issues.length > 0 ? "warning" : "pass",
5146
5142
  issues,
5147
- toolsUsed: ["npm-audit"],
5143
+ toolsUsed: [`${pm}-audit`],
5148
5144
  duration: elapsed(),
5149
5145
  metadata: { ...meta, vulnerablePackages }
5150
5146
  };
@@ -5156,14 +5152,93 @@ var NpmAuditRunner = class extends BaseRunner {
5156
5152
  score: 0,
5157
5153
  status: "fail",
5158
5154
  issues: [
5159
- { severity: "critical", message: `npm audit failed: ${err}`, reportedBy: ["npm-audit"] }
5155
+ {
5156
+ severity: "critical",
5157
+ message: `${pm} audit failed: ${err}`,
5158
+ reportedBy: ["npm-audit"]
5159
+ }
5160
5160
  ],
5161
- toolsUsed: ["npm-audit"],
5161
+ toolsUsed: [`${pm}-audit`],
5162
5162
  duration: elapsed()
5163
5163
  };
5164
5164
  }
5165
5165
  }
5166
5166
  };
5167
+ function parseNpmAudit(data) {
5168
+ const issues = [];
5169
+ const meta = data.metadata?.vulnerabilities;
5170
+ const vulnerablePackages = {};
5171
+ for (const [pkgName, vuln] of Object.entries(data.vulnerabilities ?? {})) {
5172
+ const advisoryCount = Array.isArray(vuln.via) ? vuln.via.filter((v) => typeof v === "object" && v !== null && "title" in v).length : 0;
5173
+ vulnerablePackages[pkgName] = Math.max(advisoryCount, 1);
5174
+ }
5175
+ for (const [, vuln] of Object.entries(data.vulnerabilities ?? {})) {
5176
+ const via = Array.isArray(vuln.via) ? vuln.via[0] : null;
5177
+ const title = typeof via === "object" && via?.title ? via.title : `Vulnerability in ${vuln.name}`;
5178
+ const url = typeof via === "object" && via?.url ? via.url : void 0;
5179
+ issues.push({
5180
+ severity: vuln.severity === "critical" || vuln.severity === "high" ? "critical" : "warning",
5181
+ message: formatAuditMessage(vuln.name, title, url),
5182
+ fix: typeof vuln.fixAvailable === "object" ? { description: `Upgrade to ${vuln.fixAvailable.name}@${vuln.fixAvailable.version}` } : { description: "No automatic fix available" },
5183
+ reportedBy: ["npm-audit"],
5184
+ ...url && { file: url }
5185
+ });
5186
+ }
5187
+ return { issues, vulnerablePackages, meta };
5188
+ }
5189
+ function parsePnpmAudit(data, projectPath) {
5190
+ const issues = [];
5191
+ const meta = data.metadata?.vulnerabilities;
5192
+ const vulnerablePackages = {};
5193
+ const advisories = filterPnpmAdvisories(data.advisories ?? {}, projectPath);
5194
+ for (const [, advisory] of Object.entries(advisories)) {
5195
+ const pkgName = advisory.module_name;
5196
+ vulnerablePackages[pkgName] = (vulnerablePackages[pkgName] ?? 0) + 1;
5197
+ issues.push({
5198
+ severity: advisory.severity === "critical" || advisory.severity === "high" ? "critical" : "warning",
5199
+ message: formatAuditMessage(
5200
+ pkgName,
5201
+ advisory.title || `Vulnerability in ${pkgName}`,
5202
+ advisory.url
5203
+ ),
5204
+ fix: advisory.recommendation ? { description: advisory.recommendation } : { description: "No automatic fix available" },
5205
+ reportedBy: ["npm-audit"],
5206
+ ...advisory.url && { file: advisory.url }
5207
+ });
5208
+ }
5209
+ return { issues, vulnerablePackages, meta };
5210
+ }
5211
+ function filterPnpmAdvisories(advisories, projectPath) {
5212
+ const workspaceRoot = findPnpmWorkspaceRoot(projectPath);
5213
+ if (!workspaceRoot || workspaceRoot === projectPath) {
5214
+ return advisories;
5215
+ }
5216
+ const importerPrefix = relative(workspaceRoot, projectPath).replace(/\//g, "__");
5217
+ const filtered = {};
5218
+ for (const [id, advisory] of Object.entries(advisories)) {
5219
+ const isRelevant = advisory.findings.some(
5220
+ (f) => f.paths.some((p) => p === importerPrefix || p.startsWith(importerPrefix + ">"))
5221
+ );
5222
+ if (isRelevant) {
5223
+ filtered[id] = advisory;
5224
+ }
5225
+ }
5226
+ return filtered;
5227
+ }
5228
+ function findPnpmWorkspaceRoot(startPath) {
5229
+ let dir = startPath;
5230
+ while (true) {
5231
+ if (existsSync17(join27(dir, "pnpm-workspace.yaml"))) return dir;
5232
+ const parent = dirname4(dir);
5233
+ if (parent === dir) return null;
5234
+ dir = parent;
5235
+ }
5236
+ }
5237
+ function formatAuditMessage(moduleName, title, url) {
5238
+ const ghsaId = url?.match(/GHSA-[\w-]+/)?.[0];
5239
+ const suffix = ghsaId ? ` (${ghsaId})` : "";
5240
+ return `[${moduleName}] ${title}${suffix}`;
5241
+ }
5167
5242
  var OutdatedRunner = class extends BaseRunner {
5168
5243
  name = "outdated";
5169
5244
  category = "dependencies";
@@ -5272,7 +5347,7 @@ var ReactPerfRunner = class extends BaseRunner {
5272
5347
  try {
5273
5348
  const hasReactCompiler = detectReactCompiler(projectPath);
5274
5349
  const findings = [];
5275
- const files = scanDirectory2(join27(projectPath, "src"), projectPath, isExcluded);
5350
+ const files = scanDirectory2(join28(projectPath, "src"), projectPath, isExcluded);
5276
5351
  for (const file of files) {
5277
5352
  findings.push(...analyzeFile(file.path, file.fullPath, file.lines));
5278
5353
  }
@@ -5327,7 +5402,7 @@ var ReactPerfRunner = class extends BaseRunner {
5327
5402
  };
5328
5403
  function detectReactCompiler(projectPath) {
5329
5404
  try {
5330
- const pkg = JSON.parse(readFileSync22(join27(projectPath, "package.json"), "utf-8"));
5405
+ const pkg = JSON.parse(readFileSync22(join28(projectPath, "package.json"), "utf-8"));
5331
5406
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5332
5407
  return "babel-plugin-react-compiler" in allDeps || "@react-compiler/babel" in allDeps;
5333
5408
  } catch {
@@ -5340,7 +5415,7 @@ function scanDirectory2(dir, projectRoot, isExcluded) {
5340
5415
  for (const entry of readdirSync13(dir)) {
5341
5416
  if (entry.startsWith(".") || entry === "node_modules" || entry === "__tests__" || entry === "__mocks__" || entry.includes(".test.") || entry.includes(".spec."))
5342
5417
  continue;
5343
- const fullPath = join27(dir, entry);
5418
+ const fullPath = join28(dir, entry);
5344
5419
  const relPath = fullPath.replace(projectRoot + "/", "");
5345
5420
  if (isExcluded(relPath)) continue;
5346
5421
  const stat = statSync13(fullPath);
@@ -5488,8 +5563,8 @@ var SecretsRunner = class extends BaseRunner {
5488
5563
  const findings = [];
5489
5564
  const envFiles = [".env", ".env.local", ".env.production"];
5490
5565
  for (const envFile of envFiles) {
5491
- if (existsSync17(join28(projectPath, envFile))) {
5492
- const gitignorePath = join28(projectPath, ".gitignore");
5566
+ if (existsSync18(join29(projectPath, envFile))) {
5567
+ const gitignorePath = join29(projectPath, ".gitignore");
5493
5568
  let ignored = false;
5494
5569
  try {
5495
5570
  const gitignore = readFileSync23(gitignorePath, "utf-8");
@@ -5505,8 +5580,8 @@ var SecretsRunner = class extends BaseRunner {
5505
5580
  }
5506
5581
  }
5507
5582
  }
5508
- if (existsSync17(join28(projectPath, "src"))) {
5509
- findings.push(...scanDirectory3(join28(projectPath, "src"), projectPath, isExcluded));
5583
+ if (existsSync18(join29(projectPath, "src"))) {
5584
+ findings.push(...scanDirectory3(join29(projectPath, "src"), projectPath, isExcluded));
5510
5585
  }
5511
5586
  const issues = findings.map((f) => ({
5512
5587
  severity: "critical",
@@ -5560,7 +5635,7 @@ function scanDirectory3(dir, projectRoot, isExcluded) {
5560
5635
  for (const entry of readdirSync14(dir)) {
5561
5636
  if (entry.startsWith(".") || entry === "node_modules" || entry === "__tests__" || entry === "test" || entry === "tests")
5562
5637
  continue;
5563
- const fullPath = join28(dir, entry);
5638
+ const fullPath = join29(dir, entry);
5564
5639
  const relPath = fullPath.replace(projectRoot + "/", "");
5565
5640
  if (isExcluded(relPath)) continue;
5566
5641
  const stat = statSync14(fullPath);
@@ -5608,9 +5683,9 @@ var SIZE_THRESHOLD_WARN = 500 * 1024;
5608
5683
  var SIZE_THRESHOLD_FAIL = 1024 * 1024;
5609
5684
  function findEntryChunks(buildPath) {
5610
5685
  try {
5611
- const html = readFileSync24(join29(buildPath, "index.html"), "utf8");
5686
+ const html = readFileSync24(join30(buildPath, "index.html"), "utf8");
5612
5687
  const matches = [...html.matchAll(/<script[^>]+src="([^"]+\.js)"[^>]*>/gi)];
5613
- return matches.map((m) => join29(buildPath, m[1].replace(/^\//, "")));
5688
+ return matches.map((m) => join30(buildPath, m[1].replace(/^\//, "")));
5614
5689
  } catch {
5615
5690
  return [];
5616
5691
  }
@@ -5626,7 +5701,7 @@ var SourceMapExplorerRunner = class extends BaseRunner {
5626
5701
  const warnSize = t?.warnSize ?? SIZE_THRESHOLD_WARN;
5627
5702
  const failSize = t?.failSize ?? SIZE_THRESHOLD_FAIL;
5628
5703
  const buildDir = fileExists(projectPath, "dist") ? "dist" : "build";
5629
- const buildPath = join29(projectPath, buildDir);
5704
+ const buildPath = join30(projectPath, buildDir);
5630
5705
  const mapFiles = await globby("**/*.js.map", {
5631
5706
  cwd: buildPath,
5632
5707
  absolute: false
@@ -5796,7 +5871,7 @@ var TodoScannerRunner = class extends BaseRunner {
5796
5871
  name = "todo-scanner";
5797
5872
  category = "code-quality";
5798
5873
  async isApplicable(projectPath) {
5799
- return existsSync18(join30(projectPath, "src"));
5874
+ return existsSync19(join31(projectPath, "src"));
5800
5875
  }
5801
5876
  async run(projectPath, options) {
5802
5877
  const elapsed = timer();
@@ -5805,7 +5880,7 @@ var TodoScannerRunner = class extends BaseRunner {
5805
5880
  const pattern = buildPattern(patterns);
5806
5881
  const isExcluded = createExcludeFilter(options?.checkConfig?.exclude ?? []);
5807
5882
  try {
5808
- const todos = scanDirectory4(join30(projectPath, "src"), projectPath, pattern, isExcluded);
5883
+ const todos = scanDirectory4(join31(projectPath, "src"), projectPath, pattern, isExcluded);
5809
5884
  const issues = todos.map((t) => ({
5810
5885
  severity: t.kind === "FIXME" || t.kind === "HACK" ? "warning" : "info",
5811
5886
  message: `${t.file}:${t.line} \u2014 ${t.kind}: ${t.text || "(no description)"}`,
@@ -5856,7 +5931,7 @@ function scanDirectory4(dir, projectRoot, pattern, isExcluded) {
5856
5931
  for (const entry of readdirSync15(dir)) {
5857
5932
  if (entry.startsWith(".") || entry === "node_modules") continue;
5858
5933
  if (SELF_REFERENCING_FILES.has(entry)) continue;
5859
- const fullPath = join30(dir, entry);
5934
+ const fullPath = join31(dir, entry);
5860
5935
  const relPath = fullPath.replace(projectRoot + "/", "");
5861
5936
  if (isExcluded(relPath)) continue;
5862
5937
  const stat = statSync16(fullPath);
@@ -5894,7 +5969,7 @@ var TypeScriptRunner = class extends BaseRunner {
5894
5969
  name = "typescript";
5895
5970
  category = "code-quality";
5896
5971
  async isApplicable(projectPath) {
5897
- return existsSync19(join31(projectPath, "tsconfig.json"));
5972
+ return existsSync20(join32(projectPath, "tsconfig.json"));
5898
5973
  }
5899
5974
  async run(projectPath, options) {
5900
5975
  const elapsed = timer();
@@ -6149,12 +6224,12 @@ async function discoverPackages(rootPath, patterns) {
6149
6224
  ignore: ["**/node_modules/**"]
6150
6225
  });
6151
6226
  return matched.filter((dir) => {
6152
- return dir !== resolve2(rootPath) && existsSync20(join32(dir, "package.json"));
6227
+ return dir !== resolve2(rootPath) && existsSync21(join33(dir, "package.json"));
6153
6228
  });
6154
6229
  }
6155
6230
  function detectSignals(rootPath) {
6156
- const pnpmWs = join32(rootPath, "pnpm-workspace.yaml");
6157
- if (existsSync20(pnpmWs)) {
6231
+ const pnpmWs = join33(rootPath, "pnpm-workspace.yaml");
6232
+ if (existsSync21(pnpmWs)) {
6158
6233
  try {
6159
6234
  const content = readFileSync26(pnpmWs, "utf-8");
6160
6235
  const patterns = parseYamlPackagesArray(content);
@@ -6163,8 +6238,8 @@ function detectSignals(rootPath) {
6163
6238
  return { type: "pnpm", patterns: ["packages/*", "apps/*"] };
6164
6239
  }
6165
6240
  }
6166
- const lernaJson = join32(rootPath, "lerna.json");
6167
- if (existsSync20(lernaJson)) {
6241
+ const lernaJson = join33(rootPath, "lerna.json");
6242
+ if (existsSync21(lernaJson)) {
6168
6243
  try {
6169
6244
  const lerna = JSON.parse(readFileSync26(lernaJson, "utf-8"));
6170
6245
  const patterns = Array.isArray(lerna.packages) ? lerna.packages : ["packages/*"];
@@ -6173,28 +6248,28 @@ function detectSignals(rootPath) {
6173
6248
  return { type: "lerna", patterns: ["packages/*"] };
6174
6249
  }
6175
6250
  }
6176
- const pkgJson = join32(rootPath, "package.json");
6177
- if (existsSync20(pkgJson)) {
6251
+ const pkgJson = join33(rootPath, "package.json");
6252
+ if (existsSync21(pkgJson)) {
6178
6253
  try {
6179
6254
  const pkg = JSON.parse(readFileSync26(pkgJson, "utf-8"));
6180
6255
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : Array.isArray(pkg.workspaces?.packages) ? pkg.workspaces.packages : void 0;
6181
6256
  if (workspaces && workspaces.length > 0) {
6182
- if (existsSync20(join32(rootPath, "turbo.json"))) {
6257
+ if (existsSync21(join33(rootPath, "turbo.json"))) {
6183
6258
  return { type: "turbo", patterns: workspaces };
6184
6259
  }
6185
- if (existsSync20(join32(rootPath, "nx.json"))) {
6260
+ if (existsSync21(join33(rootPath, "nx.json"))) {
6186
6261
  return { type: "nx", patterns: workspaces };
6187
6262
  }
6188
- const hasYarn = existsSync20(join32(rootPath, "yarn.lock"));
6263
+ const hasYarn = existsSync21(join33(rootPath, "yarn.lock"));
6189
6264
  return { type: hasYarn ? "yarn" : "npm", patterns: workspaces };
6190
6265
  }
6191
6266
  } catch {
6192
6267
  }
6193
6268
  }
6194
- if (existsSync20(join32(rootPath, "turbo.json"))) {
6269
+ if (existsSync21(join33(rootPath, "turbo.json"))) {
6195
6270
  return { type: "turbo", patterns: ["packages/*", "apps/*"] };
6196
6271
  }
6197
- if (existsSync20(join32(rootPath, "nx.json"))) {
6272
+ if (existsSync21(join33(rootPath, "nx.json"))) {
6198
6273
  return { type: "nx", patterns: ["packages/*", "apps/*", "libs/*"] };
6199
6274
  }
6200
6275
  return null;
@@ -6357,7 +6432,7 @@ async function runSickbayMonorepo(options = {}) {
6357
6432
  const packageReport = {
6358
6433
  name: report.projectInfo.name,
6359
6434
  path: pkgPath,
6360
- relativePath: relative(rootPath, pkgPath),
6435
+ relativePath: relative2(rootPath, pkgPath),
6361
6436
  framework: report.projectInfo.framework,
6362
6437
  runtime: context.runtime,
6363
6438
  checks: report.checks,
package/dist/config.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  defineConfig
3
- } from "./chunk-BVDAUF7Y.js";
3
+ } from "./chunk-IQGBMBSC.js";
4
4
  import "./chunk-JSBRDJBE.js";
5
5
  export {
6
6
  defineConfig
@@ -28,7 +28,7 @@ import {
28
28
  runSickbay,
29
29
  runSickbayMonorepo,
30
30
  validateConfig
31
- } from "./chunk-BVDAUF7Y.js";
31
+ } from "./chunk-IQGBMBSC.js";
32
32
  import "./chunk-JSBRDJBE.js";
33
33
  export {
34
34
  CATEGORY_WEIGHTS,
package/dist/index.js CHANGED
@@ -8,12 +8,12 @@ import {
8
8
  } from "./chunk-TYG7ZQBP.js";
9
9
  import {
10
10
  Header
11
- } from "./chunk-C7GKIF4V.js";
11
+ } from "./chunk-IPZEEXKZ.js";
12
12
  import {
13
13
  getScoreEmoji,
14
14
  runSickbay,
15
15
  runSickbayMonorepo
16
- } from "./chunk-BVDAUF7Y.js";
16
+ } from "./chunk-IQGBMBSC.js";
17
17
  import "./chunk-JSBRDJBE.js";
18
18
 
19
19
  // src/index.ts
@@ -177,7 +177,7 @@ function App({
177
177
  setMonorepoReport(r);
178
178
  setProjectName(`monorepo (${r.packages.length} packages)`);
179
179
  try {
180
- const { getDependencyTree } = await import("./dist-FKT3GNGE.js");
180
+ const { getDependencyTree } = await import("./dist-RKTIGZ5N.js");
181
181
  const { saveDepTree } = await import("./history-KXLC45VS.js");
182
182
  const packages = {};
183
183
  for (const pkg of r.packages) {
@@ -189,7 +189,7 @@ function App({
189
189
  if (openWeb) {
190
190
  setPhase("opening-web");
191
191
  try {
192
- const { serveWeb } = await import("./web-63XYUGGV.js");
192
+ const { serveWeb } = await import("./web-3HNI75TF.js");
193
193
  const { default: openBrowser } = await import("open");
194
194
  let aiService;
195
195
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -246,7 +246,7 @@ function App({
246
246
  } catch {
247
247
  }
248
248
  try {
249
- const { getDependencyTree } = await import("./dist-FKT3GNGE.js");
249
+ const { getDependencyTree } = await import("./dist-RKTIGZ5N.js");
250
250
  const { saveDepTree } = await import("./history-KXLC45VS.js");
251
251
  const tree = await getDependencyTree(projectPath, r.projectInfo.packageManager);
252
252
  saveDepTree(projectPath, tree);
@@ -255,7 +255,7 @@ function App({
255
255
  if (openWeb) {
256
256
  setPhase("opening-web");
257
257
  try {
258
- const { serveWeb } = await import("./web-63XYUGGV.js");
258
+ const { serveWeb } = await import("./web-3HNI75TF.js");
259
259
  const { default: openBrowser } = await import("open");
260
260
  let aiService;
261
261
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -304,7 +304,7 @@ if (existsSync(globalConfigPath)) {
304
304
  }
305
305
  config({ debug: false, quiet: true });
306
306
  var program = new Command();
307
- program.name("sickbay").description("React project health check CLI").version("1.10.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) => {
307
+ program.name("sickbay").description("React project health check CLI").version("1.10.2", "-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) => {
308
308
  if (options.path && options.path !== process.cwd()) {
309
309
  const projectEnvPath = join(options.path, ".env");
310
310
  if (existsSync(projectEnvPath)) {
@@ -314,13 +314,13 @@ program.name("sickbay").description("React project health check CLI").version("1
314
314
  const updatePromise = (async () => {
315
315
  try {
316
316
  const { checkForUpdate } = await import("./update-check-QLG6GA4Z.js");
317
- return await checkForUpdate("1.10.1");
317
+ return await checkForUpdate("1.10.2");
318
318
  } catch {
319
319
  return null;
320
320
  }
321
321
  })();
322
322
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
323
- const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-FKT3GNGE.js");
323
+ const { detectMonorepo, runSickbay: runSickbay2, runSickbayMonorepo: runSickbayMonorepo2 } = await import("./dist-RKTIGZ5N.js");
324
324
  const monorepoInfo = await detectMonorepo(options.path);
325
325
  if (options.package && monorepoInfo.isMonorepo) {
326
326
  const { readFileSync } = await import("fs");
@@ -384,7 +384,7 @@ program.name("sickbay").description("React project health check CLI").version("1
384
384
  } catch {
385
385
  }
386
386
  try {
387
- const { getDependencyTree } = await import("./dist-FKT3GNGE.js");
387
+ const { getDependencyTree } = await import("./dist-RKTIGZ5N.js");
388
388
  const { saveDepTree } = await import("./history-KXLC45VS.js");
389
389
  const tree = await getDependencyTree(options.path, report.projectInfo.packageManager);
390
390
  saveDepTree(options.path, tree);
@@ -411,16 +411,16 @@ program.command("init").description("Initialize .sickbay/ folder and run an init
411
411
  "add newly available checks to existing config without touching existing entries"
412
412
  ).option("--reset-config", "regenerate config from scratch (overwrites existing)").action(async (options) => {
413
413
  if (options.resetConfig) {
414
- const { generateConfigFile } = await import("./init-RJDFIHJD.js");
414
+ const { generateConfigFile } = await import("./init-DEXLYIHQ.js");
415
415
  await generateConfigFile(options.path, { force: true });
416
416
  return;
417
417
  }
418
418
  if (options.sync) {
419
- const { syncConfigFile } = await import("./init-RJDFIHJD.js");
419
+ const { syncConfigFile } = await import("./init-DEXLYIHQ.js");
420
420
  await syncConfigFile(options.path);
421
421
  return;
422
422
  }
423
- const { initSickbay } = await import("./init-RJDFIHJD.js");
423
+ const { initSickbay } = await import("./init-DEXLYIHQ.js");
424
424
  await initSickbay(options.path);
425
425
  });
426
426
  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) => {
@@ -430,9 +430,9 @@ program.command("fix").description("Interactively fix issues found by sickbay sc
430
430
  config({ path: projectEnvPath, override: true });
431
431
  }
432
432
  }
433
- const { resolveProject } = await import("./resolve-package-HXEADBCX.js");
433
+ const { resolveProject } = await import("./resolve-package-DPCHZNI7.js");
434
434
  const resolution = await resolveProject(options.path, options.package);
435
- const { FixApp } = await import("./FixApp-AJU53CXH.js");
435
+ const { FixApp } = await import("./FixApp-R6LVG3HF.js");
436
436
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
437
437
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
438
438
  render(
@@ -455,10 +455,10 @@ program.command("trend").description("Show score history and trends over time").
455
455
  config({ path: projectEnvPath, override: true });
456
456
  }
457
457
  }
458
- const { resolveProject } = await import("./resolve-package-HXEADBCX.js");
458
+ const { resolveProject } = await import("./resolve-package-DPCHZNI7.js");
459
459
  const resolution = await resolveProject(options.path, options.package);
460
460
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
461
- const { TrendApp } = await import("./TrendApp-DHCUH6JE.js");
461
+ const { TrendApp } = await import("./TrendApp-P2YANQ37.js");
462
462
  render(
463
463
  React6.createElement(TrendApp, {
464
464
  projectPath,
@@ -477,10 +477,10 @@ program.command("stats").description("Show a quick codebase overview and project
477
477
  config({ path: projectEnvPath, override: true });
478
478
  }
479
479
  }
480
- const { resolveProject } = await import("./resolve-package-HXEADBCX.js");
480
+ const { resolveProject } = await import("./resolve-package-DPCHZNI7.js");
481
481
  const resolution = await resolveProject(options.path, options.package);
482
482
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
483
- const { StatsApp } = await import("./StatsApp-VN5E3TWE.js");
483
+ const { StatsApp } = await import("./StatsApp-E6EILSRS.js");
484
484
  render(
485
485
  React6.createElement(StatsApp, {
486
486
  projectPath,
@@ -492,7 +492,7 @@ program.command("stats").description("Show a quick codebase overview and project
492
492
  );
493
493
  });
494
494
  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) => {
495
- const { TuiApp } = await import("./TuiApp-X7RS5NV6.js");
495
+ const { TuiApp } = await import("./TuiApp-6W35BITE.js");
496
496
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
497
497
  render(
498
498
  React6.createElement(TuiApp, {
@@ -512,10 +512,10 @@ program.command("doctor").description("Diagnose project setup and configuration
512
512
  config({ path: projectEnvPath, override: true });
513
513
  }
514
514
  }
515
- const { resolveProject } = await import("./resolve-package-HXEADBCX.js");
515
+ const { resolveProject } = await import("./resolve-package-DPCHZNI7.js");
516
516
  const resolution = await resolveProject(options.path, options.package);
517
517
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
518
- const { DoctorApp } = await import("./DoctorApp-JXY4Z5JV.js");
518
+ const { DoctorApp } = await import("./DoctorApp-UFYIOIQU.js");
519
519
  render(
520
520
  React6.createElement(DoctorApp, {
521
521
  projectPath,
@@ -528,13 +528,13 @@ program.command("doctor").description("Diagnose project setup and configuration
528
528
  );
529
529
  });
530
530
  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) => {
531
- const { resolveProject } = await import("./resolve-package-HXEADBCX.js");
531
+ const { resolveProject } = await import("./resolve-package-DPCHZNI7.js");
532
532
  const resolution = await resolveProject(options.path, options.package);
533
533
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
534
534
  const { loadScoreFromLastReport, badgeUrl, badgeMarkdown, badgeHtml } = await import("./badge-AO2FZVHJ.js");
535
535
  let score = options.scan ? null : loadScoreFromLastReport(projectPath);
536
536
  if (score === null) {
537
- const { runSickbay: runSickbay2 } = await import("./dist-FKT3GNGE.js");
537
+ const { runSickbay: runSickbay2 } = await import("./dist-RKTIGZ5N.js");
538
538
  const report = await runSickbay2({ projectPath, quotes: false });
539
539
  try {
540
540
  const { saveEntry, saveLastReport } = await import("./history-KXLC45VS.js");
@@ -563,7 +563,7 @@ program.command("diff <branch>").description("Compare health score against anoth
563
563
  }
564
564
  }
565
565
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
566
- const { DiffApp } = await import("./DiffApp-EVE2JSUW.js");
566
+ const { DiffApp } = await import("./DiffApp-DS2ITQ4D.js");
567
567
  render(
568
568
  React6.createElement(DiffApp, {
569
569
  projectPath: options.path,
@@ -4,7 +4,7 @@ import {
4
4
  getAvailableChecks,
5
5
  runSickbay,
6
6
  runSickbayMonorepo
7
- } from "./chunk-BVDAUF7Y.js";
7
+ } from "./chunk-IQGBMBSC.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-4C42NVK3.js";
5
- import "./chunk-BVDAUF7Y.js";
4
+ } from "./chunk-DUFOKDEH.js";
5
+ import "./chunk-IQGBMBSC.js";
6
6
  import "./chunk-JSBRDJBE.js";
7
7
  export {
8
8
  resolveProject,
@@ -108,7 +108,7 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
108
108
  if (url === "/sickbay-config.json") {
109
109
  const basePath = "isMonorepo" in report ? report.rootPath : report.projectPath;
110
110
  try {
111
- const { loadConfig } = await import("./dist-FKT3GNGE.js");
111
+ const { loadConfig } = await import("./dist-RKTIGZ5N.js");
112
112
  const config = await loadConfig(basePath);
113
113
  if (config) {
114
114
  res.writeHead(200, { "Content-Type": "application/json" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sickbay",
3
- "version": "1.10.2",
3
+ "version": "1.10.3",
4
4
  "description": "Zero-config health check CLI for JavaScript and TypeScript projects",
5
5
  "license": "MIT",
6
6
  "repository": {