@principal-ai/principal-view-cli 0.3.3 → 0.3.5

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 (58) hide show
  1. package/dist/commands/coverage.d.ts +9 -0
  2. package/dist/commands/coverage.d.ts.map +1 -0
  3. package/dist/commands/coverage.js +158 -0
  4. package/dist/commands/create.d.ts +6 -0
  5. package/dist/commands/create.d.ts.map +1 -0
  6. package/dist/commands/create.js +50 -0
  7. package/dist/commands/doctor.d.ts +10 -0
  8. package/dist/commands/doctor.d.ts.map +1 -0
  9. package/dist/commands/doctor.js +274 -0
  10. package/dist/commands/formats.d.ts +6 -0
  11. package/dist/commands/formats.d.ts.map +1 -0
  12. package/dist/commands/formats.js +475 -0
  13. package/dist/commands/hooks.d.ts +9 -0
  14. package/dist/commands/hooks.d.ts.map +1 -0
  15. package/dist/commands/hooks.js +295 -0
  16. package/dist/commands/init.d.ts +6 -0
  17. package/dist/commands/init.d.ts.map +1 -0
  18. package/dist/commands/init.js +271 -0
  19. package/dist/commands/lint.d.ts +6 -0
  20. package/dist/commands/lint.d.ts.map +1 -0
  21. package/dist/commands/lint.js +506 -0
  22. package/dist/commands/list.d.ts +6 -0
  23. package/dist/commands/list.d.ts.map +1 -0
  24. package/dist/commands/list.js +80 -0
  25. package/dist/commands/narrative/index.d.ts +3 -0
  26. package/dist/commands/narrative/index.d.ts.map +1 -0
  27. package/dist/commands/narrative/index.js +17 -0
  28. package/dist/commands/narrative/inspect.d.ts +3 -0
  29. package/dist/commands/narrative/inspect.d.ts.map +1 -0
  30. package/dist/commands/narrative/inspect.js +109 -0
  31. package/dist/commands/narrative/list.d.ts +3 -0
  32. package/dist/commands/narrative/list.d.ts.map +1 -0
  33. package/dist/commands/narrative/list.js +101 -0
  34. package/dist/commands/narrative/render.d.ts +3 -0
  35. package/dist/commands/narrative/render.d.ts.map +1 -0
  36. package/dist/commands/narrative/render.js +99 -0
  37. package/dist/commands/narrative/test.d.ts +3 -0
  38. package/dist/commands/narrative/test.d.ts.map +1 -0
  39. package/dist/commands/narrative/test.js +150 -0
  40. package/dist/commands/narrative/utils.d.ts +49 -0
  41. package/dist/commands/narrative/utils.d.ts.map +1 -0
  42. package/dist/commands/narrative/utils.js +164 -0
  43. package/dist/commands/narrative/validate.d.ts +3 -0
  44. package/dist/commands/narrative/validate.d.ts.map +1 -0
  45. package/dist/commands/narrative/validate.js +149 -0
  46. package/dist/commands/schema.d.ts +6 -0
  47. package/dist/commands/schema.d.ts.map +1 -0
  48. package/dist/commands/schema.js +336 -0
  49. package/dist/commands/validate-execution.d.ts +11 -0
  50. package/dist/commands/validate-execution.d.ts.map +1 -0
  51. package/dist/commands/validate-execution.js +223 -0
  52. package/dist/commands/validate.d.ts +6 -0
  53. package/dist/commands/validate.d.ts.map +1 -0
  54. package/dist/commands/validate.js +1065 -0
  55. package/dist/index.d.ts +8 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +45 -0
  58. package/package.json +2 -2
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Coverage command - Measure telemetry coverage for canvas nodes
3
+ *
4
+ * This command analyzes .otel.canvas files and checks which nodes have
5
+ * OpenTelemetry instrumentation in their linked source files.
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare function createCoverageCommand(): Command;
9
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/commands/coverage.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgIpC,wBAAgB,qBAAqB,IAAI,OAAO,CAiD/C"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Coverage command - Measure telemetry coverage for canvas nodes
3
+ *
4
+ * This command analyzes .otel.canvas files and checks which nodes have
5
+ * OpenTelemetry instrumentation in their linked source files.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { resolve } from 'node:path';
9
+ import chalk from 'chalk';
10
+ import { analyzeCoverage } from '@principal-ai/principal-view-core';
11
+ /**
12
+ * Print coverage report in human-readable format
13
+ */
14
+ function printCoverageReport(metrics, rootDir, options) {
15
+ const { verbose } = options;
16
+ console.log(chalk.bold('\n' + '─'.repeat(70)));
17
+ console.log(chalk.bold('šŸ“ˆ TELEMETRY COVERAGE REPORT'));
18
+ console.log(chalk.bold('─'.repeat(70)));
19
+ // Canvas files
20
+ console.log(chalk.bold(`\nšŸ“‹ Canvas Files Analyzed: ${metrics.canvasFiles.length}`));
21
+ if (verbose) {
22
+ metrics.canvasFiles.forEach(f => console.log(chalk.dim(` - ${f}`)));
23
+ }
24
+ // Node summary
25
+ console.log(chalk.bold('\nšŸŽÆ Node Summary:'));
26
+ console.log(` Total nodes: ${metrics.totalNodes}`);
27
+ console.log(` Nodes with anchors: ${chalk.cyan(metrics.nodesWithFiles.toString())}`);
28
+ console.log(` Nodes without anchors: ${chalk.dim(metrics.totalNodes - metrics.nodesWithFiles)}`);
29
+ console.log(` Nodes with instrumentation: ${chalk.green(metrics.nodesWithInstrumentation.toString())}`);
30
+ const coverageColor = metrics.coveragePercentage >= 80
31
+ ? chalk.green
32
+ : metrics.coveragePercentage >= 50
33
+ ? chalk.yellow
34
+ : chalk.red;
35
+ console.log(` Coverage: ${coverageColor(metrics.coveragePercentage.toFixed(1) + '%')}`);
36
+ // Nodes WITHOUT anchors
37
+ const noAnchors = metrics.nodeCoverage.filter(n => n.filePaths.length === 0);
38
+ if (noAnchors.length > 0 && verbose) {
39
+ console.log(chalk.yellow(`\nāš ļø Nodes Without Anchors (${noAnchors.length}):`));
40
+ const display = verbose ? noAnchors : noAnchors.slice(0, 10);
41
+ display.forEach(node => {
42
+ console.log(chalk.dim(` - ${node.nodeId}`));
43
+ });
44
+ if (!verbose && noAnchors.length > 10) {
45
+ console.log(chalk.dim(` ... and ${noAnchors.length - 10} more (use --verbose to see all)`));
46
+ }
47
+ }
48
+ // Nodes WITHOUT instrumentation
49
+ const uninstrumented = metrics.nodeCoverage.filter(n => n.filePaths.length > 0 && !n.hasInstrumentation);
50
+ if (uninstrumented.length > 0) {
51
+ console.log(chalk.red(`\nāŒ Nodes Missing Instrumentation (${uninstrumented.length}):`));
52
+ const display = verbose ? uninstrumented : uninstrumented.slice(0, 5);
53
+ display.forEach(node => {
54
+ console.log(chalk.yellow(`\n Node: ${node.nodeId}`));
55
+ console.log(chalk.dim(` Files: ${node.filePaths.join(', ')}`));
56
+ });
57
+ if (!verbose && uninstrumented.length > 5) {
58
+ console.log(chalk.dim(`\n ... and ${uninstrumented.length - 5} more (use --verbose to see all)`));
59
+ }
60
+ }
61
+ // Nodes WITH instrumentation
62
+ const instrumented = metrics.nodeCoverage.filter(n => n.hasInstrumentation);
63
+ if (instrumented.length > 0) {
64
+ console.log(chalk.green(`\nāœ… Nodes With Instrumentation (${instrumented.length}):`));
65
+ const display = verbose ? instrumented : instrumented.slice(0, 5);
66
+ display.forEach(node => {
67
+ console.log(chalk.dim(` - ${node.nodeId}: ${node.instrumentedFiles.join(', ')}`));
68
+ });
69
+ if (!verbose && instrumented.length > 5) {
70
+ console.log(chalk.dim(` ... and ${instrumented.length - 5} more (use --verbose to see all)`));
71
+ }
72
+ }
73
+ // Nodes with missing files
74
+ const withMissingFiles = metrics.nodeCoverage.filter(n => n.missingFiles.length > 0);
75
+ if (withMissingFiles.length > 0) {
76
+ console.log(chalk.yellow(`\nāš ļø Nodes Referencing Missing Files (${withMissingFiles.length}):`));
77
+ const display = verbose ? withMissingFiles : withMissingFiles.slice(0, 5);
78
+ display.forEach(node => {
79
+ console.log(chalk.dim(` - ${node.nodeId}: ${node.missingFiles.join(', ')}`));
80
+ });
81
+ if (!verbose && withMissingFiles.length > 5) {
82
+ console.log(chalk.dim(` ... and ${withMissingFiles.length - 5} more (use --verbose to see all)`));
83
+ }
84
+ }
85
+ // Recommendations
86
+ console.log(chalk.bold('\nšŸ’” Recommendations:'));
87
+ if (noAnchors.length > 0) {
88
+ console.log(chalk.red(' šŸ”“ Add anchors to canvas nodes before measuring coverage'));
89
+ }
90
+ else if (metrics.coveragePercentage < 30) {
91
+ console.log(chalk.red(' šŸ”“ Low coverage - add OTEL instrumentation to files referenced in canvas'));
92
+ }
93
+ else if (metrics.coveragePercentage < 60) {
94
+ console.log(chalk.yellow(' 🟔 Moderate coverage - continue instrumenting remaining nodes'));
95
+ }
96
+ else {
97
+ console.log(chalk.green(' 🟢 Good coverage - maintain instrumentation quality'));
98
+ }
99
+ // Next steps
100
+ console.log(chalk.bold('\nšŸ“ Next Steps:'));
101
+ if (noAnchors.length > 0) {
102
+ console.log(chalk.dim(` 1. Add anchors to ${noAnchors.length} node(s) without file references`));
103
+ console.log(chalk.dim(` 2. Example: { "id": "node-id", "anchors": [{ "path": "packages/core/src/File.ts" }] }`));
104
+ console.log(chalk.dim(` 3. Re-run: privu coverage`));
105
+ }
106
+ else if (uninstrumented.length > 0) {
107
+ console.log(chalk.dim(` 1. Review the ${uninstrumented.length} uninstrumented node(s) above`));
108
+ console.log(chalk.dim(` 2. Add OpenTelemetry spans to their source files`));
109
+ console.log(chalk.dim(` 3. Follow patterns from packages/core/src/rules/engine.ts`));
110
+ console.log(chalk.dim(` 4. Re-run: privu coverage`));
111
+ }
112
+ else {
113
+ console.log(chalk.green(` āœ… All canvas nodes have instrumentation!`));
114
+ }
115
+ console.log(chalk.bold('\n' + '─'.repeat(70) + '\n'));
116
+ }
117
+ export function createCoverageCommand() {
118
+ const command = new Command('coverage');
119
+ command
120
+ .description('Measure telemetry coverage for canvas nodes')
121
+ .option('-d, --dir <path>', 'Project directory (defaults to current directory)')
122
+ .option('--json', 'Output results as JSON')
123
+ .option('-t, --threshold <percentage>', 'Minimum coverage percentage (exit with error if below)')
124
+ .option('-v, --verbose', 'Show all nodes in output')
125
+ .action(async (options) => {
126
+ try {
127
+ const rootDir = resolve(options.dir || process.cwd());
128
+ // Analyze coverage
129
+ const metrics = await analyzeCoverage(rootDir);
130
+ // Output results
131
+ if (options.json) {
132
+ console.log(JSON.stringify(metrics, null, 2));
133
+ }
134
+ else {
135
+ printCoverageReport(metrics, rootDir, options);
136
+ }
137
+ // Check threshold
138
+ if (options.threshold) {
139
+ const threshold = parseFloat(options.threshold);
140
+ if (isNaN(threshold) || threshold < 0 || threshold > 100) {
141
+ console.error(chalk.red('Error: Threshold must be a number between 0 and 100'));
142
+ process.exit(1);
143
+ }
144
+ if (metrics.coveragePercentage < threshold) {
145
+ if (!options.json) {
146
+ console.error(chalk.red(`\nāœ— Coverage ${metrics.coveragePercentage.toFixed(1)}% is below threshold ${threshold}%`));
147
+ }
148
+ process.exit(1);
149
+ }
150
+ }
151
+ }
152
+ catch (error) {
153
+ console.error(chalk.red('Error:'), error.message);
154
+ process.exit(1);
155
+ }
156
+ });
157
+ return command;
158
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Create command - Create a new canvas file in the .principal-views folder
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function createCreateCommand(): Command;
6
+ //# sourceMappingURL=create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,mBAAmB,IAAI,OAAO,CAkD7C"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Create command - Create a new canvas file in the .principal-views folder
3
+ */
4
+ import { Command } from 'commander';
5
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import chalk from 'chalk';
8
+ const TEMPLATE_CANVAS = {
9
+ nodes: [],
10
+ edges: [],
11
+ };
12
+ export function createCreateCommand() {
13
+ const command = new Command('create');
14
+ command
15
+ .description('Create a new canvas file in the .principal-views folder')
16
+ .requiredOption('-n, --name <name>', 'Name for the canvas file (e.g., "cache-sync-architecture")')
17
+ .option('-f, --force', 'Overwrite existing file')
18
+ .action(async (options) => {
19
+ try {
20
+ const vgcDir = join(process.cwd(), '.principal-views');
21
+ const canvasFile = join(vgcDir, `${options.name}.canvas`);
22
+ // Check if .principal-views directory exists
23
+ if (!existsSync(vgcDir)) {
24
+ mkdirSync(vgcDir, { recursive: true });
25
+ console.log(chalk.green(`Created directory: .principal-views/`));
26
+ }
27
+ // Check if canvas file already exists
28
+ if (existsSync(canvasFile) && !options.force) {
29
+ console.error(chalk.red(`Error: Canvas file already exists: .principal-views/${options.name}.canvas`));
30
+ console.log(chalk.yellow(`Use ${chalk.cyan('--force')} to overwrite`));
31
+ process.exit(1);
32
+ }
33
+ // Create the canvas file
34
+ writeFileSync(canvasFile, JSON.stringify(TEMPLATE_CANVAS, null, 2));
35
+ console.log(chalk.green(`āœ“ Created canvas file: .principal-views/${options.name}.canvas`));
36
+ // Show next steps
37
+ console.log('');
38
+ console.log(chalk.bold('Next steps:'));
39
+ console.log(` 1. Open ${chalk.cyan(`.principal-views/${options.name}.canvas`)} in your editor`);
40
+ console.log(` 2. Add nodes and edges to define your architecture`);
41
+ console.log(` 3. Run ${chalk.cyan('npx @principal-ai/principal-view-cli validate')} to check your configuration`);
42
+ console.log(` 4. Run ${chalk.cyan('npx @principal-ai/principal-view-cli doctor')} to verify source mappings`);
43
+ }
44
+ catch (error) {
45
+ console.error(chalk.red('Error:'), error.message);
46
+ process.exit(1);
47
+ }
48
+ });
49
+ return command;
50
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Doctor command - Check configuration staleness and source pattern validity
3
+ *
4
+ * This command performs two types of checks:
5
+ * 1. Pattern validation: Ensures source patterns in .principal-views/*.yaml configs match actual files
6
+ * 2. Freshness check: Compares config modification times vs source file changes
7
+ */
8
+ import { Command } from 'commander';
9
+ export declare function createDoctorCommand(): Command;
10
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmLpC,wBAAgB,mBAAmB,IAAI,OAAO,CAsK7C"}
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Doctor command - Check configuration staleness and source pattern validity
3
+ *
4
+ * This command performs two types of checks:
5
+ * 1. Pattern validation: Ensures source patterns in .principal-views/*.yaml configs match actual files
6
+ * 2. Freshness check: Compares config modification times vs source file changes
7
+ */
8
+ import { Command } from 'commander';
9
+ import { existsSync, readFileSync, statSync } from 'node:fs';
10
+ import { resolve, relative } from 'node:path';
11
+ import chalk from 'chalk';
12
+ import { globby } from 'globby';
13
+ import yaml from 'js-yaml';
14
+ /**
15
+ * Format a time difference in human-readable form
16
+ */
17
+ function formatTimeDiff(ms) {
18
+ const seconds = Math.floor(ms / 1000);
19
+ const minutes = Math.floor(seconds / 60);
20
+ const hours = Math.floor(minutes / 60);
21
+ const days = Math.floor(hours / 24);
22
+ if (days > 0)
23
+ return `${days} day${days > 1 ? 's' : ''}`;
24
+ if (hours > 0)
25
+ return `${hours} hour${hours > 1 ? 's' : ''}`;
26
+ if (minutes > 0)
27
+ return `${minutes} minute${minutes > 1 ? 's' : ''}`;
28
+ return `${seconds} second${seconds !== 1 ? 's' : ''}`;
29
+ }
30
+ /**
31
+ * Check a single .principal-views config file for staleness issues
32
+ */
33
+ async function checkConfig(configPath, projectRoot) {
34
+ const absolutePath = resolve(configPath);
35
+ const relativePath = relative(projectRoot, absolutePath);
36
+ const issues = [];
37
+ const stats = {
38
+ nodeTypesChecked: 0,
39
+ patternsChecked: 0,
40
+ filesMatched: 0,
41
+ staleConfigs: 0,
42
+ };
43
+ let configName = 'Unknown';
44
+ try {
45
+ const content = readFileSync(absolutePath, 'utf8');
46
+ const config = yaml.load(content);
47
+ const configStats = statSync(absolutePath);
48
+ const configMtime = configStats.mtime.getTime();
49
+ configName = config.metadata?.name || relativePath;
50
+ // Check if config has nodeTypes with sources
51
+ if (!config.nodeTypes || Object.keys(config.nodeTypes).length === 0) {
52
+ issues.push({
53
+ type: 'info',
54
+ configFile: relativePath,
55
+ message: 'No node types defined in configuration',
56
+ });
57
+ return { configFile: relativePath, configName, issues, stats };
58
+ }
59
+ // Check each node type's source patterns
60
+ for (const [nodeTypeName, nodeType] of Object.entries(config.nodeTypes)) {
61
+ stats.nodeTypesChecked++;
62
+ if (!nodeType.sources || nodeType.sources.length === 0) {
63
+ issues.push({
64
+ type: 'info',
65
+ configFile: relativePath,
66
+ nodeType: nodeTypeName,
67
+ message: `Node type "${nodeTypeName}" has no source patterns defined`,
68
+ });
69
+ continue;
70
+ }
71
+ for (const pattern of nodeType.sources) {
72
+ stats.patternsChecked++;
73
+ // Find files matching this pattern
74
+ const matchedFiles = await globby(pattern, {
75
+ cwd: projectRoot,
76
+ gitignore: true,
77
+ ignore: ['node_modules/**', 'dist/**', '.git/**'],
78
+ });
79
+ if (matchedFiles.length === 0) {
80
+ // Pattern doesn't match any files - this is a warning
81
+ issues.push({
82
+ type: 'warning',
83
+ configFile: relativePath,
84
+ nodeType: nodeTypeName,
85
+ message: `Source pattern "${pattern}" doesn't match any files`,
86
+ details: 'This pattern may be outdated or the files may have been moved/deleted',
87
+ });
88
+ }
89
+ else {
90
+ stats.filesMatched += matchedFiles.length;
91
+ // Check if any matched files are newer than the config
92
+ let newestFile = null;
93
+ let newestMtime = 0;
94
+ for (const file of matchedFiles) {
95
+ const filePath = resolve(projectRoot, file);
96
+ try {
97
+ const fileStats = statSync(filePath);
98
+ const fileMtime = fileStats.mtime.getTime();
99
+ if (fileMtime > newestMtime) {
100
+ newestMtime = fileMtime;
101
+ newestFile = file;
102
+ }
103
+ }
104
+ catch {
105
+ // File may have been deleted between glob and stat
106
+ }
107
+ }
108
+ // Check staleness (with 5-second buffer for build tools)
109
+ const STALE_THRESHOLD_MS = 5000;
110
+ if (newestFile && newestMtime > configMtime + STALE_THRESHOLD_MS) {
111
+ const timeDiff = newestMtime - configMtime;
112
+ stats.staleConfigs++;
113
+ issues.push({
114
+ type: 'warning',
115
+ configFile: relativePath,
116
+ nodeType: nodeTypeName,
117
+ message: `Config may be stale: "${newestFile}" was modified ${formatTimeDiff(timeDiff)} after the config`,
118
+ details: `Pattern: ${pattern} (matched ${matchedFiles.length} file${matchedFiles.length > 1 ? 's' : ''})`,
119
+ });
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ catch (error) {
126
+ issues.push({
127
+ type: 'error',
128
+ configFile: relativePath,
129
+ message: `Failed to parse config: ${error.message}`,
130
+ });
131
+ }
132
+ return { configFile: relativePath, configName, issues, stats };
133
+ }
134
+ export function createDoctorCommand() {
135
+ const command = new Command('doctor');
136
+ command
137
+ .description('Check configuration staleness and source pattern validity')
138
+ .option('-q, --quiet', 'Only show errors and warnings')
139
+ .option('--errors-only', 'Only show errors (for pre-commit hooks)')
140
+ .option('--json', 'Output results as JSON')
141
+ .option('-d, --dir <path>', 'Project directory (defaults to current directory)')
142
+ .action(async (options) => {
143
+ try {
144
+ const projectRoot = resolve(options.dir || process.cwd());
145
+ const vgcDir = resolve(projectRoot, '.principal-views');
146
+ if (!existsSync(vgcDir)) {
147
+ if (options.json) {
148
+ console.log(JSON.stringify({ error: 'No .principal-views directory found', results: [] }));
149
+ }
150
+ else {
151
+ console.log(chalk.yellow('No .principal-views directory found.'));
152
+ console.log(chalk.dim('Run "npx @principal-ai/principal-view-cli init" to create a configuration.'));
153
+ }
154
+ return;
155
+ }
156
+ // Find all .yaml config files
157
+ const configFiles = await globby(['*.yaml', '*.yml'], {
158
+ cwd: vgcDir,
159
+ absolute: true,
160
+ ignore: ['README.md'],
161
+ });
162
+ if (configFiles.length === 0) {
163
+ if (options.json) {
164
+ console.log(JSON.stringify({ error: 'No config files found in .principal-views', results: [] }));
165
+ }
166
+ else {
167
+ console.log(chalk.yellow('No configuration files found in .principal-views/'));
168
+ }
169
+ return;
170
+ }
171
+ // Check each config
172
+ const results = [];
173
+ for (const configFile of configFiles) {
174
+ const result = await checkConfig(configFile, projectRoot);
175
+ results.push(result);
176
+ }
177
+ // Aggregate stats
178
+ const totalStats = results.reduce((acc, r) => ({
179
+ nodeTypesChecked: acc.nodeTypesChecked + r.stats.nodeTypesChecked,
180
+ patternsChecked: acc.patternsChecked + r.stats.patternsChecked,
181
+ filesMatched: acc.filesMatched + r.stats.filesMatched,
182
+ staleConfigs: acc.staleConfigs + r.stats.staleConfigs,
183
+ }), { nodeTypesChecked: 0, patternsChecked: 0, filesMatched: 0, staleConfigs: 0 });
184
+ // Filter issues based on options
185
+ const filterIssues = (issues) => {
186
+ if (options.errorsOnly) {
187
+ return issues.filter((i) => i.type === 'error');
188
+ }
189
+ if (options.quiet) {
190
+ return issues.filter((i) => i.type === 'error' || i.type === 'warning');
191
+ }
192
+ return issues;
193
+ };
194
+ // Count issues
195
+ const allIssues = results.flatMap((r) => filterIssues(r.issues));
196
+ const errorCount = allIssues.filter((i) => i.type === 'error').length;
197
+ const warningCount = allIssues.filter((i) => i.type === 'warning').length;
198
+ // Output results
199
+ if (options.json) {
200
+ console.log(JSON.stringify({
201
+ results: results.map((r) => ({
202
+ ...r,
203
+ issues: filterIssues(r.issues),
204
+ })),
205
+ summary: {
206
+ configs: results.length,
207
+ errors: errorCount,
208
+ warnings: warningCount,
209
+ ...totalStats,
210
+ },
211
+ }, null, 2));
212
+ }
213
+ else {
214
+ if (!options.quiet && !options.errorsOnly) {
215
+ console.log(chalk.bold(`\nChecking ${results.length} configuration file(s)...\n`));
216
+ }
217
+ for (const result of results) {
218
+ const issues = filterIssues(result.issues);
219
+ if (issues.length === 0 && !options.quiet && !options.errorsOnly) {
220
+ console.log(chalk.green(`āœ“ ${result.configFile}`) + chalk.dim(` (${result.configName})`));
221
+ continue;
222
+ }
223
+ if (issues.length > 0) {
224
+ const hasErrors = issues.some((i) => i.type === 'error');
225
+ const icon = hasErrors ? chalk.red('āœ—') : chalk.yellow('⚠');
226
+ console.log(`${icon} ${result.configFile}` + chalk.dim(` (${result.configName})`));
227
+ for (const issue of issues) {
228
+ const prefix = issue.nodeType ? `[${issue.nodeType}] ` : '';
229
+ if (issue.type === 'error') {
230
+ console.log(chalk.red(` āœ— ${prefix}${issue.message}`));
231
+ }
232
+ else if (issue.type === 'warning') {
233
+ console.log(chalk.yellow(` ⚠ ${prefix}${issue.message}`));
234
+ }
235
+ else {
236
+ console.log(chalk.dim(` ℹ ${prefix}${issue.message}`));
237
+ }
238
+ if (issue.details) {
239
+ console.log(chalk.dim(` → ${issue.details}`));
240
+ }
241
+ }
242
+ console.log('');
243
+ }
244
+ }
245
+ // Summary
246
+ if (!options.errorsOnly) {
247
+ console.log(chalk.dim('─'.repeat(50)));
248
+ console.log(chalk.dim(`Checked ${totalStats.nodeTypesChecked} node types, ` +
249
+ `${totalStats.patternsChecked} patterns, ` +
250
+ `matched ${totalStats.filesMatched} files`));
251
+ }
252
+ if (errorCount > 0) {
253
+ console.log(chalk.red(`\nāœ— ${errorCount} error(s) found`));
254
+ process.exit(1);
255
+ }
256
+ else if (warningCount > 0 && options.errorsOnly) {
257
+ // In errors-only mode, don't fail on warnings
258
+ process.exit(0);
259
+ }
260
+ else if (warningCount > 0) {
261
+ console.log(chalk.yellow(`\n⚠ ${warningCount} warning(s) found`));
262
+ }
263
+ else if (!options.quiet && !options.errorsOnly) {
264
+ console.log(chalk.green(`\nāœ“ All configurations are up to date`));
265
+ }
266
+ }
267
+ }
268
+ catch (error) {
269
+ console.error(chalk.red('Error:'), error.message);
270
+ process.exit(1);
271
+ }
272
+ });
273
+ return command;
274
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Formats command - Display documentation about file formats
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function createFormatsCommand(): Command;
6
+ //# sourceMappingURL=formats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/commands/formats.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAycpC,wBAAgB,oBAAoB,IAAI,OAAO,CA6B9C"}