@principal-ai/principal-view-cli 0.1.29 → 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.
package/dist/index.cjs CHANGED
@@ -7745,41 +7745,41 @@ var require_queue = __commonJS({
7745
7745
  queue.drained = drained;
7746
7746
  return queue;
7747
7747
  function push(value) {
7748
- var p = new Promise(function(resolve8, reject) {
7748
+ var p = new Promise(function(resolve9, reject) {
7749
7749
  pushCb(value, function(err, result) {
7750
7750
  if (err) {
7751
7751
  reject(err);
7752
7752
  return;
7753
7753
  }
7754
- resolve8(result);
7754
+ resolve9(result);
7755
7755
  });
7756
7756
  });
7757
7757
  p.catch(noop2);
7758
7758
  return p;
7759
7759
  }
7760
7760
  function unshift(value) {
7761
- var p = new Promise(function(resolve8, reject) {
7761
+ var p = new Promise(function(resolve9, reject) {
7762
7762
  unshiftCb(value, function(err, result) {
7763
7763
  if (err) {
7764
7764
  reject(err);
7765
7765
  return;
7766
7766
  }
7767
- resolve8(result);
7767
+ resolve9(result);
7768
7768
  });
7769
7769
  });
7770
7770
  p.catch(noop2);
7771
7771
  return p;
7772
7772
  }
7773
7773
  function drained() {
7774
- var p = new Promise(function(resolve8) {
7774
+ var p = new Promise(function(resolve9) {
7775
7775
  process.nextTick(function() {
7776
7776
  if (queue.idle()) {
7777
- resolve8();
7777
+ resolve9();
7778
7778
  } else {
7779
7779
  var previousDrain = queue.drain;
7780
7780
  queue.drain = function() {
7781
7781
  if (typeof previousDrain === "function") previousDrain();
7782
- resolve8();
7782
+ resolve9();
7783
7783
  queue.drain = previousDrain;
7784
7784
  };
7785
7785
  }
@@ -8265,9 +8265,9 @@ var require_stream3 = __commonJS({
8265
8265
  });
8266
8266
  }
8267
8267
  _getStat(filepath) {
8268
- return new Promise((resolve8, reject) => {
8268
+ return new Promise((resolve9, reject) => {
8269
8269
  this._stat(filepath, this._fsStatSettings, (error, stats) => {
8270
- return error === null ? resolve8(stats) : reject(error);
8270
+ return error === null ? resolve9(stats) : reject(error);
8271
8271
  });
8272
8272
  });
8273
8273
  }
@@ -8291,10 +8291,10 @@ var require_async5 = __commonJS({
8291
8291
  this._readerStream = new stream_1.default(this._settings);
8292
8292
  }
8293
8293
  dynamic(root, options) {
8294
- return new Promise((resolve8, reject) => {
8294
+ return new Promise((resolve9, reject) => {
8295
8295
  this._walkAsync(root, options, (error, entries) => {
8296
8296
  if (error === null) {
8297
- resolve8(entries);
8297
+ resolve9(entries);
8298
8298
  } else {
8299
8299
  reject(error);
8300
8300
  }
@@ -8304,10 +8304,10 @@ var require_async5 = __commonJS({
8304
8304
  async static(patterns, options) {
8305
8305
  const entries = [];
8306
8306
  const stream = this._readerStream.static(patterns, options);
8307
- return new Promise((resolve8, reject) => {
8307
+ return new Promise((resolve9, reject) => {
8308
8308
  stream.once("error", reject);
8309
8309
  stream.on("data", (entry) => entries.push(entry));
8310
- stream.once("end", () => resolve8(entries));
8310
+ stream.once("end", () => resolve9(entries));
8311
8311
  });
8312
8312
  }
8313
8313
  };
@@ -23759,6 +23759,504 @@ function createDefaultRulesEngine() {
23759
23759
  return createRulesEngine(builtinRules);
23760
23760
  }
23761
23761
 
23762
+ // node_modules/@principal-ai/principal-view-core/dist/narrative/validator.js
23763
+ var import_fs = require("fs");
23764
+ var import_path2 = require("path");
23765
+ var NarrativeValidator = class {
23766
+ /**
23767
+ * Validate a narrative template
23768
+ */
23769
+ async validate(context) {
23770
+ const violations = [];
23771
+ violations.push(...this.checkSchema(context));
23772
+ violations.push(...this.checkCanvasExists(context));
23773
+ if (context.canvas) {
23774
+ violations.push(...this.checkEventReferences(context));
23775
+ violations.push(...this.checkAttributeReferences(context));
23776
+ }
23777
+ violations.push(...this.checkScenarios(context));
23778
+ violations.push(...this.checkTemplateSyntax(context));
23779
+ violations.push(...this.checkFormattingOptions(context));
23780
+ return this.aggregateResults(violations);
23781
+ }
23782
+ /**
23783
+ * Check schema validity (required fields, valid values)
23784
+ */
23785
+ checkSchema(context) {
23786
+ const violations = [];
23787
+ const { narrative, narrativePath } = context;
23788
+ if (!narrative.version) {
23789
+ violations.push({
23790
+ ruleId: "narrative-schema-valid",
23791
+ severity: "error",
23792
+ file: narrativePath,
23793
+ path: "version",
23794
+ message: 'Missing required field "version"',
23795
+ impact: "Cannot determine template version for compatibility",
23796
+ suggestion: 'Add a version field (e.g., "1.0.0")',
23797
+ fixable: false
23798
+ });
23799
+ } else if (!this.isValidSemver(narrative.version)) {
23800
+ violations.push({
23801
+ ruleId: "narrative-schema-valid",
23802
+ severity: "error",
23803
+ file: narrativePath,
23804
+ path: "version",
23805
+ message: `Invalid version format: "${narrative.version}"`,
23806
+ impact: "Version must follow semver format",
23807
+ suggestion: 'Use semver format like "1.0.0"',
23808
+ fixable: false
23809
+ });
23810
+ }
23811
+ if (!narrative.canvas) {
23812
+ violations.push({
23813
+ ruleId: "narrative-schema-valid",
23814
+ severity: "error",
23815
+ file: narrativePath,
23816
+ path: "canvas",
23817
+ message: 'Missing required field "canvas"',
23818
+ impact: "Cannot determine which canvas this narrative belongs to",
23819
+ suggestion: "Add a canvas field pointing to an .otel.canvas file",
23820
+ fixable: false
23821
+ });
23822
+ }
23823
+ if (!narrative.name) {
23824
+ violations.push({
23825
+ ruleId: "narrative-schema-valid",
23826
+ severity: "error",
23827
+ file: narrativePath,
23828
+ path: "name",
23829
+ message: 'Missing required field "name"',
23830
+ impact: "Cannot identify this narrative template",
23831
+ suggestion: "Add a human-readable name",
23832
+ fixable: false
23833
+ });
23834
+ }
23835
+ if (!narrative.description) {
23836
+ violations.push({
23837
+ ruleId: "narrative-schema-valid",
23838
+ severity: "error",
23839
+ file: narrativePath,
23840
+ path: "description",
23841
+ message: 'Missing required field "description"',
23842
+ impact: "Cannot understand the purpose of this narrative",
23843
+ suggestion: "Add a description explaining what this narrative shows",
23844
+ fixable: false
23845
+ });
23846
+ }
23847
+ const validModes = ["span-tree", "timeline", "summary-only"];
23848
+ if (!narrative.mode) {
23849
+ violations.push({
23850
+ ruleId: "narrative-schema-valid",
23851
+ severity: "error",
23852
+ file: narrativePath,
23853
+ path: "mode",
23854
+ message: 'Missing required field "mode"',
23855
+ impact: "Cannot determine how to structure the narrative",
23856
+ suggestion: `Set mode to one of: ${validModes.join(", ")}`,
23857
+ fixable: false
23858
+ });
23859
+ } else if (!validModes.includes(narrative.mode)) {
23860
+ violations.push({
23861
+ ruleId: "narrative-schema-valid",
23862
+ severity: "error",
23863
+ file: narrativePath,
23864
+ path: "mode",
23865
+ message: `Invalid mode: "${narrative.mode}"`,
23866
+ impact: "Mode must be one of the supported types",
23867
+ suggestion: `Use one of: ${validModes.join(", ")}`,
23868
+ fixable: false
23869
+ });
23870
+ }
23871
+ const validSelections = ["first-match", "manual"];
23872
+ if (narrative.scenarioSelection && !validSelections.includes(narrative.scenarioSelection)) {
23873
+ violations.push({
23874
+ ruleId: "narrative-schema-valid",
23875
+ severity: "error",
23876
+ file: narrativePath,
23877
+ path: "scenarioSelection",
23878
+ message: `Invalid scenarioSelection: "${narrative.scenarioSelection}"`,
23879
+ impact: "Scenario selection must be a valid type",
23880
+ suggestion: `Use one of: ${validSelections.join(", ")}`,
23881
+ fixable: false
23882
+ });
23883
+ }
23884
+ if (!narrative.scenarios || !Array.isArray(narrative.scenarios)) {
23885
+ violations.push({
23886
+ ruleId: "narrative-schema-valid",
23887
+ severity: "error",
23888
+ file: narrativePath,
23889
+ path: "scenarios",
23890
+ message: 'Missing or invalid "scenarios" field',
23891
+ impact: "Cannot generate narratives without scenarios",
23892
+ suggestion: "Add a scenarios array with at least one scenario",
23893
+ fixable: false
23894
+ });
23895
+ } else if (narrative.scenarios.length === 0) {
23896
+ violations.push({
23897
+ ruleId: "narrative-schema-valid",
23898
+ severity: "error",
23899
+ file: narrativePath,
23900
+ path: "scenarios",
23901
+ message: "Scenarios array is empty",
23902
+ impact: "Cannot generate narratives without scenarios",
23903
+ suggestion: "Add at least one scenario definition",
23904
+ fixable: false
23905
+ });
23906
+ }
23907
+ return violations;
23908
+ }
23909
+ /**
23910
+ * Check that the referenced canvas file exists
23911
+ */
23912
+ checkCanvasExists(context) {
23913
+ const violations = [];
23914
+ const { narrative, narrativePath, basePath, canvasPath } = context;
23915
+ if (!narrative.canvas) {
23916
+ return violations;
23917
+ }
23918
+ const resolvedPath = canvasPath || (0, import_path2.resolve)(basePath, narrative.canvas);
23919
+ if (!(0, import_fs.existsSync)(resolvedPath)) {
23920
+ violations.push({
23921
+ ruleId: "narrative-canvas-exists",
23922
+ severity: "error",
23923
+ file: narrativePath,
23924
+ path: "canvas",
23925
+ message: `Referenced canvas file does not exist: ${narrative.canvas}`,
23926
+ impact: "Cannot validate event references without the canvas",
23927
+ suggestion: "Ensure the canvas field points to a valid .otel.canvas file",
23928
+ fixable: false
23929
+ });
23930
+ }
23931
+ return violations;
23932
+ }
23933
+ /**
23934
+ * Check that events referenced in templates exist in the canvas
23935
+ *
23936
+ * Note: This is currently a placeholder as canvas files don't yet define event schemas.
23937
+ * In the future, when canvas files include OTEL event schema definitions,
23938
+ * this will validate event references.
23939
+ */
23940
+ checkEventReferences(context) {
23941
+ const violations = [];
23942
+ return violations;
23943
+ }
23944
+ /**
23945
+ * Check that scenarios are well-formed
23946
+ */
23947
+ checkScenarios(context) {
23948
+ const violations = [];
23949
+ const { narrative, narrativePath } = context;
23950
+ if (!narrative.scenarios || narrative.scenarios.length === 0) {
23951
+ return violations;
23952
+ }
23953
+ const scenarioIds = /* @__PURE__ */ new Set();
23954
+ const priorities = /* @__PURE__ */ new Set();
23955
+ let hasDefault = false;
23956
+ narrative.scenarios.forEach((scenario, idx) => {
23957
+ if (!scenario.id) {
23958
+ violations.push({
23959
+ ruleId: "narrative-scenario-valid",
23960
+ severity: "error",
23961
+ file: narrativePath,
23962
+ path: `scenarios[${idx}].id`,
23963
+ message: 'Scenario is missing required "id" field',
23964
+ impact: "Cannot identify this scenario",
23965
+ suggestion: "Add a unique ID for this scenario",
23966
+ fixable: false
23967
+ });
23968
+ } else {
23969
+ if (scenarioIds.has(scenario.id)) {
23970
+ violations.push({
23971
+ ruleId: "narrative-scenario-valid",
23972
+ severity: "error",
23973
+ file: narrativePath,
23974
+ path: `scenarios[${idx}].id`,
23975
+ message: `Duplicate scenario ID: "${scenario.id}"`,
23976
+ impact: "Scenario IDs must be unique",
23977
+ suggestion: "Use a unique identifier for each scenario",
23978
+ fixable: false
23979
+ });
23980
+ }
23981
+ scenarioIds.add(scenario.id);
23982
+ }
23983
+ if (scenario.priority === void 0 || scenario.priority === null) {
23984
+ violations.push({
23985
+ ruleId: "narrative-scenario-valid",
23986
+ severity: "error",
23987
+ file: narrativePath,
23988
+ path: `scenarios[${idx}].priority`,
23989
+ message: 'Scenario is missing required "priority" field',
23990
+ impact: "Cannot determine scenario selection order",
23991
+ suggestion: "Add a priority (lower number = higher priority)",
23992
+ fixable: false
23993
+ });
23994
+ } else {
23995
+ if (scenario.priority < 0) {
23996
+ violations.push({
23997
+ ruleId: "narrative-scenario-valid",
23998
+ severity: "error",
23999
+ file: narrativePath,
24000
+ path: `scenarios[${idx}].priority`,
24001
+ message: "Priority must be a non-negative number",
24002
+ impact: "Invalid priority value",
24003
+ suggestion: "Use a positive integer (1 = highest priority)",
24004
+ fixable: false
24005
+ });
24006
+ }
24007
+ if (priorities.has(scenario.priority)) {
24008
+ violations.push({
24009
+ ruleId: "narrative-scenario-valid",
24010
+ severity: "error",
24011
+ file: narrativePath,
24012
+ path: `scenarios[${idx}].priority`,
24013
+ message: `Duplicate priority: ${scenario.priority}`,
24014
+ impact: "Priorities must be unique to determine selection order",
24015
+ suggestion: "Assign unique priority values to each scenario",
24016
+ fixable: false
24017
+ });
24018
+ }
24019
+ priorities.add(scenario.priority);
24020
+ }
24021
+ if (scenario.condition?.default === true) {
24022
+ hasDefault = true;
24023
+ }
24024
+ if (!scenario.condition) {
24025
+ violations.push({
24026
+ ruleId: "narrative-scenario-valid",
24027
+ severity: "error",
24028
+ file: narrativePath,
24029
+ path: `scenarios[${idx}].condition`,
24030
+ message: 'Scenario is missing required "condition" field',
24031
+ impact: "Cannot determine when to use this scenario",
24032
+ suggestion: "Add a condition or set default: true",
24033
+ fixable: false
24034
+ });
24035
+ }
24036
+ if (!scenario.template) {
24037
+ violations.push({
24038
+ ruleId: "narrative-scenario-valid",
24039
+ severity: "error",
24040
+ file: narrativePath,
24041
+ path: `scenarios[${idx}].template`,
24042
+ message: 'Scenario is missing required "template" field',
24043
+ impact: "Cannot render narrative without a template",
24044
+ suggestion: "Add a template with introduction, events, or flow",
24045
+ fixable: false
24046
+ });
24047
+ }
24048
+ });
24049
+ if (!hasDefault) {
24050
+ violations.push({
24051
+ ruleId: "narrative-scenario-valid",
24052
+ severity: "error",
24053
+ file: narrativePath,
24054
+ path: "scenarios",
24055
+ message: "No default scenario defined",
24056
+ impact: "Narrative rendering may fail if no scenario matches",
24057
+ suggestion: 'Add a scenario with "condition.default: true" as a fallback',
24058
+ fixable: false
24059
+ });
24060
+ }
24061
+ return violations;
24062
+ }
24063
+ /**
24064
+ * Check template syntax (balanced braces, valid expressions)
24065
+ */
24066
+ checkTemplateSyntax(context) {
24067
+ const violations = [];
24068
+ const { narrative, narrativePath } = context;
24069
+ narrative.scenarios.forEach((scenario, scenarioIdx) => {
24070
+ if (!scenario.template) {
24071
+ return;
24072
+ }
24073
+ const template = scenario.template;
24074
+ if (template.introduction) {
24075
+ violations.push(...this.validateTemplateString(template.introduction, narrativePath, `scenarios[${scenarioIdx}].template.introduction`));
24076
+ }
24077
+ if (template.summary) {
24078
+ violations.push(...this.validateTemplateString(template.summary, narrativePath, `scenarios[${scenarioIdx}].template.summary`));
24079
+ }
24080
+ if (template.events) {
24081
+ Object.entries(template.events).forEach(([eventName, templateStr]) => {
24082
+ violations.push(...this.validateTemplateString(templateStr, narrativePath, `scenarios[${scenarioIdx}].template.events.${eventName}`));
24083
+ });
24084
+ }
24085
+ if (template.logs) {
24086
+ Object.entries(template.logs).forEach(([severity, templateStr]) => {
24087
+ if (typeof templateStr === "string") {
24088
+ violations.push(...this.validateTemplateString(templateStr, narrativePath, `scenarios[${scenarioIdx}].template.logs.${severity}`));
24089
+ }
24090
+ });
24091
+ }
24092
+ if (template.flow && Array.isArray(template.flow)) {
24093
+ template.flow.forEach((item, flowIdx) => {
24094
+ if (typeof item === "string") {
24095
+ violations.push(...this.validateTemplateString(item, narrativePath, `scenarios[${scenarioIdx}].template.flow[${flowIdx}]`));
24096
+ } else if (typeof item === "object" && item.template) {
24097
+ violations.push(...this.validateTemplateString(item.template, narrativePath, `scenarios[${scenarioIdx}].template.flow[${flowIdx}].template`));
24098
+ }
24099
+ });
24100
+ }
24101
+ });
24102
+ return violations;
24103
+ }
24104
+ /**
24105
+ * Validate a single template string
24106
+ */
24107
+ validateTemplateString(templateStr, file, path4) {
24108
+ const violations = [];
24109
+ let braceDepth = 0;
24110
+ let inQuote = false;
24111
+ let quoteChar = "";
24112
+ for (let i = 0; i < templateStr.length; i++) {
24113
+ const char = templateStr[i];
24114
+ const prevChar = i > 0 ? templateStr[i - 1] : "";
24115
+ if ((char === "'" || char === '"') && prevChar !== "\\") {
24116
+ if (!inQuote) {
24117
+ inQuote = true;
24118
+ quoteChar = char;
24119
+ } else if (char === quoteChar) {
24120
+ inQuote = false;
24121
+ quoteChar = "";
24122
+ }
24123
+ }
24124
+ if (!inQuote) {
24125
+ if (char === "{") {
24126
+ braceDepth++;
24127
+ } else if (char === "}") {
24128
+ braceDepth--;
24129
+ if (braceDepth < 0) {
24130
+ violations.push({
24131
+ ruleId: "narrative-template-syntax",
24132
+ severity: "error",
24133
+ file,
24134
+ path: path4,
24135
+ message: "Unbalanced braces: closing } without opening {",
24136
+ impact: "Template will fail to render",
24137
+ suggestion: "Ensure all {expressions} have matching braces",
24138
+ fixable: false
24139
+ });
24140
+ break;
24141
+ }
24142
+ }
24143
+ }
24144
+ }
24145
+ if (braceDepth > 0) {
24146
+ violations.push({
24147
+ ruleId: "narrative-template-syntax",
24148
+ severity: "error",
24149
+ file,
24150
+ path: path4,
24151
+ message: "Unbalanced braces: missing closing }",
24152
+ impact: "Template will fail to render",
24153
+ suggestion: "Ensure all {expressions} have matching braces",
24154
+ fixable: false
24155
+ });
24156
+ }
24157
+ const conditionalPattern = /\{[^}]*\?[^}]*\}/g;
24158
+ const conditionals = templateStr.match(conditionalPattern) || [];
24159
+ conditionals.forEach((expr) => {
24160
+ const questionCount = (expr.match(/\?/g) || []).length;
24161
+ const colonCount = (expr.match(/:/g) || []).length;
24162
+ if (questionCount > colonCount) {
24163
+ violations.push({
24164
+ ruleId: "narrative-template-syntax",
24165
+ severity: "error",
24166
+ file,
24167
+ path: path4,
24168
+ message: `Incomplete conditional expression: ${expr}`,
24169
+ impact: "Template will fail to render",
24170
+ suggestion: 'Conditional format: {condition ? "true" : "false"}',
24171
+ fixable: false
24172
+ });
24173
+ }
24174
+ });
24175
+ return violations;
24176
+ }
24177
+ /**
24178
+ * Check attribute references (warning level)
24179
+ */
24180
+ checkAttributeReferences(context) {
24181
+ const violations = [];
24182
+ return violations;
24183
+ }
24184
+ /**
24185
+ * Check formatting options
24186
+ */
24187
+ checkFormattingOptions(context) {
24188
+ const violations = [];
24189
+ const { narrative, narrativePath } = context;
24190
+ if (!narrative.formatting) {
24191
+ return violations;
24192
+ }
24193
+ if (narrative.formatting.showAttributes) {
24194
+ const validValues = ["none", "matched", "all"];
24195
+ if (!validValues.includes(narrative.formatting.showAttributes)) {
24196
+ violations.push({
24197
+ ruleId: "narrative-formatting-options",
24198
+ severity: "warn",
24199
+ file: narrativePath,
24200
+ path: "formatting.showAttributes",
24201
+ message: `Invalid showAttributes value: "${narrative.formatting.showAttributes}"`,
24202
+ impact: "May not display attributes correctly",
24203
+ suggestion: `Use one of: ${validValues.join(", ")}`,
24204
+ fixable: false
24205
+ });
24206
+ }
24207
+ }
24208
+ return violations;
24209
+ }
24210
+ /**
24211
+ * Aggregate violations into result
24212
+ */
24213
+ aggregateResults(violations) {
24214
+ let errorCount = 0;
24215
+ let warningCount = 0;
24216
+ let fixableCount = 0;
24217
+ violations.forEach((v) => {
24218
+ if (v.severity === "error") {
24219
+ errorCount++;
24220
+ } else {
24221
+ warningCount++;
24222
+ }
24223
+ if (v.fixable) {
24224
+ fixableCount++;
24225
+ }
24226
+ });
24227
+ return {
24228
+ violations,
24229
+ errorCount,
24230
+ warningCount,
24231
+ fixableCount
24232
+ };
24233
+ }
24234
+ // ============================================================================
24235
+ // Helper Methods
24236
+ // ============================================================================
24237
+ /**
24238
+ * Check if a version string is valid semver
24239
+ */
24240
+ isValidSemver(version) {
24241
+ const semverPattern = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/;
24242
+ return semverPattern.test(version);
24243
+ }
24244
+ /**
24245
+ * Check if an event name matches any available event (supports globs)
24246
+ */
24247
+ matchesEventPattern(eventName, availableEvents) {
24248
+ if (availableEvents.includes(eventName)) {
24249
+ return true;
24250
+ }
24251
+ const pattern = eventName.replace(/\*/g, ".*");
24252
+ const regex = new RegExp(`^${pattern}$`);
24253
+ return availableEvents.some((e) => regex.test(e));
24254
+ }
24255
+ };
24256
+ function createNarrativeValidator() {
24257
+ return new NarrativeValidator();
24258
+ }
24259
+
23762
24260
  // src/commands/lint.ts
23763
24261
  var CONFIG_FILE_NAMES2 = [".privurc.yaml", ".privurc.yml", ".privurc.json"];
23764
24262
  function findConfig(startDir) {
@@ -23838,6 +24336,44 @@ function loadGraphConfig(filePath) {
23838
24336
  return null;
23839
24337
  }
23840
24338
  }
24339
+ function loadNarrativeTemplate(filePath) {
24340
+ if (!(0, import_node_fs10.existsSync)(filePath)) {
24341
+ return null;
24342
+ }
24343
+ try {
24344
+ const raw = (0, import_node_fs10.readFileSync)(filePath, "utf8");
24345
+ const narrative = JSON.parse(raw);
24346
+ return { narrative, raw };
24347
+ } catch {
24348
+ return null;
24349
+ }
24350
+ }
24351
+ function loadCanvas(filePath) {
24352
+ if (!(0, import_node_fs10.existsSync)(filePath)) {
24353
+ return null;
24354
+ }
24355
+ try {
24356
+ const content = (0, import_node_fs10.readFileSync)(filePath, "utf8");
24357
+ const ext = filePath.toLowerCase();
24358
+ if (ext.endsWith(".json")) {
24359
+ return JSON.parse(content);
24360
+ } else {
24361
+ return jsYaml.load(content);
24362
+ }
24363
+ } catch {
24364
+ return null;
24365
+ }
24366
+ }
24367
+ function getFileType(filePath) {
24368
+ const name = (0, import_node_path11.basename)(filePath).toLowerCase();
24369
+ if (name.endsWith(".narrative.json")) {
24370
+ return "narrative";
24371
+ }
24372
+ if (name.endsWith(".canvas") || name.endsWith(".otel.canvas")) {
24373
+ return "canvas";
24374
+ }
24375
+ return "config";
24376
+ }
23841
24377
  function formatPrettyOutput(results, quiet) {
23842
24378
  const lines = [];
23843
24379
  let totalErrors = 0;
@@ -23921,6 +24457,14 @@ function createLintCommand() {
23921
24457
  "Files or glob patterns to lint (defaults to .principal-views/**/*.yaml)"
23922
24458
  ).option("-c, --config <path>", "Path to config file").option("--library <path>", "Path to component library file").option("-q, --quiet", "Only output errors").option("--json", "Output results as JSON").option("--rule <rules...>", "Only run specific rules").option("--ignore-rule <rules...>", "Skip specific rules").action(async (files, options) => {
23923
24459
  try {
24460
+ let countByRule2 = function(violations) {
24461
+ const counts = {};
24462
+ for (const v of violations) {
24463
+ counts[v.ruleId] = (counts[v.ruleId] || 0) + 1;
24464
+ }
24465
+ return counts;
24466
+ };
24467
+ var countByRule = countByRule2;
23924
24468
  const cwd = process.cwd();
23925
24469
  let privuConfig = getDefaultConfig();
23926
24470
  if (options.config) {
@@ -23978,10 +24522,8 @@ function createLintCommand() {
23978
24522
  const name = (0, import_node_path11.basename)(f).toLowerCase();
23979
24523
  const isLibraryFile = name.startsWith("library.");
23980
24524
  const isConfigFile = name.startsWith(".privurc");
23981
- const isCanvasFile = f.toLowerCase().endsWith(".canvas");
23982
- const isNarrativeTemplate = name.endsWith(".narrative.json");
23983
24525
  const isExecutionArtifact = f.includes("__executions__/");
23984
- return !isLibraryFile && !isConfigFile && !isCanvasFile && !isNarrativeTemplate && !isExecutionArtifact;
24526
+ return !isLibraryFile && !isConfigFile && !isExecutionArtifact;
23985
24527
  });
23986
24528
  if (configFiles.length === 0) {
23987
24529
  if (options.json) {
@@ -24009,39 +24551,95 @@ function createLintCommand() {
24009
24551
  }
24010
24552
  }
24011
24553
  const engine = createDefaultRulesEngine();
24554
+ const narrativeValidator = createNarrativeValidator();
24012
24555
  const results = /* @__PURE__ */ new Map();
24013
24556
  for (const filePath of configFiles) {
24014
24557
  const absolutePath = (0, import_node_path11.resolve)(cwd, filePath);
24015
24558
  const relativePath = (0, import_node_path11.relative)(cwd, absolutePath);
24016
- const loaded = loadGraphConfig(absolutePath);
24017
- if (!loaded) {
24559
+ const fileType = getFileType(absolutePath);
24560
+ if (fileType === "narrative") {
24561
+ const loaded = loadNarrativeTemplate(absolutePath);
24562
+ if (!loaded) {
24563
+ results.set(relativePath, {
24564
+ violations: [
24565
+ {
24566
+ ruleId: "parse-error",
24567
+ severity: "error",
24568
+ file: relativePath,
24569
+ message: `Could not parse narrative file: ${filePath}`,
24570
+ impact: "File cannot be validated",
24571
+ fixable: false
24572
+ }
24573
+ ],
24574
+ errorCount: 1,
24575
+ warningCount: 0,
24576
+ fixableCount: 0,
24577
+ byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
24578
+ byRule: { "parse-error": 1 }
24579
+ });
24580
+ continue;
24581
+ }
24582
+ const canvasPath = loaded.narrative.canvas ? (0, import_node_path11.resolve)((0, import_node_path11.dirname)(absolutePath), loaded.narrative.canvas) : void 0;
24583
+ const canvas = canvasPath ? loadCanvas(canvasPath) : void 0;
24584
+ const narrativeResult = await narrativeValidator.validate({
24585
+ narrative: loaded.narrative,
24586
+ narrativePath: relativePath,
24587
+ canvas: canvas ?? void 0,
24588
+ canvasPath: canvasPath ? (0, import_node_path11.relative)(cwd, canvasPath) : void 0,
24589
+ basePath: (0, import_node_path11.dirname)(absolutePath),
24590
+ rawContent: loaded.raw
24591
+ });
24592
+ const violations = narrativeResult.violations.map((v) => ({
24593
+ ruleId: v.ruleId,
24594
+ severity: v.severity,
24595
+ file: v.file,
24596
+ line: v.line,
24597
+ path: v.path,
24598
+ message: v.message,
24599
+ impact: v.impact,
24600
+ suggestion: v.suggestion,
24601
+ fixable: v.fixable
24602
+ }));
24018
24603
  results.set(relativePath, {
24019
- violations: [
24020
- {
24021
- ruleId: "parse-error",
24022
- severity: "error",
24023
- file: relativePath,
24024
- message: `Could not parse file: ${filePath}`,
24025
- impact: "File cannot be validated",
24026
- fixable: false
24027
- }
24028
- ],
24029
- errorCount: 1,
24030
- warningCount: 0,
24031
- fixableCount: 0,
24032
- byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
24033
- byRule: { "parse-error": 1 }
24604
+ violations,
24605
+ errorCount: narrativeResult.errorCount,
24606
+ warningCount: narrativeResult.warningCount,
24607
+ fixableCount: narrativeResult.fixableCount,
24608
+ byCategory: { schema: 0, reference: 0, structure: 0, pattern: 0, library: 0 },
24609
+ // Could categorize narrative rules
24610
+ byRule: countByRule2(violations)
24034
24611
  });
24035
- continue;
24612
+ } else {
24613
+ const loaded = loadGraphConfig(absolutePath);
24614
+ if (!loaded) {
24615
+ results.set(relativePath, {
24616
+ violations: [
24617
+ {
24618
+ ruleId: "parse-error",
24619
+ severity: "error",
24620
+ file: relativePath,
24621
+ message: `Could not parse file: ${filePath}`,
24622
+ impact: "File cannot be validated",
24623
+ fixable: false
24624
+ }
24625
+ ],
24626
+ errorCount: 1,
24627
+ warningCount: 0,
24628
+ fixableCount: 0,
24629
+ byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
24630
+ byRule: { "parse-error": 1 }
24631
+ });
24632
+ continue;
24633
+ }
24634
+ const result = await engine.lintWithConfig(loaded.config, privuConfig, {
24635
+ library,
24636
+ configPath: relativePath,
24637
+ rawContent: loaded.raw,
24638
+ enabledRules: options.rule,
24639
+ disabledRules: options.ignoreRule
24640
+ });
24641
+ results.set(relativePath, result);
24036
24642
  }
24037
- const result = await engine.lintWithConfig(loaded.config, privuConfig, {
24038
- library,
24039
- configPath: relativePath,
24040
- rawContent: loaded.raw,
24041
- enabledRules: options.rule,
24042
- disabledRules: options.ignoreRule
24043
- });
24044
- results.set(relativePath, result);
24045
24643
  }
24046
24644
  if (options.json) {
24047
24645
  console.log(JSON.stringify(formatJsonOutput(results), null, 2));