aios-core 4.2.5 → 4.2.7

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 (32) hide show
  1. package/.aios-core/core/code-intel/code-intel-client.js +280 -0
  2. package/.aios-core/core/code-intel/code-intel-enricher.js +159 -0
  3. package/.aios-core/core/code-intel/index.js +137 -0
  4. package/.aios-core/core/code-intel/providers/code-graph-provider.js +201 -0
  5. package/.aios-core/core/code-intel/providers/provider-interface.js +108 -0
  6. package/.aios-core/core/orchestration/context-manager.js +333 -5
  7. package/.aios-core/core/orchestration/dashboard-integration.js +17 -1
  8. package/.aios-core/core/orchestration/execution-profile-resolver.js +107 -0
  9. package/.aios-core/core/orchestration/index.js +3 -0
  10. package/.aios-core/core/orchestration/skill-dispatcher.js +2 -0
  11. package/.aios-core/core/orchestration/subagent-prompt-builder.js +2 -0
  12. package/.aios-core/core/orchestration/workflow-orchestrator.js +113 -5
  13. package/.aios-core/data/entity-registry.yaml +44 -22
  14. package/.aios-core/development/agents/ux-design-expert.md +1 -1
  15. package/.aios-core/development/checklists/brownfield-compatibility-checklist.md +114 -0
  16. package/.aios-core/development/scripts/workflow-state-manager.js +128 -1
  17. package/.aios-core/development/tasks/next.md +36 -5
  18. package/.aios-core/hooks/ids-post-commit.js +29 -1
  19. package/.aios-core/hooks/ids-pre-push.js +29 -1
  20. package/.aios-core/infrastructure/contracts/compatibility/aios-4.0.4.yaml +44 -0
  21. package/.aios-core/infrastructure/scripts/validate-parity.js +238 -2
  22. package/.aios-core/install-manifest.yaml +63 -27
  23. package/.aios-core/product/templates/brownfield-risk-report-tmpl.yaml +277 -0
  24. package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +114 -5
  25. package/LICENSE +13 -1
  26. package/README.md +39 -9
  27. package/package.json +8 -6
  28. package/packages/installer/src/wizard/ide-config-generator.js +0 -117
  29. package/packages/installer/src/wizard/index.js +2 -118
  30. package/packages/installer/src/wizard/pro-setup.js +58 -12
  31. package/pro/license/license-api.js +9 -9
  32. package/scripts/semantic-lint.js +190 -0
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ const { CodeIntelProvider } = require('./provider-interface');
4
+
5
+ /**
6
+ * Code Graph MCP tool name mapping.
7
+ * Maps abstract capabilities to Code Graph MCP tool names.
8
+ */
9
+ const TOOL_MAP = {
10
+ findDefinition: 'find_definition',
11
+ findReferences: 'find_references',
12
+ findCallers: 'find_callers',
13
+ findCallees: 'find_callees',
14
+ analyzeDependencies: 'dependency_analysis',
15
+ analyzeComplexity: 'complexity_analysis',
16
+ analyzeCodebase: 'analyze_codebase',
17
+ getProjectStats: 'project_statistics',
18
+ };
19
+
20
+ /**
21
+ * CodeGraphProvider — Adapter for Code Graph MCP server.
22
+ *
23
+ * Translates the 8 abstract capabilities into Code Graph MCP tool calls.
24
+ * Normalizes responses to match the provider-interface contract.
25
+ */
26
+ class CodeGraphProvider extends CodeIntelProvider {
27
+ constructor(options = {}) {
28
+ super('code-graph', options);
29
+ this._mcpServerName = options.mcpServerName || 'code-graph';
30
+ }
31
+
32
+ /**
33
+ * Execute an MCP tool call via the configured server.
34
+ * This method is the single point of MCP communication — all capabilities route through here.
35
+ *
36
+ * @param {string} toolName - MCP tool name (e.g. 'find_definition')
37
+ * @param {Object} params - Tool parameters
38
+ * @returns {Promise<Object|null>} Tool result or null on failure
39
+ * @private
40
+ */
41
+ async _callMcpTool(toolName, params = {}) {
42
+ // MCP tool calls are executed via the Claude Code MCP protocol.
43
+ // In production, this is invoked by the Claude Code runtime.
44
+ // For testing, this method is mocked.
45
+ //
46
+ // The actual MCP call signature:
47
+ // mcp__<server>__<tool>(params)
48
+ //
49
+ // Since we run inside Claude Code agent context, the MCP call
50
+ // is abstracted. Consumers of this module use the client layer
51
+ // which handles the actual invocation.
52
+
53
+ if (typeof this.options.mcpCallFn === 'function') {
54
+ return await this.options.mcpCallFn(this._mcpServerName, toolName, params);
55
+ }
56
+
57
+ // No MCP call function configured — provider cannot operate
58
+ return null;
59
+ }
60
+
61
+ async findDefinition(symbol, options = {}) {
62
+ const result = await this._callMcpTool(TOOL_MAP.findDefinition, {
63
+ symbol,
64
+ ...options,
65
+ });
66
+ return this._normalizeDefinitionResult(result);
67
+ }
68
+
69
+ async findReferences(symbol, options = {}) {
70
+ const result = await this._callMcpTool(TOOL_MAP.findReferences, {
71
+ symbol,
72
+ ...options,
73
+ });
74
+ return this._normalizeReferencesResult(result);
75
+ }
76
+
77
+ async findCallers(symbol, options = {}) {
78
+ const result = await this._callMcpTool(TOOL_MAP.findCallers, {
79
+ symbol,
80
+ ...options,
81
+ });
82
+ return this._normalizeCallersResult(result);
83
+ }
84
+
85
+ async findCallees(symbol, options = {}) {
86
+ const result = await this._callMcpTool(TOOL_MAP.findCallees, {
87
+ symbol,
88
+ ...options,
89
+ });
90
+ return this._normalizeCalleesResult(result);
91
+ }
92
+
93
+ async analyzeDependencies(path, options = {}) {
94
+ const result = await this._callMcpTool(TOOL_MAP.analyzeDependencies, {
95
+ path,
96
+ ...options,
97
+ });
98
+ return this._normalizeDependenciesResult(result);
99
+ }
100
+
101
+ async analyzeComplexity(path, options = {}) {
102
+ const result = await this._callMcpTool(TOOL_MAP.analyzeComplexity, {
103
+ path,
104
+ ...options,
105
+ });
106
+ return this._normalizeComplexityResult(result);
107
+ }
108
+
109
+ async analyzeCodebase(path, options = {}) {
110
+ const result = await this._callMcpTool(TOOL_MAP.analyzeCodebase, {
111
+ path,
112
+ ...options,
113
+ });
114
+ return this._normalizeCodebaseResult(result);
115
+ }
116
+
117
+ async getProjectStats(options = {}) {
118
+ const result = await this._callMcpTool(TOOL_MAP.getProjectStats, options);
119
+ return this._normalizeStatsResult(result);
120
+ }
121
+
122
+ // --- Normalization helpers ---
123
+ // Normalize MCP responses to provider-interface contract format.
124
+ // If result is null/undefined, return null (fallback).
125
+
126
+ _normalizeDefinitionResult(result) {
127
+ if (!result) return null;
128
+ return {
129
+ file: result.file ?? result.path ?? null,
130
+ line: result.line != null ? result.line : (result.row != null ? result.row : null),
131
+ column: result.column != null ? result.column : (result.col != null ? result.col : null),
132
+ context: result.context || result.snippet || null,
133
+ };
134
+ }
135
+
136
+ _normalizeReferencesResult(result) {
137
+ if (!result) return null;
138
+ const items = Array.isArray(result) ? result : result.references || result.results || [];
139
+ return items.map((r) => ({
140
+ file: r.file ?? r.path ?? null,
141
+ line: r.line != null ? r.line : (r.row != null ? r.row : null),
142
+ context: r.context || r.snippet || null,
143
+ }));
144
+ }
145
+
146
+ _normalizeCallersResult(result) {
147
+ if (!result) return null;
148
+ const items = Array.isArray(result) ? result : result.callers || result.results || [];
149
+ return items.map((r) => ({
150
+ caller: r.caller || r.name || null,
151
+ file: r.file ?? r.path ?? null,
152
+ line: r.line != null ? r.line : (r.row != null ? r.row : null),
153
+ }));
154
+ }
155
+
156
+ _normalizeCalleesResult(result) {
157
+ if (!result) return null;
158
+ const items = Array.isArray(result) ? result : result.callees || result.results || [];
159
+ return items.map((r) => ({
160
+ callee: r.callee || r.name || null,
161
+ file: r.file ?? r.path ?? null,
162
+ line: r.line != null ? r.line : (r.row != null ? r.row : null),
163
+ }));
164
+ }
165
+
166
+ _normalizeDependenciesResult(result) {
167
+ if (!result) return null;
168
+ return {
169
+ nodes: result.nodes || result.files || [],
170
+ edges: result.edges || result.dependencies || [],
171
+ };
172
+ }
173
+
174
+ _normalizeComplexityResult(result) {
175
+ if (!result) return null;
176
+ return {
177
+ score: result.score != null ? result.score : (result.complexity != null ? result.complexity : 0),
178
+ details: result.details || result.metrics || {},
179
+ };
180
+ }
181
+
182
+ _normalizeCodebaseResult(result) {
183
+ if (!result) return null;
184
+ return {
185
+ files: result.files || [],
186
+ structure: result.structure || {},
187
+ patterns: result.patterns || [],
188
+ };
189
+ }
190
+
191
+ _normalizeStatsResult(result) {
192
+ if (!result) return null;
193
+ return {
194
+ files: result.files != null ? result.files : (result.total_files != null ? result.total_files : 0),
195
+ lines: result.lines != null ? result.lines : (result.total_lines != null ? result.total_lines : 0),
196
+ languages: result.languages || {},
197
+ };
198
+ }
199
+ }
200
+
201
+ module.exports = { CodeGraphProvider, TOOL_MAP };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * CodeIntelProvider — Abstract base class for all code intelligence providers.
5
+ *
6
+ * Every provider MUST extend this class and implement all 8 primitive capabilities.
7
+ * Default implementations return null (graceful fallback built-in).
8
+ *
9
+ * @abstract
10
+ */
11
+ class CodeIntelProvider {
12
+ constructor(name, options = {}) {
13
+ this.name = name;
14
+ this.options = options;
15
+ }
16
+
17
+ /**
18
+ * Locate the definition of a symbol.
19
+ * @param {string} symbol - Symbol name to find
20
+ * @param {Object} [options] - Provider-specific options
21
+ * @returns {Promise<{file: string, line: number, column: number, context: string}|null>}
22
+ */
23
+ async findDefinition(_symbol, _options) {
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Find all references (usages) of a symbol.
29
+ * @param {string} symbol - Symbol name to search
30
+ * @param {Object} [options] - Provider-specific options
31
+ * @returns {Promise<Array<{file: string, line: number, context: string}>|null>}
32
+ */
33
+ async findReferences(_symbol, _options) {
34
+ return null;
35
+ }
36
+
37
+ /**
38
+ * Find all callers of a function/method.
39
+ * @param {string} symbol - Function/method name
40
+ * @param {Object} [options] - Provider-specific options
41
+ * @returns {Promise<Array<{caller: string, file: string, line: number}>|null>}
42
+ */
43
+ async findCallers(_symbol, _options) {
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Find all callees (functions called by) a function/method.
49
+ * @param {string} symbol - Function/method name
50
+ * @param {Object} [options] - Provider-specific options
51
+ * @returns {Promise<Array<{callee: string, file: string, line: number}>|null>}
52
+ */
53
+ async findCallees(_symbol, _options) {
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Analyze dependency graph for a file or directory.
59
+ * @param {string} path - File or directory path
60
+ * @param {Object} [options] - Provider-specific options
61
+ * @returns {Promise<{nodes: Array, edges: Array}|null>}
62
+ */
63
+ async analyzeDependencies(_path, _options) {
64
+ return null;
65
+ }
66
+
67
+ /**
68
+ * Analyze code complexity metrics.
69
+ * @param {string} path - File or directory path
70
+ * @param {Object} [options] - Provider-specific options
71
+ * @returns {Promise<{score: number, details: Object}|null>}
72
+ */
73
+ async analyzeComplexity(_path, _options) {
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Analyze codebase structure, files and patterns.
79
+ * @param {string} path - Root path to analyze
80
+ * @param {Object} [options] - Provider-specific options
81
+ * @returns {Promise<{files: Array, structure: Object, patterns: Array}|null>}
82
+ */
83
+ async analyzeCodebase(_path, _options) {
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Get project-level statistics.
89
+ * @param {Object} [options] - Provider-specific options
90
+ * @returns {Promise<{files: number, lines: number, languages: Object}|null>}
91
+ */
92
+ async getProjectStats(_options) {
93
+ return null;
94
+ }
95
+ }
96
+
97
+ const CAPABILITIES = [
98
+ 'findDefinition',
99
+ 'findReferences',
100
+ 'findCallers',
101
+ 'findCallees',
102
+ 'analyzeDependencies',
103
+ 'analyzeComplexity',
104
+ 'analyzeCodebase',
105
+ 'getProjectStats',
106
+ ];
107
+
108
+ module.exports = { CodeIntelProvider, CAPABILITIES };
@@ -32,6 +32,8 @@ class ContextManager {
32
32
  // State file path
33
33
  this.stateDir = path.join(projectRoot, '.aios', 'workflow-state');
34
34
  this.statePath = path.join(this.stateDir, `${workflowId}.json`);
35
+ this.handoffDir = path.join(this.stateDir, 'handoffs');
36
+ this.confidenceDir = path.join(this.stateDir, 'confidence');
35
37
 
36
38
  // In-memory cache
37
39
  this._stateCache = null;
@@ -43,6 +45,8 @@ class ContextManager {
43
45
  */
44
46
  async ensureStateDir() {
45
47
  await fs.ensureDir(this.stateDir);
48
+ await fs.ensureDir(this.handoffDir);
49
+ await fs.ensureDir(this.confidenceDir);
46
50
  }
47
51
 
48
52
  /**
@@ -76,6 +80,7 @@ class ContextManager {
76
80
  phases: {},
77
81
  metadata: {
78
82
  projectRoot: this.projectRoot,
83
+ delivery_confidence: null,
79
84
  },
80
85
  };
81
86
  }
@@ -113,19 +118,25 @@ class ContextManager {
113
118
  * @param {number} phaseNum - Phase number
114
119
  * @param {Object} output - Phase output data
115
120
  */
116
- async savePhaseOutput(phaseNum, output) {
121
+ async savePhaseOutput(phaseNum, output, options = {}) {
117
122
  const state = await this.loadState();
123
+ const completedAt = new Date().toISOString();
124
+ state.currentPhase = phaseNum;
125
+ state.status = 'in_progress';
126
+ const handoff = this._buildHandoffPackage(phaseNum, output, state, options, completedAt);
118
127
 
119
128
  state.phases[phaseNum] = {
120
129
  ...output,
121
- completedAt: new Date().toISOString(),
130
+ completedAt,
131
+ handoff,
122
132
  };
123
-
124
- state.currentPhase = phaseNum;
125
- state.status = 'in_progress';
133
+ state.metadata = state.metadata || {};
134
+ state.metadata.delivery_confidence = this._calculateDeliveryConfidence(state);
126
135
 
127
136
  this._stateCache = state;
128
137
  await this._saveState();
138
+ await this._saveHandoffFile(handoff);
139
+ await this._saveConfidenceFile(state.metadata.delivery_confidence);
129
140
  }
130
141
 
131
142
  /**
@@ -145,10 +156,18 @@ class ContextManager {
145
156
  }
146
157
  }
147
158
 
159
+ const previousHandoffs = {};
160
+ for (const [phaseId, phaseData] of Object.entries(previousPhases)) {
161
+ if (phaseData && phaseData.handoff) {
162
+ previousHandoffs[phaseId] = phaseData.handoff;
163
+ }
164
+ }
165
+
148
166
  return {
149
167
  workflowId: this.workflowId,
150
168
  currentPhase: phaseNum,
151
169
  previousPhases,
170
+ previousHandoffs,
152
171
  metadata: state.metadata,
153
172
  };
154
173
  }
@@ -247,9 +266,318 @@ class ContextManager {
247
266
  currentPhase: state.currentPhase,
248
267
  completedPhases: phases,
249
268
  totalPhases: phases.length,
269
+ deliveryConfidence: state.metadata?.delivery_confidence || null,
250
270
  };
251
271
  }
252
272
 
273
+ /**
274
+ * Get latest delivery confidence score metadata.
275
+ * @returns {Object|null} Confidence payload
276
+ */
277
+ getDeliveryConfidence() {
278
+ return this._stateCache?.metadata?.delivery_confidence || null;
279
+ }
280
+
281
+ /**
282
+ * Build standardized handoff package for inter-agent transfer.
283
+ * @private
284
+ */
285
+ _buildHandoffPackage(phaseNum, output, state, options, completedAt) {
286
+ const handoffTarget = options.handoffTarget || {};
287
+ const decision = this._extractDecisionLog(output);
288
+ const evidence = this._extractEvidenceLinks(output);
289
+ const risks = this._extractOpenRisks(output);
290
+
291
+ return {
292
+ version: '1.0.0',
293
+ workflow_id: this.workflowId,
294
+ generated_at: completedAt,
295
+ from: {
296
+ phase: phaseNum,
297
+ agent: output.agent || null,
298
+ action: output.action || null,
299
+ task: output.task || null,
300
+ },
301
+ to: {
302
+ phase: handoffTarget.phase || null,
303
+ agent: handoffTarget.agent || null,
304
+ },
305
+ context_snapshot: {
306
+ workflow_status: state.status,
307
+ current_phase: state.currentPhase,
308
+ metadata: state.metadata || {},
309
+ },
310
+ decision_log: decision,
311
+ evidence_links: evidence,
312
+ open_risks: risks,
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Persist handoff package as a dedicated artifact.
318
+ * @private
319
+ */
320
+ async _saveHandoffFile(handoff) {
321
+ const phase = handoff?.from?.phase || 'unknown';
322
+ const filePath = path.join(this.handoffDir, `${this.workflowId}-phase-${phase}.handoff.json`);
323
+ await fs.ensureDir(this.handoffDir);
324
+ await fs.writeJson(filePath, handoff, { spaces: 2 });
325
+ }
326
+
327
+ /**
328
+ * Persist confidence score as a dedicated artifact.
329
+ * @private
330
+ */
331
+ async _saveConfidenceFile(confidence) {
332
+ if (!confidence) return;
333
+ const filePath = path.join(this.confidenceDir, `${this.workflowId}.delivery-confidence.json`);
334
+ await fs.ensureDir(this.confidenceDir);
335
+ await fs.writeJson(filePath, confidence, { spaces: 2 });
336
+ }
337
+
338
+ /**
339
+ * @private
340
+ */
341
+ _extractDecisionLog(output = {}) {
342
+ const result = output.result || {};
343
+ const entries = Array.isArray(result.decisions)
344
+ ? result.decisions
345
+ : Array.isArray(result.decision_log)
346
+ ? result.decision_log
347
+ : [];
348
+ const sourcePaths = [];
349
+
350
+ if (result.decisionLogPath) sourcePaths.push(result.decisionLogPath);
351
+ if (result.decision_log_path) sourcePaths.push(result.decision_log_path);
352
+
353
+ return {
354
+ entries,
355
+ source_paths: sourcePaths,
356
+ count: entries.length,
357
+ };
358
+ }
359
+
360
+ /**
361
+ * @private
362
+ */
363
+ _extractEvidenceLinks(output = {}) {
364
+ const evidence = [];
365
+ const result = output.result || {};
366
+ const validation = output.validation || {};
367
+
368
+ if (Array.isArray(result.evidence_links)) {
369
+ evidence.push(...result.evidence_links);
370
+ }
371
+
372
+ if (Array.isArray(validation.checks)) {
373
+ for (const check of validation.checks) {
374
+ if (check.path) evidence.push(check.path);
375
+ if (check.checklist) evidence.push(check.checklist);
376
+ }
377
+ }
378
+
379
+ return [...new Set(evidence)];
380
+ }
381
+
382
+ /**
383
+ * @private
384
+ */
385
+ _extractOpenRisks(output = {}) {
386
+ const result = output.result || {};
387
+ const risks = [];
388
+
389
+ if (Array.isArray(result.open_risks)) {
390
+ risks.push(...result.open_risks);
391
+ }
392
+ if (Array.isArray(result.risks)) {
393
+ risks.push(...result.risks);
394
+ }
395
+ if (Array.isArray(result.risk_register)) {
396
+ risks.push(...result.risk_register);
397
+ }
398
+
399
+ return risks;
400
+ }
401
+
402
+ /**
403
+ * Compute delivery confidence score from workflow state.
404
+ * @private
405
+ */
406
+ _calculateDeliveryConfidence(state) {
407
+ const phases = Object.values(state.phases || {});
408
+ const weights = {
409
+ test_coverage: 0.25,
410
+ ac_completion: 0.30,
411
+ risk_score_inv: 0.20,
412
+ debt_score_inv: 0.15,
413
+ regression_clear: 0.10,
414
+ };
415
+ const components = {
416
+ test_coverage: this._calculateTestCoverage(phases),
417
+ ac_completion: this._calculateAcCompletion(phases),
418
+ risk_score_inv: this._calculateRiskInverseScore(phases),
419
+ debt_score_inv: this._calculateDebtInverseScore(phases),
420
+ regression_clear: this._calculateRegressionClear(phases),
421
+ };
422
+
423
+ const scoreBase = Object.keys(weights).reduce(
424
+ (acc, key) => acc + (components[key] || 0) * weights[key],
425
+ 0,
426
+ );
427
+ const score = Number((scoreBase * 100).toFixed(2));
428
+ const threshold = this._resolveConfidenceThreshold();
429
+
430
+ return {
431
+ version: '1.0.0',
432
+ calculated_at: new Date().toISOString(),
433
+ score,
434
+ threshold,
435
+ gate_passed: score >= threshold,
436
+ formula: {
437
+ expression:
438
+ 'confidence = (test_coverage*0.25 + ac_completion*0.30 + risk_score_inv*0.20 + debt_score_inv*0.15 + regression_clear*0.10) * 100',
439
+ weights,
440
+ },
441
+ components,
442
+ phase_count: phases.length,
443
+ };
444
+ }
445
+
446
+ /**
447
+ * @private
448
+ */
449
+ _resolveConfidenceThreshold() {
450
+ const raw = process.env.AIOS_DELIVERY_CONFIDENCE_THRESHOLD;
451
+ const parsed = Number(raw);
452
+ return Number.isFinite(parsed) ? parsed : 70;
453
+ }
454
+
455
+ /**
456
+ * @private
457
+ */
458
+ _calculateTestCoverage(phases) {
459
+ let totalChecks = 0;
460
+ let passedChecks = 0;
461
+
462
+ for (const phase of phases) {
463
+ const checks = phase?.validation?.checks;
464
+ if (!Array.isArray(checks)) continue;
465
+ for (const check of checks) {
466
+ totalChecks += 1;
467
+ if (check?.passed === true) passedChecks += 1;
468
+ }
469
+ }
470
+
471
+ if (totalChecks === 0) {
472
+ return phases.length > 0 ? 1 : 0;
473
+ }
474
+
475
+ return passedChecks / totalChecks;
476
+ }
477
+
478
+ /**
479
+ * @private
480
+ */
481
+ _calculateAcCompletion(phases) {
482
+ let total = 0;
483
+ let done = 0;
484
+ let hasExplicitData = false;
485
+
486
+ for (const phase of phases) {
487
+ const result = phase?.result || {};
488
+ if (Number.isFinite(result.ac_total) && Number.isFinite(result.ac_completed)) {
489
+ hasExplicitData = true;
490
+ total += Math.max(0, result.ac_total);
491
+ done += Math.min(Math.max(0, result.ac_completed), Math.max(0, result.ac_total));
492
+ } else if (Array.isArray(result.acceptance_criteria)) {
493
+ hasExplicitData = true;
494
+ total += result.acceptance_criteria.length;
495
+ done += result.acceptance_criteria.filter((item) => item?.done || item?.status === 'done').length;
496
+ }
497
+ }
498
+
499
+ if (hasExplicitData && total > 0) {
500
+ return done / total;
501
+ }
502
+
503
+ if (phases.length === 0) {
504
+ return 0;
505
+ }
506
+
507
+ const successful = phases.filter((phase) => phase?.result?.status !== 'failed').length;
508
+ return successful / phases.length;
509
+ }
510
+
511
+ /**
512
+ * @private
513
+ */
514
+ _calculateRiskInverseScore(phases) {
515
+ const totalRisks = phases.reduce((sum, phase) => {
516
+ const handoffRisks = Array.isArray(phase?.handoff?.open_risks) ? phase.handoff.open_risks.length : 0;
517
+ const resultRisks = this._extractOpenRisks(phase).length;
518
+ return sum + Math.max(handoffRisks, resultRisks);
519
+ }, 0);
520
+
521
+ return Math.max(0, 1 - totalRisks / 10);
522
+ }
523
+
524
+ /**
525
+ * @private
526
+ */
527
+ _calculateDebtInverseScore(phases) {
528
+ const totalDebt = phases.reduce((sum, phase) => {
529
+ const result = phase?.result || {};
530
+ const explicitCount = Number.isFinite(result.technical_debt_count)
531
+ ? result.technical_debt_count
532
+ : Number.isFinite(result.debt_count)
533
+ ? result.debt_count
534
+ : 0;
535
+ const listCount = [
536
+ result.technical_debt,
537
+ result.debt_items,
538
+ result.todos,
539
+ result.hacks,
540
+ ].reduce((listSum, list) => listSum + (Array.isArray(list) ? list.length : 0), 0);
541
+ return sum + explicitCount + listCount;
542
+ }, 0);
543
+
544
+ return Math.max(0, 1 - totalDebt / 10);
545
+ }
546
+
547
+ /**
548
+ * @private
549
+ */
550
+ _calculateRegressionClear(phases) {
551
+ let totalRegressionChecks = 0;
552
+ let passedRegressionChecks = 0;
553
+
554
+ for (const phase of phases) {
555
+ const checks = phase?.validation?.checks;
556
+ if (!Array.isArray(checks)) continue;
557
+
558
+ for (const check of checks) {
559
+ const type = String(check?.type || '').toLowerCase();
560
+ const pathValue = String(check?.path || '').toLowerCase();
561
+ const checklist = String(check?.checklist || '').toLowerCase();
562
+ const isRegression = type.includes('regression')
563
+ || pathValue.includes('regression')
564
+ || checklist.includes('regression');
565
+
566
+ if (!isRegression) continue;
567
+ totalRegressionChecks += 1;
568
+ if (check?.passed === true) {
569
+ passedRegressionChecks += 1;
570
+ }
571
+ }
572
+ }
573
+
574
+ if (totalRegressionChecks === 0) {
575
+ return this._calculateTestCoverage(phases);
576
+ }
577
+
578
+ return passedRegressionChecks / totalRegressionChecks;
579
+ }
580
+
253
581
  /**
254
582
  * Reset workflow state (for re-execution)
255
583
  * @param {boolean} keepMetadata - Whether to preserve metadata