@wundam/orchex 1.0.0-rc.2 → 1.0.0-rc.21
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/README.md +59 -18
- package/dist/cloud-executor.d.ts +71 -0
- package/dist/cloud-executor.js +335 -0
- package/dist/cloud-sync.d.ts +8 -0
- package/dist/cloud-sync.js +52 -0
- package/dist/config.d.ts +30 -4
- package/dist/config.js +61 -2
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.js +11 -3
- package/dist/cost.js +1 -1
- package/dist/entitlements/jwt.d.ts +7 -0
- package/dist/entitlements/jwt.js +78 -0
- package/dist/entitlements/resolve.d.ts +17 -0
- package/dist/entitlements/resolve.js +49 -0
- package/dist/entitlements/types.d.ts +21 -0
- package/dist/entitlements/types.js +4 -0
- package/dist/executors/base.d.ts +1 -1
- package/dist/executors/bedrock-executor.d.ts +39 -0
- package/dist/executors/bedrock-executor.js +197 -0
- package/dist/executors/index.d.ts +1 -0
- package/dist/executors/index.js +24 -1
- package/dist/index.js +468 -23
- package/dist/intelligence/index.d.ts +44 -0
- package/dist/intelligence/index.js +160 -0
- package/dist/key-cache.d.ts +31 -0
- package/dist/key-cache.js +84 -0
- package/dist/login-helpers.d.ts +25 -0
- package/dist/login-helpers.js +54 -0
- package/dist/manifest.js +18 -1
- package/dist/mcp-instructions.d.ts +1 -0
- package/dist/mcp-instructions.js +84 -0
- package/dist/mcp-resources.d.ts +8 -0
- package/dist/mcp-resources.js +420 -0
- package/dist/model-cache.d.ts +18 -0
- package/dist/model-cache.js +62 -0
- package/dist/model-validator.d.ts +20 -0
- package/dist/model-validator.js +125 -0
- package/dist/orchestrator.d.ts +14 -0
- package/dist/orchestrator.js +191 -32
- package/dist/setup/ide-registry.d.ts +13 -0
- package/dist/setup/ide-registry.js +51 -0
- package/dist/setup/index.d.ts +1 -0
- package/dist/setup/index.js +111 -0
- package/dist/tier-gating.js +0 -16
- package/dist/tiers.d.ts +35 -5
- package/dist/tiers.js +39 -3
- package/dist/tools.d.ts +6 -1
- package/dist/tools.js +852 -95
- package/dist/types.d.ts +71 -60
- package/dist/types.js +3 -0
- package/dist/waves.d.ts +1 -1
- package/dist/waves.js +29 -2
- package/package.json +41 -5
- package/src/entitlements/public-key.pem +9 -0
- package/dist/intelligence/anti-pattern-detector.d.ts +0 -117
- package/dist/intelligence/anti-pattern-detector.js +0 -327
- package/dist/intelligence/budget-enforcer.d.ts +0 -119
- package/dist/intelligence/budget-enforcer.js +0 -226
- package/dist/intelligence/context-optimizer.d.ts +0 -111
- package/dist/intelligence/context-optimizer.js +0 -282
- package/dist/intelligence/cost-tracker.d.ts +0 -114
- package/dist/intelligence/cost-tracker.js +0 -183
- package/dist/intelligence/deliverable-extractor.d.ts +0 -134
- package/dist/intelligence/deliverable-extractor.js +0 -909
- package/dist/intelligence/dependency-inferrer.d.ts +0 -87
- package/dist/intelligence/dependency-inferrer.js +0 -403
- package/dist/intelligence/diagnostics.d.ts +0 -33
- package/dist/intelligence/diagnostics.js +0 -64
- package/dist/intelligence/error-analyzer.d.ts +0 -7
- package/dist/intelligence/error-analyzer.js +0 -76
- package/dist/intelligence/file-chunker.d.ts +0 -15
- package/dist/intelligence/file-chunker.js +0 -64
- package/dist/intelligence/fix-stream-manager.d.ts +0 -59
- package/dist/intelligence/fix-stream-manager.js +0 -212
- package/dist/intelligence/heuristics.d.ts +0 -23
- package/dist/intelligence/heuristics.js +0 -124
- package/dist/intelligence/learning-engine.d.ts +0 -157
- package/dist/intelligence/learning-engine.js +0 -433
- package/dist/intelligence/learning-feedback.d.ts +0 -96
- package/dist/intelligence/learning-feedback.js +0 -202
- package/dist/intelligence/pattern-analyzer.d.ts +0 -35
- package/dist/intelligence/pattern-analyzer.js +0 -189
- package/dist/intelligence/plan-parser.d.ts +0 -124
- package/dist/intelligence/plan-parser.js +0 -498
- package/dist/intelligence/planner.d.ts +0 -29
- package/dist/intelligence/planner.js +0 -86
- package/dist/intelligence/self-healer.d.ts +0 -16
- package/dist/intelligence/self-healer.js +0 -84
- package/dist/intelligence/slicing-metrics.d.ts +0 -62
- package/dist/intelligence/slicing-metrics.js +0 -202
- package/dist/intelligence/slicing-templates.d.ts +0 -81
- package/dist/intelligence/slicing-templates.js +0 -420
- package/dist/intelligence/split-suggester.d.ts +0 -69
- package/dist/intelligence/split-suggester.js +0 -176
- package/dist/intelligence/stream-generator.d.ts +0 -90
- package/dist/intelligence/stream-generator.js +0 -452
- package/dist/telemetry/telemetry-types.d.ts +0 -85
- package/dist/telemetry/telemetry-types.js +0 -1
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Learning engine for adaptive threshold adjustment in Orchex Learn.
|
|
3
|
-
* Correlates context characteristics with execution success and adapts thresholds.
|
|
4
|
-
*/
|
|
5
|
-
import * as fs from 'fs/promises';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import { createLogger } from '../logging.js';
|
|
8
|
-
const log = createLogger('learning-engine');
|
|
9
|
-
/** Default thresholds before learning */
|
|
10
|
-
export const DEFAULT_THRESHOLDS = {
|
|
11
|
-
maxOwnsCount: {
|
|
12
|
-
code: 4,
|
|
13
|
-
docs: 6,
|
|
14
|
-
tutorial: 3,
|
|
15
|
-
'integration-guide': 3,
|
|
16
|
-
test: 4,
|
|
17
|
-
migration: 3,
|
|
18
|
-
'api-reference': 4,
|
|
19
|
-
other: 4,
|
|
20
|
-
},
|
|
21
|
-
maxReadsCount: {
|
|
22
|
-
code: 4,
|
|
23
|
-
docs: 3,
|
|
24
|
-
tutorial: 4,
|
|
25
|
-
'integration-guide': 4,
|
|
26
|
-
test: 5,
|
|
27
|
-
migration: 4,
|
|
28
|
-
'api-reference': 3,
|
|
29
|
-
other: 4,
|
|
30
|
-
},
|
|
31
|
-
maxContextTokens: {
|
|
32
|
-
code: 140000,
|
|
33
|
-
docs: 80000,
|
|
34
|
-
tutorial: 100000,
|
|
35
|
-
'integration-guide': 100000,
|
|
36
|
-
test: 140000,
|
|
37
|
-
migration: 160000,
|
|
38
|
-
'api-reference': 80000,
|
|
39
|
-
other: 100000,
|
|
40
|
-
},
|
|
41
|
-
globalSoftLimit: 140000,
|
|
42
|
-
globalHardLimit: 180000,
|
|
43
|
-
sampleCount: 0,
|
|
44
|
-
confidence: 'low',
|
|
45
|
-
lastUpdated: new Date().toISOString(),
|
|
46
|
-
version: 1,
|
|
47
|
-
};
|
|
48
|
-
/** Minimum samples required for learning */
|
|
49
|
-
export const MIN_SAMPLES_FOR_LEARNING = 20;
|
|
50
|
-
/** Minimum samples for high confidence */
|
|
51
|
-
export const HIGH_CONFIDENCE_THRESHOLD = 100;
|
|
52
|
-
/** Minimum samples for medium confidence */
|
|
53
|
-
export const MEDIUM_CONFIDENCE_THRESHOLD = 50;
|
|
54
|
-
/**
|
|
55
|
-
* Determine confidence level from sample count.
|
|
56
|
-
*/
|
|
57
|
-
export function getConfidenceLevel(sampleCount) {
|
|
58
|
-
if (sampleCount >= HIGH_CONFIDENCE_THRESHOLD)
|
|
59
|
-
return 'high';
|
|
60
|
-
if (sampleCount >= MEDIUM_CONFIDENCE_THRESHOLD)
|
|
61
|
-
return 'medium';
|
|
62
|
-
return 'low';
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Categorize a stream based on its name and plan.
|
|
66
|
-
* Order matters: more specific patterns before general ones.
|
|
67
|
-
* Returns 'code' as default since most uncategorized streams are implementation work.
|
|
68
|
-
*/
|
|
69
|
-
export function categorizeStream(name, plan) {
|
|
70
|
-
const text = `${name} ${plan ?? ''}`.toLowerCase();
|
|
71
|
-
// Test streams (most specific)
|
|
72
|
-
if (text.includes('test') || text.includes('spec'))
|
|
73
|
-
return 'test';
|
|
74
|
-
// Migration streams (includes refactor/restructure patterns)
|
|
75
|
-
if (text.includes('migration') ||
|
|
76
|
-
text.includes('migrate') ||
|
|
77
|
-
text.includes('refactor') ||
|
|
78
|
-
text.includes('restructure')) {
|
|
79
|
-
return 'migration';
|
|
80
|
-
}
|
|
81
|
-
// Integration guides (check before generic 'guide' match)
|
|
82
|
-
if (text.includes('integration') && (text.includes('guide') || text.includes('doc')))
|
|
83
|
-
return 'integration-guide';
|
|
84
|
-
// Tutorials (explicit 'tutorial' keyword or 'getting-started')
|
|
85
|
-
if (text.includes('tutorial') || text.includes('getting-started') || text.includes('getting started'))
|
|
86
|
-
return 'tutorial';
|
|
87
|
-
// API reference
|
|
88
|
-
if (text.includes('api') && (text.includes('ref') || text.includes('doc')))
|
|
89
|
-
return 'api-reference';
|
|
90
|
-
// Documentation (includes user-guide, generic guide, doc, readme, md)
|
|
91
|
-
if (text.includes('doc') || text.includes('readme') || text.includes('md') || text.includes('guide'))
|
|
92
|
-
return 'docs';
|
|
93
|
-
// Default to code - most streams without specific keywords are implementation work
|
|
94
|
-
return 'code';
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Correlate context characteristics with success from telemetry events.
|
|
98
|
-
*/
|
|
99
|
-
export function correlateContextWithSuccess(events, minSamples = MIN_SAMPLES_FOR_LEARNING) {
|
|
100
|
-
const correlations = [];
|
|
101
|
-
const insights = [];
|
|
102
|
-
// Filter to stream events with context data
|
|
103
|
-
const streamEvents = events.filter(e => (e.eventType === 'stream_complete' || e.eventType === 'stream_failed') &&
|
|
104
|
-
e.contextTokensEstimated !== undefined);
|
|
105
|
-
if (streamEvents.length < minSamples) {
|
|
106
|
-
return {
|
|
107
|
-
correlations: [],
|
|
108
|
-
suggestedThresholds: {},
|
|
109
|
-
insights: [`Not enough data for learning. Have ${streamEvents.length}, need ${minSamples} samples.`],
|
|
110
|
-
hasEnoughData: false,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
// Calculate overall success rate
|
|
114
|
-
const successCount = streamEvents.filter(e => e.eventType === 'stream_complete').length;
|
|
115
|
-
const overallSuccessRate = successCount / streamEvents.length;
|
|
116
|
-
insights.push(`Overall success rate: ${(overallSuccessRate * 100).toFixed(1)}% (${successCount}/${streamEvents.length})`);
|
|
117
|
-
// Analyze context size buckets
|
|
118
|
-
const contextBuckets = analyzeContextSizeBuckets(streamEvents);
|
|
119
|
-
if (contextBuckets.optimalRange) {
|
|
120
|
-
correlations.push({
|
|
121
|
-
factor: 'contextTokens',
|
|
122
|
-
successRate: contextBuckets.optimalSuccessRate,
|
|
123
|
-
sampleSize: contextBuckets.optimalSampleSize,
|
|
124
|
-
threshold: contextBuckets.optimalRange.max,
|
|
125
|
-
recommendation: `Optimal context size: ${contextBuckets.optimalRange.min.toLocaleString()}-${contextBuckets.optimalRange.max.toLocaleString()} tokens (${(contextBuckets.optimalSuccessRate * 100).toFixed(1)}% success)`,
|
|
126
|
-
});
|
|
127
|
-
insights.push(correlations[correlations.length - 1].recommendation);
|
|
128
|
-
}
|
|
129
|
-
// Analyze budget violations
|
|
130
|
-
const violationAnalysis = analyzeViolations(streamEvents);
|
|
131
|
-
if (violationAnalysis.softViolationSuccessRate !== undefined) {
|
|
132
|
-
correlations.push({
|
|
133
|
-
factor: 'softViolation',
|
|
134
|
-
successRate: violationAnalysis.softViolationSuccessRate,
|
|
135
|
-
sampleSize: violationAnalysis.softViolationCount,
|
|
136
|
-
threshold: 0,
|
|
137
|
-
recommendation: violationAnalysis.softViolationSuccessRate < 0.7
|
|
138
|
-
? 'Soft limit violations have low success rate - consider lowering soft limit'
|
|
139
|
-
: 'Soft limit violations acceptable - current threshold is appropriate',
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
// Generate suggested thresholds
|
|
143
|
-
const suggestedThresholds = generateSuggestedThresholds(correlations, contextBuckets, streamEvents.length);
|
|
144
|
-
return {
|
|
145
|
-
correlations,
|
|
146
|
-
suggestedThresholds,
|
|
147
|
-
insights,
|
|
148
|
-
hasEnoughData: true,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
function analyzeContextSizeBuckets(events) {
|
|
152
|
-
// Define buckets by token count
|
|
153
|
-
const buckets = [
|
|
154
|
-
{ min: 0, max: 50000 },
|
|
155
|
-
{ min: 50000, max: 100000 },
|
|
156
|
-
{ min: 100000, max: 150000 },
|
|
157
|
-
{ min: 150000, max: 200000 },
|
|
158
|
-
];
|
|
159
|
-
let bestBucket = buckets[0];
|
|
160
|
-
let bestSuccessRate = 0;
|
|
161
|
-
let bestSampleSize = 0;
|
|
162
|
-
for (const bucket of buckets) {
|
|
163
|
-
const bucketEvents = events.filter(e => e.contextTokensEstimated !== undefined &&
|
|
164
|
-
e.contextTokensEstimated >= bucket.min &&
|
|
165
|
-
e.contextTokensEstimated < bucket.max);
|
|
166
|
-
if (bucketEvents.length >= 5) { // Minimum 5 samples per bucket
|
|
167
|
-
const successCount = bucketEvents.filter(e => e.eventType === 'stream_complete').length;
|
|
168
|
-
const successRate = successCount / bucketEvents.length;
|
|
169
|
-
if (successRate > bestSuccessRate) {
|
|
170
|
-
bestSuccessRate = successRate;
|
|
171
|
-
bestBucket = bucket;
|
|
172
|
-
bestSampleSize = bucketEvents.length;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return {
|
|
177
|
-
optimalRange: bestSampleSize >= 5 ? bestBucket : undefined,
|
|
178
|
-
optimalSuccessRate: bestSuccessRate,
|
|
179
|
-
optimalSampleSize: bestSampleSize,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
function analyzeViolations(events) {
|
|
183
|
-
const softViolations = events.filter(e => e.budgetViolationType === 'soft');
|
|
184
|
-
const hardViolations = events.filter(e => e.budgetViolationType === 'hard');
|
|
185
|
-
return {
|
|
186
|
-
softViolationCount: softViolations.length,
|
|
187
|
-
softViolationSuccessRate: softViolations.length >= 5
|
|
188
|
-
? softViolations.filter(e => e.eventType === 'stream_complete').length / softViolations.length
|
|
189
|
-
: undefined,
|
|
190
|
-
hardViolationCount: hardViolations.length,
|
|
191
|
-
hardViolationSuccessRate: hardViolations.length >= 5
|
|
192
|
-
? hardViolations.filter(e => e.eventType === 'stream_complete').length / hardViolations.length
|
|
193
|
-
: undefined,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function generateSuggestedThresholds(correlations, bucketAnalysis, sampleCount) {
|
|
197
|
-
const suggestions = {
|
|
198
|
-
sampleCount,
|
|
199
|
-
confidence: getConfidenceLevel(sampleCount),
|
|
200
|
-
lastUpdated: new Date().toISOString(),
|
|
201
|
-
version: 1,
|
|
202
|
-
};
|
|
203
|
-
// Suggest global limits based on optimal bucket
|
|
204
|
-
if (bucketAnalysis.optimalRange) {
|
|
205
|
-
suggestions.globalSoftLimit = bucketAnalysis.optimalRange.max;
|
|
206
|
-
suggestions.globalHardLimit = Math.round(bucketAnalysis.optimalRange.max * 1.3);
|
|
207
|
-
}
|
|
208
|
-
return suggestions;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Update thresholds based on learning results.
|
|
212
|
-
*/
|
|
213
|
-
export function updateThresholds(current, learningResult, learningRate = 0.3) {
|
|
214
|
-
if (!learningResult.hasEnoughData) {
|
|
215
|
-
return current;
|
|
216
|
-
}
|
|
217
|
-
const updated = { ...current };
|
|
218
|
-
const suggestions = learningResult.suggestedThresholds;
|
|
219
|
-
// Apply learning with rate control
|
|
220
|
-
if (suggestions.globalSoftLimit !== undefined) {
|
|
221
|
-
updated.globalSoftLimit = Math.round(current.globalSoftLimit * (1 - learningRate) +
|
|
222
|
-
suggestions.globalSoftLimit * learningRate);
|
|
223
|
-
}
|
|
224
|
-
if (suggestions.globalHardLimit !== undefined) {
|
|
225
|
-
updated.globalHardLimit = Math.round(current.globalHardLimit * (1 - learningRate) +
|
|
226
|
-
suggestions.globalHardLimit * learningRate);
|
|
227
|
-
}
|
|
228
|
-
if (suggestions.sampleCount !== undefined) {
|
|
229
|
-
updated.sampleCount = suggestions.sampleCount;
|
|
230
|
-
}
|
|
231
|
-
if (suggestions.confidence !== undefined) {
|
|
232
|
-
updated.confidence = suggestions.confidence;
|
|
233
|
-
}
|
|
234
|
-
updated.lastUpdated = new Date().toISOString();
|
|
235
|
-
return updated;
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Get the path for learned thresholds storage.
|
|
239
|
-
*/
|
|
240
|
-
function getThresholdsPath(projectDir) {
|
|
241
|
-
return path.join(projectDir, '.orchex', 'learn', 'thresholds.json');
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Save learned thresholds to project directory.
|
|
245
|
-
*/
|
|
246
|
-
export async function saveLearnedThresholds(projectDir, thresholds) {
|
|
247
|
-
const filePath = getThresholdsPath(projectDir);
|
|
248
|
-
const dir = path.dirname(filePath);
|
|
249
|
-
await fs.mkdir(dir, { recursive: true });
|
|
250
|
-
await fs.writeFile(filePath, JSON.stringify(thresholds, null, 2), 'utf-8');
|
|
251
|
-
}
|
|
252
|
-
/**
|
|
253
|
-
* Load learned thresholds from project directory.
|
|
254
|
-
*/
|
|
255
|
-
export async function loadLearnedThresholds(projectDir) {
|
|
256
|
-
const filePath = getThresholdsPath(projectDir);
|
|
257
|
-
try {
|
|
258
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
259
|
-
const parsed = JSON.parse(content);
|
|
260
|
-
// Validate version and structure
|
|
261
|
-
if (parsed.version !== 1) {
|
|
262
|
-
log.warn({ version: parsed.version }, 'unknown_thresholds_version');
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
return parsed;
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
// File doesn't exist or is invalid
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Get thresholds for a project, falling back to defaults.
|
|
274
|
-
*/
|
|
275
|
-
export async function getThresholds(projectDir) {
|
|
276
|
-
const loaded = await loadLearnedThresholds(projectDir);
|
|
277
|
-
return loaded ?? { ...DEFAULT_THRESHOLDS };
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Get recommended limit for a stream category.
|
|
281
|
-
*/
|
|
282
|
-
export function getRecommendedLimit(thresholds, category, limitType) {
|
|
283
|
-
switch (limitType) {
|
|
284
|
-
case 'owns':
|
|
285
|
-
return thresholds.maxOwnsCount[category] ?? DEFAULT_THRESHOLDS.maxOwnsCount.other;
|
|
286
|
-
case 'reads':
|
|
287
|
-
return thresholds.maxReadsCount[category] ?? DEFAULT_THRESHOLDS.maxReadsCount.other;
|
|
288
|
-
case 'tokens':
|
|
289
|
-
return thresholds.maxContextTokens[category] ?? thresholds.globalSoftLimit;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function getEventsPath(projectDir) {
|
|
293
|
-
return path.join(projectDir, '.orchex', 'learn', 'events.jsonl');
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Convert a stream result to a TelemetryEvent for learning.
|
|
297
|
-
*/
|
|
298
|
-
export function streamResultToTelemetryEvent(result, provider, model) {
|
|
299
|
-
return {
|
|
300
|
-
id: `learn-${Date.now()}-${result.id}`,
|
|
301
|
-
sessionHash: 'local',
|
|
302
|
-
eventType: result.status === 'complete' ? 'stream_complete' : 'stream_failed',
|
|
303
|
-
timestamp: new Date().toISOString(),
|
|
304
|
-
durationMs: result.executionTimeMs,
|
|
305
|
-
success: result.status === 'complete',
|
|
306
|
-
tokensInput: result.tokensUsed?.input,
|
|
307
|
-
tokensOutput: result.tokensUsed?.output,
|
|
308
|
-
provider,
|
|
309
|
-
model,
|
|
310
|
-
contextTokensEstimated: result.contextMetrics?.estimatedTokens,
|
|
311
|
-
contextBudgetUtilization: result.contextMetrics?.budgetUtilization,
|
|
312
|
-
budgetViolationType: result.contextMetrics?.violationType,
|
|
313
|
-
};
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Append telemetry events to the local events file (.orchex/learn/events.jsonl).
|
|
317
|
-
* Uses JSONL (one JSON object per line) for append-friendly storage.
|
|
318
|
-
*/
|
|
319
|
-
export async function appendLocalEvents(projectDir, events) {
|
|
320
|
-
if (events.length === 0)
|
|
321
|
-
return;
|
|
322
|
-
const filePath = getEventsPath(projectDir);
|
|
323
|
-
const dir = path.dirname(filePath);
|
|
324
|
-
await fs.mkdir(dir, { recursive: true });
|
|
325
|
-
const lines = events.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
326
|
-
await fs.appendFile(filePath, lines, 'utf-8');
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Load all local telemetry events from the JSONL file.
|
|
330
|
-
* Skips malformed lines silently.
|
|
331
|
-
*/
|
|
332
|
-
export async function loadLocalEvents(projectDir) {
|
|
333
|
-
const filePath = getEventsPath(projectDir);
|
|
334
|
-
try {
|
|
335
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
336
|
-
const events = [];
|
|
337
|
-
for (const line of content.split('\n')) {
|
|
338
|
-
if (!line.trim())
|
|
339
|
-
continue;
|
|
340
|
-
try {
|
|
341
|
-
events.push(JSON.parse(line));
|
|
342
|
-
}
|
|
343
|
-
catch {
|
|
344
|
-
// Skip malformed lines
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return events;
|
|
348
|
-
}
|
|
349
|
-
catch {
|
|
350
|
-
// File doesn't exist
|
|
351
|
-
return [];
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
// ============================================================================
|
|
355
|
-
// LE.2: Learning cycle — correlate, update, persist
|
|
356
|
-
// ============================================================================
|
|
357
|
-
/**
|
|
358
|
-
* Run a complete learning cycle: load events → correlate → update → save.
|
|
359
|
-
* Returns the learning result (or null if not enough data).
|
|
360
|
-
* This is the main entry point called after an orchestration completes.
|
|
361
|
-
*/
|
|
362
|
-
export async function runLearningCycle(projectDir) {
|
|
363
|
-
const events = await loadLocalEvents(projectDir);
|
|
364
|
-
if (events.length < MIN_SAMPLES_FOR_LEARNING) {
|
|
365
|
-
log.debug({ eventCount: events.length, minRequired: MIN_SAMPLES_FOR_LEARNING }, 'learning_skipped_insufficient_data');
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
const learningResult = correlateContextWithSuccess(events);
|
|
369
|
-
if (!learningResult.hasEnoughData) {
|
|
370
|
-
log.debug({ insights: learningResult.insights }, 'learning_insufficient_context_data');
|
|
371
|
-
return learningResult;
|
|
372
|
-
}
|
|
373
|
-
// Load current thresholds and apply learning
|
|
374
|
-
const current = await getThresholds(projectDir);
|
|
375
|
-
const updated = updateThresholds(current, learningResult);
|
|
376
|
-
// Persist updated thresholds
|
|
377
|
-
await saveLearnedThresholds(projectDir, updated);
|
|
378
|
-
log.info({
|
|
379
|
-
sampleCount: updated.sampleCount,
|
|
380
|
-
confidence: updated.confidence,
|
|
381
|
-
globalSoftLimit: updated.globalSoftLimit,
|
|
382
|
-
globalHardLimit: updated.globalHardLimit,
|
|
383
|
-
}, 'learning_cycle_completed');
|
|
384
|
-
return learningResult;
|
|
385
|
-
}
|
|
386
|
-
// ============================================================================
|
|
387
|
-
// LE.4: Cloud aggregation — run learning on telemetry store events
|
|
388
|
-
// ============================================================================
|
|
389
|
-
/**
|
|
390
|
-
* Run learning cycle from telemetry store events (cloud mode).
|
|
391
|
-
* Aggregates across all users for global threshold learning.
|
|
392
|
-
*/
|
|
393
|
-
export async function runCloudLearningCycle(events, projectDir) {
|
|
394
|
-
if (events.length < MIN_SAMPLES_FOR_LEARNING) {
|
|
395
|
-
return null;
|
|
396
|
-
}
|
|
397
|
-
const learningResult = correlateContextWithSuccess(events);
|
|
398
|
-
if (!learningResult.hasEnoughData) {
|
|
399
|
-
return learningResult;
|
|
400
|
-
}
|
|
401
|
-
const current = await getThresholds(projectDir);
|
|
402
|
-
const updated = updateThresholds(current, learningResult);
|
|
403
|
-
await saveLearnedThresholds(projectDir, updated);
|
|
404
|
-
log.info({
|
|
405
|
-
sampleCount: updated.sampleCount,
|
|
406
|
-
confidence: updated.confidence,
|
|
407
|
-
source: 'cloud_aggregation',
|
|
408
|
-
}, 'cloud_learning_cycle_completed');
|
|
409
|
-
return learningResult;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Format a learning report for display.
|
|
413
|
-
*/
|
|
414
|
-
export function formatLearningReport(result) {
|
|
415
|
-
const lines = ['=== Learning Report ===', ''];
|
|
416
|
-
if (!result.hasEnoughData) {
|
|
417
|
-
lines.push('Insufficient data for learning.');
|
|
418
|
-
lines.push(...result.insights);
|
|
419
|
-
return lines.join('\n');
|
|
420
|
-
}
|
|
421
|
-
lines.push('Insights:');
|
|
422
|
-
for (const insight of result.insights) {
|
|
423
|
-
lines.push(` • ${insight}`);
|
|
424
|
-
}
|
|
425
|
-
if (result.correlations.length > 0) {
|
|
426
|
-
lines.push('', 'Correlations:');
|
|
427
|
-
for (const corr of result.correlations) {
|
|
428
|
-
lines.push(` • ${corr.factor}: ${(corr.successRate * 100).toFixed(1)}% success (n=${corr.sampleSize})`);
|
|
429
|
-
lines.push(` → ${corr.recommendation}`);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return lines.join('\n');
|
|
433
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Learning feedback module for adaptive heuristic adjustment.
|
|
3
|
-
* Analyzes execution history to improve slicing thresholds.
|
|
4
|
-
*/
|
|
5
|
-
import type { StreamCategory } from './learning-engine.js';
|
|
6
|
-
/**
|
|
7
|
-
* Heuristic thresholds for stream validation.
|
|
8
|
-
*/
|
|
9
|
-
export interface HeuristicThresholds {
|
|
10
|
-
/** Maximum recommended owns count */
|
|
11
|
-
maxOwnsCount: number;
|
|
12
|
-
/** Maximum recommended reads count */
|
|
13
|
-
maxReadsCount: number;
|
|
14
|
-
/** Maximum "and" conjunctions in plan */
|
|
15
|
-
maxPlanAndCount: number;
|
|
16
|
-
/** Maximum expected output tokens */
|
|
17
|
-
maxOutputTokens: number;
|
|
18
|
-
/** Minimum acceptable success rate */
|
|
19
|
-
minSuccessRate: number;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* A suggested threshold adjustment based on learning.
|
|
23
|
-
*/
|
|
24
|
-
export interface ThresholdAdjustment {
|
|
25
|
-
/** Field being adjusted */
|
|
26
|
-
field: keyof HeuristicThresholds;
|
|
27
|
-
/** Previous value */
|
|
28
|
-
oldValue: number;
|
|
29
|
-
/** Suggested new value */
|
|
30
|
-
newValue: number;
|
|
31
|
-
/** Reason for adjustment */
|
|
32
|
-
reason: string;
|
|
33
|
-
/** Confidence in the adjustment */
|
|
34
|
-
confidence: 'high' | 'medium' | 'low';
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Metrics for a stream category (from execution history).
|
|
38
|
-
*/
|
|
39
|
-
export interface CategoryMetrics {
|
|
40
|
-
/** Category name */
|
|
41
|
-
category: StreamCategory;
|
|
42
|
-
/** Total executions */
|
|
43
|
-
totalExecutions: number;
|
|
44
|
-
/** Successful completions */
|
|
45
|
-
successCount: number;
|
|
46
|
-
/** Failures */
|
|
47
|
-
failureCount: number;
|
|
48
|
-
/** Timeout failures specifically */
|
|
49
|
-
timeoutCount: number;
|
|
50
|
-
/** Average owns count for this category */
|
|
51
|
-
avgOwnsCount: number;
|
|
52
|
-
/** Average reads count for this category */
|
|
53
|
-
avgReadsCount: number;
|
|
54
|
-
/** Average output tokens */
|
|
55
|
-
avgOutputTokens: number;
|
|
56
|
-
/** Success rate (0-1) */
|
|
57
|
-
successRate: number;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Default heuristic thresholds (conservative starting point).
|
|
61
|
-
*/
|
|
62
|
-
export declare const DEFAULT_THRESHOLDS: HeuristicThresholds;
|
|
63
|
-
/**
|
|
64
|
-
* Minimum samples required before learning from a category.
|
|
65
|
-
*/
|
|
66
|
-
export declare const MIN_CATEGORY_SAMPLES = 5;
|
|
67
|
-
/**
|
|
68
|
-
* Minimum total samples before suggesting adjustments.
|
|
69
|
-
*/
|
|
70
|
-
export declare const MIN_TOTAL_SAMPLES = 20;
|
|
71
|
-
/**
|
|
72
|
-
* Learn from execution metrics and suggest threshold adjustments.
|
|
73
|
-
*
|
|
74
|
-
* @param metrics - Map of category to execution metrics
|
|
75
|
-
* @param currentThresholds - Current threshold values
|
|
76
|
-
* @param minSamples - Minimum samples required for learning
|
|
77
|
-
* @returns Array of suggested threshold adjustments
|
|
78
|
-
*/
|
|
79
|
-
export declare function learnFromMetrics(metrics: Map<string, CategoryMetrics>, currentThresholds?: HeuristicThresholds, minSamples?: number): ThresholdAdjustment[];
|
|
80
|
-
/**
|
|
81
|
-
* Apply adjustments to thresholds with learning rate control.
|
|
82
|
-
*
|
|
83
|
-
* @param currentThresholds - Current threshold values
|
|
84
|
-
* @param adjustments - Suggested adjustments
|
|
85
|
-
* @param learningRate - How much to move toward suggested values (0-1)
|
|
86
|
-
* @returns Updated thresholds
|
|
87
|
-
*/
|
|
88
|
-
export declare function applyAdjustments(currentThresholds: HeuristicThresholds, adjustments: ThresholdAdjustment[], learningRate?: number): HeuristicThresholds;
|
|
89
|
-
/**
|
|
90
|
-
* Format adjustments as a human-readable report.
|
|
91
|
-
*/
|
|
92
|
-
export declare function formatAdjustmentsReport(adjustments: ThresholdAdjustment[]): string;
|
|
93
|
-
/**
|
|
94
|
-
* Generate insights from category metrics.
|
|
95
|
-
*/
|
|
96
|
-
export declare function generateCategoryInsights(metrics: Map<string, CategoryMetrics>): string[];
|