@buoy-design/cli 0.1.0 → 0.1.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 (146) hide show
  1. package/dist/commands/audit.d.ts +3 -0
  2. package/dist/commands/audit.d.ts.map +1 -0
  3. package/dist/commands/audit.js +235 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/baseline.d.ts +39 -0
  6. package/dist/commands/baseline.d.ts.map +1 -0
  7. package/dist/commands/baseline.js +298 -0
  8. package/dist/commands/baseline.js.map +1 -0
  9. package/dist/commands/check.d.ts +16 -0
  10. package/dist/commands/check.d.ts.map +1 -0
  11. package/dist/commands/check.js +168 -0
  12. package/dist/commands/check.js.map +1 -0
  13. package/dist/commands/ci.d.ts +2 -2
  14. package/dist/commands/ci.d.ts.map +1 -1
  15. package/dist/commands/ci.js +124 -142
  16. package/dist/commands/ci.js.map +1 -1
  17. package/dist/commands/compare.d.ts +3 -0
  18. package/dist/commands/compare.d.ts.map +1 -0
  19. package/dist/commands/compare.js +170 -0
  20. package/dist/commands/compare.js.map +1 -0
  21. package/dist/commands/drift.d.ts +1 -1
  22. package/dist/commands/drift.d.ts.map +1 -1
  23. package/dist/commands/drift.js +79 -121
  24. package/dist/commands/drift.js.map +1 -1
  25. package/dist/commands/explain.d.ts +3 -0
  26. package/dist/commands/explain.d.ts.map +1 -0
  27. package/dist/commands/explain.js +212 -0
  28. package/dist/commands/explain.js.map +1 -0
  29. package/dist/commands/graph.d.ts +3 -0
  30. package/dist/commands/graph.d.ts.map +1 -0
  31. package/dist/commands/graph.js +430 -0
  32. package/dist/commands/graph.js.map +1 -0
  33. package/dist/commands/index.d.ts +14 -8
  34. package/dist/commands/index.d.ts.map +1 -1
  35. package/dist/commands/index.js +14 -8
  36. package/dist/commands/index.js.map +1 -1
  37. package/dist/commands/init.d.ts +1 -1
  38. package/dist/commands/init.d.ts.map +1 -1
  39. package/dist/commands/init.js +404 -237
  40. package/dist/commands/init.js.map +1 -1
  41. package/dist/commands/plugins.d.ts.map +1 -1
  42. package/dist/commands/plugins.js +32 -41
  43. package/dist/commands/plugins.js.map +1 -1
  44. package/dist/commands/scan.d.ts +1 -1
  45. package/dist/commands/scan.d.ts.map +1 -1
  46. package/dist/commands/scan.js +107 -219
  47. package/dist/commands/scan.js.map +1 -1
  48. package/dist/commands/status.d.ts +1 -1
  49. package/dist/commands/status.d.ts.map +1 -1
  50. package/dist/commands/status.js +114 -122
  51. package/dist/commands/status.js.map +1 -1
  52. package/dist/commands/tokens.d.ts +3 -0
  53. package/dist/commands/tokens.d.ts.map +1 -0
  54. package/dist/commands/tokens.js +261 -0
  55. package/dist/commands/tokens.js.map +1 -0
  56. package/dist/config/auto-detect.d.ts +21 -0
  57. package/dist/config/auto-detect.d.ts.map +1 -0
  58. package/dist/config/auto-detect.js +278 -0
  59. package/dist/config/auto-detect.js.map +1 -0
  60. package/dist/config/loader.d.ts.map +1 -1
  61. package/dist/config/loader.js +17 -0
  62. package/dist/config/loader.js.map +1 -1
  63. package/dist/config/schema.d.ts +63 -63
  64. package/dist/config/schema.d.ts.map +1 -1
  65. package/dist/config/schema.js +20 -2
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/constants.d.ts +36 -0
  68. package/dist/constants.d.ts.map +1 -0
  69. package/dist/constants.js +37 -0
  70. package/dist/constants.js.map +1 -0
  71. package/dist/detect/frameworks.d.ts +11 -2
  72. package/dist/detect/frameworks.d.ts.map +1 -1
  73. package/dist/detect/frameworks.js +78 -78
  74. package/dist/detect/frameworks.js.map +1 -1
  75. package/dist/detect/index.d.ts +1 -0
  76. package/dist/detect/index.d.ts.map +1 -1
  77. package/dist/detect/index.js +3 -0
  78. package/dist/detect/index.js.map +1 -1
  79. package/dist/detect/monorepo-patterns.d.ts +54 -0
  80. package/dist/detect/monorepo-patterns.d.ts.map +1 -0
  81. package/dist/detect/monorepo-patterns.js +209 -0
  82. package/dist/detect/monorepo-patterns.js.map +1 -0
  83. package/dist/detect/project-detector.d.ts +1 -1
  84. package/dist/detect/project-detector.d.ts.map +1 -1
  85. package/dist/detect/project-detector.js +132 -0
  86. package/dist/detect/project-detector.js.map +1 -1
  87. package/dist/explain/agents.d.ts +31 -0
  88. package/dist/explain/agents.d.ts.map +1 -0
  89. package/dist/explain/agents.js +507 -0
  90. package/dist/explain/agents.js.map +1 -0
  91. package/dist/hooks/index.d.ts +26 -0
  92. package/dist/hooks/index.d.ts.map +1 -0
  93. package/dist/hooks/index.js +283 -0
  94. package/dist/hooks/index.js.map +1 -0
  95. package/dist/index.d.ts +3 -3
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +13 -7
  98. package/dist/index.js.map +1 -1
  99. package/dist/integrations/github-formatter.d.ts +55 -0
  100. package/dist/integrations/github-formatter.d.ts.map +1 -0
  101. package/dist/integrations/github-formatter.js +391 -0
  102. package/dist/integrations/github-formatter.js.map +1 -0
  103. package/dist/integrations/github.d.ts +95 -0
  104. package/dist/integrations/github.d.ts.map +1 -0
  105. package/dist/integrations/github.js +281 -0
  106. package/dist/integrations/github.js.map +1 -0
  107. package/dist/integrations/index.d.ts +4 -0
  108. package/dist/integrations/index.d.ts.map +1 -0
  109. package/dist/integrations/index.js +4 -0
  110. package/dist/integrations/index.js.map +1 -0
  111. package/dist/output/formatters.d.ts.map +1 -1
  112. package/dist/output/formatters.js +5 -10
  113. package/dist/output/formatters.js.map +1 -1
  114. package/dist/scan/orchestrator.d.ts +76 -0
  115. package/dist/scan/orchestrator.d.ts.map +1 -0
  116. package/dist/scan/orchestrator.js +247 -0
  117. package/dist/scan/orchestrator.js.map +1 -0
  118. package/dist/services/ai-analysis.d.ts +54 -0
  119. package/dist/services/ai-analysis.d.ts.map +1 -0
  120. package/dist/services/ai-analysis.js +239 -0
  121. package/dist/services/ai-analysis.js.map +1 -0
  122. package/dist/services/drift-analysis.d.ts +77 -0
  123. package/dist/services/drift-analysis.d.ts.map +1 -0
  124. package/dist/services/drift-analysis.js +145 -0
  125. package/dist/services/drift-analysis.js.map +1 -0
  126. package/package.json +17 -5
  127. package/dist/commands/__tests__/ci.test.d.ts +0 -2
  128. package/dist/commands/__tests__/ci.test.d.ts.map +0 -1
  129. package/dist/commands/__tests__/ci.test.js +0 -33
  130. package/dist/commands/__tests__/ci.test.js.map +0 -1
  131. package/dist/commands/bootstrap.d.ts +0 -3
  132. package/dist/commands/bootstrap.d.ts.map +0 -1
  133. package/dist/commands/bootstrap.js +0 -458
  134. package/dist/commands/bootstrap.js.map +0 -1
  135. package/dist/plugins/index.d.ts +0 -3
  136. package/dist/plugins/index.d.ts.map +0 -1
  137. package/dist/plugins/index.js +0 -3
  138. package/dist/plugins/index.js.map +0 -1
  139. package/dist/plugins/loader.d.ts +0 -11
  140. package/dist/plugins/loader.d.ts.map +0 -1
  141. package/dist/plugins/loader.js +0 -77
  142. package/dist/plugins/loader.js.map +0 -1
  143. package/dist/plugins/registry.d.ts +0 -15
  144. package/dist/plugins/registry.d.ts.map +0 -1
  145. package/dist/plugins/registry.js +0 -32
  146. package/dist/plugins/registry.js.map +0 -1
@@ -0,0 +1,212 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { writeFileSync, mkdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { spinner, success, error, info, newline, } from "../output/reporters.js";
6
+ import { resolveTarget, runExplainAgents, } from "../explain/agents.js";
7
+ import { loadConfig } from "../config/loader.js";
8
+ import { ScanOrchestrator } from "../scan/orchestrator.js";
9
+ export function createExplainCommand() {
10
+ const cmd = new Command("explain")
11
+ .description("AI-powered investigation of code and drift signals")
12
+ .argument("[target]", "File, directory, or drift:<id> (runs scan if omitted)")
13
+ .option("-a, --all", "Explain the entire design system")
14
+ .option("-s, --save", "Save the explanation to .buoy/explain/")
15
+ .option("-o, --output <path>", "Save explanation to custom path")
16
+ .option("-v, --verbose", "Show detailed agent progress")
17
+ .option("-q, --quick", "Quick analysis (shorter prompts, faster)")
18
+ .option("--json", "Output as JSON")
19
+ .action(async (targetArg, options) => {
20
+ try {
21
+ // Resolve target
22
+ let target = resolveTarget(targetArg, options.all);
23
+ // If no target, run scan mode - explain the current drift state
24
+ if (!target) {
25
+ target = await runScanAndBuildTarget();
26
+ if (!target) {
27
+ success("No drift detected. Your design system is aligned!");
28
+ newline();
29
+ info("To investigate specific code, try:");
30
+ console.log(chalk.gray(" buoy explain src/components/Button.tsx"));
31
+ console.log(chalk.gray(" buoy explain src/components/"));
32
+ return;
33
+ }
34
+ }
35
+ // Progress tracking
36
+ const agentStatus = {};
37
+ const spin = spinner(`Investigating ${target.name}...`);
38
+ const updateSpinner = () => {
39
+ const statuses = Object.entries(agentStatus)
40
+ .map(([agent, status]) => {
41
+ const icon = status === "completed" ? "✓" : status === "failed" ? "✗" : "○";
42
+ const color = status === "completed" ? chalk.green : status === "failed" ? chalk.red : chalk.gray;
43
+ return color(`${icon} ${agent}`);
44
+ })
45
+ .join(" ");
46
+ spin.text = `Investigating ${target.name}...\n ${statuses}`;
47
+ };
48
+ // Run agents
49
+ const result = await runExplainAgents(target, {
50
+ verbose: options.verbose,
51
+ quick: options.quick,
52
+ onProgress: (agent, status) => {
53
+ const displayName = agent
54
+ .split("-")
55
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
56
+ .join(" ");
57
+ agentStatus[displayName] = status;
58
+ updateSpinner();
59
+ },
60
+ });
61
+ spin.stop();
62
+ newline();
63
+ // Check for failures
64
+ const failures = result.findings.filter((f) => !f.success);
65
+ if (failures.length > 0) {
66
+ newline();
67
+ for (const f of failures) {
68
+ console.log(chalk.yellow(`⚠ ${f.agent}: ${f.error}`));
69
+ }
70
+ newline();
71
+ // If ALL failed, show help
72
+ if (failures.length === result.findings.length) {
73
+ info("All agents failed. Common causes:");
74
+ console.log(chalk.gray(" • Claude CLI not installed (run: npm install -g @anthropic-ai/claude-code)"));
75
+ console.log(chalk.gray(" • Not authenticated (run: claude login)"));
76
+ console.log(chalk.gray(" • Network issues"));
77
+ newline();
78
+ }
79
+ }
80
+ // Output
81
+ if (options.json) {
82
+ console.log(JSON.stringify(result, null, 2));
83
+ }
84
+ else {
85
+ // Print the synthesis with nice formatting
86
+ console.log(chalk.gray("─".repeat(60)));
87
+ newline();
88
+ console.log(result.synthesis);
89
+ newline();
90
+ console.log(chalk.gray("─".repeat(60)));
91
+ }
92
+ // Save if requested
93
+ if (options.save || options.output) {
94
+ const outputDir = options.output || join(process.cwd(), ".buoy", "explain");
95
+ mkdirSync(outputDir, { recursive: true });
96
+ const filename = `${target.name}.md`;
97
+ const filepath = join(outputDir, filename);
98
+ writeFileSync(filepath, result.synthesis);
99
+ newline();
100
+ success(`Saved to ${filepath}`);
101
+ }
102
+ // Verbose: show individual agent outputs
103
+ if (options.verbose) {
104
+ newline();
105
+ info("Individual agent findings:");
106
+ newline();
107
+ for (const finding of result.findings) {
108
+ if (finding.success) {
109
+ console.log(chalk.bold.cyan(`── ${finding.agent} ──`));
110
+ console.log(finding.output);
111
+ newline();
112
+ }
113
+ }
114
+ }
115
+ }
116
+ catch (err) {
117
+ error(err instanceof Error ? err.message : String(err));
118
+ process.exit(1);
119
+ }
120
+ });
121
+ return cmd;
122
+ }
123
+ /**
124
+ * Run a Buoy scan and build an ExplainTarget from the results
125
+ */
126
+ async function runScanAndBuildTarget() {
127
+ const spin = spinner("Scanning for drift...");
128
+ try {
129
+ const { config } = await loadConfig();
130
+ const orchestrator = new ScanOrchestrator(config);
131
+ const { components } = await orchestrator.scanComponents({
132
+ onProgress: (msg) => {
133
+ spin.text = msg;
134
+ },
135
+ });
136
+ spin.text = "Analyzing drift...";
137
+ const { SemanticDiffEngine } = await import("@buoy-design/core/analysis");
138
+ const engine = new SemanticDiffEngine();
139
+ const diffResult = engine.analyzeComponents(components, {
140
+ checkDeprecated: true,
141
+ checkNaming: true,
142
+ checkDocumentation: true,
143
+ });
144
+ spin.stop();
145
+ const drifts = diffResult.drifts;
146
+ if (drifts.length === 0) {
147
+ return null;
148
+ }
149
+ // Format drift data for AI analysis
150
+ const scanData = formatDriftsForAI(drifts, components.length);
151
+ return {
152
+ type: "scan",
153
+ path: process.cwd(),
154
+ name: "current-drift",
155
+ scanData,
156
+ };
157
+ }
158
+ catch (err) {
159
+ spin.stop();
160
+ throw err;
161
+ }
162
+ }
163
+ /**
164
+ * Format drift signals into a structured text for AI analysis
165
+ */
166
+ function formatDriftsForAI(drifts, componentCount) {
167
+ const summary = {
168
+ total: drifts.length,
169
+ critical: drifts.filter((d) => d.severity === "critical").length,
170
+ warning: drifts.filter((d) => d.severity === "warning").length,
171
+ info: drifts.filter((d) => d.severity === "info").length,
172
+ };
173
+ const byType = {};
174
+ for (const drift of drifts) {
175
+ if (!byType[drift.type]) {
176
+ byType[drift.type] = [];
177
+ }
178
+ byType[drift.type].push(drift);
179
+ }
180
+ let output = `# Buoy Scan Results
181
+
182
+ ## Summary
183
+ - Components scanned: ${componentCount}
184
+ - Total drift signals: ${summary.total}
185
+ - Critical: ${summary.critical}
186
+ - Warning: ${summary.warning}
187
+ - Info: ${summary.info}
188
+
189
+ ## Drift Signals by Type
190
+
191
+ `;
192
+ for (const [type, signals] of Object.entries(byType)) {
193
+ output += `### ${type} (${signals.length})\n\n`;
194
+ for (const signal of signals.slice(0, 10)) { // Limit to avoid token overflow
195
+ output += `- **${signal.source.entityName}** (${signal.severity})\n`;
196
+ output += ` ${signal.message}\n`;
197
+ if (signal.source.location) {
198
+ output += ` Location: ${signal.source.location}\n`;
199
+ }
200
+ const suggestions = signal.details?.suggestions;
201
+ if (suggestions && suggestions.length > 0) {
202
+ output += ` Suggestion: ${suggestions[0]}\n`;
203
+ }
204
+ output += "\n";
205
+ }
206
+ if (signals.length > 10) {
207
+ output += ` ... and ${signals.length - 10} more ${type} signals\n\n`;
208
+ }
209
+ }
210
+ return output;
211
+ }
212
+ //# sourceMappingURL=explain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare function createGraphCommand(): Command;
3
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/commands/graph.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4BpC,wBAAgB,kBAAkB,IAAI,OAAO,CAS5C"}
@@ -0,0 +1,430 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { spinner, success, error, info, header, keyValue, newline, setJsonMode, } from "../output/reporters.js";
4
+ import { GraphBuilder, getGraphStats, exportToDOT, exportToCytoscape, exportToJSON, findUnusedTokens, findUntestedComponents, findUndocumentedComponents, findRepeatOffenders, calculateCoverage, collectGitHistory, collectUsages, collectImports, } from "@buoy-design/core";
5
+ export function createGraphCommand() {
6
+ const cmd = new Command("graph")
7
+ .description("Build and query the design system knowledge graph")
8
+ .addCommand(createBuildCommand())
9
+ .addCommand(createQueryCommand())
10
+ .addCommand(createExportCommand())
11
+ .addCommand(createStatsCommand());
12
+ return cmd;
13
+ }
14
+ // ============================================================================
15
+ // Build Command
16
+ // ============================================================================
17
+ function createBuildCommand() {
18
+ return new Command("build")
19
+ .description("Build the knowledge graph from the codebase")
20
+ .option("--git", "Include git history", true)
21
+ .option("--no-git", "Skip git history collection")
22
+ .option("--usages", "Include token/component usages", true)
23
+ .option("--no-usages", "Skip usage collection")
24
+ .option("--imports", "Include import relationships", true)
25
+ .option("--no-imports", "Skip import collection")
26
+ .option("--since <date>", "Only include commits since date (ISO format)")
27
+ .option("--json", "Output as JSON")
28
+ .action(async (options) => {
29
+ if (options.json) {
30
+ setJsonMode(true);
31
+ }
32
+ const spin = spinner("Building knowledge graph...");
33
+ const projectRoot = process.cwd();
34
+ try {
35
+ const builder = new GraphBuilder({ projectId: "default" });
36
+ const stats = {
37
+ commits: 0,
38
+ developers: 0,
39
+ tokenUsages: 0,
40
+ componentUsages: 0,
41
+ imports: 0,
42
+ files: new Set(),
43
+ };
44
+ // Collect git history
45
+ if (options.git) {
46
+ spin.text = "Collecting git history...";
47
+ const gitResult = await collectGitHistory(projectRoot, {
48
+ since: options.since ? new Date(options.since) : undefined,
49
+ includeStats: true,
50
+ });
51
+ // Add commits to graph
52
+ for (const commit of gitResult.commits) {
53
+ const commitId = builder.addCommit(commit.sha, commit.message, commit.author, commit.authorEmail, commit.timestamp);
54
+ // Track files
55
+ for (const file of commit.filesChanged) {
56
+ const fileId = builder.addFile(file.path, file.path);
57
+ stats.files.add(file.path);
58
+ builder.addEdge("CHANGED", commitId, fileId, {
59
+ createdAt: commit.timestamp,
60
+ });
61
+ }
62
+ }
63
+ // Add developers
64
+ for (const dev of gitResult.developers) {
65
+ const devId = builder.addDeveloper(dev.id, dev.name, dev.email, undefined, dev.commitCount);
66
+ // Link to commits
67
+ for (const commit of gitResult.commits) {
68
+ if (commit.authorEmail === dev.email) {
69
+ const commitId = `commit:${commit.sha}`;
70
+ builder.addEdge("AUTHORED", devId, commitId);
71
+ }
72
+ }
73
+ }
74
+ stats.commits = gitResult.commits.length;
75
+ stats.developers = gitResult.developers.length;
76
+ }
77
+ // Collect usages
78
+ if (options.usages) {
79
+ spin.text = "Collecting token and component usages...";
80
+ const usageResult = await collectUsages({ projectRoot });
81
+ // Track hardcoded values as potential tokens
82
+ for (const usage of usageResult.hardcodedValues) {
83
+ stats.files.add(usage.filePath);
84
+ }
85
+ stats.tokenUsages = usageResult.tokenUsages.length;
86
+ stats.componentUsages = usageResult.componentUsages.length;
87
+ }
88
+ // Collect imports
89
+ if (options.imports) {
90
+ spin.text = "Collecting import relationships...";
91
+ const importResult = await collectImports({ projectRoot });
92
+ for (const imp of importResult.imports) {
93
+ if (!imp.isExternal) {
94
+ const sourceId = builder.addFile(imp.sourceFile, imp.sourceFile);
95
+ const targetId = builder.addFile(imp.targetFile, imp.targetFile);
96
+ builder.addEdge("IMPORTS", sourceId, targetId, {
97
+ createdAt: new Date(),
98
+ });
99
+ stats.files.add(imp.sourceFile);
100
+ stats.files.add(imp.targetFile);
101
+ }
102
+ }
103
+ stats.imports = importResult.imports.filter((i) => !i.isExternal)
104
+ .length;
105
+ }
106
+ spin.stop();
107
+ const graph = builder.build();
108
+ const graphStats = getGraphStats(graph);
109
+ if (options.json) {
110
+ console.log(JSON.stringify({
111
+ stats: {
112
+ nodes: graphStats.nodeCount,
113
+ edges: graphStats.edgeCount,
114
+ commits: stats.commits,
115
+ developers: stats.developers,
116
+ files: stats.files.size,
117
+ imports: stats.imports,
118
+ },
119
+ nodesByType: graphStats.nodesByType,
120
+ edgesByType: graphStats.edgesByType,
121
+ }, null, 2));
122
+ return;
123
+ }
124
+ success("Graph built successfully!");
125
+ newline();
126
+ header("Graph Statistics");
127
+ keyValue("Total nodes", String(graphStats.nodeCount));
128
+ keyValue("Total edges", String(graphStats.edgeCount));
129
+ newline();
130
+ header("Collected");
131
+ keyValue("Commits", String(stats.commits));
132
+ keyValue("Developers", String(stats.developers));
133
+ keyValue("Files", String(stats.files.size));
134
+ keyValue("Import relationships", String(stats.imports));
135
+ newline();
136
+ if (Object.keys(graphStats.nodesByType).length > 0) {
137
+ header("Nodes by Type");
138
+ for (const [type, count] of Object.entries(graphStats.nodesByType)) {
139
+ keyValue(type, String(count));
140
+ }
141
+ }
142
+ }
143
+ catch (err) {
144
+ spin.stop();
145
+ const message = err instanceof Error ? err.message : String(err);
146
+ error(`Graph build failed: ${message}`);
147
+ process.exit(1);
148
+ }
149
+ });
150
+ }
151
+ // ============================================================================
152
+ // Query Command
153
+ // ============================================================================
154
+ function createQueryCommand() {
155
+ return new Command("query")
156
+ .description("Query the knowledge graph")
157
+ .argument("<question>", "What to query (e.g., 'unused tokens')")
158
+ .option("--json", "Output as JSON")
159
+ .action(async (question, options) => {
160
+ if (options.json) {
161
+ setJsonMode(true);
162
+ }
163
+ const spin = spinner("Building graph for query...");
164
+ const projectRoot = process.cwd();
165
+ try {
166
+ // Build a quick graph for querying
167
+ const builder = new GraphBuilder({ projectId: "default" });
168
+ // For now, we'll build a minimal graph for queries
169
+ // In production, this would load from SQLite
170
+ spin.text = "Collecting data...";
171
+ const gitResult = await collectGitHistory(projectRoot, { maxCount: 100 });
172
+ for (const dev of gitResult.developers) {
173
+ builder.addDeveloper(dev.id, dev.name, dev.email, undefined, dev.commitCount);
174
+ }
175
+ spin.stop();
176
+ const graph = builder.build();
177
+ // Parse and execute query
178
+ const queryLower = question.toLowerCase();
179
+ let result = null;
180
+ if (queryLower.includes("unused") && queryLower.includes("token")) {
181
+ result = findUnusedTokens(graph);
182
+ if (options.json) {
183
+ console.log(JSON.stringify({ unusedTokens: result }, null, 2));
184
+ }
185
+ else {
186
+ header("Unused Tokens");
187
+ const tokens = result;
188
+ if (tokens.length === 0) {
189
+ info("No unused tokens found (graph may not have token data)");
190
+ }
191
+ else {
192
+ for (const t of tokens) {
193
+ console.log(` ${chalk.yellow("•")} ${t}`);
194
+ }
195
+ }
196
+ }
197
+ }
198
+ else if (queryLower.includes("untested") && queryLower.includes("component")) {
199
+ result = findUntestedComponents(graph);
200
+ if (options.json) {
201
+ console.log(JSON.stringify({ untestedComponents: result }, null, 2));
202
+ }
203
+ else {
204
+ header("Untested Components");
205
+ const components = result;
206
+ if (components.length === 0) {
207
+ info("No untested components found");
208
+ }
209
+ else {
210
+ for (const c of components) {
211
+ console.log(` ${chalk.yellow("•")} ${c}`);
212
+ }
213
+ }
214
+ }
215
+ }
216
+ else if (queryLower.includes("undocumented") && queryLower.includes("component")) {
217
+ result = findUndocumentedComponents(graph);
218
+ if (options.json) {
219
+ console.log(JSON.stringify({ undocumentedComponents: result }, null, 2));
220
+ }
221
+ else {
222
+ header("Undocumented Components");
223
+ const components = result;
224
+ if (components.length === 0) {
225
+ info("No undocumented components found");
226
+ }
227
+ else {
228
+ for (const c of components) {
229
+ console.log(` ${chalk.yellow("•")} ${c}`);
230
+ }
231
+ }
232
+ }
233
+ }
234
+ else if (queryLower.includes("repeat") || queryLower.includes("offender")) {
235
+ result = findRepeatOffenders(graph);
236
+ if (options.json) {
237
+ console.log(JSON.stringify({ repeatOffenders: result }, null, 2));
238
+ }
239
+ else {
240
+ header("Repeat Offenders");
241
+ const offenders = result;
242
+ if (offenders.length === 0) {
243
+ info("No repeat offenders found");
244
+ }
245
+ else {
246
+ for (const o of offenders) {
247
+ console.log(` ${chalk.red("•")} ${o.file} ${chalk.dim(`(${o.driftCount} drift signals)`)}`);
248
+ }
249
+ }
250
+ }
251
+ }
252
+ else if (queryLower.includes("coverage")) {
253
+ result = calculateCoverage(graph);
254
+ if (options.json) {
255
+ console.log(JSON.stringify({ coverage: result }, null, 2));
256
+ }
257
+ else {
258
+ header("Design System Coverage");
259
+ const coverage = result;
260
+ keyValue("Token usage", `${(coverage.tokenCoverage * 100).toFixed(1)}%`);
261
+ keyValue("Test coverage", `${(coverage.testCoverage * 100).toFixed(1)}%`);
262
+ keyValue("Story coverage", `${(coverage.storyCoverage * 100).toFixed(1)}%`);
263
+ }
264
+ }
265
+ else {
266
+ error(`Unknown query: "${question}"`);
267
+ newline();
268
+ info("Available queries:");
269
+ info(" • unused tokens");
270
+ info(" • untested components");
271
+ info(" • undocumented components");
272
+ info(" • repeat offenders");
273
+ info(" • coverage");
274
+ process.exit(1);
275
+ }
276
+ }
277
+ catch (err) {
278
+ spin.stop();
279
+ const message = err instanceof Error ? err.message : String(err);
280
+ error(`Query failed: ${message}`);
281
+ process.exit(1);
282
+ }
283
+ });
284
+ }
285
+ // ============================================================================
286
+ // Export Command
287
+ // ============================================================================
288
+ function createExportCommand() {
289
+ return new Command("export")
290
+ .description("Export the graph for visualization")
291
+ .option("-f, --format <format>", "Output format (json, dot, cytoscape)", "json")
292
+ .option("-o, --output <file>", "Output file (default: stdout)")
293
+ .action(async (options) => {
294
+ const spin = spinner("Building graph for export...");
295
+ const projectRoot = process.cwd();
296
+ try {
297
+ // Build graph
298
+ const builder = new GraphBuilder({ projectId: "default" });
299
+ spin.text = "Collecting data...";
300
+ const gitResult = await collectGitHistory(projectRoot, { maxCount: 200 });
301
+ for (const commit of gitResult.commits) {
302
+ builder.addCommit(commit.sha, commit.message, commit.author, commit.authorEmail, commit.timestamp);
303
+ }
304
+ for (const dev of gitResult.developers) {
305
+ builder.addDeveloper(dev.id, dev.name, dev.email, undefined, dev.commitCount);
306
+ }
307
+ spin.text = "Collecting imports...";
308
+ const importResult = await collectImports({ projectRoot });
309
+ for (const imp of importResult.imports) {
310
+ if (!imp.isExternal) {
311
+ const sourceId = builder.addFile(imp.sourceFile, imp.sourceFile);
312
+ const targetId = builder.addFile(imp.targetFile, imp.targetFile);
313
+ builder.addEdge("IMPORTS", sourceId, targetId);
314
+ }
315
+ }
316
+ spin.stop();
317
+ const graph = builder.build();
318
+ let output;
319
+ switch (options.format) {
320
+ case "dot":
321
+ output = exportToDOT(graph);
322
+ break;
323
+ case "cytoscape":
324
+ output = JSON.stringify(exportToCytoscape(graph), null, 2);
325
+ break;
326
+ case "json":
327
+ default:
328
+ output = JSON.stringify(exportToJSON(graph), null, 2);
329
+ break;
330
+ }
331
+ if (options.output) {
332
+ const fs = await import("fs/promises");
333
+ await fs.writeFile(options.output, output, "utf-8");
334
+ success(`Graph exported to ${options.output}`);
335
+ }
336
+ else {
337
+ console.log(output);
338
+ }
339
+ }
340
+ catch (err) {
341
+ spin.stop();
342
+ const message = err instanceof Error ? err.message : String(err);
343
+ error(`Export failed: ${message}`);
344
+ process.exit(1);
345
+ }
346
+ });
347
+ }
348
+ // ============================================================================
349
+ // Stats Command
350
+ // ============================================================================
351
+ function createStatsCommand() {
352
+ return new Command("stats")
353
+ .description("Show graph statistics")
354
+ .option("--json", "Output as JSON")
355
+ .action(async (options) => {
356
+ if (options.json) {
357
+ setJsonMode(true);
358
+ }
359
+ const spin = spinner("Analyzing codebase...");
360
+ const projectRoot = process.cwd();
361
+ try {
362
+ // Collect basic stats without building full graph
363
+ spin.text = "Analyzing git history...";
364
+ const gitResult = await collectGitHistory(projectRoot, { maxCount: 1000 });
365
+ spin.text = "Analyzing usages...";
366
+ const usageResult = await collectUsages({ projectRoot });
367
+ spin.text = "Analyzing imports...";
368
+ const importResult = await collectImports({ projectRoot });
369
+ spin.stop();
370
+ const stats = {
371
+ git: {
372
+ commits: gitResult.commits.length,
373
+ developers: gitResult.developers.length,
374
+ dateRange: gitResult.stats.dateRange,
375
+ },
376
+ usages: {
377
+ tokenUsages: usageResult.tokenUsages.length,
378
+ componentUsages: usageResult.componentUsages.length,
379
+ hardcodedValues: usageResult.hardcodedValues.length,
380
+ filesScanned: usageResult.stats.filesScanned,
381
+ },
382
+ imports: {
383
+ localImports: importResult.imports.filter((i) => !i.isExternal).length,
384
+ externalPackages: importResult.externalDependencies.size,
385
+ filesScanned: importResult.stats.filesScanned,
386
+ },
387
+ };
388
+ if (options.json) {
389
+ console.log(JSON.stringify(stats, null, 2));
390
+ return;
391
+ }
392
+ header("Git History");
393
+ keyValue("Commits", String(stats.git.commits));
394
+ keyValue("Contributors", String(stats.git.developers));
395
+ if (stats.git.dateRange) {
396
+ keyValue("Date range", `${stats.git.dateRange.start.toLocaleDateString()} - ${stats.git.dateRange.end.toLocaleDateString()}`);
397
+ }
398
+ newline();
399
+ header("Token/Component Usages");
400
+ keyValue("Token usages", String(stats.usages.tokenUsages));
401
+ keyValue("Component usages", String(stats.usages.componentUsages));
402
+ keyValue("Hardcoded values", String(stats.usages.hardcodedValues));
403
+ keyValue("Files scanned", String(stats.usages.filesScanned));
404
+ newline();
405
+ header("Import Analysis");
406
+ keyValue("Local imports", String(stats.imports.localImports));
407
+ keyValue("External packages", String(stats.imports.externalPackages));
408
+ keyValue("Files scanned", String(stats.imports.filesScanned));
409
+ // Show top external deps
410
+ if (importResult.externalDependencies.size > 0) {
411
+ newline();
412
+ header("Top External Dependencies");
413
+ const deps = Array.from(importResult.externalDependencies).slice(0, 10);
414
+ for (const dep of deps) {
415
+ console.log(` ${chalk.blue("•")} ${dep}`);
416
+ }
417
+ if (importResult.externalDependencies.size > 10) {
418
+ info(` ... and ${importResult.externalDependencies.size - 10} more`);
419
+ }
420
+ }
421
+ }
422
+ catch (err) {
423
+ spin.stop();
424
+ const message = err instanceof Error ? err.message : String(err);
425
+ error(`Stats failed: ${message}`);
426
+ process.exit(1);
427
+ }
428
+ });
429
+ }
430
+ //# sourceMappingURL=graph.js.map