@buoy-design/cli 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/dist/commands/check.d.ts.map +1 -1
  3. package/dist/commands/check.js +5 -0
  4. package/dist/commands/check.js.map +1 -1
  5. package/dist/commands/context.js +1 -1
  6. package/dist/commands/context.js.map +1 -1
  7. package/dist/commands/dock.d.ts +4 -2
  8. package/dist/commands/dock.d.ts.map +1 -1
  9. package/dist/commands/dock.js +19 -3
  10. package/dist/commands/dock.js.map +1 -1
  11. package/dist/commands/fix.js +2 -2
  12. package/dist/commands/fix.js.map +1 -1
  13. package/dist/commands/index.d.ts +0 -2
  14. package/dist/commands/index.d.ts.map +1 -1
  15. package/dist/commands/index.js +0 -2
  16. package/dist/commands/index.js.map +1 -1
  17. package/dist/commands/scan.d.ts.map +1 -1
  18. package/dist/commands/scan.js +19 -6
  19. package/dist/commands/scan.js.map +1 -1
  20. package/dist/commands/show.d.ts.map +1 -1
  21. package/dist/commands/show.js +576 -4
  22. package/dist/commands/show.js.map +1 -1
  23. package/dist/commands/skill.js +2 -2
  24. package/dist/commands/skill.js.map +1 -1
  25. package/dist/commands/tokens-lookup.d.ts +4 -4
  26. package/dist/commands/tokens-lookup.js +7 -7
  27. package/dist/commands/tokens-lookup.js.map +1 -1
  28. package/dist/commands/tokens.js +2 -2
  29. package/dist/commands/tokens.js.map +1 -1
  30. package/dist/config/auto-detect.d.ts.map +1 -1
  31. package/dist/config/auto-detect.js +2 -0
  32. package/dist/config/auto-detect.js.map +1 -1
  33. package/dist/detect/project-detector.d.ts.map +1 -1
  34. package/dist/detect/project-detector.js +6 -0
  35. package/dist/detect/project-detector.js.map +1 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +5 -10
  38. package/dist/index.js.map +1 -1
  39. package/dist/insights/project-insights.js +1 -1
  40. package/dist/insights/project-insights.js.map +1 -1
  41. package/dist/integrations/github-formatter.js +1 -1
  42. package/dist/integrations/github-formatter.js.map +1 -1
  43. package/dist/output/formatters.d.ts +1 -0
  44. package/dist/output/formatters.d.ts.map +1 -1
  45. package/dist/output/formatters.js +61 -0
  46. package/dist/output/formatters.js.map +1 -1
  47. package/dist/scan/orchestrator.d.ts.map +1 -1
  48. package/dist/scan/orchestrator.js +20 -0
  49. package/dist/scan/orchestrator.js.map +1 -1
  50. package/package.json +14 -14
@@ -1,7 +1,9 @@
1
- import { Command } from "commander";
1
+ import { Command, Option } from "commander";
2
2
  import chalk from "chalk";
3
- import { existsSync } from "fs";
3
+ import { existsSync, readFileSync, readdirSync } from "fs";
4
4
  import { writeFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
5
7
  import { loadConfig, getConfigPath } from "../config/loader.js";
6
8
  import { buildAutoConfig } from "../config/auto-detect.js";
7
9
  import { spinner, error, info, success, header, keyValue, newline, setJsonMode, } from "../output/reporters.js";
@@ -15,6 +17,8 @@ import { extractStyles, extractCssFileStyles } from "@buoy-design/scanners";
15
17
  import { parseCssValues } from "@buoy-design/core";
16
18
  import { glob } from "glob";
17
19
  import { readFile } from "fs/promises";
20
+ import { detectHookSystem } from "../hooks/index.js";
21
+ import { detectFrameworks, BUILTIN_SCANNERS, PLUGIN_INFO, } from "../detect/frameworks.js";
18
22
  export function createShowCommand() {
19
23
  const cmd = new Command("show")
20
24
  .description("Show design system information")
@@ -197,7 +201,7 @@ export function createShowCommand() {
197
201
  .option("--json", "Output as JSON")
198
202
  .option("--raw", "Output raw signals without grouping")
199
203
  .option("-f, --format <type>", "Output format: json, markdown, html, table, tree, agent")
200
- .option("-S, --severity <level>", "Filter by minimum severity (info, warning, critical)")
204
+ .addOption(new Option("-S, --severity <level>", "Filter by minimum severity").choices(["info", "warning", "critical"]))
201
205
  .option("-t, --type <type>", "Filter by drift type")
202
206
  .option("-v, --verbose", "Verbose output with full details")
203
207
  .option("--include-baseline", "Include baselined drifts (show all)")
@@ -351,7 +355,7 @@ export function createShowCommand() {
351
355
  if (!hasTokens && !hasFigma && !hasStorybook && !hasDesignTokensFile) {
352
356
  newline();
353
357
  info("No reference source configured.");
354
- info("Run " + chalk.cyan("buoy build tokens") + " to extract design tokens.");
358
+ info("Run " + chalk.cyan("buoy dock tokens") + " to extract design tokens.");
355
359
  }
356
360
  }
357
361
  }
@@ -739,6 +743,492 @@ export function createShowCommand() {
739
743
  process.exit(1);
740
744
  }
741
745
  });
746
+ // show config
747
+ cmd
748
+ .command("config")
749
+ .description("Show current .buoy.yaml configuration")
750
+ .option("--json", "Output as JSON")
751
+ .action(async (options, command) => {
752
+ const parentOpts = command.parent?.opts() || {};
753
+ const json = options.json || parentOpts.json !== false;
754
+ if (json)
755
+ setJsonMode(true);
756
+ const configPath = getConfigPath();
757
+ if (!configPath) {
758
+ if (json) {
759
+ console.log(JSON.stringify({ exists: false, path: null }, null, 2));
760
+ }
761
+ else {
762
+ info("No config found. Run " + chalk.cyan("buoy dock config") + " to create .buoy.yaml.");
763
+ }
764
+ return;
765
+ }
766
+ try {
767
+ const { config } = await loadConfig();
768
+ if (json) {
769
+ console.log(JSON.stringify({ exists: true, path: configPath, config }, null, 2));
770
+ }
771
+ else {
772
+ header("Configuration");
773
+ newline();
774
+ keyValue("Path", configPath);
775
+ if (config.project?.name)
776
+ keyValue("Project", config.project.name);
777
+ newline();
778
+ // Show enabled sources
779
+ if (config.sources) {
780
+ header("Sources");
781
+ for (const [key, source] of Object.entries(config.sources)) {
782
+ if (source && typeof source === "object" && "enabled" in source) {
783
+ const enabled = source.enabled;
784
+ const status = enabled ? chalk.green("enabled") : chalk.dim("disabled");
785
+ keyValue(` ${key}`, status);
786
+ }
787
+ }
788
+ }
789
+ newline();
790
+ }
791
+ }
792
+ catch (err) {
793
+ error(err instanceof Error ? err.message : String(err));
794
+ process.exit(1);
795
+ }
796
+ });
797
+ // show skills
798
+ cmd
799
+ .command("skills")
800
+ .description("Show AI agent skill files")
801
+ .option("--json", "Output as JSON")
802
+ .action(async (options, command) => {
803
+ const parentOpts = command.parent?.opts() || {};
804
+ const json = options.json || parentOpts.json !== false;
805
+ if (json)
806
+ setJsonMode(true);
807
+ const cwd = process.cwd();
808
+ const skillsDir = join(cwd, ".claude", "skills", "design-system");
809
+ if (!existsSync(skillsDir)) {
810
+ if (json) {
811
+ console.log(JSON.stringify({ exists: false, path: skillsDir, files: [] }, null, 2));
812
+ }
813
+ else {
814
+ info("No skills found. Run " + chalk.cyan("buoy dock skills") + " to create AI agent skills.");
815
+ }
816
+ return;
817
+ }
818
+ try {
819
+ const files = walkDir(skillsDir);
820
+ if (json) {
821
+ console.log(JSON.stringify({
822
+ exists: true,
823
+ path: skillsDir,
824
+ fileCount: files.length,
825
+ files: files.map(f => f.replace(cwd + "/", "")),
826
+ }, null, 2));
827
+ }
828
+ else {
829
+ header("Design System Skills");
830
+ newline();
831
+ keyValue("Path", skillsDir.replace(cwd + "/", ""));
832
+ keyValue("Files", String(files.length));
833
+ newline();
834
+ for (const file of files) {
835
+ console.log(` ${chalk.green("•")} ${file.replace(cwd + "/", "")}`);
836
+ }
837
+ newline();
838
+ }
839
+ }
840
+ catch (err) {
841
+ error(err instanceof Error ? err.message : String(err));
842
+ process.exit(1);
843
+ }
844
+ });
845
+ // show agents
846
+ cmd
847
+ .command("agents")
848
+ .description("Show configured AI agents and commands")
849
+ .option("--json", "Output as JSON")
850
+ .action(async (options, command) => {
851
+ const parentOpts = command.parent?.opts() || {};
852
+ const json = options.json || parentOpts.json !== false;
853
+ if (json)
854
+ setJsonMode(true);
855
+ const cwd = process.cwd();
856
+ const agentsDir = join(cwd, ".claude", "agents");
857
+ const commandsDir = join(cwd, ".claude", "commands");
858
+ const hasAgents = existsSync(agentsDir) && readdirSync(agentsDir).some(f => f.endsWith(".md"));
859
+ const hasCommands = existsSync(commandsDir) && readdirSync(commandsDir).some(f => f.endsWith(".md"));
860
+ if (!hasAgents && !hasCommands) {
861
+ if (json) {
862
+ console.log(JSON.stringify({ exists: false, agents: [], commands: [] }, null, 2));
863
+ }
864
+ else {
865
+ info("No agents configured. Run " + chalk.cyan("buoy dock agents") + " to set up AI agents.");
866
+ }
867
+ return;
868
+ }
869
+ const agents = hasAgents
870
+ ? readdirSync(agentsDir).filter(f => f.endsWith(".md")).map(f => ({
871
+ name: f.replace(".md", ""),
872
+ path: join(".claude", "agents", f),
873
+ }))
874
+ : [];
875
+ const commands = hasCommands
876
+ ? readdirSync(commandsDir).filter(f => f.endsWith(".md")).map(f => ({
877
+ name: f.replace(".md", ""),
878
+ path: join(".claude", "commands", f),
879
+ }))
880
+ : [];
881
+ if (json) {
882
+ console.log(JSON.stringify({ exists: true, agents, commands }, null, 2));
883
+ }
884
+ else {
885
+ if (agents.length > 0) {
886
+ header("Agents");
887
+ newline();
888
+ for (const agent of agents) {
889
+ console.log(` ${chalk.green("•")} ${agent.name} ${chalk.dim(agent.path)}`);
890
+ }
891
+ newline();
892
+ }
893
+ if (commands.length > 0) {
894
+ header("Commands");
895
+ newline();
896
+ for (const cmd of commands) {
897
+ console.log(` ${chalk.green("•")} /${cmd.name} ${chalk.dim(cmd.path)}`);
898
+ }
899
+ newline();
900
+ }
901
+ }
902
+ });
903
+ // show context
904
+ cmd
905
+ .command("context")
906
+ .description("Show design system context in CLAUDE.md")
907
+ .option("--json", "Output as JSON")
908
+ .action(async (options, command) => {
909
+ const parentOpts = command.parent?.opts() || {};
910
+ const json = options.json || parentOpts.json !== false;
911
+ if (json)
912
+ setJsonMode(true);
913
+ const cwd = process.cwd();
914
+ const claudeMdPath = join(cwd, "CLAUDE.md");
915
+ if (!existsSync(claudeMdPath)) {
916
+ if (json) {
917
+ console.log(JSON.stringify({ exists: false, path: claudeMdPath }, null, 2));
918
+ }
919
+ else {
920
+ info("No design system context in CLAUDE.md. Run " + chalk.cyan("buoy dock context") + " to generate it.");
921
+ }
922
+ return;
923
+ }
924
+ const content = readFileSync(claudeMdPath, "utf-8");
925
+ const sectionMatch = content.match(/^##?\s*Design\s*System\b.*$/m);
926
+ if (!sectionMatch) {
927
+ if (json) {
928
+ console.log(JSON.stringify({ exists: false, path: claudeMdPath, hasSection: false }, null, 2));
929
+ }
930
+ else {
931
+ info("No design system context in CLAUDE.md. Run " + chalk.cyan("buoy dock context") + " to generate it.");
932
+ }
933
+ return;
934
+ }
935
+ // Extract section content between ## Design System and next ## header
936
+ const sectionStart = content.indexOf(sectionMatch[0]);
937
+ const afterHeader = content.slice(sectionStart + sectionMatch[0].length);
938
+ const nextHeaderMatch = afterHeader.match(/\n##?\s+[^\n]/);
939
+ const sectionContent = nextHeaderMatch
940
+ ? afterHeader.slice(0, nextHeaderMatch.index)
941
+ : afterHeader;
942
+ const sectionLines = sectionContent.trim().split("\n").length;
943
+ const sectionWords = sectionContent.trim().split(/\s+/).length;
944
+ if (json) {
945
+ console.log(JSON.stringify({
946
+ exists: true,
947
+ path: claudeMdPath,
948
+ hasSection: true,
949
+ sectionLines,
950
+ sectionWords,
951
+ }, null, 2));
952
+ }
953
+ else {
954
+ header("Design System Context");
955
+ newline();
956
+ keyValue("Path", "CLAUDE.md");
957
+ keyValue("Lines", String(sectionLines));
958
+ keyValue("Words", String(sectionWords));
959
+ newline();
960
+ // Show preview (first 5 lines)
961
+ const preview = sectionContent.trim().split("\n").slice(0, 5);
962
+ for (const line of preview) {
963
+ console.log(chalk.dim(` ${line}`));
964
+ }
965
+ if (sectionLines > 5) {
966
+ console.log(chalk.dim(` ... ${sectionLines - 5} more lines`));
967
+ }
968
+ newline();
969
+ }
970
+ });
971
+ // show hooks
972
+ cmd
973
+ .command("hooks")
974
+ .description("Show configured hooks for drift checking")
975
+ .option("--json", "Output as JSON")
976
+ .action(async (options, command) => {
977
+ const parentOpts = command.parent?.opts() || {};
978
+ const json = options.json || parentOpts.json !== false;
979
+ if (json)
980
+ setJsonMode(true);
981
+ const cwd = process.cwd();
982
+ // Detect git hook system
983
+ const hookSystem = detectHookSystem(cwd);
984
+ // Check for buoy in the hook
985
+ let gitHookInstalled = false;
986
+ let gitHookPath = null;
987
+ if (hookSystem === "husky") {
988
+ gitHookPath = join(cwd, ".husky", "pre-commit");
989
+ }
990
+ else if (hookSystem === "pre-commit") {
991
+ gitHookPath = join(cwd, ".pre-commit-config.yaml");
992
+ }
993
+ else if (hookSystem === "git") {
994
+ gitHookPath = join(cwd, ".git", "hooks", "pre-commit");
995
+ }
996
+ if (gitHookPath && existsSync(gitHookPath)) {
997
+ try {
998
+ const hookContent = readFileSync(gitHookPath, "utf-8");
999
+ gitHookInstalled = hookContent.includes("buoy");
1000
+ }
1001
+ catch {
1002
+ // Ignore read errors
1003
+ }
1004
+ }
1005
+ // Check for Claude hooks
1006
+ const claudeSettingsPath = join(cwd, ".claude", "settings.local.json");
1007
+ let claudeHooksEnabled = false;
1008
+ let claudeEvents = [];
1009
+ if (existsSync(claudeSettingsPath)) {
1010
+ try {
1011
+ const settings = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
1012
+ const hooks = settings.hooks;
1013
+ if (hooks) {
1014
+ if (hooks.SessionStart?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy") || hk.command?.includes("Design system")))) {
1015
+ claudeHooksEnabled = true;
1016
+ claudeEvents.push("SessionStart");
1017
+ }
1018
+ if (hooks.PostToolUse?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy")))) {
1019
+ claudeHooksEnabled = true;
1020
+ claudeEvents.push("PostToolUse");
1021
+ }
1022
+ }
1023
+ }
1024
+ catch {
1025
+ // Ignore parse errors
1026
+ }
1027
+ }
1028
+ if (!gitHookInstalled && !claudeHooksEnabled) {
1029
+ if (json) {
1030
+ console.log(JSON.stringify({
1031
+ gitHooks: { type: hookSystem, installed: false },
1032
+ claudeHooks: { enabled: false, events: [] },
1033
+ }, null, 2));
1034
+ }
1035
+ else {
1036
+ info("No hooks configured. Run " + chalk.cyan("buoy dock hooks") + " to set up drift checking.");
1037
+ }
1038
+ return;
1039
+ }
1040
+ if (json) {
1041
+ console.log(JSON.stringify({
1042
+ gitHooks: { type: hookSystem, path: gitHookPath, installed: gitHookInstalled },
1043
+ claudeHooks: { enabled: claudeHooksEnabled, events: claudeEvents },
1044
+ }, null, 2));
1045
+ }
1046
+ else {
1047
+ header("Hooks");
1048
+ newline();
1049
+ if (gitHookInstalled) {
1050
+ console.log(` ${chalk.green("✓")} Git pre-commit hook (${hookSystem})`);
1051
+ if (gitHookPath)
1052
+ keyValue(" Path", gitHookPath.replace(cwd + "/", ""));
1053
+ }
1054
+ else {
1055
+ console.log(` ${chalk.dim("○")} Git pre-commit hook not installed`);
1056
+ }
1057
+ if (claudeHooksEnabled) {
1058
+ console.log(` ${chalk.green("✓")} Claude Code hooks`);
1059
+ keyValue(" Events", claudeEvents.join(", "));
1060
+ }
1061
+ else {
1062
+ console.log(` ${chalk.dim("○")} Claude Code hooks not configured`);
1063
+ }
1064
+ newline();
1065
+ }
1066
+ });
1067
+ // show commands
1068
+ cmd
1069
+ .command("commands")
1070
+ .description("Show installed slash commands")
1071
+ .option("--json", "Output as JSON")
1072
+ .action(async (options, command) => {
1073
+ const parentOpts = command.parent?.opts() || {};
1074
+ const json = options.json || parentOpts.json !== false;
1075
+ if (json)
1076
+ setJsonMode(true);
1077
+ const commandsDir = join(homedir(), ".claude", "commands");
1078
+ if (!existsSync(commandsDir)) {
1079
+ if (json) {
1080
+ console.log(JSON.stringify({ commands: [] }, null, 2));
1081
+ }
1082
+ else {
1083
+ info("No slash commands installed. Run " + chalk.cyan("buoy dock commands install") + " to set them up.");
1084
+ }
1085
+ return;
1086
+ }
1087
+ const buoyCommands = readdirSync(commandsDir)
1088
+ .filter(f => f.endsWith(".md"))
1089
+ .map(f => ({
1090
+ name: f.replace(".md", ""),
1091
+ installed: true,
1092
+ }));
1093
+ if (buoyCommands.length === 0) {
1094
+ if (json) {
1095
+ console.log(JSON.stringify({ commands: [] }, null, 2));
1096
+ }
1097
+ else {
1098
+ info("No slash commands installed. Run " + chalk.cyan("buoy dock commands install") + " to set them up.");
1099
+ }
1100
+ return;
1101
+ }
1102
+ if (json) {
1103
+ console.log(JSON.stringify({ commands: buoyCommands }, null, 2));
1104
+ }
1105
+ else {
1106
+ header("Slash Commands");
1107
+ newline();
1108
+ for (const cmd of buoyCommands) {
1109
+ console.log(` ${chalk.green("✓")} /${cmd.name}`);
1110
+ }
1111
+ newline();
1112
+ }
1113
+ });
1114
+ // show graph
1115
+ cmd
1116
+ .command("graph")
1117
+ .description("Show knowledge graph statistics")
1118
+ .option("--json", "Output as JSON")
1119
+ .action(async (options, command) => {
1120
+ const parentOpts = command.parent?.opts() || {};
1121
+ const json = options.json || parentOpts.json !== false;
1122
+ if (json)
1123
+ setJsonMode(true);
1124
+ const cwd = process.cwd();
1125
+ const graphPath = join(cwd, ".buoy", "graph.json");
1126
+ if (!existsSync(graphPath)) {
1127
+ if (json) {
1128
+ console.log(JSON.stringify({ exists: false }, null, 2));
1129
+ }
1130
+ else {
1131
+ info("No knowledge graph built. Run " + chalk.cyan("buoy dock graph") + " to build it.");
1132
+ }
1133
+ return;
1134
+ }
1135
+ try {
1136
+ const { importFromJSON, getGraphStats } = await import("@buoy-design/core");
1137
+ const graphData = JSON.parse(readFileSync(graphPath, "utf-8"));
1138
+ const graph = importFromJSON(graphData);
1139
+ const stats = getGraphStats(graph);
1140
+ if (json) {
1141
+ console.log(JSON.stringify({
1142
+ exists: true,
1143
+ path: graphPath,
1144
+ nodes: stats.nodeCount,
1145
+ edges: stats.edgeCount,
1146
+ nodesByType: stats.nodesByType,
1147
+ edgesByType: stats.edgesByType,
1148
+ }, null, 2));
1149
+ }
1150
+ else {
1151
+ header("Knowledge Graph");
1152
+ newline();
1153
+ keyValue("Path", ".buoy/graph.json");
1154
+ keyValue("Nodes", String(stats.nodeCount));
1155
+ keyValue("Edges", String(stats.edgeCount));
1156
+ newline();
1157
+ if (Object.keys(stats.nodesByType).length > 0) {
1158
+ header("Nodes by Type");
1159
+ for (const [type, count] of Object.entries(stats.nodesByType)) {
1160
+ keyValue(` ${type}`, String(count));
1161
+ }
1162
+ newline();
1163
+ }
1164
+ }
1165
+ }
1166
+ catch (err) {
1167
+ error(err instanceof Error ? err.message : String(err));
1168
+ process.exit(1);
1169
+ }
1170
+ });
1171
+ // show plugins
1172
+ cmd
1173
+ .command("plugins")
1174
+ .description("Show available scanners and plugins")
1175
+ .option("--json", "Output as JSON")
1176
+ .action(async (options, command) => {
1177
+ const parentOpts = command.parent?.opts() || {};
1178
+ const json = options.json || parentOpts.json !== false;
1179
+ if (json)
1180
+ setJsonMode(true);
1181
+ const cwd = process.cwd();
1182
+ const detected = await detectFrameworks(cwd);
1183
+ const builtIn = Object.entries(BUILTIN_SCANNERS).map(([key, info]) => ({
1184
+ key,
1185
+ description: info.description,
1186
+ detects: info.detects,
1187
+ }));
1188
+ const optional = Object.entries(PLUGIN_INFO).map(([key, info]) => ({
1189
+ key,
1190
+ name: info.name,
1191
+ description: info.description,
1192
+ }));
1193
+ if (json) {
1194
+ console.log(JSON.stringify({
1195
+ builtIn,
1196
+ detected: detected.map(fw => ({
1197
+ name: fw.name,
1198
+ scanner: fw.scanner,
1199
+ plugin: fw.plugin,
1200
+ confidence: fw.confidence,
1201
+ })),
1202
+ optional,
1203
+ }, null, 2));
1204
+ }
1205
+ else {
1206
+ header("Built-in Scanners");
1207
+ newline();
1208
+ for (const scanner of builtIn) {
1209
+ console.log(` ${chalk.green("✓")} ${chalk.cyan(scanner.description)}`);
1210
+ console.log(` ${chalk.dim(`Detects: ${scanner.detects}`)}`);
1211
+ }
1212
+ newline();
1213
+ if (detected.length > 0) {
1214
+ header("Detected Frameworks");
1215
+ newline();
1216
+ for (const fw of detected) {
1217
+ console.log(` ${chalk.green("•")} ${fw.name} ${chalk.dim(`(${fw.confidence})`)}`);
1218
+ }
1219
+ newline();
1220
+ }
1221
+ if (optional.length > 0) {
1222
+ header("Optional Plugins");
1223
+ newline();
1224
+ for (const plugin of optional) {
1225
+ console.log(` ${chalk.dim("○")} ${chalk.cyan(plugin.name)}`);
1226
+ console.log(` ${chalk.dim(plugin.description)}`);
1227
+ }
1228
+ newline();
1229
+ }
1230
+ }
1231
+ });
742
1232
  // show all
743
1233
  cmd
744
1234
  .command("all")
@@ -785,6 +1275,9 @@ export function createShowCommand() {
785
1275
  pathPatterns: aggregationConfig.pathPatterns,
786
1276
  });
787
1277
  const aggregated = aggregator.aggregate(driftResult.drifts);
1278
+ // Gather setup status
1279
+ const cwd = process.cwd();
1280
+ const setup = getSetupStatus(cwd);
788
1281
  const output = {
789
1282
  components: scanResult.components,
790
1283
  tokens: scanResult.tokens,
@@ -814,6 +1307,7 @@ export function createShowCommand() {
814
1307
  categories: healthReport.categories,
815
1308
  worstFiles: healthReport.worstFiles.slice(0, 5),
816
1309
  },
1310
+ setup,
817
1311
  };
818
1312
  console.log(JSON.stringify(output, null, 2));
819
1313
  }
@@ -835,6 +1329,69 @@ async function getOrBuildConfig() {
835
1329
  const autoResult = await buildAutoConfig(process.cwd());
836
1330
  return autoResult.config;
837
1331
  }
1332
+ // Helper: Get setup status for all dock tools
1333
+ function getSetupStatus(cwd) {
1334
+ const hasConfig = !!getConfigPath();
1335
+ const hasSkills = existsSync(join(cwd, ".claude", "skills", "design-system"));
1336
+ const agentsDir = join(cwd, ".claude", "agents");
1337
+ const hasAgents = existsSync(agentsDir) && readdirSync(agentsDir).some(f => f.endsWith(".md"));
1338
+ // Check CLAUDE.md for Design System section
1339
+ const claudeMdPath = join(cwd, "CLAUDE.md");
1340
+ let hasContext = false;
1341
+ if (existsSync(claudeMdPath)) {
1342
+ try {
1343
+ const content = readFileSync(claudeMdPath, "utf-8");
1344
+ hasContext = /^##?\s*Design\s*System/m.test(content);
1345
+ }
1346
+ catch {
1347
+ // Ignore read errors
1348
+ }
1349
+ }
1350
+ // Check hooks
1351
+ const hookSystem = detectHookSystem(cwd);
1352
+ let gitHookInstalled = false;
1353
+ if (hookSystem === "husky") {
1354
+ const p = join(cwd, ".husky", "pre-commit");
1355
+ if (existsSync(p)) {
1356
+ try {
1357
+ gitHookInstalled = readFileSync(p, "utf-8").includes("buoy");
1358
+ }
1359
+ catch { /* skip */ }
1360
+ }
1361
+ }
1362
+ else if (hookSystem === "git") {
1363
+ const p = join(cwd, ".git", "hooks", "pre-commit");
1364
+ if (existsSync(p)) {
1365
+ try {
1366
+ gitHookInstalled = readFileSync(p, "utf-8").includes("buoy");
1367
+ }
1368
+ catch { /* skip */ }
1369
+ }
1370
+ }
1371
+ let claudeHooksEnabled = false;
1372
+ const claudeSettingsPath = join(cwd, ".claude", "settings.local.json");
1373
+ if (existsSync(claudeSettingsPath)) {
1374
+ try {
1375
+ const settings = JSON.parse(readFileSync(claudeSettingsPath, "utf-8"));
1376
+ claudeHooksEnabled = !!settings.hooks?.SessionStart?.some((h) => h.hooks?.some(hk => hk.command?.includes("buoy") || hk.command?.includes("Design system")));
1377
+ }
1378
+ catch { /* skip */ }
1379
+ }
1380
+ // Check commands
1381
+ const commandsDir = join(homedir(), ".claude", "commands");
1382
+ const hasCommands = existsSync(commandsDir) && readdirSync(commandsDir).some(f => f.endsWith(".md"));
1383
+ // Check graph
1384
+ const hasGraph = existsSync(join(cwd, ".buoy", "graph.json"));
1385
+ return {
1386
+ config: hasConfig,
1387
+ skills: hasSkills,
1388
+ agents: hasAgents,
1389
+ context: hasContext,
1390
+ hooks: { git: gitHookInstalled, claude: claudeHooksEnabled },
1391
+ commands: hasCommands,
1392
+ graph: hasGraph,
1393
+ };
1394
+ }
838
1395
  // Helper: Extract all hardcoded values for health audit
839
1396
  async function extractAllValues(spin) {
840
1397
  const cwd = process.cwd();
@@ -1020,6 +1577,21 @@ function fuzzyScore(query, target) {
1020
1577
  }
1021
1578
  return 0;
1022
1579
  }
1580
+ function walkDir(dir) {
1581
+ const files = [];
1582
+ if (!existsSync(dir))
1583
+ return files;
1584
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1585
+ const fullPath = join(dir, entry.name);
1586
+ if (entry.isDirectory()) {
1587
+ files.push(...walkDir(fullPath));
1588
+ }
1589
+ else {
1590
+ files.push(fullPath);
1591
+ }
1592
+ }
1593
+ return files;
1594
+ }
1023
1595
  function formatRelativeDate(date) {
1024
1596
  const now = new Date();
1025
1597
  const diff = now.getTime() - date.getTime();