@principal-ai/principal-view-cli 0.2.1 → 0.2.3

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,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"}
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Formats command - Display documentation about file formats
3
+ */
4
+ import { Command } from 'commander';
5
+ import chalk from 'chalk';
6
+ const FORMAT_SECTIONS = {
7
+ overview: `
8
+ ${chalk.bold.cyan('Principal View OTEL File Formats')}
9
+ ${chalk.dim('═'.repeat(70))}
10
+
11
+ The Principal View OTEL workflow uses three main file types:
12
+
13
+ ${chalk.bold('1. Canvas Files')} ${chalk.yellow('.otel.canvas')}
14
+ Define OTEL event schemas and telemetry structure for a feature.
15
+ These are the single source of truth for what events should be emitted.
16
+
17
+ ${chalk.bold('2. Narrative Files')} ${chalk.yellow('.narrative.json')}
18
+ Define scenarios and templates for rendering executions as human-readable
19
+ narratives based on the emitted events.
20
+
21
+ ${chalk.bold('3. Execution Files')} ${chalk.yellow('.otel.json')}
22
+ Captured OTEL spans from test runs or production code, exported for
23
+ visualization and validation against canvas schemas.
24
+
25
+ Run ${chalk.cyan('npx @principal-ai/principal-view-cli formats <section>')} for details on:
26
+ ${chalk.yellow('canvas')} .otel.canvas format and event schemas
27
+ ${chalk.yellow('narrative')} .narrative.json format and scenario structure
28
+ ${chalk.yellow('execution')} .otel.json format for captured spans
29
+ ${chalk.yellow('examples')} Complete example files
30
+ `,
31
+ canvas: `
32
+ ${chalk.bold.cyan('Canvas Format (.otel.canvas)')}
33
+ ${chalk.dim('═'.repeat(70))}
34
+
35
+ Canvas files define the OTEL event schemas for a feature. They document what
36
+ events should be emitted and what attributes each event must/may contain.
37
+
38
+ ${chalk.bold('File Location:')}
39
+ ${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.otel.canvas')}
40
+
41
+ ${chalk.bold('Required Structure:')}
42
+ ${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
43
+ ${chalk.dim('│')} { ${chalk.dim('│')}
44
+ ${chalk.dim('│')} ${chalk.green('"nodes"')}: [ ${chalk.dim('// Array of event schemas')} ${chalk.dim('│')}
45
+ ${chalk.dim('│')} { ${chalk.dim('│')}
46
+ ${chalk.dim('│')} ${chalk.yellow('"id"')}: "event-id", ${chalk.dim('// Unique identifier')} ${chalk.dim('│')}
47
+ ${chalk.dim('│')} ${chalk.yellow('"type"')}: "text", ${chalk.dim('│')}
48
+ ${chalk.dim('│')} ${chalk.yellow('"text"')}: "# Event Name", ${chalk.dim('// Markdown description')} ${chalk.dim('│')}
49
+ ${chalk.dim('│')} ${chalk.yellow('"x"')}: 0, ${chalk.yellow('"y"')}: 0, ${chalk.yellow('"width"')}: 200, ${chalk.yellow('"height"')}: 100, ${chalk.dim('│')}
50
+ ${chalk.dim('│')} ${chalk.green('"pv"')}: { ${chalk.dim('│')}
51
+ ${chalk.dim('│')} ${chalk.cyan('"otelEvent"')}: { ${chalk.dim('// OTEL event definition')} ${chalk.dim('│')}
52
+ ${chalk.dim('│')} ${chalk.yellow('"name"')}: "feature.event.name", ${chalk.dim('│')}
53
+ ${chalk.dim('│')} ${chalk.cyan('"attributes"')}: { ${chalk.dim('// Required & optional attrs')} ${chalk.dim('│')}
54
+ ${chalk.dim('│')} ${chalk.green('"required"')}: ["attr.name", ...], ${chalk.dim('│')}
55
+ ${chalk.dim('│')} ${chalk.green('"optional"')}: ["attr.name", ...] ${chalk.dim('│')}
56
+ ${chalk.dim('│')} } ${chalk.dim('│')}
57
+ ${chalk.dim('│')} } ${chalk.dim('│')}
58
+ ${chalk.dim('│')} } ${chalk.dim('│')}
59
+ ${chalk.dim('│')} } ${chalk.dim('│')}
60
+ ${chalk.dim('│')} ], ${chalk.dim('│')}
61
+ ${chalk.dim('│')} ${chalk.green('"edges"')}: [], ${chalk.dim('// Optional: event relationships')} ${chalk.dim('│')}
62
+ ${chalk.dim('│')} ${chalk.green('"pv"')}: { ${chalk.dim('│')}
63
+ ${chalk.dim('│')} ${chalk.yellow('"name"')}: "Feature Name", ${chalk.dim('// Feature name (NOT "...Canvas")')} ${chalk.dim('│')}
64
+ ${chalk.dim('│')} ${chalk.yellow('"version"')}: "1.0.0" ${chalk.dim('│')}
65
+ ${chalk.dim('│')} } ${chalk.dim('│')}
66
+ ${chalk.dim('│')} } ${chalk.dim('│')}
67
+ ${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
68
+
69
+ ${chalk.bold('Event Schema Best Practices:')}
70
+
71
+ ${chalk.cyan('1. Canvas represents complete, encompassing functionality:')}
72
+ ✅ API route - Full request/response cycle
73
+ ✅ CLI command - Complete tool execution
74
+ ✅ Business operation - End-to-end workflow
75
+ ❌ Individual helper functions (too granular)
76
+
77
+ ${chalk.cyan('2. Event naming convention:')}
78
+ ${chalk.yellow('<feature>.<operation>.<state>')}
79
+ Examples:
80
+ - validation.started
81
+ - validation.complete
82
+ - validation.error
83
+ - import.parsing.complete
84
+
85
+ ${chalk.cyan('3. Start simple (2-4 events):')}
86
+ - ${chalk.green('started')} - When feature begins
87
+ - ${chalk.green('complete')} - When feature succeeds
88
+ - ${chalk.green('error')} - When feature fails
89
+ - (optional) progress events for clear intermediate steps
90
+
91
+ ${chalk.cyan('4. Attribute naming conventions:')}
92
+ ${chalk.yellow('<category>.<name>')}
93
+ - input.* (input.size, input.recordCount)
94
+ - output.* (output.count, output.success)
95
+ - result.* (result.validCount, result.invalidCount)
96
+ - error.* (error.type, error.message, error.stage)
97
+ - duration.* (duration.ms)
98
+
99
+ ${chalk.cyan('5. Required vs Optional attributes:')}
100
+ - ${chalk.green('required')}: Essential data needed for validation
101
+ - ${chalk.yellow('optional')}: Nice-to-have context, won't fail validation if missing
102
+
103
+ ${chalk.bold('Validation:')}
104
+ ${chalk.cyan('npx @principal-ai/principal-view-cli validate')}
105
+ `,
106
+ narrative: `
107
+ ${chalk.bold.cyan('Narrative Format (.narrative.json)')}
108
+ ${chalk.dim('═'.repeat(70))}
109
+
110
+ Narrative files define scenarios for rendering execution data as human-readable
111
+ stories. They evaluate conditions against captured events to select the best
112
+ matching narrative template.
113
+
114
+ ${chalk.bold('File Location:')}
115
+ ${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.narrative.json')}
116
+ ${chalk.dim('(co-located with corresponding .otel.canvas file)')}
117
+
118
+ ${chalk.bold('Required Structure:')}
119
+ ${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
120
+ ${chalk.dim('│')} { ${chalk.dim('│')}
121
+ ${chalk.dim('│')} ${chalk.green('"name"')}: "Feature Name", ${chalk.dim('// NOT "Feature Name Narratives"')} ${chalk.dim('│')}
122
+ ${chalk.dim('│')} ${chalk.green('"description"')}: "What the feature does", ${chalk.dim('// Purpose, not "Narratives for..."')} ${chalk.dim('│')}
123
+ ${chalk.dim('│')} ${chalk.green('"scenarios"')}: [ ${chalk.dim('│')}
124
+ ${chalk.dim('│')} { ${chalk.dim('│')}
125
+ ${chalk.dim('│')} ${chalk.yellow('"priority"')}: 1, ${chalk.dim('// Lower = higher priority')} ${chalk.dim('│')}
126
+ ${chalk.dim('│')} ${chalk.yellow('"condition"')}: "...", ${chalk.dim('// JSONPath/logic expression')} ${chalk.dim('│')}
127
+ ${chalk.dim('│')} ${chalk.yellow('"template"')}: { ${chalk.dim('// Narrative template')} ${chalk.dim('│')}
128
+ ${chalk.dim('│')} ${chalk.cyan('"summary"')}: "...", ${chalk.dim('// One-line summary')} ${chalk.dim('│')}
129
+ ${chalk.dim('│')} ${chalk.cyan('"details"')}: [ ${chalk.dim('// Step-by-step details')} ${chalk.dim('│')}
130
+ ${chalk.dim('│')} "Step 1: ...", ${chalk.dim('│')}
131
+ ${chalk.dim('│')} "Step 2: ...", ${chalk.dim('│')}
132
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
133
+ ${chalk.dim('│')} } ${chalk.dim('│')}
134
+ ${chalk.dim('│')} } ${chalk.dim('│')}
135
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
136
+ ${chalk.dim('│')} } ${chalk.dim('│')}
137
+ ${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
138
+
139
+ ${chalk.bold('Naming Guidelines:')}
140
+
141
+ ❌ DON'T append "Narratives" to the name:
142
+ "name": "Package Processor Narratives"
143
+
144
+ ✅ DO use the feature name directly:
145
+ "name": "Package Processor"
146
+
147
+ ❌ DON'T prefix description with boilerplate:
148
+ "description": "Human-readable narratives for package extraction..."
149
+
150
+ ✅ DO describe the feature's purpose:
151
+ "description": "Package extraction and analysis from repository file trees"
152
+
153
+ ${chalk.bold('Scenario Best Practices:')}
154
+
155
+ ${chalk.cyan('1. Priority ordering (scenarios evaluated in order):')}
156
+ - ${chalk.green('1-10')} Specific scenarios (success/failure cases)
157
+ - ${chalk.yellow('999')} Fallback scenario (catches anything)
158
+
159
+ ${chalk.cyan('2. Standard scenario set:')}
160
+ - ${chalk.green('Success')} (priority 1): Feature completed successfully
161
+ - ${chalk.yellow('Failure')} (priority 2): Feature encountered error
162
+ - ${chalk.dim('Fallback')} (priority 999): Generic execution captured
163
+
164
+ ${chalk.cyan('3. Template interpolation:')}
165
+ Use {{path.to.value}} to reference event attributes
166
+ Examples:
167
+ - {{record.count}}
168
+ - {{error.message}}
169
+ - {{result.invalidCount}}
170
+
171
+ ${chalk.cyan('4. Template style:')}
172
+ - Clear, concise summary line
173
+ - 3-5 detail steps showing workflow
174
+ - Use emojis for visual scanning (✅ ❌ 📋)
175
+ - Include key metrics and IDs
176
+
177
+ ${chalk.bold('Validation:')}
178
+ ${chalk.cyan('npx @principal-ai/principal-view-cli narrative validate')}
179
+ `,
180
+ execution: `
181
+ ${chalk.bold.cyan('Execution Format (.otel.json)')}
182
+ ${chalk.dim('═'.repeat(70))}
183
+
184
+ Execution files contain captured OTEL spans from test runs or production code.
185
+ These files are exported by your test infrastructure and used for visualization
186
+ and validation against canvas schemas.
187
+
188
+ ${chalk.bold('File Location:')}
189
+ ${chalk.yellow('__executions__/')}${chalk.dim('<feature-name>.otel.json')}
190
+ ${chalk.dim('(auto-generated by test infrastructure)')}
191
+
192
+ ${chalk.bold('IMPORTANT:')} __executions__/ directory must be committed to git!
193
+
194
+ ${chalk.bold('File Structure:')}
195
+ ${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
196
+ ${chalk.dim('│')} { ${chalk.dim('│')}
197
+ ${chalk.dim('│')} ${chalk.green('"exportedAt"')}: "2025-01-21T10:30:00.000Z", ${chalk.dim('// ISO timestamp')} ${chalk.dim('│')}
198
+ ${chalk.dim('│')} ${chalk.green('"serviceName"')}: "my-service", ${chalk.dim('// Service name')} ${chalk.dim('│')}
199
+ ${chalk.dim('│')} ${chalk.green('"spanCount"')}: 5, ${chalk.dim('// Total spans')} ${chalk.dim('│')}
200
+ ${chalk.dim('│')} ${chalk.green('"spans"')}: [ ${chalk.dim('│')}
201
+ ${chalk.dim('│')} { ${chalk.dim('│')}
202
+ ${chalk.dim('│')} ${chalk.yellow('"traceId"')}: "4bf92f3577b34da6...", ${chalk.dim('// 32 hex chars')} ${chalk.dim('│')}
203
+ ${chalk.dim('│')} ${chalk.yellow('"spanId"')}: "00f067aa0ba902b7", ${chalk.dim('// 16 hex chars')} ${chalk.dim('│')}
204
+ ${chalk.dim('│')} ${chalk.yellow('"parentSpanId"')}: "abc123...", ${chalk.dim('// Parent span (null for root)')} ${chalk.dim('│')}
205
+ ${chalk.dim('│')} ${chalk.yellow('"name"')}: "test:feature-name", ${chalk.dim('// Span name')} ${chalk.dim('│')}
206
+ ${chalk.dim('│')} ${chalk.yellow('"kind"')}: "INTERNAL", ${chalk.dim('// Span kind')} ${chalk.dim('│')}
207
+ ${chalk.dim('│')} ${chalk.yellow('"startTime"')}: 1703548800000, ${chalk.dim('// Unix ms')} ${chalk.dim('│')}
208
+ ${chalk.dim('│')} ${chalk.yellow('"endTime"')}: 1703548800050, ${chalk.dim('// Unix ms')} ${chalk.dim('│')}
209
+ ${chalk.dim('│')} ${chalk.yellow('"duration"')}: 50, ${chalk.dim('// Duration in ms')} ${chalk.dim('│')}
210
+ ${chalk.dim('│')} ${chalk.yellow('"attributes"')}: { ${chalk.dim('// Event attributes')} ${chalk.dim('│')}
211
+ ${chalk.dim('│')} "input.size": 42, ${chalk.dim('│')}
212
+ ${chalk.dim('│')} "output.success": true ${chalk.dim('│')}
213
+ ${chalk.dim('│')} }, ${chalk.dim('│')}
214
+ ${chalk.dim('│')} ${chalk.yellow('"status"')}: { ${chalk.dim('│')}
215
+ ${chalk.dim('│')} "code": "OK" ${chalk.dim('// OK, ERROR, UNSET')} ${chalk.dim('│')}
216
+ ${chalk.dim('│')} }, ${chalk.dim('│')}
217
+ ${chalk.dim('│')} ${chalk.yellow('"events"')}: [] ${chalk.dim('// Optional span events')} ${chalk.dim('│')}
218
+ ${chalk.dim('│')} } ${chalk.dim('│')}
219
+ ${chalk.dim('│')} ] ${chalk.dim('│')}
220
+ ${chalk.dim('│')} } ${chalk.dim('│')}
221
+ ${chalk.dim('└────────────────────────────────────────────────────────────────────┘')}
222
+
223
+ ${chalk.bold('Field Descriptions:')}
224
+
225
+ ${chalk.cyan('exportedAt')} ${chalk.dim('(string, required)')}
226
+ ISO 8601 timestamp when the file was exported
227
+
228
+ ${chalk.cyan('serviceName')} ${chalk.dim('(string, required)')}
229
+ Name of the service/test suite that generated these spans
230
+
231
+ ${chalk.cyan('spanCount')} ${chalk.dim('(number, required)')}
232
+ Total number of spans in this execution
233
+
234
+ ${chalk.cyan('spans')} ${chalk.dim('(array, required)')}
235
+ Array of span objects containing OTEL trace data
236
+
237
+ ${chalk.bold('Span Object Fields:')}
238
+
239
+ ${chalk.cyan('traceId')} ${chalk.dim('(string, 32 hex chars)')}
240
+ Unique identifier for the entire trace
241
+
242
+ ${chalk.cyan('spanId')} ${chalk.dim('(string, 16 hex chars)')}
243
+ Unique identifier for this specific span
244
+
245
+ ${chalk.cyan('parentSpanId')} ${chalk.dim('(string | null)')}
246
+ Parent span ID, or null for root span
247
+
248
+ ${chalk.cyan('name')} ${chalk.dim('(string)')}
249
+ Descriptive name for the operation
250
+
251
+ ${chalk.cyan('kind')} ${chalk.dim('(string)')}
252
+ Span kind: INTERNAL, CLIENT, SERVER, PRODUCER, CONSUMER
253
+
254
+ ${chalk.cyan('startTime')} ${chalk.dim('(number)')}
255
+ Unix timestamp in milliseconds when span started
256
+
257
+ ${chalk.cyan('endTime')} ${chalk.dim('(number)')}
258
+ Unix timestamp in milliseconds when span ended
259
+
260
+ ${chalk.cyan('duration')} ${chalk.dim('(number)')}
261
+ Duration in milliseconds (endTime - startTime)
262
+
263
+ ${chalk.cyan('attributes')} ${chalk.dim('(object)')}
264
+ Key-value pairs of event attributes (validated against canvas schema)
265
+
266
+ ${chalk.cyan('status')} ${chalk.dim('(object)')}
267
+ Status code (OK, ERROR, UNSET) and optional message
268
+
269
+ ${chalk.cyan('events')} ${chalk.dim('(array)')}
270
+ Optional array of timestamped events within the span
271
+
272
+ ${chalk.bold('Generation:')}
273
+ Set up test infrastructure with OTEL exporters that write to __executions__/
274
+ See: docs/guides/adding-opentelemetry-to-tests.md
275
+
276
+ ${chalk.bold('Validation:')}
277
+ ${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution <file>')}
278
+ `,
279
+ examples: `
280
+ ${chalk.bold.cyan('Complete File Examples')}
281
+ ${chalk.dim('═'.repeat(70))}
282
+
283
+ ${chalk.bold('Example 1: Data Validator Canvas')}
284
+ ${chalk.dim('─'.repeat(70))}
285
+ ${chalk.yellow('.principal-views/data-validator.otel.canvas')}
286
+
287
+ {
288
+ "nodes": [
289
+ {
290
+ "id": "validation-started",
291
+ "type": "text",
292
+ "text": "# validation.started\\n\\nEmitted when validation begins",
293
+ "x": 0, "y": 0, "width": 200, "height": 100,
294
+ "pv": {
295
+ "otelEvent": {
296
+ "name": "validation.started",
297
+ "attributes": {
298
+ "required": ["input.recordCount"],
299
+ "optional": ["input.source"]
300
+ }
301
+ }
302
+ }
303
+ },
304
+ {
305
+ "id": "validation-complete",
306
+ "type": "text",
307
+ "text": "# validation.complete\\n\\nEmitted when validation succeeds",
308
+ "x": 250, "y": 0, "width": 200, "height": 100,
309
+ "pv": {
310
+ "otelEvent": {
311
+ "name": "validation.complete",
312
+ "attributes": {
313
+ "required": ["result.validCount", "result.invalidCount"],
314
+ "optional": ["duration.ms"]
315
+ }
316
+ }
317
+ }
318
+ },
319
+ {
320
+ "id": "validation-error",
321
+ "type": "text",
322
+ "text": "# validation.error\\n\\nEmitted when validation fails",
323
+ "x": 500, "y": 0, "width": 200, "height": 100,
324
+ "pv": {
325
+ "otelEvent": {
326
+ "name": "validation.error",
327
+ "attributes": {
328
+ "required": ["error.type", "error.message"],
329
+ "optional": ["error.stage"]
330
+ }
331
+ }
332
+ }
333
+ }
334
+ ],
335
+ "edges": [],
336
+ "pv": {
337
+ "name": "Data Validator",
338
+ "version": "1.0.0"
339
+ }
340
+ }
341
+
342
+ ${chalk.bold('Example 2: Narrative Scenarios')}
343
+ ${chalk.dim('─'.repeat(70))}
344
+ ${chalk.yellow('.principal-views/data-validator.narrative.json')}
345
+
346
+ {
347
+ "name": "Data Validator",
348
+ "description": "Validates data records against defined schemas",
349
+ "scenarios": [
350
+ {
351
+ "priority": 1,
352
+ "condition": "events[?name=='validation.complete']",
353
+ "template": {
354
+ "summary": "✅ Validated {{result.validCount}} records successfully",
355
+ "details": [
356
+ "🔍 Started validation",
357
+ "📊 Processed {{input.recordCount}} records",
358
+ "✅ {{result.validCount}} valid",
359
+ "❌ {{result.invalidCount}} invalid"
360
+ ]
361
+ }
362
+ },
363
+ {
364
+ "priority": 2,
365
+ "condition": "events[?name=='validation.error']",
366
+ "template": {
367
+ "summary": "❌ Validation failed: {{error.message}}",
368
+ "details": [
369
+ "🔍 Started validation",
370
+ "💥 Error occurred: {{error.message}}",
371
+ "🏷️ Error type: {{error.type}}"
372
+ ]
373
+ }
374
+ },
375
+ {
376
+ "priority": 999,
377
+ "condition": "true",
378
+ "template": {
379
+ "summary": "📋 Validation execution captured",
380
+ "details": [
381
+ "📦 Captured {{spans.length}} events",
382
+ "⏱️ Duration: {{duration.ms}}ms"
383
+ ]
384
+ }
385
+ }
386
+ ]
387
+ }
388
+
389
+ ${chalk.bold('Example 3: Execution File')}
390
+ ${chalk.dim('─'.repeat(70))}
391
+ ${chalk.yellow('__executions__/data-validator.otel.json')}
392
+
393
+ {
394
+ "exportedAt": "2025-01-21T10:30:45.123Z",
395
+ "serviceName": "my-app-tests",
396
+ "spanCount": 3,
397
+ "spans": [
398
+ {
399
+ "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
400
+ "spanId": "00f067aa0ba902b7",
401
+ "parentSpanId": null,
402
+ "name": "test:data-validator",
403
+ "kind": "INTERNAL",
404
+ "startTime": 1703548800000,
405
+ "endTime": 1703548800150,
406
+ "duration": 150,
407
+ "attributes": {
408
+ "test.name": "data validator success case"
409
+ },
410
+ "status": { "code": "OK" },
411
+ "events": []
412
+ },
413
+ {
414
+ "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
415
+ "spanId": "abc123def4567890",
416
+ "parentSpanId": "00f067aa0ba902b7",
417
+ "name": "validation.started",
418
+ "kind": "INTERNAL",
419
+ "startTime": 1703548800010,
420
+ "endTime": 1703548800015,
421
+ "duration": 5,
422
+ "attributes": {
423
+ "input.recordCount": 100,
424
+ "input.source": "test-data.csv"
425
+ },
426
+ "status": { "code": "OK" },
427
+ "events": []
428
+ },
429
+ {
430
+ "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
431
+ "spanId": "def789abc1234567",
432
+ "parentSpanId": "00f067aa0ba902b7",
433
+ "name": "validation.complete",
434
+ "kind": "INTERNAL",
435
+ "startTime": 1703548800140,
436
+ "endTime": 1703548800145,
437
+ "duration": 5,
438
+ "attributes": {
439
+ "result.validCount": 95,
440
+ "result.invalidCount": 5,
441
+ "duration.ms": 130
442
+ },
443
+ "status": { "code": "OK" },
444
+ "events": []
445
+ }
446
+ ]
447
+ }
448
+
449
+ ${chalk.bold('Next Steps:')}
450
+ ${chalk.cyan('npx @principal-ai/principal-view-cli validate')} Validate canvas
451
+ ${chalk.cyan('npx @principal-ai/principal-view-cli narrative validate')} Validate narratives
452
+ ${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution')} Validate execution
453
+ `,
454
+ };
455
+ export function createFormatsCommand() {
456
+ const command = new Command('formats');
457
+ command
458
+ .description('Display documentation about file formats')
459
+ .argument('[section]', 'Section to display: overview, canvas, narrative, execution, examples')
460
+ .action((section) => {
461
+ const validSections = Object.keys(FORMAT_SECTIONS);
462
+ if (!section) {
463
+ console.log(FORMAT_SECTIONS.overview);
464
+ return;
465
+ }
466
+ const normalizedSection = section.toLowerCase();
467
+ if (!validSections.includes(normalizedSection)) {
468
+ console.log(chalk.red(`Unknown section: ${section}`));
469
+ console.log(`Valid sections: ${validSections.join(', ')}`);
470
+ process.exit(1);
471
+ }
472
+ console.log(FORMAT_SECTIONS[normalizedSection]);
473
+ });
474
+ return command;
475
+ }
@@ -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;AAunCpC,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;AA+pCpC,wBAAgB,qBAAqB,IAAI,OAAO,CAiI/C"}
@@ -396,8 +396,9 @@ function hasOtelFeatures(canvas) {
396
396
  * - Edge types reference defined types in pv.edgeTypes or library.edgeComponents
397
397
  * - Node types reference defined types in pv.nodeTypes or library.nodeComponents
398
398
  * - Canvas has pv extension with name and version
399
+ * - OTEL nodes have source file references and the files exist
399
400
  */
400
- function validateCanvas(canvas, filePath, library) {
401
+ function validateCanvas(canvas, filePath, library, repositoryPath) {
401
402
  const issues = [];
402
403
  if (!canvas || typeof canvas !== 'object') {
403
404
  issues.push({ type: 'error', message: 'Canvas must be an object' });
@@ -633,6 +634,37 @@ function validateCanvas(canvas, filePath, library) {
633
634
  if (nodePv.otel && typeof nodePv.otel === 'object') {
634
635
  checkUnknownFields(nodePv.otel, ALLOWED_CANVAS_FIELDS.nodePvOtel, `${nodePath}.pv.otel`, issues);
635
636
  }
637
+ // Validate source file references for OTEL event nodes
638
+ const hasOtelFeatures = nodePv.otel !== undefined || nodePv.events !== undefined;
639
+ if (hasOtelFeatures) {
640
+ // OTEL nodes must have at least one source file reference
641
+ if (!Array.isArray(nodePv.sources) || nodePv.sources.length === 0) {
642
+ issues.push({
643
+ type: 'error',
644
+ message: `Node "${nodeLabel}" has OTEL features but is missing required "pv.sources" field`,
645
+ path: `${nodePath}.pv.sources`,
646
+ suggestion: 'Add at least one source file reference, e.g.: "sources": ["src/services/MyService.ts"]',
647
+ });
648
+ }
649
+ }
650
+ // Validate that all source files exist (if repository path is provided)
651
+ if (Array.isArray(nodePv.sources) && repositoryPath) {
652
+ nodePv.sources.forEach((source, sourceIndex) => {
653
+ if (typeof source === 'string') {
654
+ // Remove glob patterns (* characters) to get the base path
655
+ const cleanPath = source.replace(/\*/g, '');
656
+ const fullPath = resolve(repositoryPath, cleanPath);
657
+ if (!existsSync(fullPath)) {
658
+ issues.push({
659
+ type: 'error',
660
+ message: `Node "${nodeLabel}" references non-existent source file: ${source}`,
661
+ path: `${nodePath}.pv.sources[${sourceIndex}]`,
662
+ suggestion: `Verify the file path is correct relative to repository root: ${repositoryPath}`,
663
+ });
664
+ }
665
+ }
666
+ });
667
+ }
636
668
  if (Array.isArray(nodePv.actions)) {
637
669
  nodePv.actions.forEach((action, actionIndex) => {
638
670
  if (action && typeof action === 'object') {
@@ -855,7 +887,7 @@ function validateCanvas(canvas, filePath, library) {
855
887
  /**
856
888
  * Validate a single .canvas file
857
889
  */
858
- function validateFile(filePath, library) {
890
+ function validateFile(filePath, library, repositoryPath) {
859
891
  const absolutePath = resolve(filePath);
860
892
  const relativePath = relative(process.cwd(), absolutePath);
861
893
  if (!existsSync(absolutePath)) {
@@ -868,7 +900,7 @@ function validateFile(filePath, library) {
868
900
  try {
869
901
  const content = readFileSync(absolutePath, 'utf8');
870
902
  const canvas = JSON.parse(content);
871
- const issues = validateCanvas(canvas, relativePath, library);
903
+ const issues = validateCanvas(canvas, relativePath, library, repositoryPath);
872
904
  const hasErrors = issues.some((i) => i.type === 'error');
873
905
  return {
874
906
  file: relativePath,
@@ -892,6 +924,7 @@ export function createValidateCommand() {
892
924
  .argument('[files...]', 'Files or glob patterns to validate (defaults to .principal-views/*.canvas)')
893
925
  .option('-q, --quiet', 'Only output errors')
894
926
  .option('--json', 'Output results as JSON')
927
+ .option('-r, --repository <path>', 'Repository root path for validating source file references (defaults to current directory)')
895
928
  .action(async (files, options) => {
896
929
  try {
897
930
  // Default to .principal-views/*.canvas if no files specified
@@ -914,6 +947,10 @@ export function createValidateCommand() {
914
947
  // Load library from .principal-views directory (used for type validation)
915
948
  const principalViewsDir = resolve(process.cwd(), '.principal-views');
916
949
  const library = loadLibrary(principalViewsDir);
950
+ // Determine repository path for source file validation
951
+ const repositoryPath = options.repository
952
+ ? resolve(options.repository)
953
+ : process.cwd();
917
954
  // Validate library if present
918
955
  let libraryResult = null;
919
956
  if (library && Object.keys(library.raw).length > 0) {
@@ -926,7 +963,7 @@ export function createValidateCommand() {
926
963
  };
927
964
  }
928
965
  // Validate all canvas files
929
- const results = matchedFiles.map((f) => validateFile(f, library));
966
+ const results = matchedFiles.map((f) => validateFile(f, library, repositoryPath));
930
967
  // Combine results
931
968
  const allResults = libraryResult ? [libraryResult, ...results] : results;
932
969
  const validCount = allResults.filter((r) => r.isValid).length;