@principal-ai/principal-view-cli 0.1.27 ā 0.1.29
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.
- package/dist/commands/coverage.d.ts +9 -0
- package/dist/commands/coverage.d.ts.map +1 -0
- package/dist/commands/coverage.js +158 -0
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +3 -2
- package/dist/index.cjs +12472 -6440
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +3 -1
- package/package.json +11 -11
|
@@ -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,
|
|
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,CA6L3C"}
|
package/dist/commands/lint.js
CHANGED
|
@@ -282,14 +282,15 @@ export function createLintCommand() {
|
|
|
282
282
|
ignore: privuConfig.exclude || ['**/node_modules/**'],
|
|
283
283
|
expandDirectories: false,
|
|
284
284
|
});
|
|
285
|
-
// Filter out library files, config files, canvas files, and execution artifacts
|
|
285
|
+
// Filter out library files, config files, canvas files, narrative templates, 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
|
+
const isNarrativeTemplate = name.endsWith('.narrative.json');
|
|
291
292
|
const isExecutionArtifact = f.includes('__executions__/');
|
|
292
|
-
return !isLibraryFile && !isConfigFile && !isCanvasFile && !isExecutionArtifact;
|
|
293
|
+
return !isLibraryFile && !isConfigFile && !isCanvasFile && !isNarrativeTemplate && !isExecutionArtifact;
|
|
293
294
|
});
|
|
294
295
|
if (configFiles.length === 0) {
|
|
295
296
|
if (options.json) {
|