@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/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +155 -34
- package/dist/index.cjs +640 -42
- package/dist/index.cjs.map +4 -4
- package/package.json +2 -2
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
7774
|
+
var p = new Promise(function(resolve9) {
|
|
7775
7775
|
process.nextTick(function() {
|
|
7776
7776
|
if (queue.idle()) {
|
|
7777
|
-
|
|
7777
|
+
resolve9();
|
|
7778
7778
|
} else {
|
|
7779
7779
|
var previousDrain = queue.drain;
|
|
7780
7780
|
queue.drain = function() {
|
|
7781
7781
|
if (typeof previousDrain === "function") previousDrain();
|
|
7782
|
-
|
|
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((
|
|
8268
|
+
return new Promise((resolve9, reject) => {
|
|
8269
8269
|
this._stat(filepath, this._fsStatSettings, (error, stats) => {
|
|
8270
|
-
return error === null ?
|
|
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((
|
|
8294
|
+
return new Promise((resolve9, reject) => {
|
|
8295
8295
|
this._walkAsync(root, options, (error, entries) => {
|
|
8296
8296
|
if (error === null) {
|
|
8297
|
-
|
|
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((
|
|
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", () =>
|
|
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 && !
|
|
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
|
|
24017
|
-
if (
|
|
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
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
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
|
-
|
|
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));
|