@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.
- package/package.json +16 -15
- package/src/golden-structure.mjs +2 -2
- package/src/materialize-apply.mjs +2 -2
- package/README.md +0 -53
- package/src/api-contract-validator.mjs +0 -711
- package/src/auto-test-generator.mjs +0 -444
- package/src/autonomic-mapek.mjs +0 -511
- package/src/capabilities-manifest.mjs +0 -125
- package/src/code-complexity-js.mjs +0 -368
- package/src/dependency-graph.mjs +0 -276
- package/src/doc-drift-checker.mjs +0 -172
- package/src/doc-generator.mjs +0 -229
- package/src/domain-infer.mjs +0 -966
- package/src/drift-snapshot.mjs +0 -775
- package/src/file-roles.mjs +0 -94
- package/src/fs-scan.mjs +0 -305
- package/src/gap-finder.mjs +0 -376
- package/src/hotspot-analyzer.mjs +0 -412
- package/src/index.mjs +0 -151
- package/src/initialize.mjs +0 -957
- package/src/lens/project-structure.mjs +0 -74
- package/src/mapek-orchestration.mjs +0 -665
- package/src/materialize-plan.mjs +0 -422
- package/src/materialize.mjs +0 -137
- package/src/policy-derivation.mjs +0 -869
- package/src/project-config.mjs +0 -142
- package/src/project-diff.mjs +0 -28
- package/src/project-engine/build-utils.mjs +0 -237
- package/src/project-engine/code-analyzer.mjs +0 -248
- package/src/project-engine/doc-generator.mjs +0 -407
- package/src/project-engine/infrastructure.mjs +0 -213
- package/src/project-engine/metrics.mjs +0 -146
- package/src/project-model.mjs +0 -111
- package/src/project-report.mjs +0 -348
- package/src/refactoring-guide.mjs +0 -242
- package/src/stack-detect.mjs +0 -102
- package/src/stack-linter.mjs +0 -213
- package/src/template-infer.mjs +0 -674
- package/src/type-auditor.mjs +0 -609
package/src/gap-finder.mjs
DELETED
|
@@ -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
|
-
}
|