aios-core 4.2.13 → 4.2.15
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/helpers/dev-helper.js +206 -0
- package/.aios-core/core/registry/registry-schema.json +166 -166
- package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
- package/.aios-core/data/entity-registry.yaml +27 -0
- package/.aios-core/development/scripts/approval-workflow.js +642 -642
- package/.aios-core/development/scripts/backup-manager.js +606 -606
- package/.aios-core/development/scripts/branch-manager.js +389 -389
- package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
- package/.aios-core/development/scripts/commit-message-generator.js +849 -849
- package/.aios-core/development/scripts/conflict-resolver.js +674 -674
- package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
- package/.aios-core/development/scripts/diff-generator.js +351 -351
- package/.aios-core/development/scripts/elicitation-engine.js +384 -384
- package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
- package/.aios-core/development/scripts/git-wrapper.js +461 -461
- package/.aios-core/development/scripts/manifest-preview.js +244 -244
- package/.aios-core/development/scripts/metrics-tracker.js +775 -775
- package/.aios-core/development/scripts/modification-validator.js +554 -554
- package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
- package/.aios-core/development/scripts/performance-analyzer.js +757 -757
- package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
- package/.aios-core/development/scripts/rollback-handler.js +530 -530
- package/.aios-core/development/scripts/security-checker.js +358 -358
- package/.aios-core/development/scripts/template-engine.js +239 -239
- package/.aios-core/development/scripts/template-validator.js +278 -278
- package/.aios-core/development/scripts/test-generator.js +843 -843
- package/.aios-core/development/scripts/transaction-manager.js +589 -589
- package/.aios-core/development/scripts/usage-tracker.js +673 -673
- package/.aios-core/development/scripts/validate-filenames.js +226 -226
- package/.aios-core/development/scripts/version-tracker.js +526 -526
- package/.aios-core/development/scripts/yaml-validator.js +396 -396
- package/.aios-core/development/tasks/build-autonomous.md +10 -4
- package/.aios-core/development/tasks/create-service.md +23 -0
- package/.aios-core/development/tasks/dev-develop-story.md +12 -6
- package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
- package/.aios-core/development/tasks/publish-npm.md +3 -3
- package/.aios-core/hooks/unified/README.md +1 -1
- package/.aios-core/install-manifest.yaml +65 -61
- package/.aios-core/manifests/schema/manifest-schema.json +190 -190
- package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
- package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
- package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
- package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
- package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
- package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
- package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
- package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
- package/.aios-core/product/templates/eslintrc-security.json +32 -32
- package/.aios-core/product/templates/github-actions-cd.yml +212 -212
- package/.aios-core/product/templates/github-actions-ci.yml +172 -172
- package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
- package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
- package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
- package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
- package/README.en.md +747 -0
- package/README.md +4 -2
- package/bin/aios.js +7 -4
- package/package.json +1 -1
- package/packages/aios-pro-cli/src/recover.js +1 -1
- package/packages/installer/src/wizard/ide-config-generator.js +6 -6
- package/packages/installer/src/wizard/pro-setup.js +3 -3
- package/pro/license/degradation.js +220 -220
- package/pro/license/errors.js +450 -450
- package/pro/license/feature-gate.js +354 -354
- package/pro/license/index.js +181 -181
- package/pro/license/license-cache.js +523 -523
- package/pro/license/license-crypto.js +303 -303
- package/scripts/package-synapse.js +5 -5
- package/scripts/validate-package-completeness.js +3 -3
- package/.aios-core/.session/current-session.json +0 -14
- package/.aios-core/data/registry-update-log.jsonl +0 -191
- package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
- package/.aios-core/docs/component-creation-guide.md +0 -458
- package/.aios-core/docs/session-update-pattern.md +0 -307
- package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
- package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
- package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
- package/.aios-core/docs/template-syntax.md +0 -267
- package/.aios-core/docs/troubleshooting-guide.md +0 -625
- package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
- package/.aios-core/manifests/agents.csv +0 -29
- package/.aios-core/manifests/tasks.csv +0 -198
- package/.aios-core/manifests/workers.csv +0 -204
- package/.claude/rules/agent-authority.md +0 -105
- package/.claude/rules/coderabbit-integration.md +0 -93
- package/.claude/rules/ids-principles.md +0 -112
- package/.claude/rules/story-lifecycle.md +0 -139
- package/.claude/rules/workflow-execution.md +0 -150
- package/scripts/glue/README.md +0 -355
- package/scripts/glue/compose-agent-prompt.cjs +0 -362
- /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
- /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
|
@@ -1,643 +1,643 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const inquirer = require('inquirer');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Approval workflow for AIOS-FULLSTACK framework
|
|
8
|
-
* Manages approval process for high-impact modifications
|
|
9
|
-
*/
|
|
10
|
-
class ApprovalWorkflow {
|
|
11
|
-
constructor(options = {}) {
|
|
12
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
13
|
-
this.approvalThresholds = {
|
|
14
|
-
low: { auto_approve: true, requires_review: false },
|
|
15
|
-
medium: { auto_approve: false, requires_review: true },
|
|
16
|
-
high: { auto_approve: false, requires_review: true, requires_approval: true },
|
|
17
|
-
critical: { auto_approve: false, requires_review: true, requires_approval: true, requires_multiple_approvers: true }
|
|
18
|
-
};
|
|
19
|
-
this.approvalHistory = [];
|
|
20
|
-
this.pendingApprovals = new Map();
|
|
21
|
-
this.approvalRules = new Map();
|
|
22
|
-
this.initializeApprovalRules();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Initialize default approval rules
|
|
27
|
-
*/
|
|
28
|
-
initializeApprovalRules() {
|
|
29
|
-
// Component type rules
|
|
30
|
-
this.approvalRules.set('agent_modification', {
|
|
31
|
-
risk_threshold: 'medium',
|
|
32
|
-
required_approvers: 1,
|
|
33
|
-
timeout_hours: 24,
|
|
34
|
-
auto_approve_conditions: ['low_risk', 'has_tests', 'non_breaking']
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
this.approvalRules.set('workflow_modification', {
|
|
38
|
-
risk_threshold: 'medium',
|
|
39
|
-
required_approvers: 1,
|
|
40
|
-
timeout_hours: 48,
|
|
41
|
-
auto_approve_conditions: ['low_risk', 'has_tests']
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
this.approvalRules.set('core_util_modification', {
|
|
45
|
-
risk_threshold: 'low',
|
|
46
|
-
required_approvers: 2,
|
|
47
|
-
timeout_hours: 72,
|
|
48
|
-
auto_approve_conditions: ['minimal_risk', 'comprehensive_tests']
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// Modification type rules
|
|
52
|
-
this.approvalRules.set('component_removal', {
|
|
53
|
-
risk_threshold: 'low',
|
|
54
|
-
required_approvers: 2,
|
|
55
|
-
timeout_hours: 168, // 1 week
|
|
56
|
-
auto_approve_conditions: [] // Never auto-approve removals
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.approvalRules.set('breaking_change', {
|
|
60
|
-
risk_threshold: 'low',
|
|
61
|
-
required_approvers: 2,
|
|
62
|
-
timeout_hours: 96,
|
|
63
|
-
auto_approve_conditions: []
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Process approval request for impact report
|
|
69
|
-
*/
|
|
70
|
-
async processApprovalRequest(impactReport, options = {}) {
|
|
71
|
-
const requestId = `approval-${Date.now()}`;
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
console.log(chalk.blue(`🔍 Processing approval request for: ${impactReport.targetComponent.path}`));
|
|
75
|
-
|
|
76
|
-
const _config = {
|
|
77
|
-
skip_approval: options.skip_approval || false,
|
|
78
|
-
auto_approve_low_risk: options.auto_approve_low_risk !== false,
|
|
79
|
-
timeout_hours: options.timeout_hours || 24,
|
|
80
|
-
required_approvers: options.required_approvers,
|
|
81
|
-
...options
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
// Determine approval requirements
|
|
85
|
-
const approvalRequirements = await this.determineApprovalRequirements(impactReport, config);
|
|
86
|
-
|
|
87
|
-
// Check if modification can be auto-approved
|
|
88
|
-
const autoApprovalResult = await this.checkAutoApproval(impactReport, approvalRequirements);
|
|
89
|
-
|
|
90
|
-
if (autoApprovalResult.can_auto_approve) {
|
|
91
|
-
const approvalResult = await this.executeAutoApproval(impactReport, autoApprovalResult, requestId);
|
|
92
|
-
return approvalResult;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Manual approval required
|
|
96
|
-
const approvalResult = await this.executeManualApproval(
|
|
97
|
-
impactReport,
|
|
98
|
-
approvalRequirements,
|
|
99
|
-
config,
|
|
100
|
-
requestId
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
return approvalResult;
|
|
104
|
-
|
|
105
|
-
} catch (_error) {
|
|
106
|
-
console.error(chalk.red(`Approval process failed: ${error.message}`));
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Determine approval requirements based on impact analysis
|
|
113
|
-
*/
|
|
114
|
-
async determineApprovalRequirements(impactReport, config) {
|
|
115
|
-
const requirements = {
|
|
116
|
-
approval_needed: false,
|
|
117
|
-
risk_level: impactReport.riskAssessment.overallRisk,
|
|
118
|
-
risk_score: impactReport.riskAssessment.riskScore,
|
|
119
|
-
required_approvers: 1,
|
|
120
|
-
timeout_hours: 24,
|
|
121
|
-
review_criteria: [],
|
|
122
|
-
blocking_issues: []
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const riskLevel = impactReport.riskAssessment.overallRisk;
|
|
126
|
-
const thresholds = this.approvalThresholds[riskLevel];
|
|
127
|
-
|
|
128
|
-
// Basic approval requirements from risk level
|
|
129
|
-
if (thresholds) {
|
|
130
|
-
requirements.approval_needed = !thresholds.auto_approve;
|
|
131
|
-
requirements.requires_review = thresholds.requires_review;
|
|
132
|
-
requirements.requires_approval = thresholds.requires_approval;
|
|
133
|
-
requirements.requires_multiple_approvers = thresholds.requires_multiple_approvers;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Component-specific rules
|
|
137
|
-
const componentRule = this.getComponentApprovalRule(impactReport.targetComponent);
|
|
138
|
-
if (componentRule) {
|
|
139
|
-
requirements.required_approvers = Math.max(requirements.required_approvers, componentRule.required_approvers);
|
|
140
|
-
requirements.timeout_hours = Math.max(requirements.timeout_hours, componentRule.timeout_hours);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Modification-specific rules
|
|
144
|
-
const modificationRule = this.getModificationApprovalRule(impactReport.modificationType);
|
|
145
|
-
if (modificationRule) {
|
|
146
|
-
requirements.required_approvers = Math.max(requirements.required_approvers, modificationRule.required_approvers);
|
|
147
|
-
requirements.timeout_hours = Math.max(requirements.timeout_hours, modificationRule.timeout_hours);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Critical issues that block auto-approval
|
|
151
|
-
if (impactReport.riskAssessment.criticalIssues.length > 0) {
|
|
152
|
-
requirements.approval_needed = true;
|
|
153
|
-
requirements.blocking_issues = impactReport.riskAssessment.criticalIssues.map(issue => issue.description);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// High-impact propagation
|
|
157
|
-
if (impactReport.propagationAnalysis.criticalPaths?.length > 2) {
|
|
158
|
-
requirements.approval_needed = true;
|
|
159
|
-
requirements.review_criteria.push('Multiple critical propagation paths require review');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Many affected components
|
|
163
|
-
if (impactReport.summary.affectedComponents > 20) {
|
|
164
|
-
requirements.approval_needed = true;
|
|
165
|
-
requirements.review_criteria.push('Large number of affected components requires careful review');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Breaking changes
|
|
169
|
-
const hasBreakingChanges = impactReport.propagationAnalysis.directEffects?.some(
|
|
170
|
-
effect => effect.changeType?.severity === 'breaking'
|
|
171
|
-
) || false;
|
|
172
|
-
|
|
173
|
-
if (hasBreakingChanges) {
|
|
174
|
-
requirements.approval_needed = true;
|
|
175
|
-
requirements.required_approvers = Math.max(requirements.required_approvers, 2);
|
|
176
|
-
requirements.review_criteria.push('Breaking changes require multiple approvers');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Security-sensitive modifications
|
|
180
|
-
if (impactReport.riskAssessment.riskDimensions?.security_risk?.score >= 6) {
|
|
181
|
-
requirements.approval_needed = true;
|
|
182
|
-
requirements.review_criteria.push('Security-sensitive modification requires approval');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return requirements;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Check if modification can be auto-approved
|
|
190
|
-
*/
|
|
191
|
-
async checkAutoApproval(impactReport, requirements) {
|
|
192
|
-
const result = {
|
|
193
|
-
can_auto_approve: false,
|
|
194
|
-
reasons: [],
|
|
195
|
-
conditions_met: [],
|
|
196
|
-
conditions_failed: []
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Never auto-approve if manual approval is explicitly needed
|
|
200
|
-
if (requirements.approval_needed) {
|
|
201
|
-
result.reasons.push('Manual approval explicitly required due to risk level or critical issues');
|
|
202
|
-
return result;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Never auto-approve critical risk modifications
|
|
206
|
-
if (impactReport.riskAssessment.overallRisk === 'critical') {
|
|
207
|
-
result.reasons.push('Critical risk level requires manual approval');
|
|
208
|
-
return result;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Never auto-approve component removals
|
|
212
|
-
if (impactReport.modificationType === 'remove') {
|
|
213
|
-
result.reasons.push('Component removal always requires manual approval');
|
|
214
|
-
return result;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Check auto-approval conditions
|
|
218
|
-
const autoApprovalConditions = await this.evaluateAutoApprovalConditions(impactReport);
|
|
219
|
-
|
|
220
|
-
if (autoApprovalConditions.all_conditions_met) {
|
|
221
|
-
result.can_auto_approve = true;
|
|
222
|
-
result.conditions_met = autoApprovalConditions.met_conditions;
|
|
223
|
-
result.reasons.push('All auto-approval conditions satisfied');
|
|
224
|
-
} else {
|
|
225
|
-
result.conditions_failed = autoApprovalConditions.failed_conditions;
|
|
226
|
-
result.reasons.push('Auto-approval conditions not met');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return result;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Evaluate auto-approval conditions
|
|
234
|
-
*/
|
|
235
|
-
async evaluateAutoApprovalConditions(impactReport) {
|
|
236
|
-
const conditions = {
|
|
237
|
-
low_risk: impactReport.riskAssessment.overallRisk === 'low',
|
|
238
|
-
minimal_impact: impactReport.summary.affectedComponents <= 5,
|
|
239
|
-
no_critical_issues: impactReport.riskAssessment.criticalIssues.length === 0,
|
|
240
|
-
no_breaking_changes: !this.hasBreakingChanges(impactReport),
|
|
241
|
-
has_tests: await this.componentHasTests(impactReport.targetComponent),
|
|
242
|
-
small_change: this.isSmallChange(impactReport),
|
|
243
|
-
no_security_risk: impactReport.riskAssessment.riskDimensions?.security_risk?.score < 5
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const metConditions = Object.entries(conditions)
|
|
247
|
-
.filter(([condition, met]) => met)
|
|
248
|
-
.map(([condition]) => condition);
|
|
249
|
-
|
|
250
|
-
const failedConditions = Object.entries(conditions)
|
|
251
|
-
.filter(([condition, met]) => !met)
|
|
252
|
-
.map(([condition]) => condition);
|
|
253
|
-
|
|
254
|
-
// Require at least 5 out of 7 conditions for auto-approval
|
|
255
|
-
const requiredConditions = 5;
|
|
256
|
-
const allConditionsMet = metConditions.length >= requiredConditions;
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
all_conditions_met: allConditionsMet,
|
|
260
|
-
met_conditions: metConditions,
|
|
261
|
-
failed_conditions: failedConditions,
|
|
262
|
-
condition_score: `${metConditions.length}/${Object.keys(conditions).length}`
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Execute auto-approval
|
|
268
|
-
*/
|
|
269
|
-
async executeAutoApproval(impactReport, autoApprovalResult, requestId) {
|
|
270
|
-
const approval = {
|
|
271
|
-
request_id: requestId,
|
|
272
|
-
target_component: impactReport.targetComponent.path,
|
|
273
|
-
modification_type: impactReport.modificationType,
|
|
274
|
-
approval_status: 'auto_approved',
|
|
275
|
-
approval_type: 'automatic',
|
|
276
|
-
risk_level: impactReport.riskAssessment.overallRisk,
|
|
277
|
-
auto_approval_reasons: autoApprovalResult.reasons,
|
|
278
|
-
conditions_met: autoApprovalResult.conditions_met,
|
|
279
|
-
approved_by: 'system_auto_approval',
|
|
280
|
-
approved_at: new Date().toISOString(),
|
|
281
|
-
valid_until: this.calculateExpirationTime(24), // Auto-approvals valid for 24 hours
|
|
282
|
-
metadata: {
|
|
283
|
-
impact_summary: impactReport.summary,
|
|
284
|
-
approval_confidence: this.calculateApprovalConfidence(autoApprovalResult)
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
// Log approval
|
|
289
|
-
await this.logApproval(approval);
|
|
290
|
-
|
|
291
|
-
console.log(chalk.green(`✅ Auto-approved: ${impactReport.targetComponent.path}`));
|
|
292
|
-
console.log(chalk.gray(` Risk level: ${impactReport.riskAssessment.overallRisk}`));
|
|
293
|
-
console.log(chalk.gray(` Conditions met: ${autoApprovalResult.conditions_met.length}`));
|
|
294
|
-
|
|
295
|
-
return approval;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Execute manual approval process
|
|
300
|
-
*/
|
|
301
|
-
async executeManualApproval(impactReport, requirements, config, requestId) {
|
|
302
|
-
console.log(chalk.yellow(`\n⚠️ MANUAL APPROVAL REQUIRED`));
|
|
303
|
-
console.log(chalk.gray(`Component: ${impactReport.targetComponent.path}`));
|
|
304
|
-
console.log(chalk.gray(`Risk Level: ${impactReport.riskAssessment.overallRisk.toUpperCase()}`));
|
|
305
|
-
console.log(chalk.gray(`Affected Components: ${impactReport.summary.affectedComponents}`));
|
|
306
|
-
|
|
307
|
-
if (requirements.blocking_issues.length > 0) {
|
|
308
|
-
console.log(chalk.red(`\nBlocking Issues:`));
|
|
309
|
-
requirements.blocking_issues.forEach((issue, index) => {
|
|
310
|
-
console.log(chalk.red(` ${index + 1}. ${issue}`));
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (requirements.review_criteria.length > 0) {
|
|
315
|
-
console.log(chalk.yellow(`\nReview Criteria:`));
|
|
316
|
-
requirements.review_criteria.forEach((criteria, index) => {
|
|
317
|
-
console.log(chalk.yellow(` ${index + 1}. ${criteria}`));
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Display key recommendations
|
|
322
|
-
if (impactReport.riskAssessment.recommendations.length > 0) {
|
|
323
|
-
console.log(chalk.blue(`\nKey Recommendations:`));
|
|
324
|
-
impactReport.riskAssessment.recommendations.slice(0, 3).forEach((rec, index) => {
|
|
325
|
-
console.log(chalk.blue(` ${index + 1}. ${rec.title}`));
|
|
326
|
-
console.log(chalk.gray(` ${rec.description}`));
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Approval prompt
|
|
331
|
-
const approvalQuestions = await this.buildApprovalQuestions(impactReport, requirements);
|
|
332
|
-
const approvalAnswers = await inquirer.prompt(approvalQuestions);
|
|
333
|
-
|
|
334
|
-
const approval = {
|
|
335
|
-
request_id: requestId,
|
|
336
|
-
target_component: impactReport.targetComponent.path,
|
|
337
|
-
modification_type: impactReport.modificationType,
|
|
338
|
-
approval_status: approvalAnswers.approved ? 'approved' : 'rejected',
|
|
339
|
-
approval_type: 'manual',
|
|
340
|
-
risk_level: impactReport.riskAssessment.overallRisk,
|
|
341
|
-
approved_by: approvalAnswers.approver_name || 'user',
|
|
342
|
-
approved_at: new Date().toISOString(),
|
|
343
|
-
approval_reason: approvalAnswers.approval_reason,
|
|
344
|
-
conditions_acknowledged: approvalAnswers.conditions_acknowledged || false,
|
|
345
|
-
valid_until: this.calculateExpirationTime(requirements.timeout_hours),
|
|
346
|
-
requirements_met: requirements,
|
|
347
|
-
metadata: {
|
|
348
|
-
impact_summary: impactReport.summary,
|
|
349
|
-
approval_answers: approvalAnswers
|
|
350
|
-
}
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// Add approval conditions if approved
|
|
354
|
-
if (approvalAnswers.approved) {
|
|
355
|
-
approval.approval_conditions = approvalAnswers.approval_conditions;
|
|
356
|
-
approval.monitoring_required = approvalAnswers.monitoring_required || false;
|
|
357
|
-
approval.rollback_plan_required = approvalAnswers.rollback_plan || false;
|
|
358
|
-
} else {
|
|
359
|
-
approval.rejection_reason = approvalAnswers.rejection_reason;
|
|
360
|
-
approval.recommended_actions = approvalAnswers.recommended_actions;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Log approval decision
|
|
364
|
-
await this.logApproval(approval);
|
|
365
|
-
|
|
366
|
-
if (approvalAnswers.approved) {
|
|
367
|
-
console.log(chalk.green(`\n✅ Manual approval granted`));
|
|
368
|
-
console.log(chalk.gray(` Approved by: ${approval.approved_by}`));
|
|
369
|
-
console.log(chalk.gray(` Valid until: ${new Date(approval.valid_until).toLocaleString()}`));
|
|
370
|
-
|
|
371
|
-
if (approval.approval_conditions) {
|
|
372
|
-
console.log(chalk.blue(` Conditions: ${approval.approval_conditions}`));
|
|
373
|
-
}
|
|
374
|
-
} else {
|
|
375
|
-
console.log(chalk.red(`\n❌ Approval rejected`));
|
|
376
|
-
console.log(chalk.gray(` Reason: ${approval.rejection_reason}`));
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
return approval;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Build approval question flow
|
|
384
|
-
*/
|
|
385
|
-
async buildApprovalQuestions(impactReport, requirements) {
|
|
386
|
-
const questions = [];
|
|
387
|
-
|
|
388
|
-
// Main approval question
|
|
389
|
-
questions.push({
|
|
390
|
-
type: 'confirm',
|
|
391
|
-
name: 'approved',
|
|
392
|
-
message: `Approve ${impactReport.modificationType} of ${impactReport.targetComponent.path}?`,
|
|
393
|
-
default: false
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
// Conditional questions based on approval
|
|
397
|
-
questions.push({
|
|
398
|
-
type: 'input',
|
|
399
|
-
name: 'approver_name',
|
|
400
|
-
message: 'Enter your name/identifier:',
|
|
401
|
-
when: (answers) => answers.approved,
|
|
402
|
-
validate: (input) => input.length > 0 || 'Name is required'
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
questions.push({
|
|
406
|
-
type: 'input',
|
|
407
|
-
name: 'approval_reason',
|
|
408
|
-
message: 'Reason for approval:',
|
|
409
|
-
when: (answers) => answers.approved,
|
|
410
|
-
default: 'Impact analysis reviewed and acceptable'
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
// High-risk additional questions
|
|
414
|
-
if (requirements.risk_level === 'high' || requirements.risk_level === 'critical') {
|
|
415
|
-
questions.push({
|
|
416
|
-
type: 'confirm',
|
|
417
|
-
name: 'conditions_acknowledged',
|
|
418
|
-
message: 'Do you acknowledge all risk factors and recommendations?',
|
|
419
|
-
when: (answers) => answers.approved,
|
|
420
|
-
default: false
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
questions.push({
|
|
424
|
-
type: 'input',
|
|
425
|
-
name: 'approval_conditions',
|
|
426
|
-
message: 'Enter any approval conditions or requirements:',
|
|
427
|
-
when: (answers) => answers.approved && answers.conditions_acknowledged,
|
|
428
|
-
default: 'Standard monitoring and rollback procedures apply'
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
questions.push({
|
|
432
|
-
type: 'confirm',
|
|
433
|
-
name: 'monitoring_required',
|
|
434
|
-
message: 'Require enhanced monitoring after deployment?',
|
|
435
|
-
when: (answers) => answers.approved,
|
|
436
|
-
default: true
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
questions.push({
|
|
440
|
-
type: 'confirm',
|
|
441
|
-
name: 'rollback_plan',
|
|
442
|
-
message: 'Require documented rollback plan?',
|
|
443
|
-
when: (answers) => answers.approved,
|
|
444
|
-
default: true
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Rejection questions
|
|
449
|
-
questions.push({
|
|
450
|
-
type: 'input',
|
|
451
|
-
name: 'rejection_reason',
|
|
452
|
-
message: 'Reason for rejection:',
|
|
453
|
-
when: (answers) => !answers.approved,
|
|
454
|
-
validate: (input) => input.length > 0 || 'Rejection reason is required'
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
questions.push({
|
|
458
|
-
type: 'input',
|
|
459
|
-
name: 'recommended_actions',
|
|
460
|
-
message: 'Recommended actions before resubmission:',
|
|
461
|
-
when: (answers) => !answers.approved,
|
|
462
|
-
default: 'Address critical issues and reduce risk factors'
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
return questions;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Log approval decision for audit trail
|
|
470
|
-
*/
|
|
471
|
-
async logApproval(approval) {
|
|
472
|
-
// Add to approval history
|
|
473
|
-
this.approvalHistory.push({
|
|
474
|
-
request_id: approval.request_id,
|
|
475
|
-
component: approval.target_component,
|
|
476
|
-
status: approval.approval_status,
|
|
477
|
-
risk_level: approval.risk_level,
|
|
478
|
-
approved_by: approval.approved_by,
|
|
479
|
-
timestamp: approval.approved_at
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
// Write to audit log file
|
|
483
|
-
try {
|
|
484
|
-
const logDir = path.join(this.rootPath, '.aios', 'audit');
|
|
485
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
486
|
-
|
|
487
|
-
const logFile = path.join(logDir, 'approval_log.jsonl');
|
|
488
|
-
const logEntry = JSON.stringify(approval) + '\n';
|
|
489
|
-
|
|
490
|
-
await fs.appendFile(logFile, logEntry);
|
|
491
|
-
|
|
492
|
-
console.log(chalk.gray(` Approval logged to audit trail`));
|
|
493
|
-
|
|
494
|
-
} catch (_error) {
|
|
495
|
-
console.warn(chalk.yellow(`Failed to write approval log: ${error.message}`));
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Helper methods
|
|
500
|
-
|
|
501
|
-
getComponentApprovalRule(component) {
|
|
502
|
-
if (component.type === 'agent') {
|
|
503
|
-
return this.approvalRules.get('agent_modification');
|
|
504
|
-
} else if (component.type === 'workflow') {
|
|
505
|
-
return this.approvalRules.get('workflow_modification');
|
|
506
|
-
} else if (component.type === 'util' && component.path.includes('core')) {
|
|
507
|
-
return this.approvalRules.get('core_util_modification');
|
|
508
|
-
}
|
|
509
|
-
return null;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
getModificationApprovalRule(modificationType) {
|
|
513
|
-
if (modificationType === 'remove') {
|
|
514
|
-
return this.approvalRules.get('component_removal');
|
|
515
|
-
}
|
|
516
|
-
return null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
hasBreakingChanges(impactReport) {
|
|
520
|
-
return impactReport.propagationAnalysis.directEffects?.some(
|
|
521
|
-
effect => effect.changeType?.severity === 'breaking'
|
|
522
|
-
) || false;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async componentHasTests(component) {
|
|
526
|
-
const testPaths = [
|
|
527
|
-
path.join(this.rootPath, 'tests', 'unit', component.type, `${component.name}.test.js`),
|
|
528
|
-
path.join(this.rootPath, 'tests', 'integration', component.type, `${component.name}.integration.test.js`),
|
|
529
|
-
path.join(this.rootPath, 'test', `${component.name}.test.js`)
|
|
530
|
-
];
|
|
531
|
-
|
|
532
|
-
for (const testPath of testPaths) {
|
|
533
|
-
try {
|
|
534
|
-
await fs.access(testPath);
|
|
535
|
-
return true;
|
|
536
|
-
} catch (_error) {
|
|
537
|
-
// File doesn't exist, continue
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
isSmallChange(impactReport) {
|
|
545
|
-
return impactReport.summary.affectedComponents <= 3 &&
|
|
546
|
-
impactReport.summary.propagationDepth <= 2;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
calculateExpirationTime(hours) {
|
|
550
|
-
const now = new Date();
|
|
551
|
-
now.setHours(now.getHours() + hours);
|
|
552
|
-
return now.toISOString();
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
calculateApprovalConfidence(autoApprovalResult) {
|
|
556
|
-
const conditionsMet = autoApprovalResult.conditions_met.length;
|
|
557
|
-
const totalConditions = 7; // Based on evaluateAutoApprovalConditions
|
|
558
|
-
return Math.round((conditionsMet / totalConditions) * 100);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* Check if approval is still valid
|
|
563
|
-
*/
|
|
564
|
-
isApprovalValid(approval) {
|
|
565
|
-
const now = new Date();
|
|
566
|
-
const validUntil = new Date(approval.valid_until);
|
|
567
|
-
return now < validUntil;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
/**
|
|
571
|
-
* Get approval history
|
|
572
|
-
*/
|
|
573
|
-
getApprovalHistory(options = {}) {
|
|
574
|
-
const history = {
|
|
575
|
-
total_approvals: this.approvalHistory.length,
|
|
576
|
-
approval_stats: this.calculateApprovalStats(),
|
|
577
|
-
recent_approvals: this.approvalHistory.slice(-10)
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
if (options.component) {
|
|
581
|
-
history.component_approvals = this.approvalHistory.filter(
|
|
582
|
-
approval => approval.component === options.component
|
|
583
|
-
);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
if (options.risk_level) {
|
|
587
|
-
history.risk_level_approvals = this.approvalHistory.filter(
|
|
588
|
-
approval => approval.risk_level === options.risk_level
|
|
589
|
-
);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
return history;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
calculateApprovalStats() {
|
|
596
|
-
const stats = {
|
|
597
|
-
approved: 0,
|
|
598
|
-
rejected: 0,
|
|
599
|
-
auto_approved: 0,
|
|
600
|
-
by_risk_level: { low: 0, medium: 0, high: 0, critical: 0 }
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
this.approvalHistory.forEach(approval => {
|
|
604
|
-
if (approval.status === 'approved') stats.approved++;
|
|
605
|
-
else if (approval.status === 'rejected') stats.rejected++;
|
|
606
|
-
else if (approval.status === 'auto_approved') stats.auto_approved++;
|
|
607
|
-
|
|
608
|
-
stats.by_risk_level[approval.risk_level]++;
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
return stats;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
/**
|
|
615
|
-
* Get pending approvals
|
|
616
|
-
*/
|
|
617
|
-
getPendingApprovals() {
|
|
618
|
-
return Array.from(this.pendingApprovals.values());
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* Clear expired approvals
|
|
623
|
-
*/
|
|
624
|
-
clearExpiredApprovals() {
|
|
625
|
-
const now = new Date();
|
|
626
|
-
let clearedCount = 0;
|
|
627
|
-
|
|
628
|
-
for (const [requestId, approval] of this.pendingApprovals) {
|
|
629
|
-
if (new Date(approval.valid_until) < now) {
|
|
630
|
-
this.pendingApprovals.delete(requestId);
|
|
631
|
-
clearedCount++;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (clearedCount > 0) {
|
|
636
|
-
console.log(chalk.gray(`Cleared ${clearedCount} expired approvals`));
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
return clearedCount;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const inquirer = require('inquirer');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Approval workflow for AIOS-FULLSTACK framework
|
|
8
|
+
* Manages approval process for high-impact modifications
|
|
9
|
+
*/
|
|
10
|
+
class ApprovalWorkflow {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
13
|
+
this.approvalThresholds = {
|
|
14
|
+
low: { auto_approve: true, requires_review: false },
|
|
15
|
+
medium: { auto_approve: false, requires_review: true },
|
|
16
|
+
high: { auto_approve: false, requires_review: true, requires_approval: true },
|
|
17
|
+
critical: { auto_approve: false, requires_review: true, requires_approval: true, requires_multiple_approvers: true }
|
|
18
|
+
};
|
|
19
|
+
this.approvalHistory = [];
|
|
20
|
+
this.pendingApprovals = new Map();
|
|
21
|
+
this.approvalRules = new Map();
|
|
22
|
+
this.initializeApprovalRules();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize default approval rules
|
|
27
|
+
*/
|
|
28
|
+
initializeApprovalRules() {
|
|
29
|
+
// Component type rules
|
|
30
|
+
this.approvalRules.set('agent_modification', {
|
|
31
|
+
risk_threshold: 'medium',
|
|
32
|
+
required_approvers: 1,
|
|
33
|
+
timeout_hours: 24,
|
|
34
|
+
auto_approve_conditions: ['low_risk', 'has_tests', 'non_breaking']
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.approvalRules.set('workflow_modification', {
|
|
38
|
+
risk_threshold: 'medium',
|
|
39
|
+
required_approvers: 1,
|
|
40
|
+
timeout_hours: 48,
|
|
41
|
+
auto_approve_conditions: ['low_risk', 'has_tests']
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.approvalRules.set('core_util_modification', {
|
|
45
|
+
risk_threshold: 'low',
|
|
46
|
+
required_approvers: 2,
|
|
47
|
+
timeout_hours: 72,
|
|
48
|
+
auto_approve_conditions: ['minimal_risk', 'comprehensive_tests']
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Modification type rules
|
|
52
|
+
this.approvalRules.set('component_removal', {
|
|
53
|
+
risk_threshold: 'low',
|
|
54
|
+
required_approvers: 2,
|
|
55
|
+
timeout_hours: 168, // 1 week
|
|
56
|
+
auto_approve_conditions: [] // Never auto-approve removals
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
this.approvalRules.set('breaking_change', {
|
|
60
|
+
risk_threshold: 'low',
|
|
61
|
+
required_approvers: 2,
|
|
62
|
+
timeout_hours: 96,
|
|
63
|
+
auto_approve_conditions: []
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Process approval request for impact report
|
|
69
|
+
*/
|
|
70
|
+
async processApprovalRequest(impactReport, options = {}) {
|
|
71
|
+
const requestId = `approval-${Date.now()}`;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
console.log(chalk.blue(`🔍 Processing approval request for: ${impactReport.targetComponent.path}`));
|
|
75
|
+
|
|
76
|
+
const _config = {
|
|
77
|
+
skip_approval: options.skip_approval || false,
|
|
78
|
+
auto_approve_low_risk: options.auto_approve_low_risk !== false,
|
|
79
|
+
timeout_hours: options.timeout_hours || 24,
|
|
80
|
+
required_approvers: options.required_approvers,
|
|
81
|
+
...options
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Determine approval requirements
|
|
85
|
+
const approvalRequirements = await this.determineApprovalRequirements(impactReport, config);
|
|
86
|
+
|
|
87
|
+
// Check if modification can be auto-approved
|
|
88
|
+
const autoApprovalResult = await this.checkAutoApproval(impactReport, approvalRequirements);
|
|
89
|
+
|
|
90
|
+
if (autoApprovalResult.can_auto_approve) {
|
|
91
|
+
const approvalResult = await this.executeAutoApproval(impactReport, autoApprovalResult, requestId);
|
|
92
|
+
return approvalResult;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Manual approval required
|
|
96
|
+
const approvalResult = await this.executeManualApproval(
|
|
97
|
+
impactReport,
|
|
98
|
+
approvalRequirements,
|
|
99
|
+
config,
|
|
100
|
+
requestId
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return approvalResult;
|
|
104
|
+
|
|
105
|
+
} catch (_error) {
|
|
106
|
+
console.error(chalk.red(`Approval process failed: ${error.message}`));
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Determine approval requirements based on impact analysis
|
|
113
|
+
*/
|
|
114
|
+
async determineApprovalRequirements(impactReport, config) {
|
|
115
|
+
const requirements = {
|
|
116
|
+
approval_needed: false,
|
|
117
|
+
risk_level: impactReport.riskAssessment.overallRisk,
|
|
118
|
+
risk_score: impactReport.riskAssessment.riskScore,
|
|
119
|
+
required_approvers: 1,
|
|
120
|
+
timeout_hours: 24,
|
|
121
|
+
review_criteria: [],
|
|
122
|
+
blocking_issues: []
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const riskLevel = impactReport.riskAssessment.overallRisk;
|
|
126
|
+
const thresholds = this.approvalThresholds[riskLevel];
|
|
127
|
+
|
|
128
|
+
// Basic approval requirements from risk level
|
|
129
|
+
if (thresholds) {
|
|
130
|
+
requirements.approval_needed = !thresholds.auto_approve;
|
|
131
|
+
requirements.requires_review = thresholds.requires_review;
|
|
132
|
+
requirements.requires_approval = thresholds.requires_approval;
|
|
133
|
+
requirements.requires_multiple_approvers = thresholds.requires_multiple_approvers;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Component-specific rules
|
|
137
|
+
const componentRule = this.getComponentApprovalRule(impactReport.targetComponent);
|
|
138
|
+
if (componentRule) {
|
|
139
|
+
requirements.required_approvers = Math.max(requirements.required_approvers, componentRule.required_approvers);
|
|
140
|
+
requirements.timeout_hours = Math.max(requirements.timeout_hours, componentRule.timeout_hours);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Modification-specific rules
|
|
144
|
+
const modificationRule = this.getModificationApprovalRule(impactReport.modificationType);
|
|
145
|
+
if (modificationRule) {
|
|
146
|
+
requirements.required_approvers = Math.max(requirements.required_approvers, modificationRule.required_approvers);
|
|
147
|
+
requirements.timeout_hours = Math.max(requirements.timeout_hours, modificationRule.timeout_hours);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Critical issues that block auto-approval
|
|
151
|
+
if (impactReport.riskAssessment.criticalIssues.length > 0) {
|
|
152
|
+
requirements.approval_needed = true;
|
|
153
|
+
requirements.blocking_issues = impactReport.riskAssessment.criticalIssues.map(issue => issue.description);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// High-impact propagation
|
|
157
|
+
if (impactReport.propagationAnalysis.criticalPaths?.length > 2) {
|
|
158
|
+
requirements.approval_needed = true;
|
|
159
|
+
requirements.review_criteria.push('Multiple critical propagation paths require review');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Many affected components
|
|
163
|
+
if (impactReport.summary.affectedComponents > 20) {
|
|
164
|
+
requirements.approval_needed = true;
|
|
165
|
+
requirements.review_criteria.push('Large number of affected components requires careful review');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Breaking changes
|
|
169
|
+
const hasBreakingChanges = impactReport.propagationAnalysis.directEffects?.some(
|
|
170
|
+
effect => effect.changeType?.severity === 'breaking'
|
|
171
|
+
) || false;
|
|
172
|
+
|
|
173
|
+
if (hasBreakingChanges) {
|
|
174
|
+
requirements.approval_needed = true;
|
|
175
|
+
requirements.required_approvers = Math.max(requirements.required_approvers, 2);
|
|
176
|
+
requirements.review_criteria.push('Breaking changes require multiple approvers');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Security-sensitive modifications
|
|
180
|
+
if (impactReport.riskAssessment.riskDimensions?.security_risk?.score >= 6) {
|
|
181
|
+
requirements.approval_needed = true;
|
|
182
|
+
requirements.review_criteria.push('Security-sensitive modification requires approval');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return requirements;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if modification can be auto-approved
|
|
190
|
+
*/
|
|
191
|
+
async checkAutoApproval(impactReport, requirements) {
|
|
192
|
+
const result = {
|
|
193
|
+
can_auto_approve: false,
|
|
194
|
+
reasons: [],
|
|
195
|
+
conditions_met: [],
|
|
196
|
+
conditions_failed: []
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Never auto-approve if manual approval is explicitly needed
|
|
200
|
+
if (requirements.approval_needed) {
|
|
201
|
+
result.reasons.push('Manual approval explicitly required due to risk level or critical issues');
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Never auto-approve critical risk modifications
|
|
206
|
+
if (impactReport.riskAssessment.overallRisk === 'critical') {
|
|
207
|
+
result.reasons.push('Critical risk level requires manual approval');
|
|
208
|
+
return result;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Never auto-approve component removals
|
|
212
|
+
if (impactReport.modificationType === 'remove') {
|
|
213
|
+
result.reasons.push('Component removal always requires manual approval');
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check auto-approval conditions
|
|
218
|
+
const autoApprovalConditions = await this.evaluateAutoApprovalConditions(impactReport);
|
|
219
|
+
|
|
220
|
+
if (autoApprovalConditions.all_conditions_met) {
|
|
221
|
+
result.can_auto_approve = true;
|
|
222
|
+
result.conditions_met = autoApprovalConditions.met_conditions;
|
|
223
|
+
result.reasons.push('All auto-approval conditions satisfied');
|
|
224
|
+
} else {
|
|
225
|
+
result.conditions_failed = autoApprovalConditions.failed_conditions;
|
|
226
|
+
result.reasons.push('Auto-approval conditions not met');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Evaluate auto-approval conditions
|
|
234
|
+
*/
|
|
235
|
+
async evaluateAutoApprovalConditions(impactReport) {
|
|
236
|
+
const conditions = {
|
|
237
|
+
low_risk: impactReport.riskAssessment.overallRisk === 'low',
|
|
238
|
+
minimal_impact: impactReport.summary.affectedComponents <= 5,
|
|
239
|
+
no_critical_issues: impactReport.riskAssessment.criticalIssues.length === 0,
|
|
240
|
+
no_breaking_changes: !this.hasBreakingChanges(impactReport),
|
|
241
|
+
has_tests: await this.componentHasTests(impactReport.targetComponent),
|
|
242
|
+
small_change: this.isSmallChange(impactReport),
|
|
243
|
+
no_security_risk: impactReport.riskAssessment.riskDimensions?.security_risk?.score < 5
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const metConditions = Object.entries(conditions)
|
|
247
|
+
.filter(([condition, met]) => met)
|
|
248
|
+
.map(([condition]) => condition);
|
|
249
|
+
|
|
250
|
+
const failedConditions = Object.entries(conditions)
|
|
251
|
+
.filter(([condition, met]) => !met)
|
|
252
|
+
.map(([condition]) => condition);
|
|
253
|
+
|
|
254
|
+
// Require at least 5 out of 7 conditions for auto-approval
|
|
255
|
+
const requiredConditions = 5;
|
|
256
|
+
const allConditionsMet = metConditions.length >= requiredConditions;
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
all_conditions_met: allConditionsMet,
|
|
260
|
+
met_conditions: metConditions,
|
|
261
|
+
failed_conditions: failedConditions,
|
|
262
|
+
condition_score: `${metConditions.length}/${Object.keys(conditions).length}`
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Execute auto-approval
|
|
268
|
+
*/
|
|
269
|
+
async executeAutoApproval(impactReport, autoApprovalResult, requestId) {
|
|
270
|
+
const approval = {
|
|
271
|
+
request_id: requestId,
|
|
272
|
+
target_component: impactReport.targetComponent.path,
|
|
273
|
+
modification_type: impactReport.modificationType,
|
|
274
|
+
approval_status: 'auto_approved',
|
|
275
|
+
approval_type: 'automatic',
|
|
276
|
+
risk_level: impactReport.riskAssessment.overallRisk,
|
|
277
|
+
auto_approval_reasons: autoApprovalResult.reasons,
|
|
278
|
+
conditions_met: autoApprovalResult.conditions_met,
|
|
279
|
+
approved_by: 'system_auto_approval',
|
|
280
|
+
approved_at: new Date().toISOString(),
|
|
281
|
+
valid_until: this.calculateExpirationTime(24), // Auto-approvals valid for 24 hours
|
|
282
|
+
metadata: {
|
|
283
|
+
impact_summary: impactReport.summary,
|
|
284
|
+
approval_confidence: this.calculateApprovalConfidence(autoApprovalResult)
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
// Log approval
|
|
289
|
+
await this.logApproval(approval);
|
|
290
|
+
|
|
291
|
+
console.log(chalk.green(`✅ Auto-approved: ${impactReport.targetComponent.path}`));
|
|
292
|
+
console.log(chalk.gray(` Risk level: ${impactReport.riskAssessment.overallRisk}`));
|
|
293
|
+
console.log(chalk.gray(` Conditions met: ${autoApprovalResult.conditions_met.length}`));
|
|
294
|
+
|
|
295
|
+
return approval;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Execute manual approval process
|
|
300
|
+
*/
|
|
301
|
+
async executeManualApproval(impactReport, requirements, config, requestId) {
|
|
302
|
+
console.log(chalk.yellow(`\n⚠️ MANUAL APPROVAL REQUIRED`));
|
|
303
|
+
console.log(chalk.gray(`Component: ${impactReport.targetComponent.path}`));
|
|
304
|
+
console.log(chalk.gray(`Risk Level: ${impactReport.riskAssessment.overallRisk.toUpperCase()}`));
|
|
305
|
+
console.log(chalk.gray(`Affected Components: ${impactReport.summary.affectedComponents}`));
|
|
306
|
+
|
|
307
|
+
if (requirements.blocking_issues.length > 0) {
|
|
308
|
+
console.log(chalk.red(`\nBlocking Issues:`));
|
|
309
|
+
requirements.blocking_issues.forEach((issue, index) => {
|
|
310
|
+
console.log(chalk.red(` ${index + 1}. ${issue}`));
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (requirements.review_criteria.length > 0) {
|
|
315
|
+
console.log(chalk.yellow(`\nReview Criteria:`));
|
|
316
|
+
requirements.review_criteria.forEach((criteria, index) => {
|
|
317
|
+
console.log(chalk.yellow(` ${index + 1}. ${criteria}`));
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Display key recommendations
|
|
322
|
+
if (impactReport.riskAssessment.recommendations.length > 0) {
|
|
323
|
+
console.log(chalk.blue(`\nKey Recommendations:`));
|
|
324
|
+
impactReport.riskAssessment.recommendations.slice(0, 3).forEach((rec, index) => {
|
|
325
|
+
console.log(chalk.blue(` ${index + 1}. ${rec.title}`));
|
|
326
|
+
console.log(chalk.gray(` ${rec.description}`));
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Approval prompt
|
|
331
|
+
const approvalQuestions = await this.buildApprovalQuestions(impactReport, requirements);
|
|
332
|
+
const approvalAnswers = await inquirer.prompt(approvalQuestions);
|
|
333
|
+
|
|
334
|
+
const approval = {
|
|
335
|
+
request_id: requestId,
|
|
336
|
+
target_component: impactReport.targetComponent.path,
|
|
337
|
+
modification_type: impactReport.modificationType,
|
|
338
|
+
approval_status: approvalAnswers.approved ? 'approved' : 'rejected',
|
|
339
|
+
approval_type: 'manual',
|
|
340
|
+
risk_level: impactReport.riskAssessment.overallRisk,
|
|
341
|
+
approved_by: approvalAnswers.approver_name || 'user',
|
|
342
|
+
approved_at: new Date().toISOString(),
|
|
343
|
+
approval_reason: approvalAnswers.approval_reason,
|
|
344
|
+
conditions_acknowledged: approvalAnswers.conditions_acknowledged || false,
|
|
345
|
+
valid_until: this.calculateExpirationTime(requirements.timeout_hours),
|
|
346
|
+
requirements_met: requirements,
|
|
347
|
+
metadata: {
|
|
348
|
+
impact_summary: impactReport.summary,
|
|
349
|
+
approval_answers: approvalAnswers
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Add approval conditions if approved
|
|
354
|
+
if (approvalAnswers.approved) {
|
|
355
|
+
approval.approval_conditions = approvalAnswers.approval_conditions;
|
|
356
|
+
approval.monitoring_required = approvalAnswers.monitoring_required || false;
|
|
357
|
+
approval.rollback_plan_required = approvalAnswers.rollback_plan || false;
|
|
358
|
+
} else {
|
|
359
|
+
approval.rejection_reason = approvalAnswers.rejection_reason;
|
|
360
|
+
approval.recommended_actions = approvalAnswers.recommended_actions;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Log approval decision
|
|
364
|
+
await this.logApproval(approval);
|
|
365
|
+
|
|
366
|
+
if (approvalAnswers.approved) {
|
|
367
|
+
console.log(chalk.green(`\n✅ Manual approval granted`));
|
|
368
|
+
console.log(chalk.gray(` Approved by: ${approval.approved_by}`));
|
|
369
|
+
console.log(chalk.gray(` Valid until: ${new Date(approval.valid_until).toLocaleString()}`));
|
|
370
|
+
|
|
371
|
+
if (approval.approval_conditions) {
|
|
372
|
+
console.log(chalk.blue(` Conditions: ${approval.approval_conditions}`));
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.red(`\n❌ Approval rejected`));
|
|
376
|
+
console.log(chalk.gray(` Reason: ${approval.rejection_reason}`));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return approval;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Build approval question flow
|
|
384
|
+
*/
|
|
385
|
+
async buildApprovalQuestions(impactReport, requirements) {
|
|
386
|
+
const questions = [];
|
|
387
|
+
|
|
388
|
+
// Main approval question
|
|
389
|
+
questions.push({
|
|
390
|
+
type: 'confirm',
|
|
391
|
+
name: 'approved',
|
|
392
|
+
message: `Approve ${impactReport.modificationType} of ${impactReport.targetComponent.path}?`,
|
|
393
|
+
default: false
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// Conditional questions based on approval
|
|
397
|
+
questions.push({
|
|
398
|
+
type: 'input',
|
|
399
|
+
name: 'approver_name',
|
|
400
|
+
message: 'Enter your name/identifier:',
|
|
401
|
+
when: (answers) => answers.approved,
|
|
402
|
+
validate: (input) => input.length > 0 || 'Name is required'
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
questions.push({
|
|
406
|
+
type: 'input',
|
|
407
|
+
name: 'approval_reason',
|
|
408
|
+
message: 'Reason for approval:',
|
|
409
|
+
when: (answers) => answers.approved,
|
|
410
|
+
default: 'Impact analysis reviewed and acceptable'
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
// High-risk additional questions
|
|
414
|
+
if (requirements.risk_level === 'high' || requirements.risk_level === 'critical') {
|
|
415
|
+
questions.push({
|
|
416
|
+
type: 'confirm',
|
|
417
|
+
name: 'conditions_acknowledged',
|
|
418
|
+
message: 'Do you acknowledge all risk factors and recommendations?',
|
|
419
|
+
when: (answers) => answers.approved,
|
|
420
|
+
default: false
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
questions.push({
|
|
424
|
+
type: 'input',
|
|
425
|
+
name: 'approval_conditions',
|
|
426
|
+
message: 'Enter any approval conditions or requirements:',
|
|
427
|
+
when: (answers) => answers.approved && answers.conditions_acknowledged,
|
|
428
|
+
default: 'Standard monitoring and rollback procedures apply'
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
questions.push({
|
|
432
|
+
type: 'confirm',
|
|
433
|
+
name: 'monitoring_required',
|
|
434
|
+
message: 'Require enhanced monitoring after deployment?',
|
|
435
|
+
when: (answers) => answers.approved,
|
|
436
|
+
default: true
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
questions.push({
|
|
440
|
+
type: 'confirm',
|
|
441
|
+
name: 'rollback_plan',
|
|
442
|
+
message: 'Require documented rollback plan?',
|
|
443
|
+
when: (answers) => answers.approved,
|
|
444
|
+
default: true
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Rejection questions
|
|
449
|
+
questions.push({
|
|
450
|
+
type: 'input',
|
|
451
|
+
name: 'rejection_reason',
|
|
452
|
+
message: 'Reason for rejection:',
|
|
453
|
+
when: (answers) => !answers.approved,
|
|
454
|
+
validate: (input) => input.length > 0 || 'Rejection reason is required'
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
questions.push({
|
|
458
|
+
type: 'input',
|
|
459
|
+
name: 'recommended_actions',
|
|
460
|
+
message: 'Recommended actions before resubmission:',
|
|
461
|
+
when: (answers) => !answers.approved,
|
|
462
|
+
default: 'Address critical issues and reduce risk factors'
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return questions;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Log approval decision for audit trail
|
|
470
|
+
*/
|
|
471
|
+
async logApproval(approval) {
|
|
472
|
+
// Add to approval history
|
|
473
|
+
this.approvalHistory.push({
|
|
474
|
+
request_id: approval.request_id,
|
|
475
|
+
component: approval.target_component,
|
|
476
|
+
status: approval.approval_status,
|
|
477
|
+
risk_level: approval.risk_level,
|
|
478
|
+
approved_by: approval.approved_by,
|
|
479
|
+
timestamp: approval.approved_at
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
// Write to audit log file
|
|
483
|
+
try {
|
|
484
|
+
const logDir = path.join(this.rootPath, '.aios', 'audit');
|
|
485
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
486
|
+
|
|
487
|
+
const logFile = path.join(logDir, 'approval_log.jsonl');
|
|
488
|
+
const logEntry = JSON.stringify(approval) + '\n';
|
|
489
|
+
|
|
490
|
+
await fs.appendFile(logFile, logEntry);
|
|
491
|
+
|
|
492
|
+
console.log(chalk.gray(` Approval logged to audit trail`));
|
|
493
|
+
|
|
494
|
+
} catch (_error) {
|
|
495
|
+
console.warn(chalk.yellow(`Failed to write approval log: ${error.message}`));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Helper methods
|
|
500
|
+
|
|
501
|
+
getComponentApprovalRule(component) {
|
|
502
|
+
if (component.type === 'agent') {
|
|
503
|
+
return this.approvalRules.get('agent_modification');
|
|
504
|
+
} else if (component.type === 'workflow') {
|
|
505
|
+
return this.approvalRules.get('workflow_modification');
|
|
506
|
+
} else if (component.type === 'util' && component.path.includes('core')) {
|
|
507
|
+
return this.approvalRules.get('core_util_modification');
|
|
508
|
+
}
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
getModificationApprovalRule(modificationType) {
|
|
513
|
+
if (modificationType === 'remove') {
|
|
514
|
+
return this.approvalRules.get('component_removal');
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
hasBreakingChanges(impactReport) {
|
|
520
|
+
return impactReport.propagationAnalysis.directEffects?.some(
|
|
521
|
+
effect => effect.changeType?.severity === 'breaking'
|
|
522
|
+
) || false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async componentHasTests(component) {
|
|
526
|
+
const testPaths = [
|
|
527
|
+
path.join(this.rootPath, 'tests', 'unit', component.type, `${component.name}.test.js`),
|
|
528
|
+
path.join(this.rootPath, 'tests', 'integration', component.type, `${component.name}.integration.test.js`),
|
|
529
|
+
path.join(this.rootPath, 'test', `${component.name}.test.js`)
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
for (const testPath of testPaths) {
|
|
533
|
+
try {
|
|
534
|
+
await fs.access(testPath);
|
|
535
|
+
return true;
|
|
536
|
+
} catch (_error) {
|
|
537
|
+
// File doesn't exist, continue
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
isSmallChange(impactReport) {
|
|
545
|
+
return impactReport.summary.affectedComponents <= 3 &&
|
|
546
|
+
impactReport.summary.propagationDepth <= 2;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
calculateExpirationTime(hours) {
|
|
550
|
+
const now = new Date();
|
|
551
|
+
now.setHours(now.getHours() + hours);
|
|
552
|
+
return now.toISOString();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
calculateApprovalConfidence(autoApprovalResult) {
|
|
556
|
+
const conditionsMet = autoApprovalResult.conditions_met.length;
|
|
557
|
+
const totalConditions = 7; // Based on evaluateAutoApprovalConditions
|
|
558
|
+
return Math.round((conditionsMet / totalConditions) * 100);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Check if approval is still valid
|
|
563
|
+
*/
|
|
564
|
+
isApprovalValid(approval) {
|
|
565
|
+
const now = new Date();
|
|
566
|
+
const validUntil = new Date(approval.valid_until);
|
|
567
|
+
return now < validUntil;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Get approval history
|
|
572
|
+
*/
|
|
573
|
+
getApprovalHistory(options = {}) {
|
|
574
|
+
const history = {
|
|
575
|
+
total_approvals: this.approvalHistory.length,
|
|
576
|
+
approval_stats: this.calculateApprovalStats(),
|
|
577
|
+
recent_approvals: this.approvalHistory.slice(-10)
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
if (options.component) {
|
|
581
|
+
history.component_approvals = this.approvalHistory.filter(
|
|
582
|
+
approval => approval.component === options.component
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (options.risk_level) {
|
|
587
|
+
history.risk_level_approvals = this.approvalHistory.filter(
|
|
588
|
+
approval => approval.risk_level === options.risk_level
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return history;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
calculateApprovalStats() {
|
|
596
|
+
const stats = {
|
|
597
|
+
approved: 0,
|
|
598
|
+
rejected: 0,
|
|
599
|
+
auto_approved: 0,
|
|
600
|
+
by_risk_level: { low: 0, medium: 0, high: 0, critical: 0 }
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
this.approvalHistory.forEach(approval => {
|
|
604
|
+
if (approval.status === 'approved') stats.approved++;
|
|
605
|
+
else if (approval.status === 'rejected') stats.rejected++;
|
|
606
|
+
else if (approval.status === 'auto_approved') stats.auto_approved++;
|
|
607
|
+
|
|
608
|
+
stats.by_risk_level[approval.risk_level]++;
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
return stats;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get pending approvals
|
|
616
|
+
*/
|
|
617
|
+
getPendingApprovals() {
|
|
618
|
+
return Array.from(this.pendingApprovals.values());
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Clear expired approvals
|
|
623
|
+
*/
|
|
624
|
+
clearExpiredApprovals() {
|
|
625
|
+
const now = new Date();
|
|
626
|
+
let clearedCount = 0;
|
|
627
|
+
|
|
628
|
+
for (const [requestId, approval] of this.pendingApprovals) {
|
|
629
|
+
if (new Date(approval.valid_until) < now) {
|
|
630
|
+
this.pendingApprovals.delete(requestId);
|
|
631
|
+
clearedCount++;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (clearedCount > 0) {
|
|
636
|
+
console.log(chalk.gray(`Cleared ${clearedCount} expired approvals`));
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return clearedCount;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
643
|
module.exports = ApprovalWorkflow;
|