@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,376 +0,0 @@
1
- /**
2
- * @file Predictive Gap Finder - analyze domain model + project files to identify missing roles
3
- * @module project-engine/gap-finder
4
- */
5
-
6
- import { UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
7
- import { z } from 'zod';
8
-
9
- const { namedNode } = DataFactory;
10
-
11
- /* ========================================================================= */
12
- /* Namespace prefixes */
13
- /* ========================================================================= */
14
-
15
- const NS = {
16
- rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
17
- rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
18
- dom: 'http://example.org/unrdf/domain#',
19
- fs: 'http://example.org/unrdf/filesystem#',
20
- proj: 'http://example.org/unrdf/project#',
21
- };
22
-
23
- /* ========================================================================= */
24
- /* Zod Schemas */
25
- /* ========================================================================= */
26
-
27
- const StackProfileSchema = z
28
- .object({
29
- webFramework: z.string().nullable().optional(),
30
- uiFramework: z.string().nullable().optional(),
31
- apiFramework: z.string().nullable().optional(),
32
- testFramework: z.string().nullable().optional(),
33
- })
34
- .passthrough()
35
- .optional();
36
-
37
- const FindMissingRolesOptionsSchema = z.object({
38
- domainStore: z.custom(val => val && typeof val.getQuads === 'function', {
39
- message: 'domainStore must be an RDF store with getQuads method',
40
- }),
41
- projectStore: z.custom(val => val && typeof val.getQuads === 'function', {
42
- message: 'projectStore must be an RDF store with getQuads method',
43
- }),
44
- templateGraph: z.custom(val => val && typeof val.getQuads === 'function').optional(),
45
- stackProfile: StackProfileSchema,
46
- });
47
-
48
- /* ========================================================================= */
49
- /* Role configuration */
50
- /* ========================================================================= */
51
-
52
- /**
53
- * Base role importance scores (0-100)
54
- */
55
- const ROLE_BASE_SCORES = {
56
- Api: 95,
57
- Route: 95,
58
- Test: 90,
59
- Component: 80,
60
- Page: 80,
61
- View: 80,
62
- Schema: 70,
63
- Service: 60,
64
- Hook: 55,
65
- Doc: 50,
66
- };
67
-
68
- /**
69
- * Required roles per framework
70
- */
71
- const FRAMEWORK_ROLES = {
72
- next: ['Page', 'Api', 'Component', 'Test', 'Schema'],
73
- 'next-app-router': ['Page', 'Api', 'Component', 'Test', 'Schema'],
74
- 'next-pages': ['Page', 'Api', 'Component', 'Test', 'Schema'],
75
- express: ['Route', 'Service', 'Test', 'Schema'],
76
- nest: ['Controller', 'Service', 'Test', 'Schema'],
77
- react: ['Component', 'Test', 'Schema'],
78
- default: ['Api', 'Component', 'Test', 'Schema'],
79
- };
80
-
81
- /**
82
- * Framework-specific role score boosts
83
- */
84
- const FRAMEWORK_BOOSTS = {
85
- next: { Page: 15, Api: 10 },
86
- 'next-app-router': { Page: 15, Api: 10 },
87
- 'next-pages': { Page: 15, Api: 10 },
88
- express: { Route: 15, Service: 10 },
89
- nest: { Controller: 15, Service: 10 },
90
- };
91
-
92
- /* ========================================================================= */
93
- /* Entity extraction */
94
- /* ========================================================================= */
95
-
96
- /**
97
- * Extract entity names from domain store
98
- * @param {Store} domainStore
99
- * @returns {string[]}
100
- */
101
- function extractEntities(domainStore) {
102
- const entities = [];
103
- const quads = domainStore.getQuads(
104
- null,
105
- namedNode(`${NS.rdf}type`),
106
- namedNode(`${NS.dom}Entity`)
107
- );
108
-
109
- for (const quad of quads) {
110
- const iri = quad.subject.value;
111
- const name = iri.split('#').pop() || iri.split('/').pop();
112
- if (name) entities.push(name);
113
- }
114
-
115
- return entities;
116
- }
117
-
118
- /* ========================================================================= */
119
- /* File role extraction */
120
- /* ========================================================================= */
121
-
122
- /**
123
- * Extract file paths with their roles from project store
124
- * @param {Store} projectStore
125
- * @returns {Array<{path: string, role: string|null}>}
126
- */
127
- function extractFilesWithRoles(projectStore) {
128
- const files = [];
129
-
130
- const pathQuads = projectStore.getQuads(null, namedNode(`${NS.fs}relativePath`), null);
131
-
132
- for (const quad of pathQuads) {
133
- const fileIri = quad.subject;
134
- const path = quad.object.value;
135
-
136
- // Get role for this file
137
- const roleQuads = projectStore.getQuads(fileIri, namedNode(`${NS.proj}roleString`), null);
138
-
139
- const role = roleQuads.length > 0 ? roleQuads[0].object.value : null;
140
- files.push({ path, role });
141
- }
142
-
143
- return files;
144
- }
145
-
146
- /* ========================================================================= */
147
- /* Entity-file matching */
148
- /* ========================================================================= */
149
-
150
- /**
151
- * Match file to entity by name pattern
152
- * @param {string} filePath
153
- * @param {string} entityName
154
- * @returns {boolean}
155
- */
156
- function fileMatchesEntity(filePath, entityName) {
157
- const lowerPath = filePath.toLowerCase();
158
- const lowerEntity = entityName.toLowerCase();
159
-
160
- // Direct match
161
- if (lowerPath.includes(lowerEntity)) return true;
162
-
163
- // Plural form: User -> users
164
- if (lowerPath.includes(lowerEntity + 's')) return true;
165
-
166
- // Kebab-case: UserProfile -> user-profile
167
- const kebabEntity = entityName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
168
- if (lowerPath.includes(kebabEntity)) return true;
169
-
170
- // Snake_case: UserProfile -> user_profile
171
- const snakeEntity = entityName.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
172
- if (lowerPath.includes(snakeEntity)) return true;
173
-
174
- return false;
175
- }
176
-
177
- /**
178
- * Find roles present for an entity
179
- * @param {string} entityName
180
- * @param {Array<{path: string, role: string|null}>} files
181
- * @returns {Set<string>}
182
- */
183
- function findPresentRoles(entityName, files) {
184
- const roles = new Set();
185
-
186
- for (const { path, role } of files) {
187
- if (role && fileMatchesEntity(path, entityName)) {
188
- roles.add(role);
189
- }
190
- }
191
-
192
- return roles;
193
- }
194
-
195
- /* ========================================================================= */
196
- /* Gap detection */
197
- /* ========================================================================= */
198
-
199
- /**
200
- * Determine required roles based on stack profile
201
- * @param {Object} stackProfile
202
- * @returns {string[]}
203
- */
204
- function getRequiredRoles(stackProfile) {
205
- if (!stackProfile) return FRAMEWORK_ROLES.default;
206
-
207
- const framework = stackProfile.webFramework || stackProfile.apiFramework;
208
- if (framework && FRAMEWORK_ROLES[framework]) {
209
- return FRAMEWORK_ROLES[framework];
210
- }
211
-
212
- return FRAMEWORK_ROLES.default;
213
- }
214
-
215
- /**
216
- * Calculate missing roles for an entity
217
- * @param {string} entityName
218
- * @param {Set<string>} presentRoles
219
- * @param {string[]} requiredRoles
220
- * @returns {string[]}
221
- */
222
- function calculateMissingRoles(entityName, presentRoles, requiredRoles) {
223
- return requiredRoles.filter(role => !presentRoles.has(role));
224
- }
225
-
226
- /**
227
- * Generate suggestion for a missing role
228
- * @param {string} entityName
229
- * @param {string} role
230
- * @returns {string}
231
- */
232
- function generateSuggestion(entityName, role) {
233
- const suggestions = {
234
- Api: `${entityName}Api is needed for /${entityName.toLowerCase()} endpoint`,
235
- Route: `${entityName}Route handler needed for /${entityName.toLowerCase()} routes`,
236
- Page: `${entityName}Page needed for /${entityName.toLowerCase()} view`,
237
- Component: `${entityName}View component needed for UI`,
238
- Test: `${entityName} test suite needed for coverage`,
239
- Schema: `${entityName}Schema needed for validation`,
240
- Service: `${entityName}Service needed for business logic`,
241
- Doc: `${entityName} documentation needed`,
242
- };
243
-
244
- return suggestions[role] || `${entityName}${role} needed`;
245
- }
246
-
247
- /* ========================================================================= */
248
- /* Main API */
249
- /* ========================================================================= */
250
-
251
- /**
252
- * @typedef {Object} GapResult
253
- * @property {string} entity - Entity name
254
- * @property {string[]} missingRoles - Roles that are missing
255
- * @property {number} score - Priority score (0-100)
256
- * @property {string} suggestion - Human-readable suggestion
257
- * @property {string[]} [files] - Related files if they exist but aren't classified
258
- */
259
-
260
- /**
261
- * @typedef {Object} FindMissingRolesResult
262
- * @property {GapResult[]} gaps - Array of gap results
263
- * @property {string} summary - Human-readable summary
264
- * @property {string} callToAction - CLI command suggestion
265
- */
266
-
267
- /**
268
- * Analyze domain model + project files to predict which features are missing roles
269
- *
270
- * @param {Object} options
271
- * @param {Store} options.domainStore - Domain model store (from inferDomainModel)
272
- * @param {Store} options.projectStore - Project structure store (from buildProjectModelFromFs + classifyFiles)
273
- * @param {Store} [options.templateGraph] - Optional template graph
274
- * @param {Object} [options.stackProfile] - Stack profile for framework-specific rules
275
- * @returns {FindMissingRolesResult}
276
- */
277
- export function findMissingRoles(options) {
278
- const validated = FindMissingRolesOptionsSchema.parse(options);
279
- const { domainStore, projectStore, stackProfile } = validated;
280
-
281
- // Extract entities from domain model
282
- const entities = extractEntities(domainStore);
283
-
284
- // Early exit if no entities
285
- if (entities.length === 0) {
286
- return {
287
- gaps: [],
288
- summary: '0 gaps found (no entities in domain model)',
289
- callToAction: 'Run: unrdf infer-domain to detect domain entities',
290
- };
291
- }
292
-
293
- // Extract files with roles from project store
294
- const files = extractFilesWithRoles(projectStore);
295
-
296
- // Determine required roles based on stack
297
- const requiredRoles = getRequiredRoles(stackProfile);
298
-
299
- // Find gaps for each entity
300
- const gaps = [];
301
- const gapCounts = { Api: 0, Test: 0, Component: 0, Schema: 0, other: 0 };
302
-
303
- for (const entityName of entities) {
304
- const presentRoles = findPresentRoles(entityName, files);
305
- const missingRoles = calculateMissingRoles(entityName, presentRoles, requiredRoles);
306
-
307
- // Calculate max score for this entity's gaps
308
- const maxScore = missingRoles.reduce((max, role) => {
309
- const score = scoreMissingRole(entityName, role, stackProfile || {});
310
- return Math.max(max, score);
311
- }, 0);
312
-
313
- // Count gaps by type
314
- for (const role of missingRoles) {
315
- if (gapCounts[role] !== undefined) {
316
- gapCounts[role]++;
317
- } else {
318
- gapCounts.other++;
319
- }
320
- }
321
-
322
- gaps.push({
323
- entity: entityName,
324
- missingRoles,
325
- score: maxScore,
326
- suggestion: missingRoles.length > 0 ? generateSuggestion(entityName, missingRoles[0]) : '',
327
- });
328
- }
329
-
330
- // Sort by score (highest first)
331
- gaps.sort((a, b) => b.score - a.score);
332
-
333
- // Generate summary
334
- const summaryParts = [];
335
- if (gapCounts.Api > 0) summaryParts.push(`${gapCounts.Api} missing APIs`);
336
- if (gapCounts.Test > 0) summaryParts.push(`${gapCounts.Test} missing tests`);
337
- if (gapCounts.Component > 0) summaryParts.push(`${gapCounts.Component} missing components`);
338
- if (gapCounts.Schema > 0) summaryParts.push(`${gapCounts.Schema} missing schemas`);
339
- if (gapCounts.other > 0) summaryParts.push(`${gapCounts.other} other gaps`);
340
-
341
- const summary =
342
- summaryParts.length > 0
343
- ? summaryParts.join(', ')
344
- : '0 gaps found - all entities have required roles';
345
-
346
- // Generate call to action
347
- const topGap = gaps.find(g => g.missingRoles.length > 0);
348
- const callToAction = topGap
349
- ? `Run: unrdf generate --entity ${topGap.entity} --roles ${topGap.missingRoles.join(',')}`
350
- : 'Run: unrdf analyze to check for other issues';
351
-
352
- return { gaps, summary, callToAction };
353
- }
354
-
355
- /**
356
- * Score the importance of a missing role for an entity
357
- *
358
- * @param {string} entity - Entity name
359
- * @param {string} role - Missing role name
360
- * @param {Object} stackProfile - Stack profile for framework-specific boosts
361
- * @returns {number} Score from 0-100
362
- */
363
- export function scoreMissingRole(entity, role, stackProfile) {
364
- // Get base score
365
- let score = ROLE_BASE_SCORES[role] || 40;
366
-
367
- // Apply framework-specific boosts
368
- const framework = stackProfile?.webFramework || stackProfile?.apiFramework;
369
- if (framework && FRAMEWORK_BOOSTS[framework]) {
370
- const boost = FRAMEWORK_BOOSTS[framework][role] || 0;
371
- score += boost;
372
- }
373
-
374
- // Cap at 100
375
- return Math.min(score, 100);
376
- }