aios-core 4.2.13 → 4.2.14
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/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/pro/README.md +0 -66
- package/pro/license/degradation.js +0 -220
- package/pro/license/errors.js +0 -450
- package/pro/license/feature-gate.js +0 -354
- package/pro/license/index.js +0 -181
- package/pro/license/license-api.js +0 -651
- package/pro/license/license-cache.js +0 -523
- package/pro/license/license-crypto.js +0 -303
- 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,776 +1,776 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Tracks metrics for self-improvement operations
|
|
7
|
-
*/
|
|
8
|
-
class MetricsTracker {
|
|
9
|
-
constructor(options = {}) {
|
|
10
|
-
this.rootPath = options.rootPath || process.cwd();
|
|
11
|
-
this.metricsFile = path.join(this.rootPath, '.aios', 'improvement-metrics.json');
|
|
12
|
-
this.maxEntries = options.maxEntries || 1000;
|
|
13
|
-
|
|
14
|
-
// Metric categories
|
|
15
|
-
this.categories = {
|
|
16
|
-
performance: ['execution_time', 'memory_usage', 'cpu_usage'],
|
|
17
|
-
quality: ['test_coverage', 'code_complexity', 'error_rate'],
|
|
18
|
-
impact: ['files_modified', 'functions_improved', 'bugs_fixed'],
|
|
19
|
-
user: ['approval_rate', 'rollback_rate', 'satisfaction_score']
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Initialize metrics system
|
|
25
|
-
* @returns {Promise<void>}
|
|
26
|
-
*/
|
|
27
|
-
async initialize() {
|
|
28
|
-
const metricsDir = path.dirname(this.metricsFile);
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
await fs.mkdir(metricsDir, { recursive: true });
|
|
32
|
-
|
|
33
|
-
// Initialize file if doesn't exist
|
|
34
|
-
try {
|
|
35
|
-
await fs.access(this.metricsFile);
|
|
36
|
-
} catch {
|
|
37
|
-
await this.saveMetrics({
|
|
38
|
-
version: '1.0.0',
|
|
39
|
-
created: new Date().toISOString(),
|
|
40
|
-
improvements: [],
|
|
41
|
-
aggregates: this.initializeAggregates(),
|
|
42
|
-
trends: {}
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error(chalk.red(`Failed to initialize metrics: ${error.message}`));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Record improvement metrics
|
|
52
|
-
* @param {Object} improvement - Improvement data
|
|
53
|
-
* @returns {Promise<void>}
|
|
54
|
-
*/
|
|
55
|
-
async recordImprovement(improvement) {
|
|
56
|
-
await this.initialize();
|
|
57
|
-
|
|
58
|
-
const metrics = await this.loadMetrics();
|
|
59
|
-
|
|
60
|
-
const entry = {
|
|
61
|
-
improvement_id: improvement.improvement_id,
|
|
62
|
-
timestamp: new Date().toISOString(),
|
|
63
|
-
metrics: improvement.metrics || {},
|
|
64
|
-
analysis: improvement.analysis || {},
|
|
65
|
-
plan: improvement.plan || {},
|
|
66
|
-
outcome: 'pending',
|
|
67
|
-
measurements: await this.gatherMeasurements(improvement)
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
metrics.improvements.push(entry);
|
|
71
|
-
|
|
72
|
-
// Keep only recent entries
|
|
73
|
-
if (metrics.improvements.length > this.maxEntries) {
|
|
74
|
-
metrics.improvements = metrics.improvements.slice(-this.maxEntries);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Update aggregates
|
|
78
|
-
await this.updateAggregates(metrics, entry);
|
|
79
|
-
|
|
80
|
-
// Calculate trends
|
|
81
|
-
metrics.trends = await this.calculateTrends(metrics);
|
|
82
|
-
|
|
83
|
-
await this.saveMetrics(metrics);
|
|
84
|
-
|
|
85
|
-
console.log(chalk.green(`📊 Metrics recorded for improvement: ${improvement.improvement_id}`));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Update improvement outcome
|
|
90
|
-
* @param {string} improvementId - Improvement ID
|
|
91
|
-
* @param {Object} outcome - Outcome data
|
|
92
|
-
* @returns {Promise<void>}
|
|
93
|
-
*/
|
|
94
|
-
async updateOutcome(improvementId, outcome) {
|
|
95
|
-
const metrics = await this.loadMetrics();
|
|
96
|
-
|
|
97
|
-
const entry = metrics.improvements.find(i => i.improvement_id === improvementId);
|
|
98
|
-
if (!entry) {
|
|
99
|
-
throw new Error(`Improvement not found: ${improvementId}`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
entry.outcome = outcome.status; // 'success', 'failed', 'rolled_back'
|
|
103
|
-
entry.outcome_details = outcome;
|
|
104
|
-
entry.end_timestamp = new Date().toISOString();
|
|
105
|
-
|
|
106
|
-
// Calculate duration
|
|
107
|
-
const start = new Date(entry.timestamp);
|
|
108
|
-
const end = new Date(entry.end_timestamp);
|
|
109
|
-
entry.duration_ms = end - start;
|
|
110
|
-
|
|
111
|
-
// Update aggregates based on outcome
|
|
112
|
-
await this.updateOutcomeAggregates(metrics, entry);
|
|
113
|
-
|
|
114
|
-
await this.saveMetrics(metrics);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Get improvement report
|
|
119
|
-
* @param {string} improvementId - Improvement ID
|
|
120
|
-
* @returns {Promise<Object>} Improvement report
|
|
121
|
-
*/
|
|
122
|
-
async getImprovementReport(improvementId) {
|
|
123
|
-
const metrics = await this.loadMetrics();
|
|
124
|
-
const entry = metrics.improvements.find(i => i.improvement_id === improvementId);
|
|
125
|
-
|
|
126
|
-
if (!entry) {
|
|
127
|
-
throw new Error(`Improvement not found: ${improvementId}`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const report = {
|
|
131
|
-
improvement_id: improvementId,
|
|
132
|
-
timestamp: entry.timestamp,
|
|
133
|
-
outcome: entry.outcome,
|
|
134
|
-
duration: entry.duration_ms ? `${(entry.duration_ms / 1000).toFixed(2)}s` : 'ongoing',
|
|
135
|
-
metrics: entry.metrics,
|
|
136
|
-
measurements: entry.measurements,
|
|
137
|
-
impact_summary: this.generateImpactSummary(entry),
|
|
138
|
-
recommendations: this.generateRecommendations(entry)
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
return report;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Get dashboard data
|
|
146
|
-
* @param {Object} options - Dashboard options
|
|
147
|
-
* @returns {Promise<Object>} Dashboard data
|
|
148
|
-
*/
|
|
149
|
-
async getDashboard(options = {}) {
|
|
150
|
-
const { period = '7d' } = options;
|
|
151
|
-
const metrics = await this.loadMetrics();
|
|
152
|
-
|
|
153
|
-
const cutoff = this.getPeriodCutoff(period);
|
|
154
|
-
const recentImprovements = metrics.improvements.filter(
|
|
155
|
-
i => new Date(i.timestamp) > cutoff
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
const dashboard = {
|
|
159
|
-
period,
|
|
160
|
-
summary: {
|
|
161
|
-
total_improvements: recentImprovements.length,
|
|
162
|
-
successful: recentImprovements.filter(i => i.outcome === 'success').length,
|
|
163
|
-
failed: recentImprovements.filter(i => i.outcome === 'failed').length,
|
|
164
|
-
rolled_back: recentImprovements.filter(i => i.outcome === 'rolled_back').length,
|
|
165
|
-
pending: recentImprovements.filter(i => i.outcome === 'pending').length
|
|
166
|
-
},
|
|
167
|
-
performance: this.calculatePerformanceMetrics(recentImprovements),
|
|
168
|
-
quality: this.calculateQualityMetrics(recentImprovements),
|
|
169
|
-
trends: metrics.trends,
|
|
170
|
-
top_improvements: this.getTopImprovements(recentImprovements, 5),
|
|
171
|
-
recommendations: this.generateDashboardRecommendations(metrics)
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
return dashboard;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Generate analytics report
|
|
179
|
-
* @param {Object} options - Report options
|
|
180
|
-
* @returns {Promise<Object>} Analytics report
|
|
181
|
-
*/
|
|
182
|
-
async generateAnalytics(options = {}) {
|
|
183
|
-
const metrics = await this.loadMetrics();
|
|
184
|
-
|
|
185
|
-
const analytics = {
|
|
186
|
-
generated: new Date().toISOString(),
|
|
187
|
-
period: options.period || 'all-time',
|
|
188
|
-
improvements: {
|
|
189
|
-
total: metrics.improvements.length,
|
|
190
|
-
by_outcome: this.groupByOutcome(metrics.improvements),
|
|
191
|
-
by_category: this.groupByCategory(metrics.improvements),
|
|
192
|
-
by_month: this.groupByMonth(metrics.improvements)
|
|
193
|
-
},
|
|
194
|
-
performance: {
|
|
195
|
-
average_duration: this.calculateAverageDuration(metrics.improvements),
|
|
196
|
-
success_rate: this.calculateSuccessRate(metrics.improvements),
|
|
197
|
-
improvement_velocity: this.calculateVelocity(metrics.improvements)
|
|
198
|
-
},
|
|
199
|
-
impact: {
|
|
200
|
-
total_files_modified: metrics.aggregates.total_files_modified,
|
|
201
|
-
total_functions_improved: metrics.aggregates.total_functions_improved,
|
|
202
|
-
average_improvement_score: this.calculateAverageImprovementScore(metrics.improvements)
|
|
203
|
-
},
|
|
204
|
-
patterns: this.identifyPatterns(metrics.improvements),
|
|
205
|
-
insights: this.generateInsights(metrics)
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
return analytics;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Gather measurements for improvement
|
|
213
|
-
* @private
|
|
214
|
-
*/
|
|
215
|
-
async gatherMeasurements(improvement) {
|
|
216
|
-
const measurements = {
|
|
217
|
-
baseline: {},
|
|
218
|
-
projected: {},
|
|
219
|
-
actual: {}
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
// Baseline measurements from analysis
|
|
223
|
-
if (improvement.analysis) {
|
|
224
|
-
measurements.baseline = {
|
|
225
|
-
overall_score: improvement.analysis.overall_score,
|
|
226
|
-
category_scores: improvement.analysis.categories
|
|
227
|
-
? Object.entries(improvement.analysis.categories).reduce((acc, [cat, data]) => {
|
|
228
|
-
acc[cat] = data.score;
|
|
229
|
-
return acc;
|
|
230
|
-
}, {})
|
|
231
|
-
: {}
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Projected improvements from plan
|
|
236
|
-
if (improvement.plan) {
|
|
237
|
-
measurements.projected = {
|
|
238
|
-
impact: improvement.plan.estimatedImpact,
|
|
239
|
-
effort: improvement.plan.estimatedEffort,
|
|
240
|
-
risk: improvement.plan.riskLevel,
|
|
241
|
-
files: improvement.plan.affectedFiles?.length || 0
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Actual measurements will be filled later
|
|
246
|
-
measurements.actual = {
|
|
247
|
-
timestamp: new Date().toISOString()
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
return measurements;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Update aggregates
|
|
255
|
-
* @private
|
|
256
|
-
*/
|
|
257
|
-
async updateAggregates(metrics, entry) {
|
|
258
|
-
const agg = metrics.aggregates;
|
|
259
|
-
|
|
260
|
-
agg.total_improvements++;
|
|
261
|
-
|
|
262
|
-
if (entry.measurements.projected.files) {
|
|
263
|
-
agg.total_files_modified += entry.measurements.projected.files;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Update category counts
|
|
267
|
-
if (entry.plan && entry.plan.target_areas) {
|
|
268
|
-
entry.plan.target_areas.forEach(area => {
|
|
269
|
-
agg.improvements_by_category[area] = (agg.improvements_by_category[area] || 0) + 1;
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Update hourly distribution
|
|
274
|
-
const hour = new Date(entry.timestamp).getHours();
|
|
275
|
-
agg.improvements_by_hour[hour] = (agg.improvements_by_hour[hour] || 0) + 1;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Update outcome aggregates
|
|
280
|
-
* @private
|
|
281
|
-
*/
|
|
282
|
-
async updateOutcomeAggregates(metrics, entry) {
|
|
283
|
-
const agg = metrics.aggregates;
|
|
284
|
-
|
|
285
|
-
switch (entry.outcome) {
|
|
286
|
-
case 'success':
|
|
287
|
-
agg.successful_improvements++;
|
|
288
|
-
if (entry.duration_ms) {
|
|
289
|
-
agg.total_duration_ms += entry.duration_ms;
|
|
290
|
-
}
|
|
291
|
-
break;
|
|
292
|
-
case 'failed':
|
|
293
|
-
agg.failed_improvements++;
|
|
294
|
-
break;
|
|
295
|
-
case 'rolled_back':
|
|
296
|
-
agg.rolled_back_improvements++;
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Update success rate
|
|
301
|
-
const total = agg.successful_improvements + agg.failed_improvements + agg.rolled_back_improvements;
|
|
302
|
-
agg.success_rate = total > 0 ? (agg.successful_improvements / total) * 100 : 0;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Calculate trends
|
|
307
|
-
* @private
|
|
308
|
-
*/
|
|
309
|
-
async calculateTrends(metrics) {
|
|
310
|
-
const trends = {};
|
|
311
|
-
|
|
312
|
-
// Success rate trend (last 5 periods)
|
|
313
|
-
const periods = 5;
|
|
314
|
-
const periodLength = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
315
|
-
|
|
316
|
-
trends.success_rate = [];
|
|
317
|
-
|
|
318
|
-
for (let i = 0; i < periods; i++) {
|
|
319
|
-
const end = Date.now() - (i * periodLength);
|
|
320
|
-
const start = end - periodLength;
|
|
321
|
-
|
|
322
|
-
const periodImprovements = metrics.improvements.filter(imp => {
|
|
323
|
-
const timestamp = new Date(imp.timestamp).getTime();
|
|
324
|
-
return timestamp >= start && timestamp < end;
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
const successRate = this.calculateSuccessRate(periodImprovements);
|
|
328
|
-
trends.success_rate.unshift({
|
|
329
|
-
period: i,
|
|
330
|
-
rate: successRate,
|
|
331
|
-
count: periodImprovements.length
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Velocity trend
|
|
336
|
-
trends.velocity = this.calculateVelocityTrend(metrics.improvements);
|
|
337
|
-
|
|
338
|
-
// Category trends
|
|
339
|
-
trends.categories = this.calculateCategoryTrends(metrics.improvements);
|
|
340
|
-
|
|
341
|
-
return trends;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Calculate success rate
|
|
346
|
-
* @private
|
|
347
|
-
*/
|
|
348
|
-
calculateSuccessRate(_improvements) {
|
|
349
|
-
const completed = improvements.filter(i => i.outcome !== 'pending');
|
|
350
|
-
if (completed.length === 0) return 0;
|
|
351
|
-
|
|
352
|
-
const successful = completed.filter(i => i.outcome === 'success').length;
|
|
353
|
-
return (successful / completed.length) * 100;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Calculate velocity trend
|
|
358
|
-
* @private
|
|
359
|
-
*/
|
|
360
|
-
calculateVelocityTrend(_improvements) {
|
|
361
|
-
const last30Days = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
362
|
-
const last60Days = Date.now() - (60 * 24 * 60 * 60 * 1000);
|
|
363
|
-
|
|
364
|
-
const recent = improvements.filter(i => new Date(i.timestamp) > last30Days).length;
|
|
365
|
-
const previous = improvements.filter(i => {
|
|
366
|
-
const timestamp = new Date(i.timestamp);
|
|
367
|
-
return timestamp > last60Days && timestamp <= last30Days;
|
|
368
|
-
}).length;
|
|
369
|
-
|
|
370
|
-
const change = previous > 0 ? ((recent - previous) / previous) * 100 : 0;
|
|
371
|
-
|
|
372
|
-
return {
|
|
373
|
-
current: recent,
|
|
374
|
-
previous,
|
|
375
|
-
change: change.toFixed(1),
|
|
376
|
-
direction: change > 0 ? 'up' : change < 0 ? 'down' : 'stable'
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Generate impact summary
|
|
382
|
-
* @private
|
|
383
|
-
*/
|
|
384
|
-
generateImpactSummary(entry) {
|
|
385
|
-
const summary = {
|
|
386
|
-
scope: 'unknown',
|
|
387
|
-
magnitude: 'unknown',
|
|
388
|
-
areas_affected: []
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
if (entry.measurements.projected) {
|
|
392
|
-
const files = entry.measurements.projected.files || 0;
|
|
393
|
-
|
|
394
|
-
if (files === 0) summary.scope = 'none';
|
|
395
|
-
else if (files <= 3) summary.scope = 'small';
|
|
396
|
-
else if (files <= 10) summary.scope = 'medium';
|
|
397
|
-
else summary.scope = 'large';
|
|
398
|
-
|
|
399
|
-
summary.magnitude = entry.measurements.projected.impact || 'unknown';
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (entry.plan && entry.plan.target_areas) {
|
|
403
|
-
summary.areas_affected = entry.plan.target_areas;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return summary;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Generate recommendations
|
|
411
|
-
* @private
|
|
412
|
-
*/
|
|
413
|
-
generateRecommendations(entry) {
|
|
414
|
-
const recommendations = [];
|
|
415
|
-
|
|
416
|
-
if (entry.outcome === 'failed') {
|
|
417
|
-
recommendations.push({
|
|
418
|
-
type: 'investigation',
|
|
419
|
-
message: 'Investigate failure cause and adjust validation criteria'
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (entry.outcome === 'rolled_back') {
|
|
424
|
-
recommendations.push({
|
|
425
|
-
type: 'review',
|
|
426
|
-
message: 'Review rollback reasons and improve testing coverage'
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
if (entry.measurements.projected && entry.measurements.projected.risk === 'high') {
|
|
431
|
-
recommendations.push({
|
|
432
|
-
type: 'caution',
|
|
433
|
-
message: 'Consider breaking high-risk improvements into smaller changes'
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return recommendations;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Generate dashboard recommendations
|
|
442
|
-
* @private
|
|
443
|
-
*/
|
|
444
|
-
generateDashboardRecommendations(metrics) {
|
|
445
|
-
const recommendations = [];
|
|
446
|
-
|
|
447
|
-
if (metrics.aggregates.success_rate < 70) {
|
|
448
|
-
recommendations.push({
|
|
449
|
-
priority: 'high',
|
|
450
|
-
message: 'Success rate below 70% - review validation and testing processes'
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (metrics.aggregates.rolled_back_improvements > metrics.aggregates.successful_improvements * 0.2) {
|
|
455
|
-
recommendations.push({
|
|
456
|
-
priority: 'medium',
|
|
457
|
-
message: 'High rollback rate detected - improve sandbox testing'
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const recentTrend = metrics.trends.velocity;
|
|
462
|
-
if (recentTrend && recentTrend.direction === 'down' && recentTrend.change < -50) {
|
|
463
|
-
recommendations.push({
|
|
464
|
-
priority: 'low',
|
|
465
|
-
message: 'Improvement velocity decreasing - consider process optimization'
|
|
466
|
-
});
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return recommendations;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Get top improvements
|
|
474
|
-
* @private
|
|
475
|
-
*/
|
|
476
|
-
getTopImprovements(_improvements, limit) {
|
|
477
|
-
return improvements
|
|
478
|
-
.filter(i => i.outcome === 'success')
|
|
479
|
-
.sort((a, b) => {
|
|
480
|
-
const scoreA = a.measurements.projected?.impact || 0;
|
|
481
|
-
const scoreB = b.measurements.projected?.impact || 0;
|
|
482
|
-
return scoreB - scoreA;
|
|
483
|
-
})
|
|
484
|
-
.slice(0, limit)
|
|
485
|
-
.map(i => ({
|
|
486
|
-
id: i.improvement_id,
|
|
487
|
-
timestamp: i.timestamp,
|
|
488
|
-
impact: i.measurements.projected?.impact,
|
|
489
|
-
areas: i.plan?.target_areas || []
|
|
490
|
-
}));
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Identify patterns
|
|
495
|
-
* @private
|
|
496
|
-
*/
|
|
497
|
-
identifyPatterns(_improvements) {
|
|
498
|
-
const patterns = {
|
|
499
|
-
common_failures: {},
|
|
500
|
-
success_factors: [],
|
|
501
|
-
time_patterns: {}
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
// Analyze failures
|
|
505
|
-
const failures = improvements.filter(i => i.outcome === 'failed');
|
|
506
|
-
failures.forEach(f => {
|
|
507
|
-
if (f.plan && f.plan.target_areas) {
|
|
508
|
-
f.plan.target_areas.forEach(area => {
|
|
509
|
-
patterns.common_failures[area] = (patterns.common_failures[area] || 0) + 1;
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
});
|
|
513
|
-
|
|
514
|
-
// Success patterns
|
|
515
|
-
const successes = improvements.filter(i => i.outcome === 'success');
|
|
516
|
-
if (successes.length > 0) {
|
|
517
|
-
const avgFiles = successes.reduce((sum, s) =>
|
|
518
|
-
sum + (s.measurements.projected?.files || 0), 0) / successes.length;
|
|
519
|
-
|
|
520
|
-
patterns.success_factors.push({
|
|
521
|
-
factor: 'optimal_file_count',
|
|
522
|
-
value: Math.round(avgFiles),
|
|
523
|
-
confidence: 0.7
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
return patterns;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Generate insights
|
|
532
|
-
* @private
|
|
533
|
-
*/
|
|
534
|
-
generateInsights(metrics) {
|
|
535
|
-
const insights = [];
|
|
536
|
-
|
|
537
|
-
// Time-based insights
|
|
538
|
-
const hourlyDist = metrics.aggregates.improvements_by_hour;
|
|
539
|
-
const peakHour = Object.entries(hourlyDist)
|
|
540
|
-
.sort(([,a], [,b]) => b - a)[0];
|
|
541
|
-
|
|
542
|
-
if (peakHour) {
|
|
543
|
-
insights.push({
|
|
544
|
-
type: 'timing',
|
|
545
|
-
message: `Most improvements occur at ${peakHour[0]}:00 hours`,
|
|
546
|
-
data: { hour: peakHour[0], count: peakHour[1] }
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
// Category insights
|
|
551
|
-
const categories = metrics.aggregates.improvements_by_category;
|
|
552
|
-
const topCategory = Object.entries(categories)
|
|
553
|
-
.sort(([,a], [,b]) => b - a)[0];
|
|
554
|
-
|
|
555
|
-
if (topCategory) {
|
|
556
|
-
insights.push({
|
|
557
|
-
type: 'focus',
|
|
558
|
-
message: `${topCategory[0]} improvements are most common (${topCategory[1]} times)`,
|
|
559
|
-
data: { category: topCategory[0], count: topCategory[1] }
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
return insights;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Initialize aggregates
|
|
568
|
-
* @private
|
|
569
|
-
*/
|
|
570
|
-
initializeAggregates() {
|
|
571
|
-
return {
|
|
572
|
-
total_improvements: 0,
|
|
573
|
-
successful_improvements: 0,
|
|
574
|
-
failed_improvements: 0,
|
|
575
|
-
rolled_back_improvements: 0,
|
|
576
|
-
total_files_modified: 0,
|
|
577
|
-
total_functions_improved: 0,
|
|
578
|
-
total_duration_ms: 0,
|
|
579
|
-
success_rate: 0,
|
|
580
|
-
improvements_by_category: {},
|
|
581
|
-
improvements_by_hour: {}
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Get period cutoff date
|
|
587
|
-
* @private
|
|
588
|
-
*/
|
|
589
|
-
getPeriodCutoff(period) {
|
|
590
|
-
const now = new Date();
|
|
591
|
-
|
|
592
|
-
switch (period) {
|
|
593
|
-
case '24h':
|
|
594
|
-
return new Date(now - 24 * 60 * 60 * 1000);
|
|
595
|
-
case '7d':
|
|
596
|
-
return new Date(now - 7 * 24 * 60 * 60 * 1000);
|
|
597
|
-
case '30d':
|
|
598
|
-
return new Date(now - 30 * 24 * 60 * 60 * 1000);
|
|
599
|
-
case '90d':
|
|
600
|
-
return new Date(now - 90 * 24 * 60 * 60 * 1000);
|
|
601
|
-
default:
|
|
602
|
-
return new Date(0); // All time
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
/**
|
|
607
|
-
* Calculate average duration
|
|
608
|
-
* @private
|
|
609
|
-
*/
|
|
610
|
-
calculateAverageDuration(_improvements) {
|
|
611
|
-
const completed = improvements.filter(i => i.duration_ms);
|
|
612
|
-
if (completed.length === 0) return 0;
|
|
613
|
-
|
|
614
|
-
const total = completed.reduce((sum, i) => sum + i.duration_ms, 0);
|
|
615
|
-
return Math.round(total / completed.length);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
/**
|
|
619
|
-
* Calculate improvement velocity
|
|
620
|
-
* @private
|
|
621
|
-
*/
|
|
622
|
-
calculateVelocity(_improvements) {
|
|
623
|
-
const last7Days = this.getPeriodCutoff('7d');
|
|
624
|
-
const recent = improvements.filter(i => new Date(i.timestamp) > last7Days);
|
|
625
|
-
return recent.length / 7; // Per day
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/**
|
|
629
|
-
* Calculate average improvement score
|
|
630
|
-
* @private
|
|
631
|
-
*/
|
|
632
|
-
calculateAverageImprovementScore(_improvements) {
|
|
633
|
-
const withScores = improvements.filter(i =>
|
|
634
|
-
i.measurements?.baseline?.overall_score &&
|
|
635
|
-
i.outcome === 'success'
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
if (withScores.length === 0) return 0;
|
|
639
|
-
|
|
640
|
-
const totalImprovement = withScores.reduce((sum, i) => {
|
|
641
|
-
const baseline = parseFloat(i.measurements.baseline.overall_score) || 0;
|
|
642
|
-
const projected = baseline + (i.measurements.projected?.impact || 0);
|
|
643
|
-
return sum + (projected - baseline);
|
|
644
|
-
}, 0);
|
|
645
|
-
|
|
646
|
-
return (totalImprovement / withScores.length).toFixed(2);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Group improvements by outcome
|
|
651
|
-
* @private
|
|
652
|
-
*/
|
|
653
|
-
groupByOutcome(_improvements) {
|
|
654
|
-
return improvements.reduce((groups, imp) => {
|
|
655
|
-
const outcome = imp.outcome || 'pending';
|
|
656
|
-
groups[outcome] = (groups[outcome] || 0) + 1;
|
|
657
|
-
return groups;
|
|
658
|
-
}, {});
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* Group improvements by category
|
|
663
|
-
* @private
|
|
664
|
-
*/
|
|
665
|
-
groupByCategory(_improvements) {
|
|
666
|
-
const groups = {};
|
|
667
|
-
|
|
668
|
-
improvements.forEach(imp => {
|
|
669
|
-
if (imp.plan && imp.plan.target_areas) {
|
|
670
|
-
imp.plan.target_areas.forEach(area => {
|
|
671
|
-
groups[area] = (groups[area] || 0) + 1;
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
return groups;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Group improvements by month
|
|
681
|
-
* @private
|
|
682
|
-
*/
|
|
683
|
-
groupByMonth(_improvements) {
|
|
684
|
-
return improvements.reduce((groups, imp) => {
|
|
685
|
-
const month = new Date(imp.timestamp).toISOString().substring(0, 7);
|
|
686
|
-
groups[month] = (groups[month] || 0) + 1;
|
|
687
|
-
return groups;
|
|
688
|
-
}, {});
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Calculate performance metrics
|
|
693
|
-
* @private
|
|
694
|
-
*/
|
|
695
|
-
calculatePerformanceMetrics(_improvements) {
|
|
696
|
-
const successful = improvements.filter(i => i.outcome === 'success');
|
|
697
|
-
|
|
698
|
-
return {
|
|
699
|
-
average_duration: this.calculateAverageDuration(successful),
|
|
700
|
-
fastest_improvement: successful
|
|
701
|
-
.filter(i => i.duration_ms)
|
|
702
|
-
.sort((a, b) => a.duration_ms - b.duration_ms)[0]?.duration_ms || null,
|
|
703
|
-
slowest_improvement: successful
|
|
704
|
-
.filter(i => i.duration_ms)
|
|
705
|
-
.sort((a, b) => b.duration_ms - a.duration_ms)[0]?.duration_ms || null
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Calculate quality metrics
|
|
711
|
-
* @private
|
|
712
|
-
*/
|
|
713
|
-
calculateQualityMetrics(_improvements) {
|
|
714
|
-
return {
|
|
715
|
-
test_coverage_impact: 'N/A', // Would need actual test data
|
|
716
|
-
complexity_reduction: 'N/A', // Would need complexity analysis
|
|
717
|
-
error_rate_change: 'N/A' // Would need error tracking
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Calculate category trends
|
|
723
|
-
* @private
|
|
724
|
-
*/
|
|
725
|
-
calculateCategoryTrends(_improvements) {
|
|
726
|
-
const trends = {};
|
|
727
|
-
const categories = Object.keys(this.categories);
|
|
728
|
-
|
|
729
|
-
categories.forEach(cat => {
|
|
730
|
-
const catImprovements = improvements.filter(i =>
|
|
731
|
-
i.plan?.target_areas?.includes(cat)
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
trends[cat] = {
|
|
735
|
-
total: catImprovements.length,
|
|
736
|
-
success_rate: this.calculateSuccessRate(catImprovements),
|
|
737
|
-
recent_activity: catImprovements.filter(i =>
|
|
738
|
-
new Date(i.timestamp) > this.getPeriodCutoff('7d')
|
|
739
|
-
).length
|
|
740
|
-
};
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
return trends;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* Load metrics
|
|
748
|
-
* @private
|
|
749
|
-
*/
|
|
750
|
-
async loadMetrics() {
|
|
751
|
-
try {
|
|
752
|
-
const content = await fs.readFile(this.metricsFile, 'utf-8');
|
|
753
|
-
return JSON.parse(content);
|
|
754
|
-
} catch {
|
|
755
|
-
return {
|
|
756
|
-
version: '1.0.0',
|
|
757
|
-
improvements: [],
|
|
758
|
-
aggregates: this.initializeAggregates(),
|
|
759
|
-
trends: {}
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Save metrics
|
|
766
|
-
* @private
|
|
767
|
-
*/
|
|
768
|
-
async saveMetrics(metrics) {
|
|
769
|
-
await fs.writeFile(
|
|
770
|
-
this.metricsFile,
|
|
771
|
-
JSON.stringify(metrics, null, 2)
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tracks metrics for self-improvement operations
|
|
7
|
+
*/
|
|
8
|
+
class MetricsTracker {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.rootPath = options.rootPath || process.cwd();
|
|
11
|
+
this.metricsFile = path.join(this.rootPath, '.aios', 'improvement-metrics.json');
|
|
12
|
+
this.maxEntries = options.maxEntries || 1000;
|
|
13
|
+
|
|
14
|
+
// Metric categories
|
|
15
|
+
this.categories = {
|
|
16
|
+
performance: ['execution_time', 'memory_usage', 'cpu_usage'],
|
|
17
|
+
quality: ['test_coverage', 'code_complexity', 'error_rate'],
|
|
18
|
+
impact: ['files_modified', 'functions_improved', 'bugs_fixed'],
|
|
19
|
+
user: ['approval_rate', 'rollback_rate', 'satisfaction_score']
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize metrics system
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
async initialize() {
|
|
28
|
+
const metricsDir = path.dirname(this.metricsFile);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await fs.mkdir(metricsDir, { recursive: true });
|
|
32
|
+
|
|
33
|
+
// Initialize file if doesn't exist
|
|
34
|
+
try {
|
|
35
|
+
await fs.access(this.metricsFile);
|
|
36
|
+
} catch {
|
|
37
|
+
await this.saveMetrics({
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
created: new Date().toISOString(),
|
|
40
|
+
improvements: [],
|
|
41
|
+
aggregates: this.initializeAggregates(),
|
|
42
|
+
trends: {}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(chalk.red(`Failed to initialize metrics: ${error.message}`));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Record improvement metrics
|
|
52
|
+
* @param {Object} improvement - Improvement data
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
async recordImprovement(improvement) {
|
|
56
|
+
await this.initialize();
|
|
57
|
+
|
|
58
|
+
const metrics = await this.loadMetrics();
|
|
59
|
+
|
|
60
|
+
const entry = {
|
|
61
|
+
improvement_id: improvement.improvement_id,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
metrics: improvement.metrics || {},
|
|
64
|
+
analysis: improvement.analysis || {},
|
|
65
|
+
plan: improvement.plan || {},
|
|
66
|
+
outcome: 'pending',
|
|
67
|
+
measurements: await this.gatherMeasurements(improvement)
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
metrics.improvements.push(entry);
|
|
71
|
+
|
|
72
|
+
// Keep only recent entries
|
|
73
|
+
if (metrics.improvements.length > this.maxEntries) {
|
|
74
|
+
metrics.improvements = metrics.improvements.slice(-this.maxEntries);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update aggregates
|
|
78
|
+
await this.updateAggregates(metrics, entry);
|
|
79
|
+
|
|
80
|
+
// Calculate trends
|
|
81
|
+
metrics.trends = await this.calculateTrends(metrics);
|
|
82
|
+
|
|
83
|
+
await this.saveMetrics(metrics);
|
|
84
|
+
|
|
85
|
+
console.log(chalk.green(`📊 Metrics recorded for improvement: ${improvement.improvement_id}`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Update improvement outcome
|
|
90
|
+
* @param {string} improvementId - Improvement ID
|
|
91
|
+
* @param {Object} outcome - Outcome data
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async updateOutcome(improvementId, outcome) {
|
|
95
|
+
const metrics = await this.loadMetrics();
|
|
96
|
+
|
|
97
|
+
const entry = metrics.improvements.find(i => i.improvement_id === improvementId);
|
|
98
|
+
if (!entry) {
|
|
99
|
+
throw new Error(`Improvement not found: ${improvementId}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
entry.outcome = outcome.status; // 'success', 'failed', 'rolled_back'
|
|
103
|
+
entry.outcome_details = outcome;
|
|
104
|
+
entry.end_timestamp = new Date().toISOString();
|
|
105
|
+
|
|
106
|
+
// Calculate duration
|
|
107
|
+
const start = new Date(entry.timestamp);
|
|
108
|
+
const end = new Date(entry.end_timestamp);
|
|
109
|
+
entry.duration_ms = end - start;
|
|
110
|
+
|
|
111
|
+
// Update aggregates based on outcome
|
|
112
|
+
await this.updateOutcomeAggregates(metrics, entry);
|
|
113
|
+
|
|
114
|
+
await this.saveMetrics(metrics);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get improvement report
|
|
119
|
+
* @param {string} improvementId - Improvement ID
|
|
120
|
+
* @returns {Promise<Object>} Improvement report
|
|
121
|
+
*/
|
|
122
|
+
async getImprovementReport(improvementId) {
|
|
123
|
+
const metrics = await this.loadMetrics();
|
|
124
|
+
const entry = metrics.improvements.find(i => i.improvement_id === improvementId);
|
|
125
|
+
|
|
126
|
+
if (!entry) {
|
|
127
|
+
throw new Error(`Improvement not found: ${improvementId}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const report = {
|
|
131
|
+
improvement_id: improvementId,
|
|
132
|
+
timestamp: entry.timestamp,
|
|
133
|
+
outcome: entry.outcome,
|
|
134
|
+
duration: entry.duration_ms ? `${(entry.duration_ms / 1000).toFixed(2)}s` : 'ongoing',
|
|
135
|
+
metrics: entry.metrics,
|
|
136
|
+
measurements: entry.measurements,
|
|
137
|
+
impact_summary: this.generateImpactSummary(entry),
|
|
138
|
+
recommendations: this.generateRecommendations(entry)
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return report;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get dashboard data
|
|
146
|
+
* @param {Object} options - Dashboard options
|
|
147
|
+
* @returns {Promise<Object>} Dashboard data
|
|
148
|
+
*/
|
|
149
|
+
async getDashboard(options = {}) {
|
|
150
|
+
const { period = '7d' } = options;
|
|
151
|
+
const metrics = await this.loadMetrics();
|
|
152
|
+
|
|
153
|
+
const cutoff = this.getPeriodCutoff(period);
|
|
154
|
+
const recentImprovements = metrics.improvements.filter(
|
|
155
|
+
i => new Date(i.timestamp) > cutoff
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const dashboard = {
|
|
159
|
+
period,
|
|
160
|
+
summary: {
|
|
161
|
+
total_improvements: recentImprovements.length,
|
|
162
|
+
successful: recentImprovements.filter(i => i.outcome === 'success').length,
|
|
163
|
+
failed: recentImprovements.filter(i => i.outcome === 'failed').length,
|
|
164
|
+
rolled_back: recentImprovements.filter(i => i.outcome === 'rolled_back').length,
|
|
165
|
+
pending: recentImprovements.filter(i => i.outcome === 'pending').length
|
|
166
|
+
},
|
|
167
|
+
performance: this.calculatePerformanceMetrics(recentImprovements),
|
|
168
|
+
quality: this.calculateQualityMetrics(recentImprovements),
|
|
169
|
+
trends: metrics.trends,
|
|
170
|
+
top_improvements: this.getTopImprovements(recentImprovements, 5),
|
|
171
|
+
recommendations: this.generateDashboardRecommendations(metrics)
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return dashboard;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Generate analytics report
|
|
179
|
+
* @param {Object} options - Report options
|
|
180
|
+
* @returns {Promise<Object>} Analytics report
|
|
181
|
+
*/
|
|
182
|
+
async generateAnalytics(options = {}) {
|
|
183
|
+
const metrics = await this.loadMetrics();
|
|
184
|
+
|
|
185
|
+
const analytics = {
|
|
186
|
+
generated: new Date().toISOString(),
|
|
187
|
+
period: options.period || 'all-time',
|
|
188
|
+
improvements: {
|
|
189
|
+
total: metrics.improvements.length,
|
|
190
|
+
by_outcome: this.groupByOutcome(metrics.improvements),
|
|
191
|
+
by_category: this.groupByCategory(metrics.improvements),
|
|
192
|
+
by_month: this.groupByMonth(metrics.improvements)
|
|
193
|
+
},
|
|
194
|
+
performance: {
|
|
195
|
+
average_duration: this.calculateAverageDuration(metrics.improvements),
|
|
196
|
+
success_rate: this.calculateSuccessRate(metrics.improvements),
|
|
197
|
+
improvement_velocity: this.calculateVelocity(metrics.improvements)
|
|
198
|
+
},
|
|
199
|
+
impact: {
|
|
200
|
+
total_files_modified: metrics.aggregates.total_files_modified,
|
|
201
|
+
total_functions_improved: metrics.aggregates.total_functions_improved,
|
|
202
|
+
average_improvement_score: this.calculateAverageImprovementScore(metrics.improvements)
|
|
203
|
+
},
|
|
204
|
+
patterns: this.identifyPatterns(metrics.improvements),
|
|
205
|
+
insights: this.generateInsights(metrics)
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
return analytics;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Gather measurements for improvement
|
|
213
|
+
* @private
|
|
214
|
+
*/
|
|
215
|
+
async gatherMeasurements(improvement) {
|
|
216
|
+
const measurements = {
|
|
217
|
+
baseline: {},
|
|
218
|
+
projected: {},
|
|
219
|
+
actual: {}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Baseline measurements from analysis
|
|
223
|
+
if (improvement.analysis) {
|
|
224
|
+
measurements.baseline = {
|
|
225
|
+
overall_score: improvement.analysis.overall_score,
|
|
226
|
+
category_scores: improvement.analysis.categories
|
|
227
|
+
? Object.entries(improvement.analysis.categories).reduce((acc, [cat, data]) => {
|
|
228
|
+
acc[cat] = data.score;
|
|
229
|
+
return acc;
|
|
230
|
+
}, {})
|
|
231
|
+
: {}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Projected improvements from plan
|
|
236
|
+
if (improvement.plan) {
|
|
237
|
+
measurements.projected = {
|
|
238
|
+
impact: improvement.plan.estimatedImpact,
|
|
239
|
+
effort: improvement.plan.estimatedEffort,
|
|
240
|
+
risk: improvement.plan.riskLevel,
|
|
241
|
+
files: improvement.plan.affectedFiles?.length || 0
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Actual measurements will be filled later
|
|
246
|
+
measurements.actual = {
|
|
247
|
+
timestamp: new Date().toISOString()
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return measurements;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Update aggregates
|
|
255
|
+
* @private
|
|
256
|
+
*/
|
|
257
|
+
async updateAggregates(metrics, entry) {
|
|
258
|
+
const agg = metrics.aggregates;
|
|
259
|
+
|
|
260
|
+
agg.total_improvements++;
|
|
261
|
+
|
|
262
|
+
if (entry.measurements.projected.files) {
|
|
263
|
+
agg.total_files_modified += entry.measurements.projected.files;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Update category counts
|
|
267
|
+
if (entry.plan && entry.plan.target_areas) {
|
|
268
|
+
entry.plan.target_areas.forEach(area => {
|
|
269
|
+
agg.improvements_by_category[area] = (agg.improvements_by_category[area] || 0) + 1;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Update hourly distribution
|
|
274
|
+
const hour = new Date(entry.timestamp).getHours();
|
|
275
|
+
agg.improvements_by_hour[hour] = (agg.improvements_by_hour[hour] || 0) + 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Update outcome aggregates
|
|
280
|
+
* @private
|
|
281
|
+
*/
|
|
282
|
+
async updateOutcomeAggregates(metrics, entry) {
|
|
283
|
+
const agg = metrics.aggregates;
|
|
284
|
+
|
|
285
|
+
switch (entry.outcome) {
|
|
286
|
+
case 'success':
|
|
287
|
+
agg.successful_improvements++;
|
|
288
|
+
if (entry.duration_ms) {
|
|
289
|
+
agg.total_duration_ms += entry.duration_ms;
|
|
290
|
+
}
|
|
291
|
+
break;
|
|
292
|
+
case 'failed':
|
|
293
|
+
agg.failed_improvements++;
|
|
294
|
+
break;
|
|
295
|
+
case 'rolled_back':
|
|
296
|
+
agg.rolled_back_improvements++;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Update success rate
|
|
301
|
+
const total = agg.successful_improvements + agg.failed_improvements + agg.rolled_back_improvements;
|
|
302
|
+
agg.success_rate = total > 0 ? (agg.successful_improvements / total) * 100 : 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Calculate trends
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
async calculateTrends(metrics) {
|
|
310
|
+
const trends = {};
|
|
311
|
+
|
|
312
|
+
// Success rate trend (last 5 periods)
|
|
313
|
+
const periods = 5;
|
|
314
|
+
const periodLength = 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
315
|
+
|
|
316
|
+
trends.success_rate = [];
|
|
317
|
+
|
|
318
|
+
for (let i = 0; i < periods; i++) {
|
|
319
|
+
const end = Date.now() - (i * periodLength);
|
|
320
|
+
const start = end - periodLength;
|
|
321
|
+
|
|
322
|
+
const periodImprovements = metrics.improvements.filter(imp => {
|
|
323
|
+
const timestamp = new Date(imp.timestamp).getTime();
|
|
324
|
+
return timestamp >= start && timestamp < end;
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const successRate = this.calculateSuccessRate(periodImprovements);
|
|
328
|
+
trends.success_rate.unshift({
|
|
329
|
+
period: i,
|
|
330
|
+
rate: successRate,
|
|
331
|
+
count: periodImprovements.length
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Velocity trend
|
|
336
|
+
trends.velocity = this.calculateVelocityTrend(metrics.improvements);
|
|
337
|
+
|
|
338
|
+
// Category trends
|
|
339
|
+
trends.categories = this.calculateCategoryTrends(metrics.improvements);
|
|
340
|
+
|
|
341
|
+
return trends;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Calculate success rate
|
|
346
|
+
* @private
|
|
347
|
+
*/
|
|
348
|
+
calculateSuccessRate(_improvements) {
|
|
349
|
+
const completed = improvements.filter(i => i.outcome !== 'pending');
|
|
350
|
+
if (completed.length === 0) return 0;
|
|
351
|
+
|
|
352
|
+
const successful = completed.filter(i => i.outcome === 'success').length;
|
|
353
|
+
return (successful / completed.length) * 100;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Calculate velocity trend
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
calculateVelocityTrend(_improvements) {
|
|
361
|
+
const last30Days = Date.now() - (30 * 24 * 60 * 60 * 1000);
|
|
362
|
+
const last60Days = Date.now() - (60 * 24 * 60 * 60 * 1000);
|
|
363
|
+
|
|
364
|
+
const recent = improvements.filter(i => new Date(i.timestamp) > last30Days).length;
|
|
365
|
+
const previous = improvements.filter(i => {
|
|
366
|
+
const timestamp = new Date(i.timestamp);
|
|
367
|
+
return timestamp > last60Days && timestamp <= last30Days;
|
|
368
|
+
}).length;
|
|
369
|
+
|
|
370
|
+
const change = previous > 0 ? ((recent - previous) / previous) * 100 : 0;
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
current: recent,
|
|
374
|
+
previous,
|
|
375
|
+
change: change.toFixed(1),
|
|
376
|
+
direction: change > 0 ? 'up' : change < 0 ? 'down' : 'stable'
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Generate impact summary
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
generateImpactSummary(entry) {
|
|
385
|
+
const summary = {
|
|
386
|
+
scope: 'unknown',
|
|
387
|
+
magnitude: 'unknown',
|
|
388
|
+
areas_affected: []
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (entry.measurements.projected) {
|
|
392
|
+
const files = entry.measurements.projected.files || 0;
|
|
393
|
+
|
|
394
|
+
if (files === 0) summary.scope = 'none';
|
|
395
|
+
else if (files <= 3) summary.scope = 'small';
|
|
396
|
+
else if (files <= 10) summary.scope = 'medium';
|
|
397
|
+
else summary.scope = 'large';
|
|
398
|
+
|
|
399
|
+
summary.magnitude = entry.measurements.projected.impact || 'unknown';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (entry.plan && entry.plan.target_areas) {
|
|
403
|
+
summary.areas_affected = entry.plan.target_areas;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return summary;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Generate recommendations
|
|
411
|
+
* @private
|
|
412
|
+
*/
|
|
413
|
+
generateRecommendations(entry) {
|
|
414
|
+
const recommendations = [];
|
|
415
|
+
|
|
416
|
+
if (entry.outcome === 'failed') {
|
|
417
|
+
recommendations.push({
|
|
418
|
+
type: 'investigation',
|
|
419
|
+
message: 'Investigate failure cause and adjust validation criteria'
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (entry.outcome === 'rolled_back') {
|
|
424
|
+
recommendations.push({
|
|
425
|
+
type: 'review',
|
|
426
|
+
message: 'Review rollback reasons and improve testing coverage'
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (entry.measurements.projected && entry.measurements.projected.risk === 'high') {
|
|
431
|
+
recommendations.push({
|
|
432
|
+
type: 'caution',
|
|
433
|
+
message: 'Consider breaking high-risk improvements into smaller changes'
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return recommendations;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Generate dashboard recommendations
|
|
442
|
+
* @private
|
|
443
|
+
*/
|
|
444
|
+
generateDashboardRecommendations(metrics) {
|
|
445
|
+
const recommendations = [];
|
|
446
|
+
|
|
447
|
+
if (metrics.aggregates.success_rate < 70) {
|
|
448
|
+
recommendations.push({
|
|
449
|
+
priority: 'high',
|
|
450
|
+
message: 'Success rate below 70% - review validation and testing processes'
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (metrics.aggregates.rolled_back_improvements > metrics.aggregates.successful_improvements * 0.2) {
|
|
455
|
+
recommendations.push({
|
|
456
|
+
priority: 'medium',
|
|
457
|
+
message: 'High rollback rate detected - improve sandbox testing'
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const recentTrend = metrics.trends.velocity;
|
|
462
|
+
if (recentTrend && recentTrend.direction === 'down' && recentTrend.change < -50) {
|
|
463
|
+
recommendations.push({
|
|
464
|
+
priority: 'low',
|
|
465
|
+
message: 'Improvement velocity decreasing - consider process optimization'
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return recommendations;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get top improvements
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
getTopImprovements(_improvements, limit) {
|
|
477
|
+
return improvements
|
|
478
|
+
.filter(i => i.outcome === 'success')
|
|
479
|
+
.sort((a, b) => {
|
|
480
|
+
const scoreA = a.measurements.projected?.impact || 0;
|
|
481
|
+
const scoreB = b.measurements.projected?.impact || 0;
|
|
482
|
+
return scoreB - scoreA;
|
|
483
|
+
})
|
|
484
|
+
.slice(0, limit)
|
|
485
|
+
.map(i => ({
|
|
486
|
+
id: i.improvement_id,
|
|
487
|
+
timestamp: i.timestamp,
|
|
488
|
+
impact: i.measurements.projected?.impact,
|
|
489
|
+
areas: i.plan?.target_areas || []
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Identify patterns
|
|
495
|
+
* @private
|
|
496
|
+
*/
|
|
497
|
+
identifyPatterns(_improvements) {
|
|
498
|
+
const patterns = {
|
|
499
|
+
common_failures: {},
|
|
500
|
+
success_factors: [],
|
|
501
|
+
time_patterns: {}
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
// Analyze failures
|
|
505
|
+
const failures = improvements.filter(i => i.outcome === 'failed');
|
|
506
|
+
failures.forEach(f => {
|
|
507
|
+
if (f.plan && f.plan.target_areas) {
|
|
508
|
+
f.plan.target_areas.forEach(area => {
|
|
509
|
+
patterns.common_failures[area] = (patterns.common_failures[area] || 0) + 1;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Success patterns
|
|
515
|
+
const successes = improvements.filter(i => i.outcome === 'success');
|
|
516
|
+
if (successes.length > 0) {
|
|
517
|
+
const avgFiles = successes.reduce((sum, s) =>
|
|
518
|
+
sum + (s.measurements.projected?.files || 0), 0) / successes.length;
|
|
519
|
+
|
|
520
|
+
patterns.success_factors.push({
|
|
521
|
+
factor: 'optimal_file_count',
|
|
522
|
+
value: Math.round(avgFiles),
|
|
523
|
+
confidence: 0.7
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return patterns;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Generate insights
|
|
532
|
+
* @private
|
|
533
|
+
*/
|
|
534
|
+
generateInsights(metrics) {
|
|
535
|
+
const insights = [];
|
|
536
|
+
|
|
537
|
+
// Time-based insights
|
|
538
|
+
const hourlyDist = metrics.aggregates.improvements_by_hour;
|
|
539
|
+
const peakHour = Object.entries(hourlyDist)
|
|
540
|
+
.sort(([,a], [,b]) => b - a)[0];
|
|
541
|
+
|
|
542
|
+
if (peakHour) {
|
|
543
|
+
insights.push({
|
|
544
|
+
type: 'timing',
|
|
545
|
+
message: `Most improvements occur at ${peakHour[0]}:00 hours`,
|
|
546
|
+
data: { hour: peakHour[0], count: peakHour[1] }
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Category insights
|
|
551
|
+
const categories = metrics.aggregates.improvements_by_category;
|
|
552
|
+
const topCategory = Object.entries(categories)
|
|
553
|
+
.sort(([,a], [,b]) => b - a)[0];
|
|
554
|
+
|
|
555
|
+
if (topCategory) {
|
|
556
|
+
insights.push({
|
|
557
|
+
type: 'focus',
|
|
558
|
+
message: `${topCategory[0]} improvements are most common (${topCategory[1]} times)`,
|
|
559
|
+
data: { category: topCategory[0], count: topCategory[1] }
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return insights;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Initialize aggregates
|
|
568
|
+
* @private
|
|
569
|
+
*/
|
|
570
|
+
initializeAggregates() {
|
|
571
|
+
return {
|
|
572
|
+
total_improvements: 0,
|
|
573
|
+
successful_improvements: 0,
|
|
574
|
+
failed_improvements: 0,
|
|
575
|
+
rolled_back_improvements: 0,
|
|
576
|
+
total_files_modified: 0,
|
|
577
|
+
total_functions_improved: 0,
|
|
578
|
+
total_duration_ms: 0,
|
|
579
|
+
success_rate: 0,
|
|
580
|
+
improvements_by_category: {},
|
|
581
|
+
improvements_by_hour: {}
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get period cutoff date
|
|
587
|
+
* @private
|
|
588
|
+
*/
|
|
589
|
+
getPeriodCutoff(period) {
|
|
590
|
+
const now = new Date();
|
|
591
|
+
|
|
592
|
+
switch (period) {
|
|
593
|
+
case '24h':
|
|
594
|
+
return new Date(now - 24 * 60 * 60 * 1000);
|
|
595
|
+
case '7d':
|
|
596
|
+
return new Date(now - 7 * 24 * 60 * 60 * 1000);
|
|
597
|
+
case '30d':
|
|
598
|
+
return new Date(now - 30 * 24 * 60 * 60 * 1000);
|
|
599
|
+
case '90d':
|
|
600
|
+
return new Date(now - 90 * 24 * 60 * 60 * 1000);
|
|
601
|
+
default:
|
|
602
|
+
return new Date(0); // All time
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Calculate average duration
|
|
608
|
+
* @private
|
|
609
|
+
*/
|
|
610
|
+
calculateAverageDuration(_improvements) {
|
|
611
|
+
const completed = improvements.filter(i => i.duration_ms);
|
|
612
|
+
if (completed.length === 0) return 0;
|
|
613
|
+
|
|
614
|
+
const total = completed.reduce((sum, i) => sum + i.duration_ms, 0);
|
|
615
|
+
return Math.round(total / completed.length);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Calculate improvement velocity
|
|
620
|
+
* @private
|
|
621
|
+
*/
|
|
622
|
+
calculateVelocity(_improvements) {
|
|
623
|
+
const last7Days = this.getPeriodCutoff('7d');
|
|
624
|
+
const recent = improvements.filter(i => new Date(i.timestamp) > last7Days);
|
|
625
|
+
return recent.length / 7; // Per day
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Calculate average improvement score
|
|
630
|
+
* @private
|
|
631
|
+
*/
|
|
632
|
+
calculateAverageImprovementScore(_improvements) {
|
|
633
|
+
const withScores = improvements.filter(i =>
|
|
634
|
+
i.measurements?.baseline?.overall_score &&
|
|
635
|
+
i.outcome === 'success'
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
if (withScores.length === 0) return 0;
|
|
639
|
+
|
|
640
|
+
const totalImprovement = withScores.reduce((sum, i) => {
|
|
641
|
+
const baseline = parseFloat(i.measurements.baseline.overall_score) || 0;
|
|
642
|
+
const projected = baseline + (i.measurements.projected?.impact || 0);
|
|
643
|
+
return sum + (projected - baseline);
|
|
644
|
+
}, 0);
|
|
645
|
+
|
|
646
|
+
return (totalImprovement / withScores.length).toFixed(2);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Group improvements by outcome
|
|
651
|
+
* @private
|
|
652
|
+
*/
|
|
653
|
+
groupByOutcome(_improvements) {
|
|
654
|
+
return improvements.reduce((groups, imp) => {
|
|
655
|
+
const outcome = imp.outcome || 'pending';
|
|
656
|
+
groups[outcome] = (groups[outcome] || 0) + 1;
|
|
657
|
+
return groups;
|
|
658
|
+
}, {});
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Group improvements by category
|
|
663
|
+
* @private
|
|
664
|
+
*/
|
|
665
|
+
groupByCategory(_improvements) {
|
|
666
|
+
const groups = {};
|
|
667
|
+
|
|
668
|
+
improvements.forEach(imp => {
|
|
669
|
+
if (imp.plan && imp.plan.target_areas) {
|
|
670
|
+
imp.plan.target_areas.forEach(area => {
|
|
671
|
+
groups[area] = (groups[area] || 0) + 1;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
return groups;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Group improvements by month
|
|
681
|
+
* @private
|
|
682
|
+
*/
|
|
683
|
+
groupByMonth(_improvements) {
|
|
684
|
+
return improvements.reduce((groups, imp) => {
|
|
685
|
+
const month = new Date(imp.timestamp).toISOString().substring(0, 7);
|
|
686
|
+
groups[month] = (groups[month] || 0) + 1;
|
|
687
|
+
return groups;
|
|
688
|
+
}, {});
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Calculate performance metrics
|
|
693
|
+
* @private
|
|
694
|
+
*/
|
|
695
|
+
calculatePerformanceMetrics(_improvements) {
|
|
696
|
+
const successful = improvements.filter(i => i.outcome === 'success');
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
average_duration: this.calculateAverageDuration(successful),
|
|
700
|
+
fastest_improvement: successful
|
|
701
|
+
.filter(i => i.duration_ms)
|
|
702
|
+
.sort((a, b) => a.duration_ms - b.duration_ms)[0]?.duration_ms || null,
|
|
703
|
+
slowest_improvement: successful
|
|
704
|
+
.filter(i => i.duration_ms)
|
|
705
|
+
.sort((a, b) => b.duration_ms - a.duration_ms)[0]?.duration_ms || null
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Calculate quality metrics
|
|
711
|
+
* @private
|
|
712
|
+
*/
|
|
713
|
+
calculateQualityMetrics(_improvements) {
|
|
714
|
+
return {
|
|
715
|
+
test_coverage_impact: 'N/A', // Would need actual test data
|
|
716
|
+
complexity_reduction: 'N/A', // Would need complexity analysis
|
|
717
|
+
error_rate_change: 'N/A' // Would need error tracking
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Calculate category trends
|
|
723
|
+
* @private
|
|
724
|
+
*/
|
|
725
|
+
calculateCategoryTrends(_improvements) {
|
|
726
|
+
const trends = {};
|
|
727
|
+
const categories = Object.keys(this.categories);
|
|
728
|
+
|
|
729
|
+
categories.forEach(cat => {
|
|
730
|
+
const catImprovements = improvements.filter(i =>
|
|
731
|
+
i.plan?.target_areas?.includes(cat)
|
|
732
|
+
);
|
|
733
|
+
|
|
734
|
+
trends[cat] = {
|
|
735
|
+
total: catImprovements.length,
|
|
736
|
+
success_rate: this.calculateSuccessRate(catImprovements),
|
|
737
|
+
recent_activity: catImprovements.filter(i =>
|
|
738
|
+
new Date(i.timestamp) > this.getPeriodCutoff('7d')
|
|
739
|
+
).length
|
|
740
|
+
};
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
return trends;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Load metrics
|
|
748
|
+
* @private
|
|
749
|
+
*/
|
|
750
|
+
async loadMetrics() {
|
|
751
|
+
try {
|
|
752
|
+
const content = await fs.readFile(this.metricsFile, 'utf-8');
|
|
753
|
+
return JSON.parse(content);
|
|
754
|
+
} catch {
|
|
755
|
+
return {
|
|
756
|
+
version: '1.0.0',
|
|
757
|
+
improvements: [],
|
|
758
|
+
aggregates: this.initializeAggregates(),
|
|
759
|
+
trends: {}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Save metrics
|
|
766
|
+
* @private
|
|
767
|
+
*/
|
|
768
|
+
async saveMetrics(metrics) {
|
|
769
|
+
await fs.writeFile(
|
|
770
|
+
this.metricsFile,
|
|
771
|
+
JSON.stringify(metrics, null, 2)
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
776
|
module.exports = MetricsTracker;
|