@principal-ai/principal-view-cli 0.1.26 → 0.1.28

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.
@@ -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
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2PpC,wBAAgB,iBAAiB,IAAI,OAAO,CA2L3C"}
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2PpC,wBAAgB,iBAAiB,IAAI,OAAO,CA4L3C"}
@@ -282,13 +282,14 @@ export function createLintCommand() {
282
282
  ignore: privuConfig.exclude || ['**/node_modules/**'],
283
283
  expandDirectories: false,
284
284
  });
285
- // Filter out library files, config files, and canvas files (canvas files use `validate` command)
285
+ // Filter out library files, config files, canvas files, and execution artifacts
286
286
  const configFiles = matchedFiles.filter((f) => {
287
287
  const name = basename(f).toLowerCase();
288
288
  const isLibraryFile = name.startsWith('library.');
289
289
  const isConfigFile = name.startsWith('.privurc');
290
290
  const isCanvasFile = f.toLowerCase().endsWith('.canvas');
291
- return !isLibraryFile && !isConfigFile && !isCanvasFile;
291
+ const isExecutionArtifact = f.includes('__executions__/');
292
+ return !isLibraryFile && !isConfigFile && !isCanvasFile && !isExecutionArtifact;
292
293
  });
293
294
  if (configFiles.length === 0) {
294
295
  if (options.json) {