@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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/package.json +58 -0
  4. package/src/api-contract-validator.mjs +711 -0
  5. package/src/auto-test-generator.mjs +444 -0
  6. package/src/autonomic-mapek.mjs +511 -0
  7. package/src/capabilities-manifest.mjs +125 -0
  8. package/src/code-complexity-js.mjs +368 -0
  9. package/src/dependency-graph.mjs +276 -0
  10. package/src/doc-drift-checker.mjs +172 -0
  11. package/src/doc-generator.mjs +229 -0
  12. package/src/domain-infer.mjs +966 -0
  13. package/src/drift-snapshot.mjs +775 -0
  14. package/src/file-roles.mjs +94 -0
  15. package/src/fs-scan.mjs +305 -0
  16. package/src/gap-finder.mjs +376 -0
  17. package/src/golden-structure.mjs +149 -0
  18. package/src/hotspot-analyzer.mjs +412 -0
  19. package/src/index.mjs +151 -0
  20. package/src/initialize.mjs +957 -0
  21. package/src/lens/project-structure.mjs +74 -0
  22. package/src/mapek-orchestration.mjs +665 -0
  23. package/src/materialize-apply.mjs +505 -0
  24. package/src/materialize-plan.mjs +422 -0
  25. package/src/materialize.mjs +137 -0
  26. package/src/policy-derivation.mjs +869 -0
  27. package/src/project-config.mjs +142 -0
  28. package/src/project-diff.mjs +28 -0
  29. package/src/project-engine/build-utils.mjs +237 -0
  30. package/src/project-engine/code-analyzer.mjs +248 -0
  31. package/src/project-engine/doc-generator.mjs +407 -0
  32. package/src/project-engine/infrastructure.mjs +213 -0
  33. package/src/project-engine/metrics.mjs +146 -0
  34. package/src/project-model.mjs +111 -0
  35. package/src/project-report.mjs +348 -0
  36. package/src/refactoring-guide.mjs +242 -0
  37. package/src/stack-detect.mjs +102 -0
  38. package/src/stack-linter.mjs +213 -0
  39. package/src/template-infer.mjs +674 -0
  40. package/src/type-auditor.mjs +609 -0
@@ -0,0 +1,665 @@
1
+ /**
2
+ * @file MAPEK Orchestration - Unified execution with all innovations
3
+ * @module project-engine/mapek-orchestration
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
8
+ import { findMissingRoles } from './gap-finder.mjs';
9
+ import { auditTypeConsistency } from './type-auditor.mjs';
10
+ import { analyzeHotspots } from './hotspot-analyzer.mjs';
11
+ import { generateTestSuggestions } from './auto-test-generator.mjs';
12
+ import { checkDocDrift } from './doc-drift-checker.mjs';
13
+ import { buildDependencyGraph } from './dependency-graph.mjs';
14
+ import { generateAllAPISchemas, validateAPIFiles } from './api-contract-validator.mjs';
15
+ import { lintStack } from './stack-linter.mjs';
16
+ import { generateRefactoringGuide } from './refactoring-guide.mjs';
17
+ import { generateDocSuggestions } from './doc-generator.mjs';
18
+ import { promises as fs } from 'fs';
19
+ import path from 'path';
20
+
21
+ const { namedNode } = DataFactory;
22
+
23
+ const NS = {
24
+ fs: 'http://example.org/unrdf/filesystem#',
25
+ proj: 'http://example.org/unrdf/project#',
26
+ };
27
+
28
+ // Scoring configuration constants
29
+ const SCORE_MULTIPLIERS = {
30
+ GAP: 10,
31
+ TYPE: 15,
32
+ HOTSPOT: 20,
33
+ DEPENDENCY: 30,
34
+ };
35
+
36
+ const SCORE_WEIGHTS = {
37
+ GAP: 0.15,
38
+ TYPE: 0.15,
39
+ HOTSPOT: 0.1,
40
+ TEST: 0.15,
41
+ DOC_DRIFT: 0.05,
42
+ DEPENDENCY: 0.1,
43
+ API_CONTRACT: 0.1,
44
+ STACK_LINT: 0.05,
45
+ REFACTORING: 0.1,
46
+ DOC_COVERAGE: 0.05,
47
+ };
48
+
49
+ const SCORE_THRESHOLDS = {
50
+ MAX_SCORE: 100,
51
+ TYPE_ISSUE: 50,
52
+ TYPE_CRITICAL: 80,
53
+ TEST_ISSUE: 30,
54
+ TEST_HIGH: 60,
55
+ DOC_ISSUE: 30,
56
+ API_CONTRACT_ISSUE: 40,
57
+ API_CONTRACT_CRITICAL: 70,
58
+ REFACTORING_ISSUE: 40,
59
+ GAP_CRITICAL: 80,
60
+ HEALTH_REPEAT_THRESHOLD: 70,
61
+ COMMON_ISSUE_THRESHOLD: 50,
62
+ };
63
+
64
+ // Kaizen improvement: Extract magic number to named constant
65
+ const PRIORITIZED_ISSUES_LIMIT = 20;
66
+
67
+ // Kaizen improvement: Extract severity order mapping to constant
68
+ const SEVERITY_ORDER = {
69
+ critical: 0,
70
+ high: 1,
71
+ medium: 2,
72
+ low: 3,
73
+ };
74
+
75
+ // Kaizen improvement: Extract issue type strings to constants
76
+ const ISSUE_TYPES = {
77
+ GAP: 'gap',
78
+ TYPE_MISMATCH: 'type-mismatch',
79
+ DEPENDENCY: 'dependency',
80
+ API_CONTRACT: 'api-contract',
81
+ LINT: 'lint',
82
+ };
83
+
84
+ // Kaizen improvement: Add type safety for severity values
85
+ const SEVERITY_LEVELS = {
86
+ CRITICAL: 'critical',
87
+ HIGH: 'high',
88
+ MEDIUM: 'medium',
89
+ LOW: 'low',
90
+ };
91
+
92
+ /**
93
+ * Calculate risk score from issue count and multiplier
94
+ * Converts issue counts to risk scores (0-100)
95
+ * @param {number|undefined} count - Number of issues
96
+ * @param {number} multiplier - Score multiplier per issue
97
+ * @returns {number} Risk score (0-100), capped at MAX_SCORE
98
+ */
99
+ function calculateRiskScore(count, multiplier) {
100
+ if (!count || count === 0) return 0;
101
+ return Math.min(SCORE_THRESHOLDS.MAX_SCORE, count * multiplier);
102
+ }
103
+
104
+ /**
105
+ * Calculate coverage-based score from a value
106
+ * Converts coverage/health values (0-100) to risk scores (0-100)
107
+ * Higher coverage = lower risk score
108
+ * @param {number|undefined} value - Coverage or health score value (0-100)
109
+ * @returns {number} Risk score (0-100), where 0 = perfect, 100 = worst
110
+ */
111
+ function calculateCoverageScore(value) {
112
+ return SCORE_THRESHOLDS.MAX_SCORE - (value || SCORE_THRESHOLDS.MAX_SCORE);
113
+ }
114
+
115
+ /**
116
+ * Map lint severity to issue severity
117
+ * Converts ESLint severity levels to standardized issue severity levels
118
+ * @param {string} lintSeverity - ESLint severity ("error", "warning", "info")
119
+ * @returns {string} Issue severity (SEVERITY_LEVELS.HIGH, MEDIUM, or LOW)
120
+ */
121
+ function mapLintSeverity(lintSeverity) {
122
+ if (lintSeverity === 'error') return SEVERITY_LEVELS.HIGH;
123
+ if (lintSeverity === 'warning') return SEVERITY_LEVELS.MEDIUM;
124
+ return SEVERITY_LEVELS.LOW;
125
+ }
126
+
127
+ /**
128
+ * Extract issues from results and add to allIssues array
129
+ * Reduces repetitive issue aggregation code
130
+ * @param {Array} items - Array of items to convert to issues
131
+ * @param {string} issueType - Type of issue (from ISSUE_TYPES)
132
+ * @param {Function} [severityMapper] - Optional function to map item severity
133
+ * @returns {Array} Array of issue objects
134
+ */
135
+ function extractIssues(items, issueType, severityMapper = item => item.severity) {
136
+ if (!items || items.length === 0) return [];
137
+ return items.map(item => ({
138
+ type: issueType,
139
+ severity: severityMapper(item),
140
+ item,
141
+ }));
142
+ }
143
+
144
+ const OrchestrationOptionsSchema = z.object({
145
+ projectStore: z.custom(val => val && typeof val.getQuads === 'function', {
146
+ message: 'projectStore must be an RDF store with getQuads method',
147
+ }),
148
+ domainStore: z
149
+ .custom(val => val && typeof val.getQuads === 'function', {
150
+ message: 'domainStore must be an RDF store with getQuads method',
151
+ })
152
+ .optional(),
153
+ projectRoot: z.string().optional(),
154
+ stackProfile: z.object({}).passthrough().optional(),
155
+ innovations: z.array(z.string()).optional(),
156
+ });
157
+
158
+ const ALL_INNOVATIONS = [
159
+ 'gap-finder',
160
+ 'type-auditor',
161
+ 'hotspot-analyzer',
162
+ 'auto-test-generator',
163
+ 'doc-drift-checker',
164
+ 'dependency-graph',
165
+ 'api-contract-validator',
166
+ 'stack-linter',
167
+ 'refactoring-guide',
168
+ 'doc-generator',
169
+ ];
170
+
171
+ /**
172
+ * Innovation execution strategies - replaces large switch statement
173
+ * Maps innovation names to their execution functions
174
+ */
175
+ const INNOVATION_STRATEGIES = {
176
+ 'gap-finder': ({ domainStore, projectStore, stackProfile }) =>
177
+ findMissingRoles({ domainStore, projectStore, stackProfile }),
178
+ 'type-auditor': async ({ domainStore, projectStore, stackProfile, projectRoot }) =>
179
+ await auditTypeConsistency({ domainStore, fsStore: projectStore, stackProfile, projectRoot }),
180
+ 'hotspot-analyzer': ({ projectStore, domainStore, stackProfile }) =>
181
+ analyzeHotspots({ projectStore, domainStore, stackProfile }),
182
+ 'auto-test-generator': ({ projectStore, domainStore, stackProfile }) =>
183
+ generateTestSuggestions({ projectStore, domainStore, stackProfile }),
184
+ 'doc-drift-checker': ({ projectStore, projectRoot }) =>
185
+ checkDocDrift({ projectStore, projectRoot }),
186
+ 'dependency-graph': ({ projectStore, projectRoot }) =>
187
+ buildDependencyGraph({ projectStore, projectRoot }),
188
+ 'api-contract-validator': async ({ projectStore, domainStore, projectRoot }) =>
189
+ await validateApiContracts({ projectStore, domainStore, projectRoot }),
190
+ 'stack-linter': ({ projectStore, stackProfile, projectRoot }) =>
191
+ lintStack({ projectStore, stackProfile, projectRoot }),
192
+ 'refactoring-guide': ({ projectStore, domainStore, stackProfile }) =>
193
+ generateRefactoringGuide({ projectStore, domainStore, stackProfile }),
194
+ 'doc-generator': ({ projectStore, domainStore, stackProfile, projectRoot }) =>
195
+ generateDocSuggestions({ projectStore, domainStore, stackProfile, projectRoot }),
196
+ };
197
+
198
+ /**
199
+ * Validate API contracts by matching API files to domain schemas
200
+ * @param {Object} options
201
+ * @param {Store} options.projectStore - RDF project structure
202
+ * @param {Store} options.domainStore - RDF domain model
203
+ * @param {string} [options.projectRoot] - File system root
204
+ * @returns {Promise<Object>} Validation results
205
+ */
206
+ async function validateApiContracts({ projectStore, domainStore, projectRoot }) {
207
+ if (!domainStore) {
208
+ return {
209
+ violations: [],
210
+ coverage: 0,
211
+ breaking: false,
212
+ summary: 'No domain store provided',
213
+ };
214
+ }
215
+
216
+ // Generate all schemas from domain store
217
+ const schemas = generateAllAPISchemas(domainStore);
218
+ if (schemas.length === 0) {
219
+ return {
220
+ violations: [],
221
+ coverage: 0,
222
+ breaking: false,
223
+ summary: 'No domain entities found',
224
+ };
225
+ }
226
+
227
+ // Extract API files from project store
228
+ const apiFileQuads = projectStore
229
+ .getQuads(null, namedNode(`${NS.proj}roleString`), null)
230
+ .filter(quad => {
231
+ const role = quad.object.value;
232
+ return role === 'Api' || role === 'Route';
233
+ });
234
+
235
+ const apiFiles = [];
236
+ for (const roleQuad of apiFileQuads) {
237
+ const fileIri = roleQuad.subject;
238
+ const pathQuads = projectStore.getQuads(fileIri, namedNode(`${NS.fs}relativePath`), null);
239
+ if (pathQuads.length > 0) {
240
+ const filePath = pathQuads[0].object.value;
241
+ let content = '';
242
+ if (projectRoot) {
243
+ try {
244
+ const fullPath = path.join(projectRoot, filePath);
245
+ content = await fs.readFile(fullPath, 'utf-8');
246
+ } catch (err) {
247
+ // File doesn't exist or can't be read, skip
248
+ continue;
249
+ }
250
+ }
251
+ apiFiles.push({ path: filePath, content });
252
+ }
253
+ }
254
+
255
+ if (apiFiles.length === 0) {
256
+ return {
257
+ violations: [],
258
+ coverage: 0,
259
+ breaking: false,
260
+ summary: 'No API files found in project',
261
+ };
262
+ }
263
+
264
+ // Determine framework from stack profile or infer from file paths
265
+ const framework = apiFiles.some(f => f.path.includes('app/api') || f.path.includes('route.ts'))
266
+ ? 'nextjs'
267
+ : apiFiles.some(f => f.path.includes('routes') || f.path.includes('router'))
268
+ ? 'express'
269
+ : undefined;
270
+
271
+ // Validate each schema against matching API files
272
+ const allViolations = [];
273
+ let totalCoverage = 0;
274
+ let hasBreaking = false;
275
+
276
+ for (const schema of schemas) {
277
+ // Find API files that might match this entity (by name pattern)
278
+ const entityName = schema.entityName.toLowerCase();
279
+ const matchingFiles = apiFiles.filter(file => {
280
+ const fileName = path.basename(file.path, path.extname(file.path)).toLowerCase();
281
+ const dirName = path.dirname(file.path).toLowerCase();
282
+ return (
283
+ fileName.includes(entityName) ||
284
+ dirName.includes(entityName) ||
285
+ file.path.toLowerCase().includes(`/${entityName}`)
286
+ );
287
+ });
288
+
289
+ if (matchingFiles.length > 0) {
290
+ const result = validateAPIFiles(matchingFiles, schema, { framework });
291
+ allViolations.push(...result.violations);
292
+ totalCoverage += result.coverage;
293
+ if (result.breaking) {
294
+ hasBreaking = true;
295
+ }
296
+ }
297
+ }
298
+
299
+ const avgCoverage = schemas.length > 0 ? Math.round(totalCoverage / schemas.length) : 0;
300
+ const criticalCount = allViolations.filter(v => v.severity === SEVERITY_LEVELS.CRITICAL).length;
301
+ const highCount = allViolations.filter(v => v.severity === SEVERITY_LEVELS.HIGH).length;
302
+
303
+ let summary = `${allViolations.length} violations found across ${schemas.length} entities`;
304
+ if (criticalCount > 0) {
305
+ summary += ` (${criticalCount} critical)`;
306
+ } else if (highCount > 0) {
307
+ summary += ` (${highCount} high severity)`;
308
+ }
309
+ if (allViolations.length === 0) {
310
+ summary = 'API contracts are valid';
311
+ }
312
+
313
+ return {
314
+ violations: allViolations,
315
+ coverage: avgCoverage,
316
+ breaking: hasBreaking,
317
+ summary,
318
+ };
319
+ }
320
+
321
+ /**
322
+ * Run all innovation analyzers in parallel
323
+ * @param {Object} options
324
+ * @returns {Promise<Object>} Combined findings from all innovations
325
+ */
326
+ export async function runInnovationsParallel(options) {
327
+ const validated = OrchestrationOptionsSchema.parse(options);
328
+ const { projectStore, domainStore, stackProfile, projectRoot, innovations } = validated;
329
+
330
+ const enabledInnovations = innovations || ALL_INNOVATIONS;
331
+ const results = {};
332
+ const errors = [];
333
+
334
+ // Kaizen improvement: Add error handling for missing innovation results
335
+ if (!projectStore) {
336
+ errors.push({ innovation: 'all', error: 'projectStore is required' });
337
+ return { results, errors, innovationsRun: 0 };
338
+ }
339
+
340
+ // Strategy map replaces large switch statement - reduces complexity
341
+ const resultKeys = {
342
+ 'gap-finder': 'gaps',
343
+ 'type-auditor': 'typeIssues',
344
+ 'hotspot-analyzer': 'hotspots',
345
+ 'auto-test-generator': 'testSuggestions',
346
+ 'doc-drift-checker': 'docDrift',
347
+ 'dependency-graph': 'dependencyGraph',
348
+ 'api-contract-validator': 'apiContracts',
349
+ 'stack-linter': 'stackLint',
350
+ 'refactoring-guide': 'refactoring',
351
+ 'doc-generator': 'docSuggestions',
352
+ };
353
+
354
+ const tasks = enabledInnovations.map(async name => {
355
+ try {
356
+ const strategy = INNOVATION_STRATEGIES[name];
357
+ if (!strategy) {
358
+ errors.push({ innovation: name, error: 'Unknown innovation' });
359
+ return;
360
+ }
361
+
362
+ const context = { domainStore, projectStore, stackProfile, projectRoot };
363
+ const result = await strategy(context);
364
+ const resultKey = resultKeys[name];
365
+ if (resultKey) {
366
+ results[resultKey] = result;
367
+ }
368
+ } catch (err) {
369
+ errors.push({ innovation: name, error: err.message });
370
+ }
371
+ });
372
+
373
+ await Promise.all(tasks);
374
+
375
+ return { results, errors, innovationsRun: enabledInnovations.length };
376
+ }
377
+
378
+ /**
379
+ * Aggregate findings from all innovations into unified metrics
380
+ *
381
+ * @description
382
+ * Calculates unified health scores from all innovation findings using weighted averages.
383
+ * Score formula: score = min(MAX_SCORE, issueCount * multiplier) for risk scores,
384
+ * or score = MAX_SCORE - coverage for coverage-based scores.
385
+ * Overall health = MAX_SCORE - weighted sum of all risk scores.
386
+ *
387
+ * @param {Object} innovationResults - Results from runInnovationsParallel
388
+ * @returns {Object} Aggregated findings with priorities
389
+ */
390
+ export function aggregateInnovationFindings(innovationResults) {
391
+ const { results } = innovationResults;
392
+
393
+ // Calculate unified scores
394
+ const scores = {
395
+ gapScore: calculateRiskScore(results.gaps?.gaps?.length, SCORE_MULTIPLIERS.GAP),
396
+ typeScore: calculateRiskScore(results.typeIssues?.mismatches?.length, SCORE_MULTIPLIERS.TYPE),
397
+ hotspotScore: calculateRiskScore(
398
+ results.hotspots?.hotspots?.filter(h => h.risk === 'HIGH').length,
399
+ SCORE_MULTIPLIERS.HOTSPOT
400
+ ),
401
+ testScore: calculateCoverageScore(results.testSuggestions?.coverage),
402
+ docDriftScore: calculateCoverageScore(results.docDrift?.healthScore),
403
+ dependencyScore: calculateRiskScore(
404
+ results.dependencyGraph?.issues?.filter(i => i.severity === SEVERITY_LEVELS.CRITICAL).length,
405
+ SCORE_MULTIPLIERS.DEPENDENCY
406
+ ),
407
+ apiContractScore: calculateCoverageScore(results.apiContracts?.coverage),
408
+ stackLintScore: calculateCoverageScore(results.stackLint?.score),
409
+ refactoringScore: results.refactoring?.technicalDebt || 0,
410
+ docCoverageScore: calculateCoverageScore(results.docSuggestions?.coverage),
411
+ };
412
+
413
+ // Calculate overall health (weighted average)
414
+ const weights = {
415
+ gapScore: SCORE_WEIGHTS.GAP,
416
+ typeScore: SCORE_WEIGHTS.TYPE,
417
+ hotspotScore: SCORE_WEIGHTS.HOTSPOT,
418
+ testScore: SCORE_WEIGHTS.TEST,
419
+ docDriftScore: SCORE_WEIGHTS.DOC_DRIFT,
420
+ dependencyScore: SCORE_WEIGHTS.DEPENDENCY,
421
+ apiContractScore: SCORE_WEIGHTS.API_CONTRACT,
422
+ stackLintScore: SCORE_WEIGHTS.STACK_LINT,
423
+ refactoringScore: SCORE_WEIGHTS.REFACTORING,
424
+ docCoverageScore: SCORE_WEIGHTS.DOC_COVERAGE,
425
+ };
426
+
427
+ // Kaizen improvement: Validate score weights sum to 1.0
428
+ const weightSum = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
429
+ if (Math.abs(weightSum - 1.0) > 0.001) {
430
+ console.warn(`Warning: SCORE_WEIGHTS sum to ${weightSum}, expected 1.0`);
431
+ }
432
+
433
+ const overallRisk = Object.entries(scores).reduce((total, [key, score]) => {
434
+ return total + score * (weights[key] || 0);
435
+ }, 0);
436
+
437
+ const overallHealth = Math.round(SCORE_THRESHOLDS.MAX_SCORE - overallRisk);
438
+
439
+ // Prioritize issues - extract repetitive pattern
440
+ const allIssues = [
441
+ ...extractIssues(results.gaps?.gaps, ISSUE_TYPES.GAP, g =>
442
+ g.score > SCORE_THRESHOLDS.GAP_CRITICAL ? SEVERITY_LEVELS.CRITICAL : SEVERITY_LEVELS.MEDIUM
443
+ ),
444
+ ...extractIssues(results.typeIssues?.mismatches, ISSUE_TYPES.TYPE_MISMATCH),
445
+ ...extractIssues(results.dependencyGraph?.issues, ISSUE_TYPES.DEPENDENCY),
446
+ ...extractIssues(results.apiContracts?.violations, ISSUE_TYPES.API_CONTRACT),
447
+ ...extractIssues(results.stackLint?.issues, ISSUE_TYPES.LINT, i => mapLintSeverity(i.severity)),
448
+ ];
449
+
450
+ // Sort by severity
451
+ allIssues.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
452
+
453
+ return {
454
+ scores,
455
+ overallHealth,
456
+ overallRisk: Math.round(overallRisk),
457
+ totalIssues: allIssues.length,
458
+ criticalIssues: allIssues.filter(i => i.severity === SEVERITY_LEVELS.CRITICAL).length,
459
+ highIssues: allIssues.filter(i => i.severity === SEVERITY_LEVELS.HIGH).length,
460
+ prioritizedIssues: allIssues.slice(0, PRIORITIZED_ISSUES_LIMIT),
461
+ summaries: {
462
+ gaps: results.gaps?.summary || 'N/A',
463
+ types: results.typeIssues?.summary || 'N/A',
464
+ hotspots: results.hotspots?.summary || 'N/A',
465
+ tests: results.testSuggestions?.summary || 'N/A',
466
+ docDrift: results.docDrift?.summary || 'N/A',
467
+ dependencies: results.dependencyGraph?.summary || 'N/A',
468
+ apiContracts: results.apiContracts?.summary || 'N/A',
469
+ stackLint: results.stackLint?.summary || 'N/A',
470
+ refactoring: results.refactoring?.summary || 'N/A',
471
+ docSuggestions: results.docSuggestions?.summary || 'N/A',
472
+ },
473
+ };
474
+ }
475
+
476
+ /**
477
+ * Create a decision object - reduces repetitive decision generation code
478
+ * @param {string} issue - Issue identifier
479
+ * @param {string} severity - Severity level
480
+ * @param {string} action - Action to take
481
+ * @param {boolean} autoFixable - Whether action can be auto-fixed
482
+ * @param {string} description - Description of the decision
483
+ * @returns {Object} Decision object
484
+ */
485
+ function createDecision(issue, severity, action, autoFixable, description) {
486
+ return { issue, severity, action, autoFixable, description };
487
+ }
488
+
489
+ /**
490
+ * Kaizen improvement: Extract decision generation logic to helper function
491
+ * Generates decisions based on aggregated findings and innovation results
492
+ * @param {Object} aggregated - Aggregated findings from aggregateInnovationFindings
493
+ * @param {Object} innovationResults - Results from runInnovationsParallel
494
+ * @returns {Array<Object>} Array of decision objects
495
+ */
496
+ function generateDecisions(aggregated, innovationResults) {
497
+ const decisions = [];
498
+
499
+ // Decision: Fix type mismatches
500
+ if (aggregated.scores.typeScore > SCORE_THRESHOLDS.TYPE_ISSUE) {
501
+ decisions.push(
502
+ createDecision(
503
+ 'type-mismatch',
504
+ aggregated.scores.typeScore > SCORE_THRESHOLDS.TYPE_CRITICAL
505
+ ? SEVERITY_LEVELS.CRITICAL
506
+ : SEVERITY_LEVELS.HIGH,
507
+ 'sync-zod-ts-types',
508
+ true,
509
+ 'Sync Zod schemas with TypeScript types'
510
+ )
511
+ );
512
+ }
513
+
514
+ // Decision: Generate missing tests
515
+ if (aggregated.scores.testScore > SCORE_THRESHOLDS.TEST_ISSUE) {
516
+ decisions.push(
517
+ createDecision(
518
+ 'low-test-coverage',
519
+ aggregated.scores.testScore > SCORE_THRESHOLDS.TEST_HIGH
520
+ ? SEVERITY_LEVELS.HIGH
521
+ : SEVERITY_LEVELS.MEDIUM,
522
+ 'generate-tests',
523
+ true,
524
+ 'Generate tests for uncovered files'
525
+ )
526
+ );
527
+ }
528
+
529
+ // Decision: Update documentation
530
+ if (
531
+ aggregated.scores.docDriftScore > SCORE_THRESHOLDS.DOC_ISSUE ||
532
+ aggregated.scores.docCoverageScore > SCORE_THRESHOLDS.DOC_ISSUE
533
+ ) {
534
+ decisions.push(
535
+ createDecision(
536
+ 'documentation-drift',
537
+ SEVERITY_LEVELS.MEDIUM,
538
+ 'update-docs',
539
+ false,
540
+ 'Update out-of-sync documentation'
541
+ )
542
+ );
543
+ }
544
+
545
+ // Decision: Fix circular dependencies
546
+ if (innovationResults.results.dependencyGraph?.metrics?.circularCount > 0) {
547
+ decisions.push(
548
+ createDecision(
549
+ 'circular-dependency',
550
+ SEVERITY_LEVELS.CRITICAL,
551
+ 'refactor-dependencies',
552
+ false,
553
+ 'Break circular dependency chains'
554
+ )
555
+ );
556
+ }
557
+
558
+ // Decision: Fix API contract violations
559
+ if (aggregated.scores.apiContractScore > SCORE_THRESHOLDS.API_CONTRACT_ISSUE) {
560
+ decisions.push(
561
+ createDecision(
562
+ 'api-contract-violation',
563
+ aggregated.scores.apiContractScore > SCORE_THRESHOLDS.API_CONTRACT_CRITICAL
564
+ ? SEVERITY_LEVELS.CRITICAL
565
+ : SEVERITY_LEVELS.HIGH,
566
+ 'fix-api-contracts',
567
+ true,
568
+ 'Add missing API validation and documentation'
569
+ )
570
+ );
571
+ }
572
+
573
+ // Decision: Address stack lint errors
574
+ const lintErrors =
575
+ innovationResults.results.stackLint?.issues?.filter(i => i.severity === 'error').length || 0;
576
+ if (lintErrors > 0) {
577
+ decisions.push(
578
+ createDecision(
579
+ 'lint-errors',
580
+ SEVERITY_LEVELS.HIGH,
581
+ 'fix-lint-errors',
582
+ true,
583
+ `${lintErrors} lint errors require fixing`
584
+ )
585
+ );
586
+ }
587
+
588
+ // Decision: Refactoring
589
+ if (aggregated.scores.refactoringScore > SCORE_THRESHOLDS.REFACTORING_ISSUE) {
590
+ decisions.push(
591
+ createDecision(
592
+ 'technical-debt',
593
+ SEVERITY_LEVELS.MEDIUM,
594
+ 'refactor-code',
595
+ false,
596
+ 'Address high technical debt areas'
597
+ )
598
+ );
599
+ }
600
+
601
+ return decisions;
602
+ }
603
+
604
+ /**
605
+ * Run full MAPEK cycle with all 10 innovations
606
+ * @param {Object} options
607
+ * @returns {Promise<Object>} Complete MAPEK result with all findings and decisions
608
+ */
609
+ export async function runFullMapekWithAllInnovations(options) {
610
+ const _validated = OrchestrationOptionsSchema.parse(options);
611
+ const startTime = Date.now();
612
+
613
+ // Phase 1: Monitor - Run all innovations in parallel
614
+ const innovationResults = await runInnovationsParallel(options);
615
+
616
+ // Phase 2: Analyze - Aggregate findings
617
+ const aggregated = aggregateInnovationFindings(innovationResults);
618
+
619
+ // Phase 3: Plan - Generate decisions based on findings
620
+ const decisions = generateDecisions(aggregated, innovationResults);
621
+
622
+ // Phase 4: Execute - Plan actions
623
+ const actions = decisions
624
+ .filter(d => d.autoFixable)
625
+ .map(d => ({
626
+ type: d.action,
627
+ status: 'planned',
628
+ timestamp: new Date().toISOString(),
629
+ }));
630
+
631
+ // Phase 5: Knowledge - Extract learnings
632
+ const learnings = {
633
+ timestamp: new Date().toISOString(),
634
+ patterns: {
635
+ highRiskAreas: aggregated.prioritizedIssues.slice(0, 5).map(i => i.type),
636
+ commonIssueTypes: Object.entries(aggregated.scores)
637
+ .filter(([_k, v]) => v > SCORE_THRESHOLDS.COMMON_ISSUE_THRESHOLD)
638
+ .map(([k]) => k.replace('Score', '')),
639
+ },
640
+ thresholds: {
641
+ criticalTypeScore: SCORE_THRESHOLDS.TYPE_CRITICAL,
642
+ criticalGapScore: SCORE_THRESHOLDS.GAP_CRITICAL,
643
+ criticalApiScore: SCORE_THRESHOLDS.API_CONTRACT_CRITICAL,
644
+ },
645
+ };
646
+
647
+ const duration = Date.now() - startTime;
648
+
649
+ return {
650
+ phase: 'knowledge',
651
+ timestamp: new Date().toISOString(),
652
+ duration,
653
+ overallHealth: aggregated.overallHealth,
654
+ allFindings: innovationResults.results,
655
+ aggregated,
656
+ decisions,
657
+ actions,
658
+ learnings,
659
+ shouldRepeat:
660
+ aggregated.overallHealth < SCORE_THRESHOLDS.HEALTH_REPEAT_THRESHOLD && actions.length > 0,
661
+ errors: innovationResults.errors,
662
+ };
663
+ }
664
+
665
+ export { ALL_INNOVATIONS };