@principal-ai/principal-view-core 0.5.6
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/README.md +126 -0
- package/dist/ConfigurationLoader.d.ts +76 -0
- package/dist/ConfigurationLoader.d.ts.map +1 -0
- package/dist/ConfigurationLoader.js +144 -0
- package/dist/ConfigurationLoader.js.map +1 -0
- package/dist/ConfigurationValidator.d.ts +31 -0
- package/dist/ConfigurationValidator.d.ts.map +1 -0
- package/dist/ConfigurationValidator.js +242 -0
- package/dist/ConfigurationValidator.js.map +1 -0
- package/dist/EventProcessor.d.ts +49 -0
- package/dist/EventProcessor.d.ts.map +1 -0
- package/dist/EventProcessor.js +215 -0
- package/dist/EventProcessor.js.map +1 -0
- package/dist/EventRecorderService.d.ts +305 -0
- package/dist/EventRecorderService.d.ts.map +1 -0
- package/dist/EventRecorderService.js +463 -0
- package/dist/EventRecorderService.js.map +1 -0
- package/dist/LibraryLoader.d.ts +63 -0
- package/dist/LibraryLoader.d.ts.map +1 -0
- package/dist/LibraryLoader.js +188 -0
- package/dist/LibraryLoader.js.map +1 -0
- package/dist/PathBasedEventProcessor.d.ts +90 -0
- package/dist/PathBasedEventProcessor.d.ts.map +1 -0
- package/dist/PathBasedEventProcessor.js +239 -0
- package/dist/PathBasedEventProcessor.js.map +1 -0
- package/dist/SessionManager.d.ts +194 -0
- package/dist/SessionManager.d.ts.map +1 -0
- package/dist/SessionManager.js +299 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/ValidationEngine.d.ts +31 -0
- package/dist/ValidationEngine.d.ts.map +1 -0
- package/dist/ValidationEngine.js +158 -0
- package/dist/ValidationEngine.js.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
- package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
- package/dist/helpers/GraphInstrumentationHelper.js +248 -0
- package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/rules/config.d.ts +57 -0
- package/dist/rules/config.d.ts.map +1 -0
- package/dist/rules/config.js +382 -0
- package/dist/rules/config.js.map +1 -0
- package/dist/rules/engine.d.ts +70 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/engine.js +252 -0
- package/dist/rules/engine.js.map +1 -0
- package/dist/rules/implementations/connection-type-references.d.ts +7 -0
- package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
- package/dist/rules/implementations/connection-type-references.js +104 -0
- package/dist/rules/implementations/connection-type-references.js.map +1 -0
- package/dist/rules/implementations/dead-end-states.d.ts +17 -0
- package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
- package/dist/rules/implementations/dead-end-states.js +72 -0
- package/dist/rules/implementations/dead-end-states.js.map +1 -0
- package/dist/rules/implementations/index.d.ts +24 -0
- package/dist/rules/implementations/index.d.ts.map +1 -0
- package/dist/rules/implementations/index.js +62 -0
- package/dist/rules/implementations/index.js.map +1 -0
- package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
- package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
- package/dist/rules/implementations/library-node-type-match.js +123 -0
- package/dist/rules/implementations/library-node-type-match.js.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
- package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
- package/dist/rules/implementations/minimum-node-sources.js +54 -0
- package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
- package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
- package/dist/rules/implementations/no-unknown-fields.js +211 -0
- package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-edge-types.js +47 -0
- package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
- package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/orphaned-node-types.js +50 -0
- package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
- package/dist/rules/implementations/required-metadata.d.ts +7 -0
- package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
- package/dist/rules/implementations/required-metadata.js +57 -0
- package/dist/rules/implementations/required-metadata.js.map +1 -0
- package/dist/rules/implementations/state-transition-references.d.ts +7 -0
- package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
- package/dist/rules/implementations/state-transition-references.js +135 -0
- package/dist/rules/implementations/state-transition-references.js.map +1 -0
- package/dist/rules/implementations/unreachable-states.d.ts +7 -0
- package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
- package/dist/rules/implementations/unreachable-states.js +80 -0
- package/dist/rules/implementations/unreachable-states.js.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
- package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
- package/dist/rules/implementations/valid-action-patterns.js +109 -0
- package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
- package/dist/rules/implementations/valid-color-format.d.ts +7 -0
- package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
- package/dist/rules/implementations/valid-color-format.js +91 -0
- package/dist/rules/implementations/valid-color-format.js.map +1 -0
- package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
- package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-edge-types.js +244 -0
- package/dist/rules/implementations/valid-edge-types.js.map +1 -0
- package/dist/rules/implementations/valid-node-types.d.ts +7 -0
- package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
- package/dist/rules/implementations/valid-node-types.js +175 -0
- package/dist/rules/implementations/valid-node-types.js.map +1 -0
- package/dist/rules/index.d.ts +28 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +45 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/types.d.ts +309 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +35 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/types/canvas.d.ts +409 -0
- package/dist/types/canvas.d.ts.map +1 -0
- package/dist/types/canvas.js +70 -0
- package/dist/types/canvas.js.map +1 -0
- package/dist/types/index.d.ts +311 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/library.d.ts +185 -0
- package/dist/types/library.d.ts.map +1 -0
- package/dist/types/library.js +15 -0
- package/dist/types/library.js.map +1 -0
- package/dist/types/path-based-config.d.ts +230 -0
- package/dist/types/path-based-config.d.ts.map +1 -0
- package/dist/types/path-based-config.js +9 -0
- package/dist/types/path-based-config.js.map +1 -0
- package/dist/utils/CanvasConverter.d.ts +118 -0
- package/dist/utils/CanvasConverter.d.ts.map +1 -0
- package/dist/utils/CanvasConverter.js +315 -0
- package/dist/utils/CanvasConverter.js.map +1 -0
- package/dist/utils/GraphConverter.d.ts +18 -0
- package/dist/utils/GraphConverter.d.ts.map +1 -0
- package/dist/utils/GraphConverter.js +61 -0
- package/dist/utils/GraphConverter.js.map +1 -0
- package/dist/utils/LibraryConverter.d.ts +113 -0
- package/dist/utils/LibraryConverter.d.ts.map +1 -0
- package/dist/utils/LibraryConverter.js +166 -0
- package/dist/utils/LibraryConverter.js.map +1 -0
- package/dist/utils/PathMatcher.d.ts +55 -0
- package/dist/utils/PathMatcher.d.ts.map +1 -0
- package/dist/utils/PathMatcher.js +172 -0
- package/dist/utils/PathMatcher.js.map +1 -0
- package/dist/utils/YamlParser.d.ts +36 -0
- package/dist/utils/YamlParser.d.ts.map +1 -0
- package/dist/utils/YamlParser.js +63 -0
- package/dist/utils/YamlParser.js.map +1 -0
- package/package.json +47 -0
- package/src/ConfigurationLoader.test.ts +490 -0
- package/src/ConfigurationLoader.ts +185 -0
- package/src/ConfigurationValidator.test.ts +200 -0
- package/src/ConfigurationValidator.ts +283 -0
- package/src/EventProcessor.test.ts +405 -0
- package/src/EventProcessor.ts +250 -0
- package/src/EventRecorderService.test.ts +541 -0
- package/src/EventRecorderService.ts +744 -0
- package/src/LibraryLoader.ts +215 -0
- package/src/PathBasedEventProcessor.test.ts +567 -0
- package/src/PathBasedEventProcessor.ts +332 -0
- package/src/SessionManager.test.ts +424 -0
- package/src/SessionManager.ts +470 -0
- package/src/ValidationEngine.test.ts +371 -0
- package/src/ValidationEngine.ts +196 -0
- package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
- package/src/helpers/GraphInstrumentationHelper.ts +326 -0
- package/src/index.ts +85 -0
- package/src/rules/config.test.ts +278 -0
- package/src/rules/config.ts +459 -0
- package/src/rules/engine.test.ts +332 -0
- package/src/rules/engine.ts +318 -0
- package/src/rules/implementations/connection-type-references.ts +117 -0
- package/src/rules/implementations/dead-end-states.ts +101 -0
- package/src/rules/implementations/index.ts +73 -0
- package/src/rules/implementations/library-node-type-match.ts +148 -0
- package/src/rules/implementations/minimum-node-sources.ts +82 -0
- package/src/rules/implementations/no-unknown-fields.ts +342 -0
- package/src/rules/implementations/orphaned-edge-types.ts +55 -0
- package/src/rules/implementations/orphaned-node-types.ts +58 -0
- package/src/rules/implementations/required-metadata.ts +64 -0
- package/src/rules/implementations/state-transition-references.ts +151 -0
- package/src/rules/implementations/unreachable-states.ts +94 -0
- package/src/rules/implementations/valid-action-patterns.ts +136 -0
- package/src/rules/implementations/valid-color-format.ts +140 -0
- package/src/rules/implementations/valid-edge-types.ts +258 -0
- package/src/rules/implementations/valid-node-types.ts +189 -0
- package/src/rules/index.ts +95 -0
- package/src/rules/types.ts +426 -0
- package/src/types/canvas.ts +496 -0
- package/src/types/index.ts +382 -0
- package/src/types/library.ts +233 -0
- package/src/types/path-based-config.ts +281 -0
- package/src/utils/CanvasConverter.ts +431 -0
- package/src/utils/GraphConverter.test.ts +195 -0
- package/src/utils/GraphConverter.ts +71 -0
- package/src/utils/LibraryConverter.ts +245 -0
- package/src/utils/PathMatcher.test.ts +148 -0
- package/src/utils/PathMatcher.ts +183 -0
- package/src/utils/YamlParser.ts +75 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rule: valid-node-types
|
|
3
|
+
* Ensures nodeTypes have required fields and valid values
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { GraphRule, GraphRuleContext, GraphRuleViolation } from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Valid shape values for nodes
|
|
10
|
+
*/
|
|
11
|
+
const VALID_SHAPES = ['circle', 'rectangle', 'hexagon', 'diamond', 'custom'] as const;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Valid animation types
|
|
15
|
+
*/
|
|
16
|
+
const VALID_ANIMATION_TYPES = ['flow', 'pulse', 'particle', 'glow'] as const;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Valid data schema field types
|
|
20
|
+
*/
|
|
21
|
+
const VALID_DATA_TYPES = ['string', 'number', 'boolean', 'object', 'array'] as const;
|
|
22
|
+
|
|
23
|
+
export const validNodeTypes: GraphRule = {
|
|
24
|
+
id: 'valid-node-types',
|
|
25
|
+
name: 'Valid Node Types',
|
|
26
|
+
description: 'nodeTypes must have required fields and valid values',
|
|
27
|
+
impact: 'Invalid node types will cause rendering errors or unexpected behavior',
|
|
28
|
+
severity: 'error',
|
|
29
|
+
category: 'schema',
|
|
30
|
+
enabled: true,
|
|
31
|
+
fixable: false,
|
|
32
|
+
|
|
33
|
+
async check(context: GraphRuleContext): Promise<GraphRuleViolation[]> {
|
|
34
|
+
const violations: GraphRuleViolation[] = [];
|
|
35
|
+
const { configuration, configPath } = context;
|
|
36
|
+
|
|
37
|
+
if (!configuration.nodeTypes) {
|
|
38
|
+
violations.push({
|
|
39
|
+
ruleId: 'valid-node-types',
|
|
40
|
+
severity: 'error',
|
|
41
|
+
file: configPath,
|
|
42
|
+
path: 'nodeTypes',
|
|
43
|
+
message: 'Configuration is missing nodeTypes section',
|
|
44
|
+
impact: 'No nodes can be defined without nodeTypes',
|
|
45
|
+
suggestion: 'Add a nodeTypes section with at least one node type definition',
|
|
46
|
+
fixable: false,
|
|
47
|
+
});
|
|
48
|
+
return violations;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Object.keys(configuration.nodeTypes).length === 0) {
|
|
52
|
+
violations.push({
|
|
53
|
+
ruleId: 'valid-node-types',
|
|
54
|
+
severity: 'error',
|
|
55
|
+
file: configPath,
|
|
56
|
+
path: 'nodeTypes',
|
|
57
|
+
message: 'nodeTypes section is empty',
|
|
58
|
+
impact: 'No nodes can be defined without at least one node type',
|
|
59
|
+
suggestion: 'Add at least one node type definition',
|
|
60
|
+
fixable: false,
|
|
61
|
+
});
|
|
62
|
+
return violations;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const [typeId, nodeType] of Object.entries(configuration.nodeTypes)) {
|
|
66
|
+
const basePath = `nodeTypes.${typeId}`;
|
|
67
|
+
|
|
68
|
+
// Check required shape field
|
|
69
|
+
if (!nodeType.shape) {
|
|
70
|
+
violations.push({
|
|
71
|
+
ruleId: 'valid-node-types',
|
|
72
|
+
severity: 'error',
|
|
73
|
+
file: configPath,
|
|
74
|
+
path: `${basePath}.shape`,
|
|
75
|
+
message: `Node type "${typeId}" is missing required "shape" field`,
|
|
76
|
+
impact: 'Node cannot be rendered without a shape',
|
|
77
|
+
suggestion: `Add a shape field. Valid values: ${VALID_SHAPES.join(', ')}`,
|
|
78
|
+
fixable: false,
|
|
79
|
+
});
|
|
80
|
+
} else if (!VALID_SHAPES.includes(nodeType.shape as (typeof VALID_SHAPES)[number])) {
|
|
81
|
+
violations.push({
|
|
82
|
+
ruleId: 'valid-node-types',
|
|
83
|
+
severity: 'error',
|
|
84
|
+
file: configPath,
|
|
85
|
+
path: `${basePath}.shape`,
|
|
86
|
+
message: `Node type "${typeId}" has invalid shape "${nodeType.shape}"`,
|
|
87
|
+
impact: 'Node may not render correctly',
|
|
88
|
+
suggestion: `Valid shapes: ${VALID_SHAPES.join(', ')}`,
|
|
89
|
+
fixable: false,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check dataSchema field types
|
|
94
|
+
if (nodeType.dataSchema) {
|
|
95
|
+
for (const [fieldName, fieldDef] of Object.entries(nodeType.dataSchema)) {
|
|
96
|
+
if (!fieldDef.type) {
|
|
97
|
+
violations.push({
|
|
98
|
+
ruleId: 'valid-node-types',
|
|
99
|
+
severity: 'error',
|
|
100
|
+
file: configPath,
|
|
101
|
+
path: `${basePath}.dataSchema.${fieldName}.type`,
|
|
102
|
+
message: `Data schema field "${fieldName}" in node type "${typeId}" is missing "type"`,
|
|
103
|
+
impact: 'Data validation will not work for this field',
|
|
104
|
+
suggestion: `Add a type field. Valid values: ${VALID_DATA_TYPES.join(', ')}`,
|
|
105
|
+
fixable: false,
|
|
106
|
+
});
|
|
107
|
+
} else if (!VALID_DATA_TYPES.includes(fieldDef.type as (typeof VALID_DATA_TYPES)[number])) {
|
|
108
|
+
violations.push({
|
|
109
|
+
ruleId: 'valid-node-types',
|
|
110
|
+
severity: 'error',
|
|
111
|
+
file: configPath,
|
|
112
|
+
path: `${basePath}.dataSchema.${fieldName}.type`,
|
|
113
|
+
message: `Invalid data type "${fieldDef.type}" for field "${fieldName}" in node type "${typeId}"`,
|
|
114
|
+
impact: 'Data validation may fail',
|
|
115
|
+
suggestion: `Valid types: ${VALID_DATA_TYPES.join(', ')}`,
|
|
116
|
+
fixable: false,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check states have valid structure
|
|
123
|
+
if (nodeType.states) {
|
|
124
|
+
for (const [stateName, stateDef] of Object.entries(nodeType.states)) {
|
|
125
|
+
// States should be objects (even if empty)
|
|
126
|
+
if (typeof stateDef !== 'object' || stateDef === null) {
|
|
127
|
+
violations.push({
|
|
128
|
+
ruleId: 'valid-node-types',
|
|
129
|
+
severity: 'error',
|
|
130
|
+
file: configPath,
|
|
131
|
+
path: `${basePath}.states.${stateName}`,
|
|
132
|
+
message: `State "${stateName}" in node type "${typeId}" must be an object`,
|
|
133
|
+
impact: 'State definition is invalid',
|
|
134
|
+
suggestion: 'State should be an object with optional color, icon, and label fields',
|
|
135
|
+
fixable: false,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check size has valid structure
|
|
142
|
+
if (nodeType.size) {
|
|
143
|
+
if (typeof nodeType.size.width !== 'number' || typeof nodeType.size.height !== 'number') {
|
|
144
|
+
violations.push({
|
|
145
|
+
ruleId: 'valid-node-types',
|
|
146
|
+
severity: 'error',
|
|
147
|
+
file: configPath,
|
|
148
|
+
path: `${basePath}.size`,
|
|
149
|
+
message: `Node type "${typeId}" size must have numeric width and height`,
|
|
150
|
+
impact: 'Node size may not render correctly',
|
|
151
|
+
suggestion: 'Provide size as { width: number, height: number }',
|
|
152
|
+
fixable: false,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check layout has valid structure
|
|
158
|
+
if (nodeType.layout) {
|
|
159
|
+
if (nodeType.layout.layer !== undefined && typeof nodeType.layout.layer !== 'number') {
|
|
160
|
+
violations.push({
|
|
161
|
+
ruleId: 'valid-node-types',
|
|
162
|
+
severity: 'error',
|
|
163
|
+
file: configPath,
|
|
164
|
+
path: `${basePath}.layout.layer`,
|
|
165
|
+
message: `Node type "${typeId}" layout.layer must be a number`,
|
|
166
|
+
impact: 'Layout layering may not work correctly',
|
|
167
|
+
suggestion: 'Provide layer as a number',
|
|
168
|
+
fixable: false,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (nodeType.layout.cluster !== undefined && typeof nodeType.layout.cluster !== 'string') {
|
|
173
|
+
violations.push({
|
|
174
|
+
ruleId: 'valid-node-types',
|
|
175
|
+
severity: 'error',
|
|
176
|
+
file: configPath,
|
|
177
|
+
path: `${basePath}.layout.cluster`,
|
|
178
|
+
message: `Node type "${typeId}" layout.cluster must be a string`,
|
|
179
|
+
impact: 'Cluster grouping may not work correctly',
|
|
180
|
+
suggestion: 'Provide cluster as a string identifier',
|
|
181
|
+
fixable: false,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return violations;
|
|
188
|
+
},
|
|
189
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules Engine - Configuration validation for Visual Validation Framework
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { createRulesEngine, builtinRules } from '@principal-ai/principal-view-core/rules';
|
|
7
|
+
*
|
|
8
|
+
* const engine = createRulesEngine(builtinRules);
|
|
9
|
+
* const result = await engine.lint(configuration);
|
|
10
|
+
*
|
|
11
|
+
* if (result.errorCount > 0) {
|
|
12
|
+
* console.error(`Found ${result.errorCount} errors`);
|
|
13
|
+
* for (const violation of result.violations) {
|
|
14
|
+
* console.error(` ${violation.path}: ${violation.message}`);
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Core engine
|
|
21
|
+
export { GraphRulesEngine, createRulesEngine } from './engine';
|
|
22
|
+
|
|
23
|
+
// Types
|
|
24
|
+
export type {
|
|
25
|
+
GraphRule,
|
|
26
|
+
GraphRuleContext,
|
|
27
|
+
GraphRuleViolation,
|
|
28
|
+
GraphLintResult,
|
|
29
|
+
LintOptions,
|
|
30
|
+
RuleOptions,
|
|
31
|
+
RuleSeverity,
|
|
32
|
+
NormalizedSeverity,
|
|
33
|
+
RuleCategory,
|
|
34
|
+
RuleConfig,
|
|
35
|
+
PrivuConfig,
|
|
36
|
+
FixResult,
|
|
37
|
+
} from './types';
|
|
38
|
+
|
|
39
|
+
export { normalizeSeverity, isRuleDisabled } from './types';
|
|
40
|
+
|
|
41
|
+
// Config validation
|
|
42
|
+
export {
|
|
43
|
+
validatePrivuConfig,
|
|
44
|
+
getDefaultConfig,
|
|
45
|
+
mergeConfigs,
|
|
46
|
+
formatConfigErrors,
|
|
47
|
+
CONFIG_FILE_NAMES,
|
|
48
|
+
DEFAULT_INCLUDE_PATTERNS,
|
|
49
|
+
DEFAULT_EXCLUDE_PATTERNS,
|
|
50
|
+
VALID_RULE_IDS,
|
|
51
|
+
VALID_SEVERITIES,
|
|
52
|
+
type ConfigValidationError,
|
|
53
|
+
type ConfigValidationResult,
|
|
54
|
+
type ValidRuleId,
|
|
55
|
+
} from './config';
|
|
56
|
+
|
|
57
|
+
// Built-in rules
|
|
58
|
+
export {
|
|
59
|
+
builtinRules,
|
|
60
|
+
// Schema rules
|
|
61
|
+
requiredMetadata,
|
|
62
|
+
validNodeTypes,
|
|
63
|
+
validEdgeTypes,
|
|
64
|
+
validColorFormat,
|
|
65
|
+
noUnknownFields,
|
|
66
|
+
// Reference rules
|
|
67
|
+
connectionTypeReferences,
|
|
68
|
+
stateTransitionReferences,
|
|
69
|
+
// Structure rules
|
|
70
|
+
minimumNodeSources,
|
|
71
|
+
orphanedNodeTypes,
|
|
72
|
+
orphanedEdgeTypes,
|
|
73
|
+
unreachableStates,
|
|
74
|
+
deadEndStates,
|
|
75
|
+
// Pattern rules
|
|
76
|
+
validActionPatterns,
|
|
77
|
+
// Library rules
|
|
78
|
+
libraryNodeTypeMatch,
|
|
79
|
+
// Rule option types
|
|
80
|
+
type MinimumNodeSourcesOptions,
|
|
81
|
+
type DeadEndStatesOptions,
|
|
82
|
+
type ValidActionPatternsOptions,
|
|
83
|
+
type LibraryNodeTypeMatchOptions,
|
|
84
|
+
} from './implementations';
|
|
85
|
+
|
|
86
|
+
// Convenience factory that creates an engine with all built-in rules
|
|
87
|
+
import { createRulesEngine } from './engine';
|
|
88
|
+
import { builtinRules } from './implementations';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a rules engine pre-configured with all built-in rules
|
|
92
|
+
*/
|
|
93
|
+
export function createDefaultRulesEngine() {
|
|
94
|
+
return createRulesEngine(builtinRules);
|
|
95
|
+
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rules Engine Type Definitions
|
|
3
|
+
* Provides types for configuration validation rules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Rule Severity and Categories
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Severity levels following ESLint convention
|
|
12
|
+
* - "off" or 0: Disable rule
|
|
13
|
+
* - "warn" or 1: Warning (doesn't affect exit code)
|
|
14
|
+
* - "error" or 2: Error (exit code 1)
|
|
15
|
+
*/
|
|
16
|
+
export type RuleSeverity = 'off' | 'warn' | 'error' | 0 | 1 | 2;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Normalized severity (string form only)
|
|
20
|
+
*/
|
|
21
|
+
export type NormalizedSeverity = 'off' | 'warn' | 'error';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Rule categories for grouping and filtering
|
|
25
|
+
*/
|
|
26
|
+
export type RuleCategory = 'schema' | 'reference' | 'structure' | 'pattern' | 'library';
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Rule Configuration (RC File)
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Full rule configuration object
|
|
34
|
+
*/
|
|
35
|
+
export interface RuleConfig {
|
|
36
|
+
/**
|
|
37
|
+
* Rule severity
|
|
38
|
+
* @default "error"
|
|
39
|
+
*/
|
|
40
|
+
severity?: RuleSeverity;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Rule-specific options
|
|
44
|
+
*/
|
|
45
|
+
options?: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* VGC Configuration file schema
|
|
50
|
+
*/
|
|
51
|
+
export interface PrivuConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Schema version for forward compatibility
|
|
54
|
+
*/
|
|
55
|
+
$schema?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extend from a shared configuration
|
|
59
|
+
* Can be a package name or relative path
|
|
60
|
+
*/
|
|
61
|
+
extends?: string | string[];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Root flag - stop searching parent directories
|
|
65
|
+
* @default false
|
|
66
|
+
*/
|
|
67
|
+
root?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Path to default component library
|
|
71
|
+
* Used by library-node-type-match rule
|
|
72
|
+
*/
|
|
73
|
+
library?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Glob patterns for configuration files to lint
|
|
77
|
+
* @default [".vgc/**\/*.yaml", ".vgc/**\/*.yml", ".vgc/**\/*.json"]
|
|
78
|
+
*/
|
|
79
|
+
include?: string[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Glob patterns to exclude from linting
|
|
83
|
+
* @default ["**\/node_modules/**", "**\/*.test.*"]
|
|
84
|
+
*/
|
|
85
|
+
exclude?: string[];
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Rule configurations
|
|
89
|
+
*/
|
|
90
|
+
rules?: {
|
|
91
|
+
[ruleId: string]: RuleConfig | RuleSeverity | false;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Rule Definition
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Rule-specific options type (varies per rule)
|
|
101
|
+
*/
|
|
102
|
+
export type RuleOptions = Record<string, unknown>;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Definition of a validation rule
|
|
106
|
+
*/
|
|
107
|
+
export interface GraphRule<TOptions extends RuleOptions = RuleOptions> {
|
|
108
|
+
/**
|
|
109
|
+
* Unique rule identifier (kebab-case)
|
|
110
|
+
*/
|
|
111
|
+
id: string;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Human-readable rule name
|
|
115
|
+
*/
|
|
116
|
+
name: string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Brief description of what the rule checks
|
|
120
|
+
*/
|
|
121
|
+
description: string;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Explanation of why this rule matters
|
|
125
|
+
*/
|
|
126
|
+
impact: string;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Default severity level
|
|
130
|
+
* @default "error"
|
|
131
|
+
*/
|
|
132
|
+
severity: NormalizedSeverity;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Rule category for grouping
|
|
136
|
+
*/
|
|
137
|
+
category: RuleCategory;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Whether the rule is enabled by default
|
|
141
|
+
* @default true
|
|
142
|
+
*/
|
|
143
|
+
enabled: boolean;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether the rule supports auto-fix
|
|
147
|
+
* @default false
|
|
148
|
+
*/
|
|
149
|
+
fixable: boolean;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Default options for this rule
|
|
153
|
+
*/
|
|
154
|
+
defaultOptions?: TOptions;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Execute the rule check
|
|
158
|
+
*/
|
|
159
|
+
check: (context: GraphRuleContext<TOptions>) => Promise<GraphRuleViolation[]>;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Optional auto-fix implementation
|
|
163
|
+
*/
|
|
164
|
+
fix?: (violation: GraphRuleViolation, context: GraphRuleContext<TOptions>) => Promise<FixResult>;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ============================================================================
|
|
168
|
+
// Rule Context
|
|
169
|
+
// ============================================================================
|
|
170
|
+
|
|
171
|
+
import type { GraphConfiguration } from '../types';
|
|
172
|
+
import type { ComponentLibrary } from '../types/library';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Context passed to rules during evaluation
|
|
176
|
+
*/
|
|
177
|
+
export interface GraphRuleContext<TOptions extends RuleOptions = RuleOptions> {
|
|
178
|
+
/**
|
|
179
|
+
* The configuration being validated
|
|
180
|
+
*/
|
|
181
|
+
configuration: GraphConfiguration;
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Optional component library for library rules
|
|
185
|
+
*/
|
|
186
|
+
library?: ComponentLibrary;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Path to the configuration file (for error reporting)
|
|
190
|
+
*/
|
|
191
|
+
configPath?: string;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Path to the library file (for error reporting)
|
|
195
|
+
*/
|
|
196
|
+
libraryPath?: string;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Raw YAML/JSON string (for line number lookup)
|
|
200
|
+
*/
|
|
201
|
+
rawContent?: string;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Rule-specific options (merged with defaults)
|
|
205
|
+
*/
|
|
206
|
+
options: TOptions;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* All rule options from config (for cross-rule access if needed)
|
|
210
|
+
*/
|
|
211
|
+
allRuleOptions?: Map<string, RuleOptions>;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ============================================================================
|
|
215
|
+
// Rule Violations
|
|
216
|
+
// ============================================================================
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A violation detected by a rule
|
|
220
|
+
*/
|
|
221
|
+
export interface GraphRuleViolation {
|
|
222
|
+
/**
|
|
223
|
+
* ID of the rule that detected this violation
|
|
224
|
+
*/
|
|
225
|
+
ruleId: string;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Severity of this violation
|
|
229
|
+
*/
|
|
230
|
+
severity: NormalizedSeverity;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Path to the file containing the violation
|
|
234
|
+
*/
|
|
235
|
+
file?: string;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Line number in the file (1-indexed)
|
|
239
|
+
*/
|
|
240
|
+
line?: number;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Column number in the file (1-indexed)
|
|
244
|
+
*/
|
|
245
|
+
column?: number;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* JSON path to the problematic field (e.g., "nodeTypes.service.shape")
|
|
249
|
+
*/
|
|
250
|
+
path?: string;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Clear, actionable error message
|
|
254
|
+
*/
|
|
255
|
+
message: string;
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Explanation of why this matters
|
|
259
|
+
*/
|
|
260
|
+
impact: string;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Suggestion for how to fix
|
|
264
|
+
*/
|
|
265
|
+
suggestion?: string;
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Whether this violation can be auto-fixed
|
|
269
|
+
*/
|
|
270
|
+
fixable: boolean;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Data needed for auto-fix
|
|
274
|
+
*/
|
|
275
|
+
fixData?: Record<string, unknown>;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Lint Results
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Result of running all rules
|
|
284
|
+
*/
|
|
285
|
+
export interface GraphLintResult {
|
|
286
|
+
/**
|
|
287
|
+
* All violations found
|
|
288
|
+
*/
|
|
289
|
+
violations: GraphRuleViolation[];
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Count of error-level violations
|
|
293
|
+
*/
|
|
294
|
+
errorCount: number;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Count of warning-level violations
|
|
298
|
+
*/
|
|
299
|
+
warningCount: number;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Count of fixable violations
|
|
303
|
+
*/
|
|
304
|
+
fixableCount: number;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Violations grouped by category
|
|
308
|
+
*/
|
|
309
|
+
byCategory: Record<RuleCategory, number>;
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Violations grouped by rule ID
|
|
313
|
+
*/
|
|
314
|
+
byRule: Record<string, number>;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ============================================================================
|
|
318
|
+
// Fix Results
|
|
319
|
+
// ============================================================================
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Result of attempting to fix a violation
|
|
323
|
+
*/
|
|
324
|
+
export interface FixResult {
|
|
325
|
+
/**
|
|
326
|
+
* Whether the fix was successful
|
|
327
|
+
*/
|
|
328
|
+
success: boolean;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Description of what was fixed
|
|
332
|
+
*/
|
|
333
|
+
message?: string;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* New content after fix (if applicable)
|
|
337
|
+
*/
|
|
338
|
+
newContent?: string;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Error message if fix failed
|
|
342
|
+
*/
|
|
343
|
+
error?: string;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// Lint Options
|
|
348
|
+
// ============================================================================
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Options for the lint operation
|
|
352
|
+
*/
|
|
353
|
+
export interface LintOptions {
|
|
354
|
+
/**
|
|
355
|
+
* Only run these rules (by ID)
|
|
356
|
+
*/
|
|
357
|
+
enabledRules?: string[];
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Skip these rules (by ID)
|
|
361
|
+
*/
|
|
362
|
+
disabledRules?: string[];
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Override severity for specific rules
|
|
366
|
+
*/
|
|
367
|
+
severityOverrides?: Map<string, NormalizedSeverity>;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Component library for library rules
|
|
371
|
+
*/
|
|
372
|
+
library?: ComponentLibrary;
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Path to config file (for error reporting)
|
|
376
|
+
*/
|
|
377
|
+
configPath?: string;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Path to library file (for error reporting)
|
|
381
|
+
*/
|
|
382
|
+
libraryPath?: string;
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Raw content for line number reporting
|
|
386
|
+
*/
|
|
387
|
+
rawContent?: string;
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Rule-specific options
|
|
391
|
+
*/
|
|
392
|
+
ruleOptions?: Map<string, RuleOptions>;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ============================================================================
|
|
396
|
+
// Utility Types
|
|
397
|
+
// ============================================================================
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Normalize severity to string form
|
|
401
|
+
*/
|
|
402
|
+
export function normalizeSeverity(severity: RuleSeverity): NormalizedSeverity {
|
|
403
|
+
if (typeof severity === 'number') {
|
|
404
|
+
switch (severity) {
|
|
405
|
+
case 0:
|
|
406
|
+
return 'off';
|
|
407
|
+
case 1:
|
|
408
|
+
return 'warn';
|
|
409
|
+
case 2:
|
|
410
|
+
return 'error';
|
|
411
|
+
default:
|
|
412
|
+
return 'error';
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return severity;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Check if a severity means the rule is disabled
|
|
420
|
+
*/
|
|
421
|
+
export function isRuleDisabled(severity: RuleSeverity | false | undefined): boolean {
|
|
422
|
+
if (severity === false || severity === 'off' || severity === 0) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
}
|