@aikdna/kdna-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,131 @@
1
+ /**
2
+ * KDNA Validate — Pure schema and cross-file validation.
3
+ *
4
+ * Operates on in-memory data maps. No fs, no path, no Node.js dependencies.
5
+ * ajv is an optional peer dependency — if not available, schema validation
6
+ * returns a warning instead of failing.
7
+ */
8
+
9
+ /**
10
+ * Validate KDNA domain files against their JSON Schema definitions.
11
+ *
12
+ * @param {Object} dataMap — keyed by filename, e.g. { 'KDNA_Core.json': {...}, ... }
13
+ * @param {Object} [schemaMap] — keyed by filename, e.g. { 'KDNA_Core.json': schemaObj, ... }
14
+ * If not provided, schema validation is skipped.
15
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
16
+ */
17
+ function validateDomainSchema(dataMap, schemaMap) {
18
+ const errors = [];
19
+ const warnings = [];
20
+
21
+ if (!schemaMap || Object.keys(schemaMap).length === 0) {
22
+ warnings.push('Schema validation skipped: no schemas provided');
23
+ return { valid: errors.length === 0, errors, warnings };
24
+ }
25
+
26
+ const FILE_TO_SCHEMA = {
27
+ 'KDNA_Core.json': 'KDNA_Core.schema.json',
28
+ 'KDNA_Patterns.json': 'KDNA_Patterns.schema.json',
29
+ 'KDNA_Scenarios.json': 'KDNA_Scenarios.schema.json',
30
+ 'KDNA_Cases.json': 'KDNA_Cases.schema.json',
31
+ 'KDNA_Reasoning.json': 'KDNA_Reasoning.schema.json',
32
+ 'KDNA_Evolution.json': 'KDNA_Evolution.schema.json',
33
+ };
34
+
35
+ let ajv, addFormats;
36
+ try {
37
+ ajv = require('ajv');
38
+ try {
39
+ addFormats = require('ajv-formats');
40
+ } catch {
41
+ addFormats = null;
42
+ }
43
+ } catch {
44
+ warnings.push('Schema validation skipped: ajv not installed. Install with: npm install ajv ajv-formats');
45
+ return { valid: true, errors: [], warnings };
46
+ }
47
+
48
+ let validCount = 0;
49
+ let failCount = 0;
50
+
51
+ for (const [file, schemaFile] of Object.entries(FILE_TO_SCHEMA)) {
52
+ if (!dataMap[file]) continue;
53
+ if (!schemaMap[schemaFile]) {
54
+ warnings.push(`${file}: no schema found at ${schemaFile}`);
55
+ continue;
56
+ }
57
+
58
+ const schema = { ...schemaMap[schemaFile] };
59
+ // Remove $schema ref so AJV doesn't try to fetch the meta-schema
60
+ delete schema.$schema;
61
+
62
+ const ajvInstance = new ajv({ allErrors: true, strict: false });
63
+ if (addFormats) addFormats(ajvInstance);
64
+ try {
65
+ ajvInstance.addMetaSchema(require('ajv/dist/refs/json-schema-2020-12.json'));
66
+ } catch {
67
+ /* meta-schema already available or not needed */
68
+ }
69
+ const validate = ajvInstance.compile(schema);
70
+ const valid = validate(dataMap[file]);
71
+
72
+ if (valid) {
73
+ validCount++;
74
+ } else {
75
+ failCount++;
76
+ for (const err of validate.errors || []) {
77
+ const instancePath = err.instancePath || '/';
78
+ errors.push(`${file}${instancePath}: ${err.message} (${err.keyword})`);
79
+ }
80
+ }
81
+ }
82
+
83
+ return { valid: errors.length === 0, errors, warnings };
84
+ }
85
+
86
+ /**
87
+ * Validate cross-file consistency in a KDNA domain.
88
+ *
89
+ * Checks:
90
+ * - Domain name consistency across all files' meta.domain
91
+ * - Version consistency across all files' meta.version
92
+ *
93
+ * @param {Object} dataMap — keyed by filename
94
+ * @returns {{ valid: boolean, errors: string[], warnings: string[] }}
95
+ */
96
+ function validateCrossFile(dataMap) {
97
+ const errors = [];
98
+ const warnings = [];
99
+
100
+ // Domain name consistency
101
+ let domainName = null;
102
+ for (const [file, data] of Object.entries(dataMap)) {
103
+ if (data && data.meta && data.meta.domain) {
104
+ if (domainName === null) {
105
+ domainName = data.meta.domain;
106
+ } else if (data.meta.domain !== domainName) {
107
+ errors.push(
108
+ `${file}: domain name "${data.meta.domain}" does not match "${domainName}" from other files`,
109
+ );
110
+ }
111
+ }
112
+ }
113
+
114
+ // Version consistency
115
+ let version = null;
116
+ for (const [file, data] of Object.entries(dataMap)) {
117
+ if (data && data.meta && data.meta.version) {
118
+ if (version === null) {
119
+ version = data.meta.version;
120
+ } else if (data.meta.version !== version) {
121
+ warnings.push(
122
+ `${file}: version "${data.meta.version}" differs from "${version}" in other files`,
123
+ );
124
+ }
125
+ }
126
+ }
127
+
128
+ return { valid: errors.length === 0, errors, warnings };
129
+ }
130
+
131
+ module.exports = { validateDomainSchema, validateCrossFile };