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.
- package/.aios-core/core/code-intel/code-intel-client.js +280 -0
- package/.aios-core/core/code-intel/code-intel-enricher.js +159 -0
- package/.aios-core/core/code-intel/index.js +137 -0
- package/.aios-core/core/code-intel/providers/code-graph-provider.js +201 -0
- package/.aios-core/core/code-intel/providers/provider-interface.js +108 -0
- package/.aios-core/core/orchestration/context-manager.js +333 -5
- package/.aios-core/core/orchestration/dashboard-integration.js +17 -1
- package/.aios-core/core/orchestration/execution-profile-resolver.js +107 -0
- package/.aios-core/core/orchestration/index.js +3 -0
- package/.aios-core/core/orchestration/skill-dispatcher.js +2 -0
- package/.aios-core/core/orchestration/subagent-prompt-builder.js +2 -0
- package/.aios-core/core/orchestration/workflow-orchestrator.js +113 -5
- package/.aios-core/data/entity-registry.yaml +44 -22
- package/.aios-core/development/agents/ux-design-expert.md +1 -1
- package/.aios-core/development/checklists/brownfield-compatibility-checklist.md +114 -0
- package/.aios-core/development/scripts/workflow-state-manager.js +128 -1
- package/.aios-core/development/tasks/next.md +36 -5
- package/.aios-core/hooks/ids-post-commit.js +29 -1
- package/.aios-core/hooks/ids-pre-push.js +29 -1
- package/.aios-core/infrastructure/contracts/compatibility/aios-4.0.4.yaml +44 -0
- package/.aios-core/infrastructure/scripts/validate-parity.js +238 -2
- package/.aios-core/install-manifest.yaml +63 -27
- package/.aios-core/product/templates/brownfield-risk-report-tmpl.yaml +277 -0
- package/.aios-core/workflow-intelligence/engine/suggestion-engine.js +114 -5
- package/LICENSE +13 -1
- package/README.md +39 -9
- package/package.json +8 -6
- package/packages/installer/src/wizard/ide-config-generator.js +0 -117
- package/packages/installer/src/wizard/index.js +2 -118
- package/packages/installer/src/wizard/pro-setup.js +58 -12
- package/pro/license/license-api.js +9 -9
- 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
|
|
130
|
+
completedAt,
|
|
131
|
+
handoff,
|
|
122
132
|
};
|
|
123
|
-
|
|
124
|
-
state.
|
|
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
|