@principal-ai/principal-view-cli 0.1.30 → 0.1.31

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 (35) hide show
  1. package/dist/commands/lint.d.ts.map +1 -1
  2. package/dist/commands/lint.js +13 -8
  3. package/dist/commands/narrative/eval.d.ts +3 -0
  4. package/dist/commands/narrative/eval.d.ts.map +1 -0
  5. package/dist/commands/narrative/eval.js +76 -0
  6. package/dist/commands/narrative/index.d.ts +3 -0
  7. package/dist/commands/narrative/index.d.ts.map +1 -0
  8. package/dist/commands/narrative/index.js +19 -0
  9. package/dist/commands/narrative/inspect.d.ts +3 -0
  10. package/dist/commands/narrative/inspect.d.ts.map +1 -0
  11. package/dist/commands/narrative/inspect.js +109 -0
  12. package/dist/commands/narrative/list.d.ts +3 -0
  13. package/dist/commands/narrative/list.d.ts.map +1 -0
  14. package/dist/commands/narrative/list.js +101 -0
  15. package/dist/commands/narrative/render.d.ts +3 -0
  16. package/dist/commands/narrative/render.d.ts.map +1 -0
  17. package/dist/commands/narrative/render.js +99 -0
  18. package/dist/commands/narrative/test.d.ts +3 -0
  19. package/dist/commands/narrative/test.d.ts.map +1 -0
  20. package/dist/commands/narrative/test.js +150 -0
  21. package/dist/commands/narrative/utils.d.ts +69 -0
  22. package/dist/commands/narrative/utils.d.ts.map +1 -0
  23. package/dist/commands/narrative/utils.js +158 -0
  24. package/dist/commands/narrative/validate.d.ts +3 -0
  25. package/dist/commands/narrative/validate.d.ts.map +1 -0
  26. package/dist/commands/narrative/validate.js +135 -0
  27. package/dist/commands/validate-execution.d.ts +12 -0
  28. package/dist/commands/validate-execution.d.ts.map +1 -0
  29. package/dist/commands/validate-execution.js +230 -0
  30. package/dist/commands/validate.d.ts.map +1 -1
  31. package/dist/commands/validate.js +0 -4
  32. package/dist/index.js +4 -0
  33. package/package.json +3 -2
  34. package/dist/index.cjs +0 -24870
  35. package/dist/index.cjs.map +0 -7
@@ -0,0 +1,69 @@
1
+ import type { NarrativeTemplate, OtelEvent } from '@principal-ai/principal-view-core';
2
+ export interface ExecutionData {
3
+ metadata?: {
4
+ status?: string;
5
+ testName?: string;
6
+ sessionId?: string;
7
+ startTime?: number;
8
+ endTime?: number;
9
+ };
10
+ spans: Array<{
11
+ id: string;
12
+ name: string;
13
+ startTime?: number;
14
+ endTime?: number;
15
+ duration?: number;
16
+ status?: 'OK' | 'ERROR';
17
+ attributes?: Record<string, unknown>;
18
+ events: Array<{
19
+ time: number;
20
+ name: string;
21
+ attributes: Record<string, unknown>;
22
+ }>;
23
+ }>;
24
+ }
25
+ /**
26
+ * Load a narrative template from a file
27
+ */
28
+ export declare function loadNarrative(filePath: string): Promise<NarrativeTemplate>;
29
+ /**
30
+ * Load execution data from a .otel.json file
31
+ */
32
+ export declare function loadExecution(filePath: string): Promise<ExecutionData>;
33
+ /**
34
+ * Convert execution data to OtelEvent array format expected by narrative APIs
35
+ */
36
+ export declare function executionToEvents(execution: ExecutionData): OtelEvent[];
37
+ /**
38
+ * Resolve a file path relative to a base directory
39
+ */
40
+ export declare function resolvePath(filePath: string, baseDir?: string): string;
41
+ /**
42
+ * Format a timestamp as human-readable date/time
43
+ */
44
+ export declare function formatTimestamp(timestamp: number): string;
45
+ /**
46
+ * Format duration in milliseconds
47
+ */
48
+ export declare function formatDuration(ms: number): string;
49
+ /**
50
+ * Group attributes by prefix (e.g., 'auth.method' -> 'auth')
51
+ */
52
+ export declare function groupAttributesByPrefix(attributes: Record<string, unknown>): Record<string, Record<string, unknown>>;
53
+ /**
54
+ * Filter attributes by pattern (supports glob-like patterns)
55
+ */
56
+ export declare function filterAttributes(attributes: Record<string, unknown>, pattern: string): Record<string, unknown>;
57
+ /**
58
+ * Count events by type/name
59
+ */
60
+ export declare function countEventsByType(events: OtelEvent[]): Map<string, number>;
61
+ /**
62
+ * Format attribute value for display
63
+ */
64
+ export declare function formatValue(value: unknown): string;
65
+ /**
66
+ * Capitalize first letter of a string
67
+ */
68
+ export declare function capitalize(str: string): string;
69
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;QACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,MAAM,EAAE,KAAK,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SACrC,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAUhF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU5E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,CAmBvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAclD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
@@ -0,0 +1,158 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ /**
4
+ * Load a narrative template from a file
5
+ */
6
+ export async function loadNarrative(filePath) {
7
+ try {
8
+ const content = await readFile(filePath, 'utf-8');
9
+ return JSON.parse(content);
10
+ }
11
+ catch (error) {
12
+ if (error.code === 'ENOENT') {
13
+ throw new Error(`Narrative file not found: ${filePath}`);
14
+ }
15
+ throw new Error(`Failed to parse narrative file: ${error.message}`);
16
+ }
17
+ }
18
+ /**
19
+ * Load execution data from a .otel.json file
20
+ */
21
+ export async function loadExecution(filePath) {
22
+ try {
23
+ const content = await readFile(filePath, 'utf-8');
24
+ return JSON.parse(content);
25
+ }
26
+ catch (error) {
27
+ if (error.code === 'ENOENT') {
28
+ throw new Error(`Execution file not found: ${filePath}`);
29
+ }
30
+ throw new Error(`Failed to parse execution file: ${error.message}`);
31
+ }
32
+ }
33
+ /**
34
+ * Convert execution data to OtelEvent array format expected by narrative APIs
35
+ */
36
+ export function executionToEvents(execution) {
37
+ const events = [];
38
+ for (const span of execution.spans) {
39
+ for (const event of span.events) {
40
+ events.push({
41
+ name: event.name,
42
+ timestamp: event.time,
43
+ type: 'log',
44
+ attributes: {
45
+ ...event.attributes,
46
+ 'span.id': span.id,
47
+ 'span.name': span.name,
48
+ },
49
+ });
50
+ }
51
+ }
52
+ return events;
53
+ }
54
+ /**
55
+ * Resolve a file path relative to a base directory
56
+ */
57
+ export function resolvePath(filePath, baseDir) {
58
+ if (baseDir) {
59
+ return resolve(baseDir, filePath);
60
+ }
61
+ return resolve(filePath);
62
+ }
63
+ /**
64
+ * Format a timestamp as human-readable date/time
65
+ */
66
+ export function formatTimestamp(timestamp) {
67
+ const date = new Date(timestamp);
68
+ return date.toLocaleString('en-US', {
69
+ year: 'numeric',
70
+ month: '2-digit',
71
+ day: '2-digit',
72
+ hour: '2-digit',
73
+ minute: '2-digit',
74
+ second: '2-digit',
75
+ hour12: false,
76
+ });
77
+ }
78
+ /**
79
+ * Format duration in milliseconds
80
+ */
81
+ export function formatDuration(ms) {
82
+ if (ms < 1000) {
83
+ return `${ms}ms`;
84
+ }
85
+ const seconds = (ms / 1000).toFixed(1);
86
+ return `${seconds}s`;
87
+ }
88
+ /**
89
+ * Group attributes by prefix (e.g., 'auth.method' -> 'auth')
90
+ */
91
+ export function groupAttributesByPrefix(attributes) {
92
+ const grouped = {};
93
+ for (const [key, value] of Object.entries(attributes)) {
94
+ const parts = key.split('.');
95
+ if (parts.length > 1) {
96
+ const prefix = parts[0];
97
+ if (!grouped[prefix]) {
98
+ grouped[prefix] = {};
99
+ }
100
+ grouped[prefix][key] = value;
101
+ }
102
+ else {
103
+ if (!grouped['Other']) {
104
+ grouped['Other'] = {};
105
+ }
106
+ grouped['Other'][key] = value;
107
+ }
108
+ }
109
+ return grouped;
110
+ }
111
+ /**
112
+ * Filter attributes by pattern (supports glob-like patterns)
113
+ */
114
+ export function filterAttributes(attributes, pattern) {
115
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
116
+ const filtered = {};
117
+ for (const [key, value] of Object.entries(attributes)) {
118
+ if (regex.test(key)) {
119
+ filtered[key] = value;
120
+ }
121
+ }
122
+ return filtered;
123
+ }
124
+ /**
125
+ * Count events by type/name
126
+ */
127
+ export function countEventsByType(events) {
128
+ const counts = new Map();
129
+ for (const event of events) {
130
+ const count = counts.get(event.name) || 0;
131
+ counts.set(event.name, count + 1);
132
+ }
133
+ return counts;
134
+ }
135
+ /**
136
+ * Format attribute value for display
137
+ */
138
+ export function formatValue(value) {
139
+ if (typeof value === 'string') {
140
+ return `"${value}"`;
141
+ }
142
+ if (typeof value === 'number' || typeof value === 'boolean') {
143
+ return String(value);
144
+ }
145
+ if (value === null || value === undefined) {
146
+ return 'null';
147
+ }
148
+ if (typeof value === 'object') {
149
+ return JSON.stringify(value);
150
+ }
151
+ return String(value);
152
+ }
153
+ /**
154
+ * Capitalize first letter of a string
155
+ */
156
+ export function capitalize(str) {
157
+ return str.charAt(0).toUpperCase() + str.slice(1);
158
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createValidateCommand(): Command;
3
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,qBAAqB,IAAI,OAAO,CA6J/C"}
@@ -0,0 +1,135 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { NarrativeValidator } from '@principal-ai/principal-view-core';
5
+ import { loadNarrative, resolvePath } from './utils.js';
6
+ export function createValidateCommand() {
7
+ const command = new Command('validate');
8
+ command
9
+ .description('Validate narrative template syntax, schema, and references')
10
+ .argument('<file>', 'Path to .narrative.json file')
11
+ .option('--canvas <path>', 'Override canvas file path for validation')
12
+ .option('--json', 'Output violations as JSON')
13
+ .option('-q, --quiet', 'Only show errors, suppress warnings')
14
+ .option('-d, --dir <path>', 'Project directory (default: cwd)')
15
+ .action(async (file, options) => {
16
+ try {
17
+ const baseDir = options.dir || process.cwd();
18
+ const narrativePath = resolvePath(file, baseDir);
19
+ // Load narrative
20
+ const narrative = await loadNarrative(narrativePath);
21
+ // Resolve canvas path
22
+ let canvasPath;
23
+ if (options.canvas) {
24
+ canvasPath = resolvePath(options.canvas, baseDir);
25
+ }
26
+ else if (narrative.canvas) {
27
+ const narrativeDir = dirname(narrativePath);
28
+ canvasPath = resolve(narrativeDir, narrative.canvas);
29
+ }
30
+ // Create validator
31
+ const validator = new NarrativeValidator();
32
+ // Validate
33
+ const context = {
34
+ narrative,
35
+ narrativePath,
36
+ canvasPath,
37
+ basePath: baseDir,
38
+ };
39
+ const result = await validator.validate(context);
40
+ // Filter violations if quiet mode
41
+ const violations = options.quiet
42
+ ? result.violations.filter((v) => v.severity === 'error')
43
+ : result.violations;
44
+ const errors = violations.filter((v) => v.severity === 'error');
45
+ const warnings = violations.filter((v) => v.severity === 'warn');
46
+ // Output
47
+ if (options.json) {
48
+ const output = {
49
+ file: file,
50
+ valid: errors.length === 0,
51
+ violations: violations.map((v) => ({
52
+ severity: v.severity,
53
+ ruleId: v.ruleId,
54
+ file: v.file,
55
+ path: v.path,
56
+ message: v.message,
57
+ impact: v.impact,
58
+ suggestion: v.suggestion,
59
+ fixable: v.fixable,
60
+ })),
61
+ summary: {
62
+ errors: errors.length,
63
+ warnings: warnings.length,
64
+ scenarioCount: narrative.scenarios.length,
65
+ hasDefault: narrative.scenarios.some((s) => s.condition.default),
66
+ },
67
+ };
68
+ console.log(JSON.stringify(output, null, 2));
69
+ }
70
+ else {
71
+ // Text output
72
+ console.log(chalk.bold(`\nValidating: ${file}\n`));
73
+ if (errors.length === 0 && warnings.length === 0) {
74
+ console.log(chalk.green('✓'), 'Schema validation passed');
75
+ console.log(chalk.green('✓'), `${narrative.scenarios.length} scenarios found`);
76
+ const hasDefault = narrative.scenarios.some((s) => s.condition.default);
77
+ console.log(chalk.green('✓'), hasDefault ? 'Default scenario present' : 'No default scenario');
78
+ const priorities = narrative.scenarios.map((s) => s.priority);
79
+ const allUnique = new Set(priorities).size === priorities.length;
80
+ console.log(chalk.green('✓'), allUnique ? 'All priorities unique' : 'Duplicate priorities found');
81
+ if (canvasPath) {
82
+ console.log(chalk.green('✓'), `Canvas: ${narrative.canvas || canvasPath} ✓`);
83
+ }
84
+ }
85
+ else {
86
+ // Show violations
87
+ for (const violation of violations) {
88
+ const icon = violation.severity === 'error' ? chalk.red('✗') : chalk.yellow('⚠');
89
+ const severity = violation.severity === 'error'
90
+ ? chalk.red('Error')
91
+ : chalk.yellow('Warning');
92
+ console.log(`\n${icon} ${severity}: ${violation.message}`);
93
+ if (violation.path) {
94
+ console.log(chalk.gray(` Location: ${violation.path}`));
95
+ }
96
+ if (violation.impact) {
97
+ console.log(chalk.gray(` Impact: ${violation.impact}`));
98
+ }
99
+ if (violation.suggestion) {
100
+ console.log(chalk.cyan(` Suggestion: ${violation.suggestion}`));
101
+ }
102
+ }
103
+ }
104
+ // Summary
105
+ console.log(chalk.bold('\nSummary:'));
106
+ if (errors.length > 0) {
107
+ console.log(chalk.red(` • ${errors.length} error(s)`));
108
+ }
109
+ else {
110
+ console.log(chalk.green(' • 0 errors'));
111
+ }
112
+ if (warnings.length > 0) {
113
+ console.log(chalk.yellow(` • ${warnings.length} warning(s)`));
114
+ }
115
+ else if (!options.quiet) {
116
+ console.log(chalk.green(' • 0 warnings'));
117
+ }
118
+ console.log(chalk.gray(` • ${narrative.scenarios.length} scenario(s)`));
119
+ if (canvasPath) {
120
+ console.log(chalk.gray(` • Canvas: ${narrative.canvas || canvasPath}`));
121
+ }
122
+ console.log();
123
+ }
124
+ // Exit with error code if validation failed
125
+ if (errors.length > 0) {
126
+ process.exit(1);
127
+ }
128
+ }
129
+ catch (error) {
130
+ console.error(chalk.red('Error:'), error.message);
131
+ process.exit(1);
132
+ }
133
+ });
134
+ return command;
135
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Validate execution files command
3
+ *
4
+ * Validates .spans.json, .execution.json, .otel.json, and .events.json files
5
+ * to ensure they conform to the expected ExecutionData structure.
6
+ */
7
+ import { Command } from 'commander';
8
+ /**
9
+ * Create the validate-execution command
10
+ */
11
+ export declare function createValidateExecutionCommand(): Command;
12
+ //# sourceMappingURL=validate-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-execution.d.ts","sourceRoot":"","sources":["../../src/commands/validate-execution.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqJpC;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,OAAO,CA8GxD"}
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Validate execution files command
3
+ *
4
+ * Validates .spans.json, .execution.json, .otel.json, and .events.json files
5
+ * to ensure they conform to the expected ExecutionData structure.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { readFileSync } from 'node:fs';
9
+ import { resolve, relative } from 'node:path';
10
+ import chalk from 'chalk';
11
+ import { globby } from 'globby';
12
+ import { createExecutionValidator } from '@principal-ai/principal-view-core';
13
+ /**
14
+ * Load and parse an execution file
15
+ */
16
+ function loadExecutionFile(filePath) {
17
+ try {
18
+ const content = readFileSync(filePath, 'utf-8');
19
+ return JSON.parse(content);
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Failed to parse JSON: ${error.message}`);
23
+ }
24
+ }
25
+ /**
26
+ * Format validation results for console output
27
+ */
28
+ function formatConsoleOutput(results, options) {
29
+ const lines = [];
30
+ let totalErrors = 0;
31
+ let totalWarnings = 0;
32
+ for (const { file, result } of results) {
33
+ totalErrors += result.errors.length;
34
+ totalWarnings += result.warnings.length;
35
+ // If quiet mode, only show files with issues
36
+ if (options.quiet && result.valid && result.warnings.length === 0) {
37
+ continue;
38
+ }
39
+ // Show file status
40
+ if (result.valid && result.warnings.length === 0) {
41
+ lines.push(chalk.green(`✓ ${file}`));
42
+ }
43
+ else if (result.valid && result.warnings.length > 0) {
44
+ lines.push(chalk.yellow(`⚠ ${file}`));
45
+ }
46
+ else {
47
+ lines.push(chalk.red(`✗ ${file}`));
48
+ }
49
+ // Show errors
50
+ if (result.errors.length > 0) {
51
+ lines.push('');
52
+ result.errors.forEach((error) => {
53
+ lines.push(chalk.red(` ERROR: ${error.path}`));
54
+ lines.push(` ${error.message}`);
55
+ if (error.suggestion) {
56
+ lines.push(chalk.dim(` → ${error.suggestion}`));
57
+ }
58
+ });
59
+ lines.push('');
60
+ }
61
+ // Show warnings
62
+ if (result.warnings.length > 0) {
63
+ lines.push('');
64
+ result.warnings.forEach((warning) => {
65
+ lines.push(chalk.yellow(` WARN: ${warning.path}`));
66
+ lines.push(` ${warning.message}`);
67
+ if (warning.suggestion) {
68
+ lines.push(chalk.dim(` → ${warning.suggestion}`));
69
+ }
70
+ });
71
+ lines.push('');
72
+ }
73
+ }
74
+ // Summary
75
+ lines.push('');
76
+ if (totalErrors === 0 && totalWarnings === 0) {
77
+ lines.push(chalk.green(`✓ All ${results.length} file${results.length === 1 ? '' : 's'} passed validation`));
78
+ }
79
+ else {
80
+ const parts = [];
81
+ if (totalErrors > 0) {
82
+ parts.push(chalk.red(`${totalErrors} error${totalErrors === 1 ? '' : 's'}`));
83
+ }
84
+ if (totalWarnings > 0) {
85
+ parts.push(chalk.yellow(`${totalWarnings} warning${totalWarnings === 1 ? '' : 's'}`));
86
+ }
87
+ lines.push(`✖ ${parts.join(', ')} found in ${results.length} file${results.length === 1 ? '' : 's'}`);
88
+ }
89
+ return {
90
+ output: lines.join('\n'),
91
+ hasErrors: totalErrors > 0,
92
+ };
93
+ }
94
+ /**
95
+ * Format validation results as JSON
96
+ */
97
+ function formatJsonOutput(results) {
98
+ let totalErrors = 0;
99
+ let totalWarnings = 0;
100
+ const files = results.map(({ file, result }) => {
101
+ totalErrors += result.errors.length;
102
+ totalWarnings += result.warnings.length;
103
+ return {
104
+ file,
105
+ valid: result.valid,
106
+ errorCount: result.errors.length,
107
+ warningCount: result.warnings.length,
108
+ errors: result.errors,
109
+ warnings: result.warnings,
110
+ };
111
+ });
112
+ return {
113
+ files,
114
+ summary: {
115
+ totalFiles: files.length,
116
+ totalErrors,
117
+ totalWarnings,
118
+ validFiles: files.filter((f) => f.valid).length,
119
+ invalidFiles: files.filter((f) => !f.valid).length,
120
+ },
121
+ };
122
+ }
123
+ /**
124
+ * Create the validate-execution command
125
+ */
126
+ export function createValidateExecutionCommand() {
127
+ const command = new Command('validate-execution');
128
+ command
129
+ .description('Validate execution files (.spans.json, .execution.json, .otel.json, .events.json)')
130
+ .argument('[files...]', 'Files or glob patterns to validate (defaults to **/__executions__/**/*.json)')
131
+ .option('--json', 'Output results as JSON')
132
+ .option('-q, --quiet', 'Only show files with errors or warnings')
133
+ .action(async (files, options) => {
134
+ try {
135
+ const cwd = process.cwd();
136
+ const validator = createExecutionValidator();
137
+ // Determine files to validate
138
+ let patterns;
139
+ if (files.length > 0) {
140
+ patterns = files;
141
+ }
142
+ else {
143
+ // Default: find all execution files in __executions__ directories
144
+ patterns = [
145
+ '**/__executions__/*.spans.json',
146
+ '**/__executions__/*.execution.json',
147
+ '**/__executions__/*.otel.json',
148
+ '**/__executions__/*.events.json',
149
+ '.principal-views/__executions__/*.spans.json',
150
+ '.principal-views/__executions__/*.execution.json',
151
+ '.principal-views/__executions__/*.otel.json',
152
+ '.principal-views/__executions__/*.events.json',
153
+ ];
154
+ }
155
+ // Find matching files
156
+ const matchedFiles = await globby(patterns, {
157
+ ignore: ['**/node_modules/**'],
158
+ absolute: false,
159
+ });
160
+ if (matchedFiles.length === 0) {
161
+ if (options.json) {
162
+ console.log(JSON.stringify({
163
+ files: [],
164
+ summary: {
165
+ totalFiles: 0,
166
+ totalErrors: 0,
167
+ totalWarnings: 0,
168
+ validFiles: 0,
169
+ invalidFiles: 0,
170
+ },
171
+ }));
172
+ }
173
+ else {
174
+ console.log(chalk.yellow('No execution files found matching the specified patterns.'));
175
+ console.log(chalk.dim(`Patterns searched: ${patterns.join(', ')}`));
176
+ }
177
+ return;
178
+ }
179
+ // Validate each file
180
+ const results = [];
181
+ for (const filePath of matchedFiles) {
182
+ const absolutePath = resolve(cwd, filePath);
183
+ const relativePath = relative(cwd, absolutePath);
184
+ try {
185
+ const data = loadExecutionFile(absolutePath);
186
+ const result = validator.validate(data, relativePath);
187
+ results.push({ file: relativePath, result });
188
+ }
189
+ catch (error) {
190
+ // Parse error
191
+ results.push({
192
+ file: relativePath,
193
+ result: {
194
+ valid: false,
195
+ errors: [
196
+ {
197
+ path: relativePath,
198
+ message: error.message,
199
+ severity: 'error',
200
+ },
201
+ ],
202
+ warnings: [],
203
+ },
204
+ });
205
+ }
206
+ }
207
+ // Output results
208
+ if (options.json) {
209
+ console.log(JSON.stringify(formatJsonOutput(results), null, 2));
210
+ }
211
+ else {
212
+ const { output, hasErrors } = formatConsoleOutput(results, options);
213
+ console.log(output);
214
+ if (hasErrors) {
215
+ process.exit(1);
216
+ }
217
+ }
218
+ }
219
+ catch (error) {
220
+ if (options.json) {
221
+ console.log(JSON.stringify({ error: error.message }));
222
+ }
223
+ else {
224
+ console.error(chalk.red('Error:'), error.message);
225
+ }
226
+ process.exit(1);
227
+ }
228
+ });
229
+ return command;
230
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA4nCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAsH/C"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAunCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAsH/C"}
@@ -121,10 +121,6 @@ function validateLibrary(library) {
121
121
  * Standard JSON Canvas node types that don't require pv metadata
122
122
  */
123
123
  const STANDARD_CANVAS_TYPES = ['text', 'group', 'file', 'link'];
124
- /**
125
- * Valid node shapes for pv.shape
126
- */
127
- const VALID_NODE_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'];
128
124
  // ============================================================================
129
125
  // Icon Validation
130
126
  // ============================================================================
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { Command } from 'commander';
8
8
  import { createValidateCommand } from './commands/validate.js';
9
+ import { createValidateExecutionCommand } from './commands/validate-execution.js';
9
10
  import { createInitCommand } from './commands/init.js';
10
11
  import { createListCommand } from './commands/list.js';
11
12
  import { createSchemaCommand } from './commands/schema.js';
@@ -14,6 +15,7 @@ import { createHooksCommand } from './commands/hooks.js';
14
15
  import { createCreateCommand } from './commands/create.js';
15
16
  import { createLintCommand } from './commands/lint.js';
16
17
  import { createCoverageCommand } from './commands/coverage.js';
18
+ import { createNarrativeCommand } from './commands/narrative/index.js';
17
19
  // Version is injected at build time via package.json
18
20
  const VERSION = '0.1.28';
19
21
  const program = new Command();
@@ -25,12 +27,14 @@ program
25
27
  program.addCommand(createInitCommand());
26
28
  program.addCommand(createCreateCommand());
27
29
  program.addCommand(createValidateCommand());
30
+ program.addCommand(createValidateExecutionCommand());
28
31
  program.addCommand(createLintCommand());
29
32
  program.addCommand(createListCommand());
30
33
  program.addCommand(createSchemaCommand());
31
34
  program.addCommand(createDoctorCommand());
32
35
  program.addCommand(createHooksCommand());
33
36
  program.addCommand(createCoverageCommand());
37
+ program.addCommand(createNarrativeCommand());
34
38
  // Parse command line arguments
35
39
  program.parse(process.argv);
36
40
  // Show help if no command provided