@unrdf/project-engine 5.0.1 → 26.4.2

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.
Files changed (39) hide show
  1. package/package.json +16 -15
  2. package/src/golden-structure.mjs +2 -2
  3. package/src/materialize-apply.mjs +2 -2
  4. package/README.md +0 -53
  5. package/src/api-contract-validator.mjs +0 -711
  6. package/src/auto-test-generator.mjs +0 -444
  7. package/src/autonomic-mapek.mjs +0 -511
  8. package/src/capabilities-manifest.mjs +0 -125
  9. package/src/code-complexity-js.mjs +0 -368
  10. package/src/dependency-graph.mjs +0 -276
  11. package/src/doc-drift-checker.mjs +0 -172
  12. package/src/doc-generator.mjs +0 -229
  13. package/src/domain-infer.mjs +0 -966
  14. package/src/drift-snapshot.mjs +0 -775
  15. package/src/file-roles.mjs +0 -94
  16. package/src/fs-scan.mjs +0 -305
  17. package/src/gap-finder.mjs +0 -376
  18. package/src/hotspot-analyzer.mjs +0 -412
  19. package/src/index.mjs +0 -151
  20. package/src/initialize.mjs +0 -957
  21. package/src/lens/project-structure.mjs +0 -74
  22. package/src/mapek-orchestration.mjs +0 -665
  23. package/src/materialize-plan.mjs +0 -422
  24. package/src/materialize.mjs +0 -137
  25. package/src/policy-derivation.mjs +0 -869
  26. package/src/project-config.mjs +0 -142
  27. package/src/project-diff.mjs +0 -28
  28. package/src/project-engine/build-utils.mjs +0 -237
  29. package/src/project-engine/code-analyzer.mjs +0 -248
  30. package/src/project-engine/doc-generator.mjs +0 -407
  31. package/src/project-engine/infrastructure.mjs +0 -213
  32. package/src/project-engine/metrics.mjs +0 -146
  33. package/src/project-model.mjs +0 -111
  34. package/src/project-report.mjs +0 -348
  35. package/src/refactoring-guide.mjs +0 -242
  36. package/src/stack-detect.mjs +0 -102
  37. package/src/stack-linter.mjs +0 -213
  38. package/src/template-infer.mjs +0 -674
  39. package/src/type-auditor.mjs +0 -609
@@ -1,609 +0,0 @@
1
- /**
2
- * @file Type-safety auditor - validates Zod schemas match TypeScript types
3
- * @module project-engine/type-auditor
4
- */
5
-
6
- import { promises as fs } from 'fs';
7
- import path from 'path';
8
- import { z } from 'zod';
9
-
10
- /* ========================================================================= */
11
- /* Zod Schemas */
12
- /* ========================================================================= */
13
-
14
- const FieldInfoSchema = z.object({
15
- name: z.string(),
16
- type: z.string(),
17
- optional: z.boolean(),
18
- array: z.boolean().default(false),
19
- });
20
-
21
- const EntityFieldsSchema = z.object({
22
- file: z.string(),
23
- fields: z.record(FieldInfoSchema),
24
- });
25
-
26
- const MismatchSchema = z.object({
27
- entity: z.string(),
28
- zod: EntityFieldsSchema.optional(),
29
- typescript: EntityFieldsSchema.optional(),
30
- issues: z.array(z.string()),
31
- severity: z.enum(['low', 'medium', 'high', 'critical']),
32
- });
33
-
34
- const AuditResultSchema = z.object({
35
- mismatches: z.array(MismatchSchema),
36
- summary: z.string(),
37
- recommendation: z.string(),
38
- score: z.number().min(0).max(100),
39
- });
40
-
41
- const AuditOptionsSchema = z.object({
42
- domainStore: z.any().optional(),
43
- fsStore: z.any().optional(),
44
- stackProfile: z
45
- .object({
46
- hasZod: z.boolean().default(false),
47
- hasTypescript: z.boolean().default(false),
48
- })
49
- .passthrough()
50
- .optional(),
51
- projectRoot: z.string().optional(),
52
- schemaDir: z.string().default('src/schemas'),
53
- typesDir: z.string().default('src/types'),
54
- });
55
-
56
- const CompareTypesResultSchema = z.object({
57
- added: z.array(z.string()),
58
- removed: z.array(z.string()),
59
- modified: z.array(
60
- z.object({
61
- field: z.string(),
62
- zodInfo: FieldInfoSchema.optional(),
63
- tsInfo: FieldInfoSchema.optional(),
64
- issue: z.string(),
65
- })
66
- ),
67
- consistent: z.array(z.string()),
68
- });
69
-
70
- /**
71
- * @typedef {z.infer<typeof FieldInfoSchema>} FieldInfo
72
- * @typedef {z.infer<typeof EntityFieldsSchema>} EntityFields
73
- * @typedef {z.infer<typeof MismatchSchema>} Mismatch
74
- * @typedef {z.infer<typeof AuditResultSchema>} AuditResult
75
- * @typedef {z.infer<typeof CompareTypesResultSchema>} CompareTypesResult
76
- */
77
-
78
- /* ========================================================================= */
79
- /* File reading utilities */
80
- /* ========================================================================= */
81
-
82
- /**
83
- * Read file content from filesystem
84
- * @param {string} filePath
85
- * @returns {Promise<string|null>}
86
- */
87
- async function readFileContent(filePath) {
88
- try {
89
- return await fs.readFile(filePath, 'utf-8');
90
- } catch {
91
- return null;
92
- }
93
- }
94
-
95
- /**
96
- * Find files in directory matching pattern
97
- * @param {string} dirPath
98
- * @param {string[]} extensions
99
- * @returns {Promise<string[]>}
100
- */
101
- async function findFilesInDir(dirPath, extensions = ['.ts', '.tsx', '.mjs', '.js']) {
102
- try {
103
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
104
- const files = [];
105
-
106
- for (const entry of entries) {
107
- if (entry.isFile()) {
108
- const ext = path.extname(entry.name);
109
- if (extensions.includes(ext)) {
110
- files.push(path.join(dirPath, entry.name));
111
- }
112
- } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
113
- const subFiles = await findFilesInDir(path.join(dirPath, entry.name), extensions);
114
- files.push(...subFiles);
115
- }
116
- }
117
-
118
- return files;
119
- } catch {
120
- return [];
121
- }
122
- }
123
-
124
- /* ========================================================================= */
125
- /* Zod schema parsing */
126
- /* ========================================================================= */
127
-
128
- /**
129
- * Parse Zod schema fields from file content
130
- * @param {string} content
131
- * @returns {Map<string, Record<string, FieldInfo>>}
132
- */
133
- function parseZodSchemas(content) {
134
- const schemas = new Map();
135
-
136
- // Match: export const XxxSchema = z.object({ ... })
137
- const schemaPattern =
138
- /(?:export\s+)?(?:const|let)\s+(\w+)Schema\s*=\s*z\.object\(\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}\s*\)/g;
139
-
140
- let match;
141
- while ((match = schemaPattern.exec(content)) !== null) {
142
- const entityName = match[1];
143
- const fieldsBlock = match[2];
144
- const fields = parseZodFieldsDetailed(fieldsBlock);
145
-
146
- if (Object.keys(fields).length > 0) {
147
- schemas.set(entityName, fields);
148
- }
149
- }
150
-
151
- return schemas;
152
- }
153
-
154
- /**
155
- * Parse Zod fields with detailed type info
156
- * @param {string} fieldsBlock
157
- * @returns {Record<string, FieldInfo>}
158
- */
159
- function parseZodFieldsDetailed(fieldsBlock) {
160
- /** @type {Record<string, FieldInfo>} */
161
- const fields = {};
162
-
163
- // Match field definitions more carefully
164
- const lines = fieldsBlock
165
- .split(/[,\n]/)
166
- .map(l => l.trim())
167
- .filter(Boolean);
168
-
169
- for (const line of lines) {
170
- // Match: fieldName: z.type()
171
- const fieldMatch = line.match(/^(\w+)\s*:\s*z\.(\w+)/);
172
- if (!fieldMatch) continue;
173
-
174
- const fieldName = fieldMatch[1];
175
- const zodType = fieldMatch[2];
176
-
177
- // Check for .optional() or .nullable()
178
- const isOptional = line.includes('.optional()') || line.includes('.nullable()');
179
- const isArray = zodType === 'array' || line.includes('.array()');
180
-
181
- // Map zod types
182
- let type = 'string';
183
- if (zodType === 'number' || zodType === 'bigint') type = 'number';
184
- else if (zodType === 'boolean') type = 'boolean';
185
- else if (zodType === 'date') type = 'date';
186
- else if (zodType === 'array') type = 'array';
187
- else if (zodType === 'enum') type = 'enum';
188
- else if (zodType === 'object') type = 'object';
189
-
190
- fields[fieldName] = {
191
- name: fieldName,
192
- type,
193
- optional: isOptional,
194
- array: isArray,
195
- };
196
- }
197
-
198
- return fields;
199
- }
200
-
201
- /* ========================================================================= */
202
- /* TypeScript type parsing */
203
- /* ========================================================================= */
204
-
205
- /**
206
- * Parse TypeScript interfaces and types from file content
207
- * @param {string} content
208
- * @returns {Map<string, Record<string, FieldInfo>>}
209
- */
210
- function parseTypeScriptTypes(content) {
211
- const types = new Map();
212
-
213
- // Match interface declarations
214
- const interfacePattern =
215
- /(?:export\s+)?interface\s+(\w+)(?:\s+extends\s+[\w,\s]+)?\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
216
-
217
- let match;
218
- while ((match = interfacePattern.exec(content)) !== null) {
219
- const typeName = match[1];
220
- const fieldsBlock = match[2];
221
-
222
- // Skip utility types
223
- if (typeName.endsWith('Props') || typeName.endsWith('Options') || typeName.endsWith('Config')) {
224
- continue;
225
- }
226
-
227
- const fields = parseTypeScriptFieldsDetailed(fieldsBlock);
228
- if (Object.keys(fields).length > 0) {
229
- types.set(typeName, fields);
230
- }
231
- }
232
-
233
- // Match type declarations
234
- const typePattern = /(?:export\s+)?type\s+(\w+)\s*=\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
235
-
236
- while ((match = typePattern.exec(content)) !== null) {
237
- const typeName = match[1];
238
- const fieldsBlock = match[2];
239
-
240
- // Skip utility types
241
- if (typeName.endsWith('Props') || typeName.endsWith('Options') || typeName.endsWith('Config')) {
242
- continue;
243
- }
244
-
245
- const fields = parseTypeScriptFieldsDetailed(fieldsBlock);
246
- if (Object.keys(fields).length > 0) {
247
- types.set(typeName, fields);
248
- }
249
- }
250
-
251
- return types;
252
- }
253
-
254
- /**
255
- * Parse TypeScript fields with detailed info
256
- * @param {string} fieldsBlock
257
- * @returns {Record<string, FieldInfo>}
258
- */
259
- function parseTypeScriptFieldsDetailed(fieldsBlock) {
260
- /** @type {Record<string, FieldInfo>} */
261
- const fields = {};
262
-
263
- // Match: fieldName: Type, fieldName?: Type
264
- const fieldPattern = /(?:readonly\s+)?(\w+)(\?)?:\s*([^;,\n]+)/g;
265
-
266
- let match;
267
- while ((match = fieldPattern.exec(fieldsBlock)) !== null) {
268
- const fieldName = match[1];
269
- const isOptional = !!match[2];
270
- let rawType = match[3].trim();
271
-
272
- // Check for array types
273
- const isArray = rawType.endsWith('[]') || rawType.startsWith('Array<');
274
-
275
- // Clean up the type
276
- let baseType = rawType
277
- .replace(/\[\]$/, '')
278
- .replace(/^Array<(.+)>$/, '$1')
279
- .replace(/\s*\|\s*null$/, '')
280
- .replace(/\s*\|\s*undefined$/, '')
281
- .trim();
282
-
283
- // Normalize type names
284
- let type = 'string';
285
- const normalizedType = baseType.toLowerCase();
286
- if (normalizedType === 'number' || normalizedType === 'bigint') type = 'number';
287
- else if (normalizedType === 'boolean') type = 'boolean';
288
- else if (normalizedType === 'date') type = 'date';
289
- else if (normalizedType === 'object' || normalizedType.startsWith('{')) type = 'object';
290
-
291
- fields[fieldName] = {
292
- name: fieldName,
293
- type,
294
- optional: isOptional,
295
- array: isArray,
296
- };
297
- }
298
-
299
- return fields;
300
- }
301
-
302
- /* ========================================================================= */
303
- /* Type comparison */
304
- /* ========================================================================= */
305
-
306
- /**
307
- * Compare Zod fields with TypeScript fields
308
- *
309
- * @param {Record<string, FieldInfo>} zodFields
310
- * @param {Record<string, FieldInfo>} tsFields
311
- * @returns {CompareTypesResult}
312
- */
313
- export function compareTypes(zodFields, tsFields) {
314
- const zodFieldNames = new Set(Object.keys(zodFields));
315
- const tsFieldNames = new Set(Object.keys(tsFields));
316
-
317
- /** @type {string[]} */
318
- const added = []; // In Zod but not in TS
319
- /** @type {string[]} */
320
- const removed = []; // In TS but not in Zod
321
- /** @type {CompareTypesResult['modified']} */
322
- const modified = [];
323
- /** @type {string[]} */
324
- const consistent = [];
325
-
326
- // Fields in Zod but not in TS
327
- for (const fieldName of zodFieldNames) {
328
- if (!tsFieldNames.has(fieldName)) {
329
- added.push(fieldName);
330
- }
331
- }
332
-
333
- // Fields in TS but not in Zod
334
- for (const fieldName of tsFieldNames) {
335
- if (!zodFieldNames.has(fieldName)) {
336
- removed.push(fieldName);
337
- }
338
- }
339
-
340
- // Compare common fields
341
- for (const fieldName of zodFieldNames) {
342
- if (!tsFieldNames.has(fieldName)) continue;
343
-
344
- const zodField = zodFields[fieldName];
345
- const tsField = tsFields[fieldName];
346
-
347
- const issues = [];
348
-
349
- // Check optionality mismatch
350
- if (zodField.optional && !tsField.optional) {
351
- issues.push(`${fieldName}: optional in Zod, required in TS`);
352
- } else if (!zodField.optional && tsField.optional) {
353
- issues.push(`${fieldName}: required in Zod, optional in TS`);
354
- }
355
-
356
- // Check array mismatch
357
- if (zodField.array !== tsField.array) {
358
- issues.push(`${fieldName}: array=${zodField.array} in Zod, array=${tsField.array} in TS`);
359
- }
360
-
361
- // Check type mismatch (simple check)
362
- if (zodField.type !== tsField.type) {
363
- issues.push(`${fieldName}: type=${zodField.type} in Zod, type=${tsField.type} in TS`);
364
- }
365
-
366
- if (issues.length > 0) {
367
- modified.push({
368
- field: fieldName,
369
- zodInfo: zodField,
370
- tsInfo: tsField,
371
- issue: issues.join('; '),
372
- });
373
- } else {
374
- consistent.push(fieldName);
375
- }
376
- }
377
-
378
- return CompareTypesResultSchema.parse({
379
- added,
380
- removed,
381
- modified,
382
- consistent,
383
- });
384
- }
385
-
386
- /* ========================================================================= */
387
- /* Main audit API */
388
- /* ========================================================================= */
389
-
390
- /**
391
- * Audit type consistency between Zod schemas and TypeScript types
392
- *
393
- * @param {Object} options
394
- * @param {any} [options.domainStore] - Domain model RDF store (optional)
395
- * @param {any} [options.fsStore] - Filesystem RDF store (optional)
396
- * @param {Object} [options.stackProfile] - Stack detection profile
397
- * @param {string} [options.projectRoot] - Project root directory
398
- * @param {string} [options.schemaDir] - Directory containing Zod schemas
399
- * @param {string} [options.typesDir] - Directory containing TypeScript types
400
- * @returns {Promise<AuditResult>}
401
- */
402
- export async function auditTypeConsistency(options) {
403
- const validated = AuditOptionsSchema.parse(options);
404
- const { projectRoot, schemaDir, typesDir } = validated;
405
-
406
- /** @type {Mismatch[]} */
407
- const mismatches = [];
408
-
409
- if (!projectRoot) {
410
- return AuditResultSchema.parse({
411
- mismatches: [],
412
- summary: 'No project root specified',
413
- recommendation: 'Provide projectRoot to enable type auditing',
414
- score: 100,
415
- });
416
- }
417
-
418
- const schemaPath = path.join(projectRoot, schemaDir);
419
- const typesPath = path.join(projectRoot, typesDir);
420
-
421
- // Find all schema files
422
- const schemaFiles = await findFilesInDir(schemaPath, ['.ts', '.tsx', '.mjs', '.js']);
423
- const typeFiles = await findFilesInDir(typesPath, ['.ts', '.tsx', '.d.ts']);
424
-
425
- // Parse all Zod schemas
426
- /** @type {Map<string, {file: string, fields: Record<string, FieldInfo>}>} */
427
- const zodSchemas = new Map();
428
-
429
- for (const file of schemaFiles) {
430
- const content = await readFileContent(file);
431
- if (!content) continue;
432
-
433
- // Skip if not a Zod file
434
- if (!content.includes("from 'zod'") && !content.includes('from "zod"')) continue;
435
-
436
- const schemas = parseZodSchemas(content);
437
- for (const [name, fields] of schemas.entries()) {
438
- zodSchemas.set(name, { file: path.relative(projectRoot, file), fields });
439
- }
440
- }
441
-
442
- // Parse all TypeScript types
443
- /** @type {Map<string, {file: string, fields: Record<string, FieldInfo>}>} */
444
- const tsTypes = new Map();
445
-
446
- for (const file of typeFiles) {
447
- const content = await readFileContent(file);
448
- if (!content) continue;
449
-
450
- const types = parseTypeScriptTypes(content);
451
- for (const [name, fields] of types.entries()) {
452
- tsTypes.set(name, { file: path.relative(projectRoot, file), fields });
453
- }
454
- }
455
-
456
- // Compare matching entities
457
- const allEntities = new Set([...zodSchemas.keys(), ...tsTypes.keys()]);
458
-
459
- for (const entityName of allEntities) {
460
- const zodInfo = zodSchemas.get(entityName);
461
- const tsInfo = tsTypes.get(entityName);
462
-
463
- /** @type {string[]} */
464
- const issues = [];
465
- let severity = /** @type {'low' | 'medium' | 'high' | 'critical'} */ ('low');
466
-
467
- if (zodInfo && !tsInfo) {
468
- issues.push(`Entity "${entityName}" exists in Zod but not in TypeScript`);
469
- severity = 'medium';
470
- } else if (!zodInfo && tsInfo) {
471
- issues.push(`Entity "${entityName}" exists in TypeScript but not in Zod`);
472
- severity = 'medium';
473
- } else if (zodInfo && tsInfo) {
474
- const comparison = compareTypes(zodInfo.fields, tsInfo.fields);
475
-
476
- // Report added fields (in Zod but not TS)
477
- for (const fieldName of comparison.added) {
478
- issues.push(`Field "${fieldName}" exists in Zod but not in TypeScript`);
479
- severity = severity === 'low' ? 'medium' : severity;
480
- }
481
-
482
- // Report removed fields (in TS but not Zod)
483
- for (const fieldName of comparison.removed) {
484
- issues.push(`Field "${fieldName}" removed from TS but exists in Zod`);
485
- severity = 'high'; // More critical - TS code expects field that Zod won't validate
486
- }
487
-
488
- // Report modified fields
489
- for (const mod of comparison.modified) {
490
- issues.push(mod.issue);
491
- // Optionality mismatch is high severity
492
- if (mod.issue.includes('optional') && mod.issue.includes('required')) {
493
- severity = 'high';
494
- }
495
- }
496
- }
497
-
498
- if (issues.length > 0) {
499
- mismatches.push({
500
- entity: entityName,
501
- zod: zodInfo ? { file: zodInfo.file, fields: zodInfo.fields } : undefined,
502
- typescript: tsInfo ? { file: tsInfo.file, fields: tsInfo.fields } : undefined,
503
- issues,
504
- severity,
505
- });
506
- }
507
- }
508
-
509
- // Calculate score
510
- const totalEntities = allEntities.size;
511
- const entitiesWithIssues = mismatches.length;
512
- const score =
513
- totalEntities > 0
514
- ? Math.round(((totalEntities - entitiesWithIssues) / totalEntities) * 100)
515
- : 100;
516
-
517
- // Generate summary and recommendation
518
- const summary =
519
- entitiesWithIssues === 0
520
- ? 'All Zod schemas and TypeScript types are consistent'
521
- : `${entitiesWithIssues} entities with type mismatches out of ${totalEntities} total`;
522
-
523
- const criticalCount = mismatches.filter(m => m.severity === 'critical').length;
524
- const highCount = mismatches.filter(m => m.severity === 'high').length;
525
-
526
- let recommendation = 'No action needed';
527
- if (criticalCount > 0) {
528
- recommendation = 'Critical type mismatches detected - fix immediately before production';
529
- } else if (highCount > 0) {
530
- recommendation = 'High severity mismatches - update TypeScript types to match Zod schemas';
531
- } else if (entitiesWithIssues > 0) {
532
- recommendation = 'Minor type mismatches - consider synchronizing schemas and types';
533
- }
534
-
535
- return AuditResultSchema.parse({
536
- mismatches,
537
- summary,
538
- recommendation,
539
- score,
540
- });
541
- }
542
-
543
- /**
544
- * Audit a single entity's type consistency
545
- *
546
- * @param {string} zodContent - Content of file containing Zod schema
547
- * @param {string} tsContent - Content of file containing TypeScript type
548
- * @param {string} entityName - Name of entity to audit
549
- * @returns {Mismatch | null}
550
- */
551
- export function auditEntityTypes(zodContent, tsContent, entityName) {
552
- const zodSchemas = parseZodSchemas(zodContent);
553
- const tsTypes = parseTypeScriptTypes(tsContent);
554
-
555
- const zodFields = zodSchemas.get(entityName);
556
- const tsFields = tsTypes.get(entityName);
557
-
558
- if (!zodFields && !tsFields) {
559
- return null;
560
- }
561
-
562
- /** @type {string[]} */
563
- const issues = [];
564
- let severity = /** @type {'low' | 'medium' | 'high' | 'critical'} */ ('low');
565
-
566
- if (zodFields && !tsFields) {
567
- issues.push(`Entity "${entityName}" exists in Zod but not in TypeScript`);
568
- severity = 'medium';
569
- } else if (!zodFields && tsFields) {
570
- issues.push(`Entity "${entityName}" exists in TypeScript but not in Zod`);
571
- severity = 'medium';
572
- } else if (zodFields && tsFields) {
573
- const comparison = compareTypes(zodFields, tsFields);
574
-
575
- for (const fieldName of comparison.added) {
576
- issues.push(`Field "${fieldName}" exists in Zod but not in TypeScript`);
577
- }
578
-
579
- for (const fieldName of comparison.removed) {
580
- issues.push(`Field "${fieldName}" removed from TS but exists in Zod`);
581
- severity = 'high';
582
- }
583
-
584
- for (const mod of comparison.modified) {
585
- issues.push(mod.issue);
586
- if (mod.issue.includes('optional') && mod.issue.includes('required')) {
587
- severity = 'high';
588
- }
589
- }
590
- }
591
-
592
- if (issues.length === 0) {
593
- return null;
594
- }
595
-
596
- return MismatchSchema.parse({
597
- entity: entityName,
598
- zod: zodFields ? { file: 'inline', fields: zodFields } : undefined,
599
- typescript: tsFields ? { file: 'inline', fields: tsFields } : undefined,
600
- issues,
601
- severity,
602
- });
603
- }
604
-
605
- /* ========================================================================= */
606
- /* Exports for module */
607
- /* ========================================================================= */
608
-
609
- export { FieldInfoSchema, MismatchSchema, AuditResultSchema, CompareTypesResultSchema };