sickbay 1.16.0 → 1.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/dist/DiffApp-DeNEtlY7.js +181 -0
  3. package/dist/DiffApp-DeNEtlY7.js.map +1 -0
  4. package/dist/DoctorApp-CaiqnhHH.js +415 -0
  5. package/dist/DoctorApp-CaiqnhHH.js.map +1 -0
  6. package/dist/FixApp-MWXohP60.js +337 -0
  7. package/dist/FixApp-MWXohP60.js.map +1 -0
  8. package/dist/StatsApp-BC4P4hII.js +372 -0
  9. package/dist/StatsApp-BC4P4hII.js.map +1 -0
  10. package/dist/TrendApp-DOBt7ITA.js +127 -0
  11. package/dist/TrendApp-DOBt7ITA.js.map +1 -0
  12. package/dist/TuiApp-D4Wn81Qv.js +1042 -0
  13. package/dist/TuiApp-D4Wn81Qv.js.map +1 -0
  14. package/dist/ai-DxA5r3hA.js +69 -0
  15. package/dist/ai-DxA5r3hA.js.map +1 -0
  16. package/dist/badge-DB3y0fWt.js +37 -0
  17. package/dist/badge-DB3y0fWt.js.map +1 -0
  18. package/dist/{claude-SN6IVGBI.js → claude-C5dUtWBJ.js} +13 -12
  19. package/dist/claude-C5dUtWBJ.js.map +1 -0
  20. package/dist/config.d.ts +27 -1
  21. package/dist/config.d.ts.map +1 -0
  22. package/dist/config.js +2 -6
  23. package/dist/dist-Bjhxbcak.js +5431 -0
  24. package/dist/dist-Bjhxbcak.js.map +1 -0
  25. package/dist/history-DVegAwFp.js +83 -0
  26. package/dist/history-DVegAwFp.js.map +1 -0
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +692 -564
  29. package/dist/index.js.map +1 -0
  30. package/dist/init-Dn7eDMEp.js +199 -0
  31. package/dist/init-Dn7eDMEp.js.map +1 -0
  32. package/dist/resolve-package-DS27JBhN.js +60 -0
  33. package/dist/resolve-package-DS27JBhN.js.map +1 -0
  34. package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
  35. package/dist/trend-D-Hufthb.js +45 -0
  36. package/dist/trend-D-Hufthb.js.map +1 -0
  37. package/dist/update-check-C7bNNfvf.js +58 -0
  38. package/dist/update-check-C7bNNfvf.js.map +1 -0
  39. package/dist/web-D5etye5Q.js +259 -0
  40. package/dist/web-D5etye5Q.js.map +1 -0
  41. package/package.json +6 -5
  42. package/dist/DiffApp-ASQWWDLS.js +0 -172
  43. package/dist/DoctorApp-SMIZDD64.js +0 -440
  44. package/dist/FixApp-63PU5VF7.js +0 -351
  45. package/dist/StatsApp-CFZBEITZ.js +0 -356
  46. package/dist/TrendApp-BKGRSP6Q.js +0 -106
  47. package/dist/TuiApp-K7KP35RC.js +0 -980
  48. package/dist/ai-7DGOLNJX.js +0 -64
  49. package/dist/badge-KQ73KEIN.js +0 -41
  50. package/dist/chunk-352RAVRZ.js +0 -19
  51. package/dist/chunk-3OR2GFVE.js +0 -90
  52. package/dist/chunk-DJA6K3MN.js +0 -5564
  53. package/dist/chunk-LZOEMCE7.js +0 -62
  54. package/dist/chunk-MBVA75EM.js +0 -19
  55. package/dist/chunk-SHO3ZXTH.js +0 -23
  56. package/dist/chunk-TWCTPAXQ.js +0 -73
  57. package/dist/chunk-VBM6NZAY.js +0 -60
  58. package/dist/dist-LQY3FYCP.js +0 -64
  59. package/dist/history-XLNVZEDI.js +0 -14
  60. package/dist/init-DD2HOQE2.js +0 -217
  61. package/dist/resolve-package-774YTW5Q.js +0 -9
  62. package/dist/update-check-M7IFOMLJ.js +0 -12
  63. package/dist/web-2KWCYTW4.js +0 -269
package/README.md CHANGED
@@ -242,6 +242,6 @@ node dist/index.js --path ~/Desktop/sickbay-test-app --json
242
242
  ## Build
243
243
 
244
244
  ```bash
245
- pnpm build # tsup → dist/index.js + dist/web-*.js (code-split)
245
+ pnpm build # tsdown → dist/index.js + dist/web-*.js (code-split)
246
246
  pnpm clean # rm -rf dist/
247
247
  ```
@@ -0,0 +1,181 @@
1
+ import { r as Header } from "./index.js";
2
+ import React, { useEffect, useState } from "react";
3
+ import { Box, Text, useApp } from "ink";
4
+ import Spinner from "ink-spinner";
5
+ import { execFileSync } from "child_process";
6
+ //#region src/commands/diff.ts
7
+ function loadBaseReport(projectPath, branch) {
8
+ try {
9
+ const output = execFileSync("git", ["show", `${branch}:.sickbay/last-report.json`], {
10
+ cwd: projectPath,
11
+ encoding: "utf-8",
12
+ stdio: [
13
+ "pipe",
14
+ "pipe",
15
+ "pipe"
16
+ ]
17
+ });
18
+ return JSON.parse(output);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+ const STATUS_ORDER = {
24
+ regressed: 0,
25
+ improved: 1,
26
+ new: 2,
27
+ removed: 3,
28
+ unchanged: 4
29
+ };
30
+ function compareReports(current, base, branch) {
31
+ const baseMap = new Map(base.checks.map((c) => [c.id, c]));
32
+ const currentMap = new Map(current.checks.map((c) => [c.id, c]));
33
+ const checks = [];
34
+ for (const check of current.checks) {
35
+ const baseCheck = baseMap.get(check.id);
36
+ if (!baseCheck) checks.push({
37
+ id: check.id,
38
+ name: check.name,
39
+ category: check.category,
40
+ currentScore: check.score,
41
+ baseScore: 0,
42
+ delta: check.score,
43
+ status: "new"
44
+ });
45
+ else {
46
+ const delta = check.score - baseCheck.score;
47
+ checks.push({
48
+ id: check.id,
49
+ name: check.name,
50
+ category: check.category,
51
+ currentScore: check.score,
52
+ baseScore: baseCheck.score,
53
+ delta,
54
+ status: delta > 0 ? "improved" : delta < 0 ? "regressed" : "unchanged"
55
+ });
56
+ }
57
+ }
58
+ for (const check of base.checks) if (!currentMap.has(check.id)) checks.push({
59
+ id: check.id,
60
+ name: check.name,
61
+ category: check.category,
62
+ currentScore: 0,
63
+ baseScore: check.score,
64
+ delta: -check.score,
65
+ status: "removed"
66
+ });
67
+ checks.sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);
68
+ const summary = {
69
+ improved: checks.filter((c) => c.status === "improved").length,
70
+ regressed: checks.filter((c) => c.status === "regressed").length,
71
+ unchanged: checks.filter((c) => c.status === "unchanged").length,
72
+ newChecks: checks.filter((c) => c.status === "new").length,
73
+ removedChecks: checks.filter((c) => c.status === "removed").length
74
+ };
75
+ return {
76
+ branch,
77
+ currentScore: current.overallScore,
78
+ baseScore: base.overallScore,
79
+ scoreDelta: current.overallScore - base.overallScore,
80
+ checks,
81
+ summary
82
+ };
83
+ }
84
+ //#endregion
85
+ //#region src/components/DiffApp.tsx
86
+ const STATUS_ICONS = {
87
+ improved: "↑",
88
+ regressed: "↓",
89
+ unchanged: "=",
90
+ new: "+",
91
+ removed: "−"
92
+ };
93
+ const STATUS_COLORS = {
94
+ improved: "green",
95
+ regressed: "red",
96
+ unchanged: "gray",
97
+ new: "cyan",
98
+ removed: "yellow"
99
+ };
100
+ function formatDelta(delta) {
101
+ if (delta > 0) return `+${delta}`;
102
+ if (delta < 0) return `${delta}`;
103
+ return "0";
104
+ }
105
+ function DiffTable({ diff }) {
106
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true }, "Overall: "), /* @__PURE__ */ React.createElement(Text, { bold: true }, diff.baseScore), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " → "), /* @__PURE__ */ React.createElement(Text, { bold: true }, diff.currentScore), /* @__PURE__ */ React.createElement(Text, null, " "), /* @__PURE__ */ React.createElement(Text, {
107
+ color: diff.scoreDelta > 0 ? "green" : diff.scoreDelta < 0 ? "red" : "gray",
108
+ bold: true
109
+ }, "(", formatDelta(diff.scoreDelta), ")")), /* @__PURE__ */ React.createElement(Box, {
110
+ flexDirection: "column",
111
+ marginTop: 1,
112
+ marginLeft: 2
113
+ }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { bold: true }, " Check".padEnd(30)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Current".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Base".padEnd(10)), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Delta")), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(56)), diff.checks.map((check) => /* @__PURE__ */ React.createElement(Box, { key: check.id }, /* @__PURE__ */ React.createElement(Text, { color: STATUS_COLORS[check.status] }, STATUS_ICONS[check.status], " "), /* @__PURE__ */ React.createElement(Text, null, check.name.padEnd(28)), /* @__PURE__ */ React.createElement(Text, null, String(check.currentScore || "—").padEnd(10)), /* @__PURE__ */ React.createElement(Text, null, String(check.baseScore || "—").padEnd(10)), /* @__PURE__ */ React.createElement(Text, {
114
+ color: STATUS_COLORS[check.status],
115
+ bold: true
116
+ }, check.status === "new" ? "new" : check.status === "removed" ? "removed" : formatDelta(check.delta))))), /* @__PURE__ */ React.createElement(Box, {
117
+ marginTop: 1,
118
+ marginLeft: 2
119
+ }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, diff.summary.improved > 0 && /* @__PURE__ */ React.createElement(Text, { color: "green" }, diff.summary.improved, " improved"), diff.summary.improved > 0 && diff.summary.regressed > 0 && /* @__PURE__ */ React.createElement(Text, null, ", "), diff.summary.regressed > 0 && /* @__PURE__ */ React.createElement(Text, { color: "red" }, diff.summary.regressed, " regressed"), (diff.summary.improved > 0 || diff.summary.regressed > 0) && diff.summary.unchanged > 0 && /* @__PURE__ */ React.createElement(Text, null, ", "), diff.summary.unchanged > 0 && /* @__PURE__ */ React.createElement(Text, null, diff.summary.unchanged, " unchanged"), diff.summary.newChecks > 0 && /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, ", ", diff.summary.newChecks, " new"), diff.summary.removedChecks > 0 && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, ", ", diff.summary.removedChecks, " removed"))));
120
+ }
121
+ function DiffApp({ projectPath, branch, jsonOutput, checks, verbose }) {
122
+ const { exit } = useApp();
123
+ const [phase, setPhase] = useState("scanning");
124
+ const [diff, setDiff] = useState(null);
125
+ const [error, setError] = useState(null);
126
+ useEffect(() => {
127
+ (async () => {
128
+ try {
129
+ const { runSickbay } = await import("./dist-Bjhxbcak.js").then((n) => n.s);
130
+ const currentReport = await runSickbay({
131
+ projectPath,
132
+ checks,
133
+ verbose
134
+ });
135
+ try {
136
+ const { saveEntry, saveLastReport } = await import("./history-DVegAwFp.js").then((n) => n.n);
137
+ saveEntry(currentReport);
138
+ saveLastReport(currentReport);
139
+ } catch {}
140
+ setPhase("loading-base");
141
+ const baseReport = loadBaseReport(projectPath, branch);
142
+ if (!baseReport) {
143
+ setError(`No saved report found on "${branch}". Run \`sickbay\` on that branch and commit .sickbay/last-report.json so it can be read via git.`);
144
+ setPhase("error");
145
+ setTimeout(() => exit(), 100);
146
+ return;
147
+ }
148
+ const result = compareReports(currentReport, baseReport, branch);
149
+ setDiff(result);
150
+ setPhase("results");
151
+ if (jsonOutput) process.stdout.write(JSON.stringify(result, null, 2) + "\n");
152
+ setTimeout(() => exit(), 100);
153
+ } catch (err) {
154
+ setError(err instanceof Error ? err.message : String(err));
155
+ setPhase("error");
156
+ setTimeout(() => exit(), 100);
157
+ }
158
+ })();
159
+ }, []);
160
+ if (phase === "scanning") return /* @__PURE__ */ React.createElement(Box, {
161
+ flexDirection: "column",
162
+ padding: 1
163
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Scanning current branch..."));
164
+ if (phase === "loading-base") return /* @__PURE__ */ React.createElement(Box, {
165
+ flexDirection: "column",
166
+ padding: 1
167
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Loading ", branch, " baseline..."));
168
+ if (phase === "error") return /* @__PURE__ */ React.createElement(Box, {
169
+ flexDirection: "column",
170
+ padding: 1
171
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { color: "red" }, "✗ ", error));
172
+ if (jsonOutput || !diff) return null;
173
+ return /* @__PURE__ */ React.createElement(Box, {
174
+ flexDirection: "column",
175
+ padding: 1
176
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Branch Diff: current vs ", branch), /* @__PURE__ */ React.createElement(DiffTable, { diff }));
177
+ }
178
+ //#endregion
179
+ export { DiffApp };
180
+
181
+ //# sourceMappingURL=DiffApp-DeNEtlY7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiffApp-DeNEtlY7.js","names":[],"sources":["../src/commands/diff.ts","../src/components/DiffApp.tsx"],"sourcesContent":["import { execFileSync } from 'child_process';\n\nimport type { SickbayReport } from 'sickbay-core';\n\nexport interface CheckDiff {\n id: string;\n name: string;\n category: string;\n currentScore: number;\n baseScore: number;\n delta: number;\n status: 'improved' | 'regressed' | 'unchanged' | 'new' | 'removed';\n}\n\nexport interface DiffResult {\n branch: string;\n currentScore: number;\n baseScore: number;\n scoreDelta: number;\n checks: CheckDiff[];\n summary: {\n improved: number;\n regressed: number;\n unchanged: number;\n newChecks: number;\n removedChecks: number;\n };\n}\n\nexport function loadBaseReport(projectPath: string, branch: string): SickbayReport | null {\n try {\n const output = execFileSync('git', ['show', `${branch}:.sickbay/last-report.json`], {\n cwd: projectPath,\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return JSON.parse(output) as SickbayReport;\n } catch {\n return null;\n }\n}\n\nconst STATUS_ORDER: Record<CheckDiff['status'], number> = {\n regressed: 0,\n improved: 1,\n new: 2,\n removed: 3,\n unchanged: 4,\n};\n\nexport function compareReports(\n current: SickbayReport,\n base: SickbayReport,\n branch: string,\n): DiffResult {\n const baseMap = new Map(base.checks.map((c) => [c.id, c]));\n const currentMap = new Map(current.checks.map((c) => [c.id, c]));\n\n const checks: CheckDiff[] = [];\n\n // Checks in current\n for (const check of current.checks) {\n const baseCheck = baseMap.get(check.id);\n if (!baseCheck) {\n checks.push({\n id: check.id,\n name: check.name,\n category: check.category,\n currentScore: check.score,\n baseScore: 0,\n delta: check.score,\n status: 'new',\n });\n } else {\n const delta = check.score - baseCheck.score;\n checks.push({\n id: check.id,\n name: check.name,\n category: check.category,\n currentScore: check.score,\n baseScore: baseCheck.score,\n delta,\n status: delta > 0 ? 'improved' : delta < 0 ? 'regressed' : 'unchanged',\n });\n }\n }\n\n // Checks only in base (removed)\n for (const check of base.checks) {\n if (!currentMap.has(check.id)) {\n checks.push({\n id: check.id,\n name: check.name,\n category: check.category,\n currentScore: 0,\n baseScore: check.score,\n delta: -check.score,\n status: 'removed',\n });\n }\n }\n\n // Sort: regressions first, then improvements, then new, removed, unchanged\n checks.sort((a, b) => STATUS_ORDER[a.status] - STATUS_ORDER[b.status]);\n\n const summary = {\n improved: checks.filter((c) => c.status === 'improved').length,\n regressed: checks.filter((c) => c.status === 'regressed').length,\n unchanged: checks.filter((c) => c.status === 'unchanged').length,\n newChecks: checks.filter((c) => c.status === 'new').length,\n removedChecks: checks.filter((c) => c.status === 'removed').length,\n };\n\n return {\n branch,\n currentScore: current.overallScore,\n baseScore: base.overallScore,\n scoreDelta: current.overallScore - base.overallScore,\n checks,\n summary,\n };\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text, useApp } from 'ink';\nimport Spinner from 'ink-spinner';\n\nimport {\n loadBaseReport,\n compareReports,\n type DiffResult,\n type CheckDiff,\n} from '../commands/diff.js';\nimport { Header } from './Header.js';\n\ninterface DiffAppProps {\n projectPath: string;\n branch: string;\n jsonOutput: boolean;\n checks?: string[];\n verbose?: boolean;\n}\n\nconst STATUS_ICONS: Record<CheckDiff['status'], string> = {\n improved: '↑',\n regressed: '↓',\n unchanged: '=',\n new: '+',\n removed: '−',\n};\n\nconst STATUS_COLORS: Record<CheckDiff['status'], string> = {\n improved: 'green',\n regressed: 'red',\n unchanged: 'gray',\n new: 'cyan',\n removed: 'yellow',\n};\n\nfunction formatDelta(delta: number): string {\n if (delta > 0) return `+${delta}`;\n if (delta < 0) return `${delta}`;\n return '0';\n}\n\nfunction DiffTable({ diff }: { diff: DiffResult }) {\n return (\n <Box flexDirection=\"column\">\n <Box marginTop={1}>\n <Text bold>Overall: </Text>\n <Text bold>{diff.baseScore}</Text>\n <Text dimColor> → </Text>\n <Text bold>{diff.currentScore}</Text>\n <Text> </Text>\n <Text color={diff.scoreDelta > 0 ? 'green' : diff.scoreDelta < 0 ? 'red' : 'gray'} bold>\n ({formatDelta(diff.scoreDelta)})\n </Text>\n </Box>\n\n <Box flexDirection=\"column\" marginTop={1} marginLeft={2}>\n <Box>\n <Text bold>{' Check'.padEnd(30)}</Text>\n <Text bold>{'Current'.padEnd(10)}</Text>\n <Text bold>{'Base'.padEnd(10)}</Text>\n <Text bold>Delta</Text>\n </Box>\n <Text dimColor>{'━'.repeat(56)}</Text>\n {diff.checks.map((check) => (\n <Box key={check.id}>\n <Text color={STATUS_COLORS[check.status]}>{STATUS_ICONS[check.status]} </Text>\n <Text>{check.name.padEnd(28)}</Text>\n <Text>{String(check.currentScore || '—').padEnd(10)}</Text>\n <Text>{String(check.baseScore || '—').padEnd(10)}</Text>\n <Text color={STATUS_COLORS[check.status]} bold>\n {check.status === 'new'\n ? 'new'\n : check.status === 'removed'\n ? 'removed'\n : formatDelta(check.delta)}\n </Text>\n </Box>\n ))}\n </Box>\n\n <Box marginTop={1} marginLeft={2}>\n <Text dimColor>\n {diff.summary.improved > 0 && <Text color=\"green\">{diff.summary.improved} improved</Text>}\n {diff.summary.improved > 0 && diff.summary.regressed > 0 && <Text>, </Text>}\n {diff.summary.regressed > 0 && (\n <Text color=\"red\">{diff.summary.regressed} regressed</Text>\n )}\n {(diff.summary.improved > 0 || diff.summary.regressed > 0) &&\n diff.summary.unchanged > 0 && <Text>, </Text>}\n {diff.summary.unchanged > 0 && <Text>{diff.summary.unchanged} unchanged</Text>}\n {diff.summary.newChecks > 0 && <Text color=\"cyan\">, {diff.summary.newChecks} new</Text>}\n {diff.summary.removedChecks > 0 && (\n <Text color=\"yellow\">, {diff.summary.removedChecks} removed</Text>\n )}\n </Text>\n </Box>\n </Box>\n );\n}\n\nexport function DiffApp({ projectPath, branch, jsonOutput, checks, verbose }: DiffAppProps) {\n const { exit } = useApp();\n const [phase, setPhase] = useState<'scanning' | 'loading-base' | 'results' | 'error'>('scanning');\n const [diff, setDiff] = useState<DiffResult | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n (async () => {\n try {\n // Run fresh scan on current branch\n const { runSickbay } = await import('sickbay-core');\n const currentReport = await runSickbay({\n projectPath,\n checks,\n verbose,\n });\n\n // Save current report\n try {\n const { saveEntry, saveLastReport } = await import('../lib/history.js');\n saveEntry(currentReport);\n saveLastReport(currentReport);\n } catch {\n // Non-critical\n }\n\n setPhase('loading-base');\n\n // Load base report from target branch\n const baseReport = loadBaseReport(projectPath, branch);\n if (!baseReport) {\n setError(\n `No saved report found on \"${branch}\". Run \\`sickbay\\` on that branch and commit .sickbay/last-report.json so it can be read via git.`,\n );\n setPhase('error');\n setTimeout(() => exit(), 100);\n return;\n }\n\n const result = compareReports(currentReport, baseReport, branch);\n setDiff(result);\n setPhase('results');\n\n if (jsonOutput) {\n process.stdout.write(JSON.stringify(result, null, 2) + '\\n');\n }\n\n setTimeout(() => exit(), 100);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setPhase('error');\n setTimeout(() => exit(), 100);\n }\n })();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n if (phase === 'scanning') {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text>\n <Text color=\"green\">\n <Spinner type=\"dots\" />\n </Text>{' '}\n Scanning current branch...\n </Text>\n </Box>\n );\n }\n\n if (phase === 'loading-base') {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text>\n <Text color=\"green\">\n <Spinner type=\"dots\" />\n </Text>{' '}\n Loading {branch} baseline...\n </Text>\n </Box>\n );\n }\n\n if (phase === 'error') {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text color=\"red\">✗ {error}</Text>\n </Box>\n );\n }\n\n if (jsonOutput || !diff) return null;\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Header />\n <Text bold>Branch Diff: current vs {branch}</Text>\n <DiffTable diff={diff} />\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,415 @@
1
+ import { a as detectPackageManager, o as detectProject } from "./dist-Bjhxbcak.js";
2
+ import { r as Header } from "./index.js";
3
+ import { shortName } from "./resolve-package-DS27JBhN.js";
4
+ import { existsSync, readFileSync } from "fs";
5
+ import { join } from "path";
6
+ import React, { useEffect, useState } from "react";
7
+ import { Box, Text, useApp } from "ink";
8
+ import Spinner from "ink-spinner";
9
+ //#region src/commands/doctor.ts
10
+ async function checkGitignore(projectPath) {
11
+ const gitignorePath = join(projectPath, ".gitignore");
12
+ if (!existsSync(gitignorePath)) return {
13
+ id: "gitignore",
14
+ label: ".gitignore exists",
15
+ status: "fail",
16
+ message: "No .gitignore found",
17
+ fixDescription: "Create .gitignore with standard entries"
18
+ };
19
+ const lines = readFileSync(gitignorePath, "utf-8").split("\n").map((l) => l.trim());
20
+ const missing = [
21
+ "node_modules",
22
+ ".env",
23
+ "dist",
24
+ ".DS_Store"
25
+ ].filter((entry) => !lines.includes(entry));
26
+ if (missing.length > 0) return {
27
+ id: "gitignore",
28
+ label: ".gitignore entries",
29
+ status: "warn",
30
+ message: `Missing entries: ${missing.join(", ")}`,
31
+ fixDescription: `Add ${missing.join(", ")} to .gitignore`
32
+ };
33
+ return {
34
+ id: "gitignore",
35
+ label: ".gitignore entries",
36
+ status: "pass",
37
+ message: "All standard entries present"
38
+ };
39
+ }
40
+ async function checkEnginesField(projectPath) {
41
+ const pkg = JSON.parse(readFileSync(join(projectPath, "package.json"), "utf-8"));
42
+ if (!pkg.engines) return {
43
+ id: "engines",
44
+ label: "package.json engines field",
45
+ status: "warn",
46
+ message: "No engines field — Node.js version not specified",
47
+ fixDescription: "Add \"engines\": { \"node\": \">=18.0.0\" } to package.json"
48
+ };
49
+ return {
50
+ id: "engines",
51
+ label: "package.json engines field",
52
+ status: "pass",
53
+ message: `node ${pkg.engines.node ?? "unspecified"}`
54
+ };
55
+ }
56
+ async function checkBrowserslist(projectPath) {
57
+ const pkg = JSON.parse(readFileSync(join(projectPath, "package.json"), "utf-8"));
58
+ const hasFile = existsSync(join(projectPath, ".browserslistrc")) || existsSync(join(projectPath, "browserslist"));
59
+ const hasField = !!pkg.browserslist;
60
+ if (!hasFile && !hasField) return {
61
+ id: "browserslist",
62
+ label: "Browserslist config",
63
+ status: "warn",
64
+ message: "No browserslist config — browser targets not defined",
65
+ fixDescription: "Create .browserslistrc with default targets"
66
+ };
67
+ return {
68
+ id: "browserslist",
69
+ label: "Browserslist config",
70
+ status: "pass",
71
+ message: "Browserslist configured"
72
+ };
73
+ }
74
+ async function checkNodeVersion(projectPath) {
75
+ const hasNvmrc = existsSync(join(projectPath, ".nvmrc"));
76
+ const hasNodeVersion = existsSync(join(projectPath, ".node-version"));
77
+ const hasToolVersions = existsSync(join(projectPath, ".tool-versions"));
78
+ if (!hasNvmrc && !hasNodeVersion && !hasToolVersions) return {
79
+ id: "node-version",
80
+ label: "Node version pinning",
81
+ status: "warn",
82
+ message: "No .nvmrc or .node-version — Node version not pinned",
83
+ fixDescription: "Create .nvmrc with current Node version"
84
+ };
85
+ return {
86
+ id: "node-version",
87
+ label: "Node version pinning",
88
+ status: "pass",
89
+ message: hasNvmrc ? ".nvmrc found" : hasNodeVersion ? ".node-version found" : ".tool-versions found"
90
+ };
91
+ }
92
+ async function checkNpmScripts(projectPath) {
93
+ const scripts = JSON.parse(readFileSync(join(projectPath, "package.json"), "utf-8")).scripts ?? {};
94
+ const missing = [
95
+ "lint",
96
+ "test",
97
+ "build"
98
+ ].filter((s) => !scripts[s]);
99
+ if (missing.length > 0) return {
100
+ id: "npm-scripts",
101
+ label: "Essential npm scripts",
102
+ status: missing.includes("build") || missing.includes("test") ? "fail" : "warn",
103
+ message: `Missing scripts: ${missing.join(", ")}`,
104
+ fixDescription: `Add ${missing.join(", ")} scripts to package.json`
105
+ };
106
+ return {
107
+ id: "npm-scripts",
108
+ label: "Essential npm scripts",
109
+ status: "pass",
110
+ message: "lint, test, build all defined"
111
+ };
112
+ }
113
+ async function checkLockfile(projectPath) {
114
+ const hasNpmLock = existsSync(join(projectPath, "package-lock.json"));
115
+ const hasPnpmLock = existsSync(join(projectPath, "pnpm-lock.yaml"));
116
+ const hasYarnLock = existsSync(join(projectPath, "yarn.lock"));
117
+ const hasBunLock = existsSync(join(projectPath, "bun.lockb")) || existsSync(join(projectPath, "bun.lock"));
118
+ if (!hasNpmLock && !hasPnpmLock && !hasYarnLock && !hasBunLock) return {
119
+ id: "lockfile",
120
+ label: "Lockfile present",
121
+ status: "fail",
122
+ message: "No lockfile found — dependencies not pinned",
123
+ fixCommand: "npm install",
124
+ fixDescription: "Generate lockfile with npm install"
125
+ };
126
+ return {
127
+ id: "lockfile",
128
+ label: "Lockfile present",
129
+ status: "pass",
130
+ message: {
131
+ pnpm: "pnpm-lock.yaml",
132
+ yarn: "yarn.lock",
133
+ bun: "bun.lockb",
134
+ npm: "package-lock.json"
135
+ }[detectPackageManager(projectPath)]
136
+ };
137
+ }
138
+ async function checkEditorconfig(projectPath) {
139
+ if (!existsSync(join(projectPath, ".editorconfig"))) return {
140
+ id: "editorconfig",
141
+ label: ".editorconfig",
142
+ status: "warn",
143
+ message: "No .editorconfig — editor settings not standardized",
144
+ fixDescription: "Create .editorconfig with standard settings"
145
+ };
146
+ return {
147
+ id: "editorconfig",
148
+ label: ".editorconfig",
149
+ status: "pass",
150
+ message: ".editorconfig found"
151
+ };
152
+ }
153
+ async function checkPrettier(projectPath) {
154
+ if (!(await detectProject(projectPath)).hasPrettier) return {
155
+ id: "prettier",
156
+ label: "Prettier config",
157
+ status: "warn",
158
+ message: "No Prettier configured — code formatting not standardized",
159
+ fixCommand: "npm install -D prettier",
160
+ fixDescription: "Install prettier and create .prettierrc"
161
+ };
162
+ return {
163
+ id: "prettier",
164
+ label: "Prettier config",
165
+ status: "pass",
166
+ message: "Prettier configured"
167
+ };
168
+ }
169
+ async function checkESLintConfig(projectPath) {
170
+ if (!(await detectProject(projectPath)).hasESLint) return {
171
+ id: "eslint-config",
172
+ label: "ESLint config",
173
+ status: "fail",
174
+ message: "No ESLint configured — no lint rules enforced",
175
+ fixCommand: "npm init @eslint/config",
176
+ fixDescription: "Initialize ESLint configuration"
177
+ };
178
+ return {
179
+ id: "eslint-config",
180
+ label: "ESLint config",
181
+ status: "pass",
182
+ message: "ESLint configured"
183
+ };
184
+ }
185
+ async function checkTsconfig(projectPath) {
186
+ const info = await detectProject(projectPath);
187
+ const hasTsconfig = existsSync(join(projectPath, "tsconfig.json"));
188
+ if (info.hasTypeScript && !hasTsconfig) return {
189
+ id: "tsconfig",
190
+ label: "TypeScript config",
191
+ status: "fail",
192
+ message: "TypeScript deps found but no tsconfig.json",
193
+ fixCommand: "npx tsc --init",
194
+ fixDescription: "Generate tsconfig.json"
195
+ };
196
+ if (!info.hasTypeScript) return {
197
+ id: "tsconfig",
198
+ label: "TypeScript",
199
+ status: "warn",
200
+ message: "TypeScript not in use (consider adopting)"
201
+ };
202
+ return {
203
+ id: "tsconfig",
204
+ label: "TypeScript config",
205
+ status: "pass",
206
+ message: "tsconfig.json found"
207
+ };
208
+ }
209
+ async function checkEnvExample(projectPath) {
210
+ const hasEnv = existsSync(join(projectPath, ".env"));
211
+ const hasExample = existsSync(join(projectPath, ".env.example")) || existsSync(join(projectPath, ".env.template")) || existsSync(join(projectPath, ".env.sample"));
212
+ if (hasEnv && !hasExample) return {
213
+ id: "env-example",
214
+ label: ".env.example",
215
+ status: "warn",
216
+ message: ".env exists but no .env.example — team members won't know required vars",
217
+ fixDescription: "Create .env.example with variable names (no values)"
218
+ };
219
+ if (!hasEnv) return {
220
+ id: "env-example",
221
+ label: ".env.example",
222
+ status: "pass",
223
+ message: "No .env file (not applicable)"
224
+ };
225
+ return {
226
+ id: "env-example",
227
+ label: ".env.example",
228
+ status: "pass",
229
+ message: ".env.example exists"
230
+ };
231
+ }
232
+ async function checkReactVersions(projectPath) {
233
+ const pkg = JSON.parse(readFileSync(join(projectPath, "package.json"), "utf-8"));
234
+ const deps = {
235
+ ...pkg.dependencies ?? {},
236
+ ...pkg.devDependencies ?? {}
237
+ };
238
+ const react = deps["react"];
239
+ const reactDom = deps["react-dom"];
240
+ if (!react) return {
241
+ id: "react-versions",
242
+ label: "React version consistency",
243
+ status: "pass",
244
+ message: "Not a React project"
245
+ };
246
+ if (!reactDom) return {
247
+ id: "react-versions",
248
+ label: "React version consistency",
249
+ status: "warn",
250
+ message: "react installed but react-dom missing",
251
+ fixCommand: `npm install react-dom@${react}`,
252
+ fixDescription: "Install react-dom matching react version"
253
+ };
254
+ if (react !== reactDom) return {
255
+ id: "react-versions",
256
+ label: "React version consistency",
257
+ status: "fail",
258
+ message: `react@${react} and react-dom@${reactDom} version mismatch`,
259
+ fixCommand: `npm install react-dom@${react}`,
260
+ fixDescription: "Align react-dom version with react"
261
+ };
262
+ return {
263
+ id: "react-versions",
264
+ label: "React version consistency",
265
+ status: "pass",
266
+ message: `react and react-dom both at ${react}`
267
+ };
268
+ }
269
+ const ALL_DIAGNOSTICS = [
270
+ checkGitignore,
271
+ checkEnginesField,
272
+ checkBrowserslist,
273
+ checkNodeVersion,
274
+ checkNpmScripts,
275
+ checkLockfile,
276
+ checkEditorconfig,
277
+ checkPrettier,
278
+ checkESLintConfig,
279
+ checkTsconfig,
280
+ checkEnvExample,
281
+ checkReactVersions
282
+ ];
283
+ async function runDiagnostics(projectPath) {
284
+ const results = [];
285
+ for (const check of ALL_DIAGNOSTICS) try {
286
+ results.push(await check(projectPath));
287
+ } catch (err) {
288
+ results.push({
289
+ id: "unknown",
290
+ label: "Unknown check",
291
+ status: "fail",
292
+ message: `Check failed: ${err instanceof Error ? err.message : String(err)}`
293
+ });
294
+ }
295
+ return results;
296
+ }
297
+ //#endregion
298
+ //#region src/components/DoctorApp.tsx
299
+ const STATUS_CONFIG = {
300
+ pass: {
301
+ icon: "✓",
302
+ color: "green"
303
+ },
304
+ fail: {
305
+ icon: "✗",
306
+ color: "red"
307
+ },
308
+ warn: {
309
+ icon: "⚠",
310
+ color: "yellow"
311
+ }
312
+ };
313
+ function DiagnosticsList({ results }) {
314
+ return /* @__PURE__ */ React.createElement(Box, {
315
+ flexDirection: "column",
316
+ marginLeft: 2
317
+ }, results.map((r) => {
318
+ const { icon, color } = STATUS_CONFIG[r.status];
319
+ return /* @__PURE__ */ React.createElement(Box, {
320
+ key: r.id,
321
+ flexDirection: "column"
322
+ }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color }, icon, " "), /* @__PURE__ */ React.createElement(Text, { bold: r.status !== "pass" }, r.label), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " — ", r.message)), r.status !== "pass" && r.fixCommand && /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "fix: "), /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, r.fixCommand)), r.status !== "pass" && !r.fixCommand && r.fixDescription && /* @__PURE__ */ React.createElement(Box, { marginLeft: 4 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "→ ", r.fixDescription)));
323
+ }));
324
+ }
325
+ function DiagnosticsSummary({ results }) {
326
+ const passed = results.filter((r) => r.status === "pass").length;
327
+ const warned = results.filter((r) => r.status === "warn").length;
328
+ const failed = results.filter((r) => r.status === "fail").length;
329
+ return /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, " ✓ ", passed, " passed"), /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, " ⚠ ", warned, " warnings"), /* @__PURE__ */ React.createElement(Text, { color: "red" }, " ✗ ", failed, " failed"), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " (", results.length, " checks)"));
330
+ }
331
+ function DoctorApp({ projectPath, jsonOutput, isMonorepo, packagePaths, packageNames }) {
332
+ const { exit } = useApp();
333
+ const [results, setResults] = useState([]);
334
+ const [packageResults, setPackageResults] = useState([]);
335
+ const [running, setRunning] = useState(true);
336
+ useEffect(() => {
337
+ if (isMonorepo && packagePaths && packageNames) Promise.all(packagePaths.map(async (pkgPath) => {
338
+ return {
339
+ name: packageNames.get(pkgPath) ?? pkgPath,
340
+ path: pkgPath,
341
+ results: await runDiagnostics(pkgPath)
342
+ };
343
+ })).then((all) => {
344
+ setPackageResults(all);
345
+ setRunning(false);
346
+ if (jsonOutput) {
347
+ const output = all.map((p) => ({
348
+ package: p.name,
349
+ path: p.path,
350
+ results: p.results
351
+ }));
352
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
353
+ }
354
+ setTimeout(() => exit(), 100);
355
+ }).catch((err) => {
356
+ setPackageResults([{
357
+ name: "error",
358
+ path: projectPath,
359
+ results: [{
360
+ id: "error",
361
+ label: "Doctor",
362
+ status: "fail",
363
+ message: `Failed: ${err instanceof Error ? err.message : String(err)}`
364
+ }]
365
+ }]);
366
+ setRunning(false);
367
+ setTimeout(() => exit(), 100);
368
+ });
369
+ else runDiagnostics(projectPath).then((r) => {
370
+ setResults(r);
371
+ setRunning(false);
372
+ if (jsonOutput) process.stdout.write(JSON.stringify(r, null, 2) + "\n");
373
+ setTimeout(() => exit(), 100);
374
+ }).catch((err) => {
375
+ setResults([{
376
+ id: "error",
377
+ label: "Doctor",
378
+ status: "fail",
379
+ message: `Failed: ${err instanceof Error ? err.message : String(err)}`
380
+ }]);
381
+ setRunning(false);
382
+ setTimeout(() => exit(), 100);
383
+ });
384
+ }, []);
385
+ if (running) return /* @__PURE__ */ React.createElement(Box, {
386
+ flexDirection: "column",
387
+ padding: 1
388
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, null, /* @__PURE__ */ React.createElement(Text, { color: "green" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" })), " ", "Running project diagnostics", isMonorepo ? ` across ${packagePaths?.length} packages` : "", "..."));
389
+ if (jsonOutput) return null;
390
+ if (isMonorepo && packageResults.length > 0) {
391
+ const allResults = packageResults.flatMap((p) => p.results);
392
+ return /* @__PURE__ */ React.createElement(Box, {
393
+ flexDirection: "column",
394
+ padding: 1
395
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Monorepo Setup Diagnosis"), packageResults.map((pkg) => /* @__PURE__ */ React.createElement(Box, {
396
+ key: pkg.path,
397
+ flexDirection: "column",
398
+ marginTop: 1
399
+ }, /* @__PURE__ */ React.createElement(Text, {
400
+ bold: true,
401
+ color: "cyan"
402
+ }, shortName(pkg.name)), /* @__PURE__ */ React.createElement(DiagnosticsList, { results: pkg.results }))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(52))), /* @__PURE__ */ React.createElement(DiagnosticsSummary, { results: allResults }));
403
+ }
404
+ return /* @__PURE__ */ React.createElement(Box, {
405
+ flexDirection: "column",
406
+ padding: 1
407
+ }, /* @__PURE__ */ React.createElement(Header, null), /* @__PURE__ */ React.createElement(Text, { bold: true }, "Project Setup Diagnosis"), /* @__PURE__ */ React.createElement(Box, {
408
+ flexDirection: "column",
409
+ marginTop: 1
410
+ }, /* @__PURE__ */ React.createElement(DiagnosticsList, { results })), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "━".repeat(52))), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(DiagnosticsSummary, { results })));
411
+ }
412
+ //#endregion
413
+ export { DoctorApp };
414
+
415
+ //# sourceMappingURL=DoctorApp-CaiqnhHH.js.map