@unrdf/project-engine 5.0.1 → 26.4.3

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 +6 -5
  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,511 +0,0 @@
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';
@@ -1,125 +0,0 @@
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
- }