@unrdf/project-engine 5.0.1
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/LICENSE +21 -0
- package/README.md +53 -0
- package/package.json +58 -0
- package/src/api-contract-validator.mjs +711 -0
- package/src/auto-test-generator.mjs +444 -0
- package/src/autonomic-mapek.mjs +511 -0
- package/src/capabilities-manifest.mjs +125 -0
- package/src/code-complexity-js.mjs +368 -0
- package/src/dependency-graph.mjs +276 -0
- package/src/doc-drift-checker.mjs +172 -0
- package/src/doc-generator.mjs +229 -0
- package/src/domain-infer.mjs +966 -0
- package/src/drift-snapshot.mjs +775 -0
- package/src/file-roles.mjs +94 -0
- package/src/fs-scan.mjs +305 -0
- package/src/gap-finder.mjs +376 -0
- package/src/golden-structure.mjs +149 -0
- package/src/hotspot-analyzer.mjs +412 -0
- package/src/index.mjs +151 -0
- package/src/initialize.mjs +957 -0
- package/src/lens/project-structure.mjs +74 -0
- package/src/mapek-orchestration.mjs +665 -0
- package/src/materialize-apply.mjs +505 -0
- package/src/materialize-plan.mjs +422 -0
- package/src/materialize.mjs +137 -0
- package/src/policy-derivation.mjs +869 -0
- package/src/project-config.mjs +142 -0
- package/src/project-diff.mjs +28 -0
- package/src/project-engine/build-utils.mjs +237 -0
- package/src/project-engine/code-analyzer.mjs +248 -0
- package/src/project-engine/doc-generator.mjs +407 -0
- package/src/project-engine/infrastructure.mjs +213 -0
- package/src/project-engine/metrics.mjs +146 -0
- package/src/project-model.mjs +111 -0
- package/src/project-report.mjs +348 -0
- package/src/refactoring-guide.mjs +242 -0
- package/src/stack-detect.mjs +102 -0
- package/src/stack-linter.mjs +213 -0
- package/src/template-infer.mjs +674 -0
- package/src/type-auditor.mjs +609 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Autonomic MAPEK Loop - Self-healing knowledge system
|
|
3
|
+
* @module project-engine/autonomic-mapek
|
|
4
|
+
*
|
|
5
|
+
* @description
|
|
6
|
+
* Full autonomic system using MAPEK (Monitor-Analyze-Plan-Execute-Knowledge):
|
|
7
|
+
* - Monitor: Track drift, type mismatches, hotspots continuously
|
|
8
|
+
* - Analyze: Gap finder, type auditor, complexity analysis
|
|
9
|
+
* - Plan: Generate fix plans for detected issues
|
|
10
|
+
* - Execute: Apply fixes via knowledge hooks
|
|
11
|
+
* - Knowledge: Learn patterns and update policies
|
|
12
|
+
*
|
|
13
|
+
* Integrated with UNRDF Knowledge Hooks for autonomous self-healing.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { findMissingRoles } from './gap-finder.mjs';
|
|
18
|
+
import { auditTypeConsistency } from './type-auditor.mjs';
|
|
19
|
+
import { analyzeHotspots } from './hotspot-analyzer.mjs';
|
|
20
|
+
import { computeDrift } from './drift-snapshot.mjs';
|
|
21
|
+
|
|
22
|
+
const _MapekStateSchema = z.object({
|
|
23
|
+
phase: z.enum(['monitor', 'analyze', 'plan', 'execute', 'knowledge']),
|
|
24
|
+
timestamp: z.string().datetime(),
|
|
25
|
+
findings: z.object({}).passthrough(),
|
|
26
|
+
actions: z.array(z.object({}).passthrough()).default([]),
|
|
27
|
+
metrics: z.object({
|
|
28
|
+
gapScore: z.number().default(0),
|
|
29
|
+
typeScore: z.number().default(0),
|
|
30
|
+
hotspotScore: z.number().default(0),
|
|
31
|
+
driftSeverity: z.string().default('none'),
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Autonomic MAPEK Loop - Single iteration
|
|
37
|
+
*
|
|
38
|
+
* @param {Object} options
|
|
39
|
+
* @param {Store} options.projectStore - RDF project structure
|
|
40
|
+
* @param {Store} options.domainStore - RDF domain model
|
|
41
|
+
* @param {Store} options.baselineSnapshot - From createStructureSnapshot
|
|
42
|
+
* @param {string} options.projectRoot - File system root
|
|
43
|
+
* @param {Object} options.stackProfile - Stack detection result
|
|
44
|
+
* @param {Object} options.knowledge - Learned patterns from previous runs
|
|
45
|
+
* @returns {Object} { phase, findings, actions, nextPhase, shouldRepeat }
|
|
46
|
+
*/
|
|
47
|
+
export async function runMapekIteration(options) {
|
|
48
|
+
const validated = z
|
|
49
|
+
.object({
|
|
50
|
+
projectStore: z.custom(val => val && typeof val.getQuads === 'function', {
|
|
51
|
+
message: 'projectStore must be an RDF store with getQuads method',
|
|
52
|
+
}),
|
|
53
|
+
domainStore: z.custom(val => val && typeof val.getQuads === 'function', {
|
|
54
|
+
message: 'domainStore must be an RDF store with getQuads method',
|
|
55
|
+
}),
|
|
56
|
+
baselineSnapshot: z.object({}).passthrough().optional(),
|
|
57
|
+
projectRoot: z.string(),
|
|
58
|
+
stackProfile: z.object({}).passthrough().optional(),
|
|
59
|
+
knowledge: z.object({}).passthrough().optional(),
|
|
60
|
+
})
|
|
61
|
+
.parse(options);
|
|
62
|
+
|
|
63
|
+
const state = {
|
|
64
|
+
phase: 'monitor',
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
findings: {},
|
|
67
|
+
actions: [],
|
|
68
|
+
metrics: {
|
|
69
|
+
gapScore: 0,
|
|
70
|
+
typeScore: 0,
|
|
71
|
+
hotspotScore: 0,
|
|
72
|
+
driftSeverity: 'none',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ===== PHASE 1: MONITOR =====
|
|
77
|
+
// Continuously observe system state
|
|
78
|
+
state.findings.gaps = findMissingRoles({
|
|
79
|
+
domainStore: validated.domainStore,
|
|
80
|
+
projectStore: validated.projectStore,
|
|
81
|
+
stackProfile: validated.stackProfile,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
state.findings.typeIssues = await auditTypeConsistency({
|
|
85
|
+
domainStore: validated.domainStore,
|
|
86
|
+
fsStore: validated.projectStore,
|
|
87
|
+
stackProfile: validated.stackProfile,
|
|
88
|
+
projectRoot: validated.projectRoot,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
state.findings.hotspots = analyzeHotspots({
|
|
92
|
+
projectStore: validated.projectStore,
|
|
93
|
+
domainStore: validated.domainStore,
|
|
94
|
+
stackProfile: validated.stackProfile,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (validated.baselineSnapshot) {
|
|
98
|
+
state.findings.drift = computeDrift(validated.projectStore, validated.baselineSnapshot);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ===== PHASE 2: ANALYZE =====
|
|
102
|
+
// Interpret findings and calculate health metrics
|
|
103
|
+
state.phase = 'analyze';
|
|
104
|
+
|
|
105
|
+
// Gap severity (0-100, higher = more missing)
|
|
106
|
+
const gapCount = state.findings.gaps?.gaps?.length || 0;
|
|
107
|
+
const criticalGaps = state.findings.gaps?.gaps?.filter(g => g.score > 80).length || 0;
|
|
108
|
+
state.metrics.gapScore = Math.min(100, gapCount * 10 + criticalGaps * 20);
|
|
109
|
+
|
|
110
|
+
// Type safety score (0-100, higher = more problems)
|
|
111
|
+
const typeCount = state.findings.typeIssues?.mismatches?.length || 0;
|
|
112
|
+
const highSeverityTypes =
|
|
113
|
+
state.findings.typeIssues?.mismatches?.filter(m => m.severity === 'high').length || 0;
|
|
114
|
+
state.metrics.typeScore = Math.min(100, typeCount * 15 + highSeverityTypes * 25);
|
|
115
|
+
|
|
116
|
+
// Hotspot score (average risk of identified hotspots)
|
|
117
|
+
const hotspotCount = state.findings.hotspots?.hotspots?.length || 0;
|
|
118
|
+
const highRiskCount =
|
|
119
|
+
state.findings.hotspots?.hotspots?.filter(h => h.risk === 'HIGH').length || 0;
|
|
120
|
+
state.metrics.hotspotScore = Math.min(100, hotspotCount * 5 + highRiskCount * 30);
|
|
121
|
+
|
|
122
|
+
// Drift severity (from computeDrift)
|
|
123
|
+
state.metrics.driftSeverity = state.findings.drift?.driftSeverity || 'none';
|
|
124
|
+
|
|
125
|
+
// Overall health
|
|
126
|
+
const overallHealth =
|
|
127
|
+
(state.metrics.gapScore * 0.3 +
|
|
128
|
+
state.metrics.typeScore * 0.3 +
|
|
129
|
+
state.metrics.hotspotScore * 0.2 +
|
|
130
|
+
(state.metrics.driftSeverity === 'major'
|
|
131
|
+
? 50
|
|
132
|
+
: state.metrics.driftSeverity === 'minor'
|
|
133
|
+
? 25
|
|
134
|
+
: 0) *
|
|
135
|
+
0.2) /
|
|
136
|
+
100;
|
|
137
|
+
|
|
138
|
+
// ===== PHASE 3: PLAN =====
|
|
139
|
+
// Decide what to do about findings
|
|
140
|
+
state.phase = 'plan';
|
|
141
|
+
|
|
142
|
+
const decisions = [];
|
|
143
|
+
|
|
144
|
+
// Decision 1: Fix type mismatches (highest priority)
|
|
145
|
+
if (state.metrics.typeScore > 60) {
|
|
146
|
+
decisions.push({
|
|
147
|
+
issue: 'type-mismatch',
|
|
148
|
+
severity: 'critical',
|
|
149
|
+
action: 'sync-zod-ts-types',
|
|
150
|
+
description: `${typeCount} type mismatches detected. Sync Zod schemas with TypeScript types.`,
|
|
151
|
+
autoFixable: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Decision 2: Fill gaps (medium priority)
|
|
156
|
+
if (state.metrics.gapScore > 50) {
|
|
157
|
+
const topGaps = state.findings.gaps?.gaps?.slice(0, 3) || [];
|
|
158
|
+
decisions.push({
|
|
159
|
+
issue: 'missing-roles',
|
|
160
|
+
severity: criticalGaps > 0 ? 'high' : 'medium',
|
|
161
|
+
action: 'generate-missing-files',
|
|
162
|
+
targets: topGaps.map(g => ({
|
|
163
|
+
entity: g.entity,
|
|
164
|
+
roles: g.missingRoles,
|
|
165
|
+
score: g.score,
|
|
166
|
+
})),
|
|
167
|
+
autoFixable: true,
|
|
168
|
+
description: `Generate ${topGaps.length} missing files for ${topGaps.map(g => g.entity).join(', ')}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Decision 3: Refactor hotspots (low priority, requires human review)
|
|
173
|
+
if (state.metrics.hotspotScore > 70) {
|
|
174
|
+
const topRisks = state.findings.hotspots?.topRisks?.slice(0, 2) || [];
|
|
175
|
+
decisions.push({
|
|
176
|
+
issue: 'high-complexity',
|
|
177
|
+
severity: 'medium',
|
|
178
|
+
action: 'refactor-hotspots',
|
|
179
|
+
targets: topRisks,
|
|
180
|
+
autoFixable: false, // Requires human decision
|
|
181
|
+
description: `Features ${topRisks.map(r => r.feature).join(', ')} have high complexity.`,
|
|
182
|
+
recommendation: 'Add tests or refactor into smaller modules.',
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Decision 4: Fix drift (if baseline exists)
|
|
187
|
+
if (state.metrics.driftSeverity === 'major') {
|
|
188
|
+
decisions.push({
|
|
189
|
+
issue: 'major-drift',
|
|
190
|
+
severity: 'high',
|
|
191
|
+
action: 'resync-model',
|
|
192
|
+
autoFixable: true,
|
|
193
|
+
description: 'Project structure has drifted significantly from baseline.',
|
|
194
|
+
recommendation: 'Run: unrdf init --skip-snapshot to resync',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
state.findings.decisions = decisions;
|
|
199
|
+
|
|
200
|
+
// ===== PHASE 4: EXECUTE =====
|
|
201
|
+
// Apply fixes autonomously (for auto-fixable issues)
|
|
202
|
+
state.phase = 'execute';
|
|
203
|
+
|
|
204
|
+
for (const decision of decisions) {
|
|
205
|
+
if (decision.autoFixable) {
|
|
206
|
+
if (decision.action === 'generate-missing-files') {
|
|
207
|
+
// Plan and queue file generation
|
|
208
|
+
const targets = decision.targets || [];
|
|
209
|
+
for (const target of targets) {
|
|
210
|
+
state.actions.push({
|
|
211
|
+
type: 'generate-files',
|
|
212
|
+
entity: target.entity,
|
|
213
|
+
roles: target.roles,
|
|
214
|
+
status: 'planned',
|
|
215
|
+
timestamp: new Date().toISOString(),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
} else if (decision.action === 'sync-zod-ts-types') {
|
|
219
|
+
state.actions.push({
|
|
220
|
+
type: 'sync-types',
|
|
221
|
+
mismatches: state.findings.typeIssues?.mismatches?.length || 0,
|
|
222
|
+
status: 'planned',
|
|
223
|
+
timestamp: new Date().toISOString(),
|
|
224
|
+
});
|
|
225
|
+
} else if (decision.action === 'resync-model') {
|
|
226
|
+
state.actions.push({
|
|
227
|
+
type: 'reinitialize',
|
|
228
|
+
reason: 'drift-recovery',
|
|
229
|
+
status: 'planned',
|
|
230
|
+
timestamp: new Date().toISOString(),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ===== PHASE 5: KNOWLEDGE =====
|
|
237
|
+
// Learn from outcomes and update policies
|
|
238
|
+
state.phase = 'knowledge';
|
|
239
|
+
|
|
240
|
+
// Extract learning
|
|
241
|
+
const learnings = {
|
|
242
|
+
timestamp: new Date().toISOString(),
|
|
243
|
+
gapPatterns: (state.findings.gaps?.gaps || [])
|
|
244
|
+
.filter(g => g.score > 80)
|
|
245
|
+
.map(g => ({
|
|
246
|
+
entity: g.entity,
|
|
247
|
+
missingRoles: g.missingRoles,
|
|
248
|
+
frequency: 1,
|
|
249
|
+
})),
|
|
250
|
+
typePatterns: (state.findings.typeIssues?.mismatches || [])
|
|
251
|
+
.filter(m => m.severity === 'high')
|
|
252
|
+
.map(m => ({
|
|
253
|
+
entity: m.entity,
|
|
254
|
+
issueType: 'type-mismatch',
|
|
255
|
+
frequency: m.issues?.length || 1,
|
|
256
|
+
})),
|
|
257
|
+
hotspotThresholds: {
|
|
258
|
+
fileCountHighRisk: 40,
|
|
259
|
+
testCoverageHighRisk: 70,
|
|
260
|
+
dependenciesHighRisk: 12,
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
state.findings.learnings = learnings;
|
|
265
|
+
|
|
266
|
+
// Determine if another iteration is needed
|
|
267
|
+
const shouldRepeat = overallHealth < 0.7 && state.actions.length > 0;
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
state,
|
|
271
|
+
overallHealth: Math.round(overallHealth * 100),
|
|
272
|
+
phase: state.phase,
|
|
273
|
+
findings: state.findings,
|
|
274
|
+
metrics: state.metrics,
|
|
275
|
+
decisions: decisions.filter(d => d.autoFixable),
|
|
276
|
+
actions: state.actions,
|
|
277
|
+
learnings,
|
|
278
|
+
shouldRepeat,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Create Knowledge Hooks for autonomous execution
|
|
284
|
+
*
|
|
285
|
+
* @param {Object} mapekFindings - From runMapekIteration
|
|
286
|
+
* @param {Store} projectStore - Project RDF store
|
|
287
|
+
* @returns {Hook[]} Array of hooks for KnowledgeHookManager
|
|
288
|
+
*/
|
|
289
|
+
export function createAutonomicHooks(mapekFindings, _projectStore) {
|
|
290
|
+
const hooks = [];
|
|
291
|
+
|
|
292
|
+
// Hook 1: Auto-generate missing files on gap detection
|
|
293
|
+
if (mapekFindings.findings.gaps?.gaps?.some(g => g.score > 80)) {
|
|
294
|
+
hooks.push({
|
|
295
|
+
meta: {
|
|
296
|
+
name: 'autonomic:auto-generate-missing-files',
|
|
297
|
+
description: 'Autonomously generate missing files for high-score gaps',
|
|
298
|
+
source: 'mapek:execute',
|
|
299
|
+
},
|
|
300
|
+
channel: { graphs: ['urn:graph:project'], view: 'after' },
|
|
301
|
+
when: {
|
|
302
|
+
kind: 'sparql-ask',
|
|
303
|
+
query: `
|
|
304
|
+
ASK {
|
|
305
|
+
?entity rdf:type dom:Entity .
|
|
306
|
+
FILTER NOT EXISTS { ?file project:belongsToFeature ?entity . }
|
|
307
|
+
}
|
|
308
|
+
`,
|
|
309
|
+
},
|
|
310
|
+
run: async ({ payload, _context }) => {
|
|
311
|
+
// This hook triggers when a new entity is detected without corresponding files
|
|
312
|
+
// The actual file generation is handled by applyMapekActions in the CLI
|
|
313
|
+
const entity = payload?.subject?.value || payload?.subject || 'unknown';
|
|
314
|
+
console.log(`[Autonomic Hook] Detected missing files for entity: ${entity}`);
|
|
315
|
+
return {
|
|
316
|
+
result: {
|
|
317
|
+
action: 'generate',
|
|
318
|
+
entity,
|
|
319
|
+
triggered: true,
|
|
320
|
+
note: 'File generation will be handled by MAPEK execute phase',
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Hook 2: Auto-sync types when mismatch detected
|
|
328
|
+
if (mapekFindings.findings.typeIssues?.mismatches?.some(m => m.severity === 'high')) {
|
|
329
|
+
hooks.push({
|
|
330
|
+
meta: {
|
|
331
|
+
name: 'autonomic:auto-sync-types',
|
|
332
|
+
description: 'Autonomously sync Zod schemas with TypeScript types',
|
|
333
|
+
source: 'mapek:execute',
|
|
334
|
+
},
|
|
335
|
+
channel: { graphs: ['urn:graph:project'], view: 'after' },
|
|
336
|
+
when: {
|
|
337
|
+
kind: 'custom',
|
|
338
|
+
},
|
|
339
|
+
run: async ({ _payload, _context }) => {
|
|
340
|
+
// This hook triggers when type mismatches are detected
|
|
341
|
+
// The actual type syncing requires manual review due to complexity
|
|
342
|
+
console.log('[Autonomic Hook] Type mismatch detected - requires manual review');
|
|
343
|
+
return {
|
|
344
|
+
result: {
|
|
345
|
+
action: 'sync',
|
|
346
|
+
type: 'zod-ts',
|
|
347
|
+
triggered: true,
|
|
348
|
+
note: 'Type syncing requires manual review. Run: unrdf autonomic --full',
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Hook 3: Alert on hotspots (for human review)
|
|
356
|
+
if (mapekFindings.findings.hotspots?.hotspots?.some(h => h.risk === 'HIGH')) {
|
|
357
|
+
hooks.push({
|
|
358
|
+
meta: {
|
|
359
|
+
name: 'autonomic:hotspot-alert',
|
|
360
|
+
description: 'Alert on high-risk features that need attention',
|
|
361
|
+
source: 'mapek:execute',
|
|
362
|
+
},
|
|
363
|
+
channel: { graphs: ['urn:graph:project'], view: 'after' },
|
|
364
|
+
when: {
|
|
365
|
+
kind: 'custom',
|
|
366
|
+
},
|
|
367
|
+
run: async ({ _payload, _context }) => {
|
|
368
|
+
// This hook alerts on high-risk features that need human attention
|
|
369
|
+
const topRisk = mapekFindings.findings.hotspots?.topRisks?.[0];
|
|
370
|
+
if (topRisk) {
|
|
371
|
+
console.log(`[Autonomic Hook] High-risk feature detected: ${topRisk.feature}`);
|
|
372
|
+
console.log(` Reason: ${topRisk.reason || 'High complexity'}`);
|
|
373
|
+
return {
|
|
374
|
+
result: {
|
|
375
|
+
action: 'alert',
|
|
376
|
+
feature: topRisk.feature,
|
|
377
|
+
reason: topRisk.reason,
|
|
378
|
+
level: 'warning',
|
|
379
|
+
triggered: true,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
result: {
|
|
385
|
+
action: 'alert',
|
|
386
|
+
level: 'info',
|
|
387
|
+
triggered: false,
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return hooks;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Continuous autonomic loop (polling-based)
|
|
399
|
+
*
|
|
400
|
+
* @param {Object} options
|
|
401
|
+
* @param {Function} options.getState - Function to get current project state
|
|
402
|
+
* @param {Function} options.applyActions - Function to apply decisions
|
|
403
|
+
* @param {number} [options.intervalMs] - Polling interval (default 5000)
|
|
404
|
+
* @param {number} [options.maxIterations] - Stop after N iterations (default: run forever)
|
|
405
|
+
* @returns {Promise<Object>} Final state after convergence
|
|
406
|
+
*/
|
|
407
|
+
export async function runContinuousMapekLoop(options) {
|
|
408
|
+
const {
|
|
409
|
+
getState,
|
|
410
|
+
applyActions,
|
|
411
|
+
intervalMs = 5000,
|
|
412
|
+
maxIterations = 10,
|
|
413
|
+
} = z
|
|
414
|
+
.object({
|
|
415
|
+
getState: z.function(),
|
|
416
|
+
applyActions: z.function(),
|
|
417
|
+
intervalMs: z.number().default(5000),
|
|
418
|
+
maxIterations: z.number().default(10),
|
|
419
|
+
})
|
|
420
|
+
.parse(options);
|
|
421
|
+
|
|
422
|
+
let iteration = 0;
|
|
423
|
+
let lastState = null;
|
|
424
|
+
let converged = false;
|
|
425
|
+
|
|
426
|
+
while (iteration < maxIterations && !converged) {
|
|
427
|
+
iteration++;
|
|
428
|
+
|
|
429
|
+
// Get current state
|
|
430
|
+
const state = await getState();
|
|
431
|
+
|
|
432
|
+
// Run MAPEK iteration
|
|
433
|
+
const result = await runMapekIteration(state);
|
|
434
|
+
|
|
435
|
+
// Apply auto-fixable actions
|
|
436
|
+
if (result.actions.length > 0) {
|
|
437
|
+
await applyActions(result.actions, state);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
lastState = result;
|
|
441
|
+
|
|
442
|
+
// Check convergence
|
|
443
|
+
if (result.overallHealth > 80) {
|
|
444
|
+
converged = true;
|
|
445
|
+
} else if (!result.shouldRepeat) {
|
|
446
|
+
converged = true;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Wait before next iteration
|
|
450
|
+
if (!converged && iteration < maxIterations) {
|
|
451
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return {
|
|
456
|
+
converged,
|
|
457
|
+
iterations: iteration,
|
|
458
|
+
finalHealth: lastState?.overallHealth || 0,
|
|
459
|
+
finalState: lastState,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* MAPEK Status reporter
|
|
465
|
+
*
|
|
466
|
+
* @param {Object} mapekState - From runMapekIteration
|
|
467
|
+
* @returns {string} Human-readable status report
|
|
468
|
+
*/
|
|
469
|
+
export function reportMapekStatus(mapekState) {
|
|
470
|
+
const lines = [
|
|
471
|
+
'\n╔════════════════════════════════════════╗',
|
|
472
|
+
'║ AUTONOMIC SYSTEM STATUS ║',
|
|
473
|
+
'╚════════════════════════════════════════╝\n',
|
|
474
|
+
`🔄 Overall Health: ${mapekState.overallHealth}%`,
|
|
475
|
+
'',
|
|
476
|
+
'📊 Metrics:',
|
|
477
|
+
` Gap Score: ${mapekState.metrics.gapScore}/100`,
|
|
478
|
+
` Type Score: ${mapekState.metrics.typeScore}/100`,
|
|
479
|
+
` Hotspot Score: ${mapekState.metrics.hotspotScore}/100`,
|
|
480
|
+
` Drift: ${mapekState.metrics.driftSeverity}`,
|
|
481
|
+
'',
|
|
482
|
+
'🔍 Findings:',
|
|
483
|
+
` Gaps: ${mapekState.findings.gaps?.gaps?.length || 0} missing roles`,
|
|
484
|
+
` Type Issues: ${mapekState.findings.typeIssues?.mismatches?.length || 0} mismatches`,
|
|
485
|
+
` Hotspots: ${mapekState.findings.hotspots?.hotspots?.length || 0} high-risk features`,
|
|
486
|
+
];
|
|
487
|
+
|
|
488
|
+
if (mapekState.findings.decisions?.length > 0) {
|
|
489
|
+
lines.push('');
|
|
490
|
+
lines.push('📋 Decisions:');
|
|
491
|
+
mapekState.findings.decisions.forEach(d => {
|
|
492
|
+
const icon = d.autoFixable ? '⚙️ ' : '🤔';
|
|
493
|
+
lines.push(` ${icon} ${d.description}`);
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (mapekState.actions?.length > 0) {
|
|
498
|
+
lines.push('');
|
|
499
|
+
lines.push('⚡ Planned Actions:');
|
|
500
|
+
mapekState.actions.forEach(a => {
|
|
501
|
+
lines.push(` → ${a.type}: ${a.status}`);
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
lines.push('');
|
|
506
|
+
return lines.join('\n');
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export { findMissingRoles, scoreMissingRole } from './gap-finder.mjs';
|
|
510
|
+
export { auditTypeConsistency, compareTypes } from './type-auditor.mjs';
|
|
511
|
+
export { analyzeHotspots, scoreFeature } from './hotspot-analyzer.mjs';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Project Engine Capabilities Manifest
|
|
3
|
+
* @module project-engine/capabilities-manifest
|
|
4
|
+
* @description Defines all available capabilities with feature flags for the project initialization pipeline
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} CapabilityFeatureFlag
|
|
9
|
+
* @property {string} name - Feature flag name (e.g., 'code_complexity_js')
|
|
10
|
+
* @property {string} status - Feature status: 'stable', 'beta', 'experimental', 'deprecated'
|
|
11
|
+
* @property {string} [description] - Human-readable description
|
|
12
|
+
* @property {string} [since] - Version when introduced
|
|
13
|
+
* @property {boolean} [enabled] - Whether feature is enabled by default
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} CapabilityMetadata
|
|
18
|
+
* @property {string} id - Unique capability ID
|
|
19
|
+
* @property {string} name - Human-readable name
|
|
20
|
+
* @property {string} description - Detailed description
|
|
21
|
+
* @property {string} phase - Pipeline phase name (e.g., 'Phase 6.5')
|
|
22
|
+
* @property {string} module - Module path relative to project-engine
|
|
23
|
+
* @property {string} exportName - Function export name
|
|
24
|
+
* @property {string} version - Capability version
|
|
25
|
+
* @property {string[]} tags - Searchable tags
|
|
26
|
+
* @property {CapabilityFeatureFlag} featureFlag - Associated feature flag
|
|
27
|
+
* @property {Object} [config] - Default configuration
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* JavaScript Code Complexity Analysis Capability
|
|
32
|
+
* @type {CapabilityMetadata}
|
|
33
|
+
*/
|
|
34
|
+
export const CODE_COMPLEXITY_JS = {
|
|
35
|
+
id: 'code_complexity_js',
|
|
36
|
+
name: 'JavaScript Code Complexity Analysis',
|
|
37
|
+
description:
|
|
38
|
+
'Analyzes JavaScript/TypeScript source code for complexity metrics including cyclomatic complexity, Halstead volume, and maintainability index',
|
|
39
|
+
phase: 'Phase 6.5',
|
|
40
|
+
module: './code-complexity-js.mjs',
|
|
41
|
+
exportName: 'analyzeJsComplexity',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
tags: ['analysis', 'metrics', 'complexity', 'javascript', 'typescript', 'rdf'],
|
|
44
|
+
featureFlag: {
|
|
45
|
+
name: 'code_complexity_js',
|
|
46
|
+
status: 'stable',
|
|
47
|
+
description: 'Enable JavaScript/TypeScript code complexity analysis in project initialization',
|
|
48
|
+
since: '4.0.0',
|
|
49
|
+
enabled: true,
|
|
50
|
+
},
|
|
51
|
+
config: {
|
|
52
|
+
mode: 'observe', // 'off', 'observe', 'enforce'
|
|
53
|
+
excludePatterns: [
|
|
54
|
+
'**/node_modules/**',
|
|
55
|
+
'**/dist/**',
|
|
56
|
+
'**/build/**',
|
|
57
|
+
'**/coverage/**',
|
|
58
|
+
'**/.next/**',
|
|
59
|
+
'**/test/**',
|
|
60
|
+
'**/__tests__/**',
|
|
61
|
+
'**/spec/**',
|
|
62
|
+
'**/*.test.mjs',
|
|
63
|
+
'**/*.test.js',
|
|
64
|
+
'**/*.spec.mjs',
|
|
65
|
+
'**/*.spec.js',
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* All available capabilities in the project engine
|
|
72
|
+
* @type {CapabilityMetadata[]}
|
|
73
|
+
*/
|
|
74
|
+
export const CAPABILITIES = [CODE_COMPLEXITY_JS];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Feature flag registry - controls which capabilities are enabled
|
|
78
|
+
* @type {Map<string, boolean>}
|
|
79
|
+
*/
|
|
80
|
+
export const FEATURE_FLAGS = new Map([
|
|
81
|
+
// Phase 6.5: Code Complexity Analysis
|
|
82
|
+
[CODE_COMPLEXITY_JS.featureFlag.name, CODE_COMPLEXITY_JS.featureFlag.enabled],
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Check if a capability is enabled via feature flag
|
|
87
|
+
*
|
|
88
|
+
* @param {string} capabilityId - Capability ID (e.g., 'code_complexity_js')
|
|
89
|
+
* @param {Object} [overrides] - Override feature flags for this check
|
|
90
|
+
* @returns {boolean} - True if capability is enabled
|
|
91
|
+
*/
|
|
92
|
+
export function isCapabilityEnabled(capabilityId, overrides = {}) {
|
|
93
|
+
const flags = new Map([...FEATURE_FLAGS, ...Object.entries(overrides || {})]);
|
|
94
|
+
return flags.get(capabilityId) ?? false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get capability metadata by ID
|
|
99
|
+
*
|
|
100
|
+
* @param {string} capabilityId - Capability ID
|
|
101
|
+
* @returns {CapabilityMetadata|null} - Capability metadata or null if not found
|
|
102
|
+
*/
|
|
103
|
+
export function getCapabilityMetadata(capabilityId) {
|
|
104
|
+
return CAPABILITIES.find(cap => cap.id === capabilityId) || null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all enabled capabilities
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} [overrides] - Override feature flags
|
|
111
|
+
* @returns {CapabilityMetadata[]} - Array of enabled capabilities
|
|
112
|
+
*/
|
|
113
|
+
export function getEnabledCapabilities(overrides = {}) {
|
|
114
|
+
return CAPABILITIES.filter(cap => isCapabilityEnabled(cap.id, overrides));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Enable/disable capability at runtime
|
|
119
|
+
*
|
|
120
|
+
* @param {string} capabilityId - Capability ID
|
|
121
|
+
* @param {boolean} enabled - Enable or disable
|
|
122
|
+
*/
|
|
123
|
+
export function setCapabilityEnabled(capabilityId, enabled) {
|
|
124
|
+
FEATURE_FLAGS.set(capabilityId, enabled);
|
|
125
|
+
}
|