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

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.
@@ -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,CA4L3C"}
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;AAwTpC,wBAAgB,iBAAiB,IAAI,OAAO,CAwQ3C"}
@@ -7,7 +7,7 @@ import { resolve, relative, dirname, basename } from 'node:path';
7
7
  import chalk from 'chalk';
8
8
  import { globby } from 'globby';
9
9
  import yaml from 'js-yaml';
10
- import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig, } from '@principal-ai/principal-view-core';
10
+ import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig, createNarrativeValidator, } from '@principal-ai/principal-view-core';
11
11
  // ============================================================================
12
12
  // Config File Loading
13
13
  // ============================================================================
@@ -116,6 +116,56 @@ function loadGraphConfig(filePath) {
116
116
  return null;
117
117
  }
118
118
  }
119
+ /**
120
+ * Load a narrative template file
121
+ */
122
+ function loadNarrativeTemplate(filePath) {
123
+ if (!existsSync(filePath)) {
124
+ return null;
125
+ }
126
+ try {
127
+ const raw = readFileSync(filePath, 'utf8');
128
+ const narrative = JSON.parse(raw);
129
+ return { narrative, raw };
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
135
+ /**
136
+ * Load a canvas file for narrative validation
137
+ */
138
+ function loadCanvas(filePath) {
139
+ if (!existsSync(filePath)) {
140
+ return null;
141
+ }
142
+ try {
143
+ const content = readFileSync(filePath, 'utf8');
144
+ const ext = filePath.toLowerCase();
145
+ if (ext.endsWith('.json')) {
146
+ return JSON.parse(content);
147
+ }
148
+ else {
149
+ return yaml.load(content);
150
+ }
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ /**
157
+ * Determine file type
158
+ */
159
+ function getFileType(filePath) {
160
+ const name = basename(filePath).toLowerCase();
161
+ if (name.endsWith('.narrative.json')) {
162
+ return 'narrative';
163
+ }
164
+ if (name.endsWith('.canvas') || name.endsWith('.otel.canvas')) {
165
+ return 'canvas';
166
+ }
167
+ return 'config';
168
+ }
119
169
  // ============================================================================
120
170
  // Output Formatting
121
171
  // ============================================================================
@@ -282,14 +332,14 @@ export function createLintCommand() {
282
332
  ignore: privuConfig.exclude || ['**/node_modules/**'],
283
333
  expandDirectories: false,
284
334
  });
285
- // Filter out library files, config files, canvas files, and execution artifacts
335
+ // Filter out library files, config files, and execution artifacts
336
+ // INCLUDE both canvas files and narrative templates for linting
286
337
  const configFiles = matchedFiles.filter((f) => {
287
338
  const name = basename(f).toLowerCase();
288
339
  const isLibraryFile = name.startsWith('library.');
289
340
  const isConfigFile = name.startsWith('.privurc');
290
- const isCanvasFile = f.toLowerCase().endsWith('.canvas');
291
341
  const isExecutionArtifact = f.includes('__executions__/');
292
- return !isLibraryFile && !isConfigFile && !isCanvasFile && !isExecutionArtifact;
342
+ return !isLibraryFile && !isConfigFile && !isExecutionArtifact;
293
343
  });
294
344
  if (configFiles.length === 0) {
295
345
  if (options.json) {
@@ -314,44 +364,116 @@ export function createLintCommand() {
314
364
  console.log(chalk.yellow(`Warning: Could not load library from ${libraryPath}`));
315
365
  }
316
366
  }
317
- // Create rules engine
367
+ // Helper function to count violations by rule
368
+ function countByRule(violations) {
369
+ const counts = {};
370
+ for (const v of violations) {
371
+ counts[v.ruleId] = (counts[v.ruleId] || 0) + 1;
372
+ }
373
+ return counts;
374
+ }
375
+ // Create validators
318
376
  const engine = createDefaultRulesEngine();
377
+ const narrativeValidator = createNarrativeValidator();
319
378
  // Lint each file
320
379
  const results = new Map();
321
380
  for (const filePath of configFiles) {
322
381
  const absolutePath = resolve(cwd, filePath);
323
382
  const relativePath = relative(cwd, absolutePath);
324
- const loaded = loadGraphConfig(absolutePath);
325
- if (!loaded) {
326
- // File couldn't be loaded - report as error
383
+ const fileType = getFileType(absolutePath);
384
+ if (fileType === 'narrative') {
385
+ // Validate narrative template
386
+ const loaded = loadNarrativeTemplate(absolutePath);
387
+ if (!loaded) {
388
+ // File couldn't be loaded - report as error
389
+ results.set(relativePath, {
390
+ violations: [
391
+ {
392
+ ruleId: 'parse-error',
393
+ severity: 'error',
394
+ file: relativePath,
395
+ message: `Could not parse narrative file: ${filePath}`,
396
+ impact: 'File cannot be validated',
397
+ fixable: false,
398
+ },
399
+ ],
400
+ errorCount: 1,
401
+ warningCount: 0,
402
+ fixableCount: 0,
403
+ byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
404
+ byRule: { 'parse-error': 1 },
405
+ });
406
+ continue;
407
+ }
408
+ // Load the referenced canvas if it exists
409
+ const canvasPath = loaded.narrative.canvas
410
+ ? resolve(dirname(absolutePath), loaded.narrative.canvas)
411
+ : undefined;
412
+ const canvas = canvasPath ? loadCanvas(canvasPath) : undefined;
413
+ // Run narrative validation
414
+ const narrativeResult = await narrativeValidator.validate({
415
+ narrative: loaded.narrative,
416
+ narrativePath: relativePath,
417
+ canvas: canvas ?? undefined,
418
+ canvasPath: canvasPath ? relative(cwd, canvasPath) : undefined,
419
+ basePath: dirname(absolutePath),
420
+ rawContent: loaded.raw,
421
+ });
422
+ // Convert narrative violations to graph violations format
423
+ const violations = narrativeResult.violations.map((v) => ({
424
+ ruleId: v.ruleId,
425
+ severity: v.severity,
426
+ file: v.file,
427
+ line: v.line,
428
+ path: v.path,
429
+ message: v.message,
430
+ impact: v.impact,
431
+ suggestion: v.suggestion,
432
+ fixable: v.fixable,
433
+ }));
327
434
  results.set(relativePath, {
328
- violations: [
329
- {
330
- ruleId: 'parse-error',
331
- severity: 'error',
332
- file: relativePath,
333
- message: `Could not parse file: ${filePath}`,
334
- impact: 'File cannot be validated',
335
- fixable: false,
336
- },
337
- ],
338
- errorCount: 1,
339
- warningCount: 0,
340
- fixableCount: 0,
341
- byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
342
- byRule: { 'parse-error': 1 },
435
+ violations,
436
+ errorCount: narrativeResult.errorCount,
437
+ warningCount: narrativeResult.warningCount,
438
+ fixableCount: narrativeResult.fixableCount,
439
+ byCategory: { schema: 0, reference: 0, structure: 0, pattern: 0, library: 0 }, // Could categorize narrative rules
440
+ byRule: countByRule(violations),
441
+ });
442
+ }
443
+ else {
444
+ // Validate canvas/graph configuration
445
+ const loaded = loadGraphConfig(absolutePath);
446
+ if (!loaded) {
447
+ // File couldn't be loaded - report as error
448
+ results.set(relativePath, {
449
+ violations: [
450
+ {
451
+ ruleId: 'parse-error',
452
+ severity: 'error',
453
+ file: relativePath,
454
+ message: `Could not parse file: ${filePath}`,
455
+ impact: 'File cannot be validated',
456
+ fixable: false,
457
+ },
458
+ ],
459
+ errorCount: 1,
460
+ warningCount: 0,
461
+ fixableCount: 0,
462
+ byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
463
+ byRule: { 'parse-error': 1 },
464
+ });
465
+ continue;
466
+ }
467
+ // Run linting
468
+ const result = await engine.lintWithConfig(loaded.config, privuConfig, {
469
+ library,
470
+ configPath: relativePath,
471
+ rawContent: loaded.raw,
472
+ enabledRules: options.rule,
473
+ disabledRules: options.ignoreRule,
343
474
  });
344
- continue;
475
+ results.set(relativePath, result);
345
476
  }
346
- // Run linting
347
- const result = await engine.lintWithConfig(loaded.config, privuConfig, {
348
- library,
349
- configPath: relativePath,
350
- rawContent: loaded.raw,
351
- enabledRules: options.rule,
352
- disabledRules: options.ignoreRule,
353
- });
354
- results.set(relativePath, result);
355
477
  }
356
478
  // Output results
357
479
  if (options.json) {