cognitive-core 0.2.0 → 0.2.1
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/.claude/settings.json +111 -2
- package/.sessionlog/settings.json +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/learning/index.d.ts +1 -1
- package/dist/learning/index.d.ts.map +1 -1
- package/dist/learning/index.js.map +1 -1
- package/dist/learning/unified-pipeline.d.ts +30 -0
- package/dist/learning/unified-pipeline.d.ts.map +1 -1
- package/dist/learning/unified-pipeline.js +207 -0
- package/dist/learning/unified-pipeline.js.map +1 -1
- package/dist/memory/candidate-retrieval.d.ts.map +1 -1
- package/dist/memory/candidate-retrieval.js +3 -1
- package/dist/memory/candidate-retrieval.js.map +1 -1
- package/dist/utils/error-classifier.js +8 -8
- package/dist/utils/error-classifier.js.map +1 -1
- package/dist/workspace/efficacy-toolkit.d.ts +164 -0
- package/dist/workspace/efficacy-toolkit.d.ts.map +1 -0
- package/dist/workspace/efficacy-toolkit.js +281 -0
- package/dist/workspace/efficacy-toolkit.js.map +1 -0
- package/dist/workspace/index.d.ts +2 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +3 -1
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/templates/index.d.ts +3 -0
- package/dist/workspace/templates/index.d.ts.map +1 -1
- package/dist/workspace/templates/index.js +6 -0
- package/dist/workspace/templates/index.js.map +1 -1
- package/dist/workspace/templates/playbook-decay-detection.d.ts +46 -0
- package/dist/workspace/templates/playbook-decay-detection.d.ts.map +1 -0
- package/dist/workspace/templates/playbook-decay-detection.js +197 -0
- package/dist/workspace/templates/playbook-decay-detection.js.map +1 -0
- package/dist/workspace/templates/playbook-efficacy-audit.d.ts +46 -0
- package/dist/workspace/templates/playbook-efficacy-audit.d.ts.map +1 -0
- package/dist/workspace/templates/playbook-efficacy-audit.js +160 -0
- package/dist/workspace/templates/playbook-efficacy-audit.js.map +1 -0
- package/dist/workspace/templates/playbook-lifecycle-review.d.ts +51 -0
- package/dist/workspace/templates/playbook-lifecycle-review.d.ts.map +1 -0
- package/dist/workspace/templates/playbook-lifecycle-review.js +187 -0
- package/dist/workspace/templates/playbook-lifecycle-review.js.map +1 -0
- package/package.json +7 -1
- package/src/index.ts +27 -0
- package/src/learning/index.ts +1 -0
- package/src/learning/unified-pipeline.ts +271 -1
- package/src/memory/candidate-retrieval.ts +2 -1
- package/src/utils/error-classifier.ts +8 -8
- package/src/workspace/efficacy-toolkit.ts +496 -0
- package/src/workspace/index.ts +29 -0
- package/src/workspace/templates/index.ts +24 -0
- package/src/workspace/templates/playbook-decay-detection.ts +272 -0
- package/src/workspace/templates/playbook-efficacy-audit.ts +246 -0
- package/src/workspace/templates/playbook-lifecycle-review.ts +274 -0
- package/tests/fixtures/behavioral-trajectories.ts +210 -0
- package/tests/integration/pipeline-data-correctness.test.ts +794 -0
- package/tests/learning/meta-learner.test.ts +418 -0
- package/tests/learning/pipeline-memory-updates.test.ts +721 -0
- package/tests/learning/unified-pipeline-efficacy.test.ts +232 -0
- package/tests/memory/candidate-retrieval.test.ts +167 -0
- package/tests/memory/meta.test.ts +399 -0
- package/tests/search/evaluator.test.ts +257 -0
- package/tests/search/verification-runner.test.ts +357 -0
- package/tests/utils/error-classifier.test.ts +149 -0
- package/tests/utils/trajectory-helpers.test.ts +163 -0
- package/tests/workspace/efficacy-toolkit.test.ts +404 -0
- package/tests/workspace/templates/playbook-efficacy.test.ts +377 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { MetaLearner, createMetaLearner } from '../../src/learning/meta-learner.js';
|
|
3
|
+
import { MetaMemory, createMetaMemory } from '../../src/memory/meta.js';
|
|
4
|
+
import { createSqlitePersistence } from '../../src/persistence/index.js';
|
|
5
|
+
import { createTrajectory } from '../../src/types/trajectory.js';
|
|
6
|
+
import { createTask } from '../../src/types/task.js';
|
|
7
|
+
import { createStep } from '../../src/types/step.js';
|
|
8
|
+
import { successOutcome, failureOutcome } from '../../src/types/outcome.js';
|
|
9
|
+
import type { Trajectory } from '../../src/types/trajectory.js';
|
|
10
|
+
import type { PlaybookMatch } from '../../src/memory/playbook.js';
|
|
11
|
+
import type { RoutingDecision } from '../../src/learning/meta-learner.js';
|
|
12
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { tmpdir } from 'node:os';
|
|
15
|
+
|
|
16
|
+
function makeRouting(overrides?: Partial<RoutingDecision>): RoutingDecision {
|
|
17
|
+
return {
|
|
18
|
+
strategy: 'direct',
|
|
19
|
+
confidence: 0.8,
|
|
20
|
+
estimatedBudget: 10,
|
|
21
|
+
reasoning: 'direct match',
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function makeTrajectory(overrides?: {
|
|
27
|
+
success?: boolean;
|
|
28
|
+
steps?: Array<{ action: string; observation?: string; thought?: string }>;
|
|
29
|
+
llmCalls?: number;
|
|
30
|
+
}): Trajectory {
|
|
31
|
+
const success = overrides?.success ?? true;
|
|
32
|
+
const rawSteps = overrides?.steps ?? [
|
|
33
|
+
{ action: 'Read src/index.ts', observation: 'file contents' },
|
|
34
|
+
{ action: 'Edit src/index.ts', observation: 'edited' },
|
|
35
|
+
];
|
|
36
|
+
const steps = rawSteps.map((s) =>
|
|
37
|
+
createStep({
|
|
38
|
+
action: s.action,
|
|
39
|
+
observation: s.observation ?? '',
|
|
40
|
+
thought: s.thought,
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
return createTrajectory({
|
|
44
|
+
task: createTask({ domain: 'code', description: 'fix bug' }),
|
|
45
|
+
steps,
|
|
46
|
+
outcome: success ? successOutcome('fixed') : failureOutcome('failed'),
|
|
47
|
+
agentId: 'agent-1',
|
|
48
|
+
llmCalls: overrides?.llmCalls,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makePlaybookMatch(name: string, tactics: string[] = []): PlaybookMatch {
|
|
53
|
+
return {
|
|
54
|
+
playbook: {
|
|
55
|
+
id: `pb-${name}`,
|
|
56
|
+
name,
|
|
57
|
+
domain: 'code',
|
|
58
|
+
trigger: { pattern: 'test', examples: [] },
|
|
59
|
+
guidance: { tactics, avoidances: [], context: '' },
|
|
60
|
+
confidence: 0.8,
|
|
61
|
+
successCount: 5,
|
|
62
|
+
failureCount: 1,
|
|
63
|
+
createdAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
},
|
|
66
|
+
score: 0.9,
|
|
67
|
+
source: 'text-similarity',
|
|
68
|
+
} as PlaybookMatch;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe('MetaLearner', () => {
|
|
72
|
+
let tempDir: string;
|
|
73
|
+
let persistence: any;
|
|
74
|
+
let metaMemory: MetaMemory;
|
|
75
|
+
let learner: MetaLearner;
|
|
76
|
+
|
|
77
|
+
beforeEach(async () => {
|
|
78
|
+
tempDir = await mkdtemp(join(tmpdir(), 'meta-learner-test-'));
|
|
79
|
+
persistence = createSqlitePersistence({ baseDir: tempDir });
|
|
80
|
+
await persistence.init();
|
|
81
|
+
metaMemory = createMetaMemory(persistence);
|
|
82
|
+
await metaMemory.init();
|
|
83
|
+
learner = createMetaLearner(metaMemory);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
afterEach(async () => {
|
|
87
|
+
persistence.close();
|
|
88
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('generateReflection', () => {
|
|
92
|
+
it('should create and store an observation for a successful trajectory', async () => {
|
|
93
|
+
const trajectory = makeTrajectory({ success: true });
|
|
94
|
+
const routing = makeRouting();
|
|
95
|
+
const playbooks = [makePlaybookMatch('fix-bugs', ['read file', 'edit file'])];
|
|
96
|
+
|
|
97
|
+
const observation = await learner.generateReflection(trajectory, routing, playbooks);
|
|
98
|
+
|
|
99
|
+
expect(observation).toBeDefined();
|
|
100
|
+
expect(observation.trajectoryId).toBe(trajectory.id);
|
|
101
|
+
expect(observation.routing.decision).toBe('direct');
|
|
102
|
+
expect(observation.routing.confidence).toBe(0.8);
|
|
103
|
+
expect(observation.outcome.success).toBe(true);
|
|
104
|
+
expect(observation.lessons.whatWorked.length).toBeGreaterThan(0);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should create observation for a failed trajectory', async () => {
|
|
108
|
+
const trajectory = makeTrajectory({
|
|
109
|
+
success: false,
|
|
110
|
+
steps: [
|
|
111
|
+
{ action: 'Read src/broken.ts', observation: 'error: file not found' },
|
|
112
|
+
{ action: 'Bash npm test', observation: 'error: tests failed' },
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
const routing = makeRouting({ confidence: 0.9 });
|
|
116
|
+
|
|
117
|
+
const observation = await learner.generateReflection(trajectory, routing, []);
|
|
118
|
+
|
|
119
|
+
expect(observation.outcome.success).toBe(false);
|
|
120
|
+
expect(observation.outcome.quality).toBe('poor');
|
|
121
|
+
expect(observation.lessons.whatFailed.length).toBeGreaterThan(0);
|
|
122
|
+
expect(observation.lessons.suggestions.length).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should persist observation to MetaMemory', async () => {
|
|
126
|
+
const trajectory = makeTrajectory();
|
|
127
|
+
await learner.generateReflection(trajectory, makeRouting(), []);
|
|
128
|
+
|
|
129
|
+
const stored = await metaMemory.getByTrajectoryId(trajectory.id);
|
|
130
|
+
expect(stored).toBeDefined();
|
|
131
|
+
expect(stored!.trajectoryId).toBe(trajectory.id);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should detect backtracking when actions repeat', async () => {
|
|
135
|
+
const trajectory = makeTrajectory({
|
|
136
|
+
steps: [
|
|
137
|
+
{ action: 'Read foo.ts' },
|
|
138
|
+
{ action: 'Edit foo.ts' },
|
|
139
|
+
{ action: 'Read foo.ts' },
|
|
140
|
+
{ action: 'Edit foo.ts' },
|
|
141
|
+
{ action: 'Read foo.ts' },
|
|
142
|
+
{ action: 'Edit foo.ts' },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const observation = await learner.generateReflection(trajectory, makeRouting(), []);
|
|
147
|
+
expect(observation.execution.backtrackingOccurred).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should extract tools used', async () => {
|
|
151
|
+
const trajectory = makeTrajectory({
|
|
152
|
+
steps: [
|
|
153
|
+
{ action: 'Read src/index.ts' },
|
|
154
|
+
{ action: 'tool_use: Bash echo hello' },
|
|
155
|
+
{ action: 'Edit src/index.ts' },
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const observation = await learner.generateReflection(trajectory, makeRouting(), []);
|
|
160
|
+
expect(observation.execution.toolsUsed).toContain('Read');
|
|
161
|
+
expect(observation.execution.toolsUsed).toContain('Bash');
|
|
162
|
+
expect(observation.execution.toolsUsed).toContain('Edit');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should detect decomposition usage from step thoughts', async () => {
|
|
166
|
+
const trajectory = makeTrajectory({
|
|
167
|
+
steps: [
|
|
168
|
+
{ action: 'Read foo.ts', thought: 'first, I need to understand the code' },
|
|
169
|
+
{ action: 'Edit foo.ts', thought: 'now fix the bug' },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const observation = await learner.generateReflection(trajectory, makeRouting(), []);
|
|
174
|
+
expect(observation.execution.decompositionUsed).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should count refinement iterations', async () => {
|
|
178
|
+
const trajectory = makeTrajectory({
|
|
179
|
+
steps: [
|
|
180
|
+
{ action: 'Edit foo.ts', observation: 'error: syntax error' },
|
|
181
|
+
{ action: 'Edit foo.ts', thought: 'try fixing the syntax' },
|
|
182
|
+
{ action: 'Bash npm test', observation: 'test failed' },
|
|
183
|
+
{ action: 'Edit foo.ts', thought: 'fix the test failure' },
|
|
184
|
+
],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const observation = await learner.generateReflection(trajectory, makeRouting(), []);
|
|
188
|
+
expect(observation.execution.refinementIterations).toBeGreaterThanOrEqual(1);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('assessOutcomeQuality (via generateReflection)', () => {
|
|
193
|
+
it('should rate excellent when effort is much less than estimate', async () => {
|
|
194
|
+
const trajectory = makeTrajectory({ success: true, llmCalls: 3 });
|
|
195
|
+
const routing = makeRouting({ estimatedBudget: 10 });
|
|
196
|
+
const obs = await learner.generateReflection(trajectory, routing, []);
|
|
197
|
+
// 3/10 = 0.3 <= 0.5 → excellent
|
|
198
|
+
expect(obs.outcome.quality).toBe('excellent');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should rate good when effort is within estimate', async () => {
|
|
202
|
+
const trajectory = makeTrajectory({ success: true, llmCalls: 8 });
|
|
203
|
+
const routing = makeRouting({ estimatedBudget: 10 });
|
|
204
|
+
const obs = await learner.generateReflection(trajectory, routing, []);
|
|
205
|
+
// 8/10 = 0.8 <= 1.0 → good
|
|
206
|
+
expect(obs.outcome.quality).toBe('good');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should rate acceptable when effort moderately overruns', async () => {
|
|
210
|
+
const trajectory = makeTrajectory({ success: true, llmCalls: 15 });
|
|
211
|
+
const routing = makeRouting({ estimatedBudget: 10 });
|
|
212
|
+
const obs = await learner.generateReflection(trajectory, routing, []);
|
|
213
|
+
// 15/10 = 1.5 <= 2.0 → acceptable
|
|
214
|
+
expect(obs.outcome.quality).toBe('acceptable');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should rate poor when effort vastly overruns', async () => {
|
|
218
|
+
const trajectory = makeTrajectory({ success: true, llmCalls: 25 });
|
|
219
|
+
const routing = makeRouting({ estimatedBudget: 10 });
|
|
220
|
+
const obs = await learner.generateReflection(trajectory, routing, []);
|
|
221
|
+
// 25/10 = 2.5 > 2.0 → poor
|
|
222
|
+
expect(obs.outcome.quality).toBe('poor');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should rate poor for failed trajectories regardless of effort', async () => {
|
|
226
|
+
const trajectory = makeTrajectory({ success: false, llmCalls: 3 });
|
|
227
|
+
const routing = makeRouting({ estimatedBudget: 10 });
|
|
228
|
+
const obs = await learner.generateReflection(trajectory, routing, []);
|
|
229
|
+
expect(obs.outcome.quality).toBe('poor');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
describe('assessRetrievalQuality (via generateReflection)', () => {
|
|
234
|
+
it('should be neutral when no playbooks used', async () => {
|
|
235
|
+
const trajectory = makeTrajectory({ success: true });
|
|
236
|
+
const obs = await learner.generateReflection(trajectory, makeRouting(), []);
|
|
237
|
+
expect(obs.memoryUsage.retrievalQuality).toBe('neutral');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should be helpful when guidance followed and succeeded', async () => {
|
|
241
|
+
const trajectory = makeTrajectory({
|
|
242
|
+
success: true,
|
|
243
|
+
steps: [
|
|
244
|
+
{ action: 'read the file carefully' },
|
|
245
|
+
{ action: 'edit the function body' },
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
const playbooks = [makePlaybookMatch('fix', ['read the file', 'edit the function'])];
|
|
249
|
+
const obs = await learner.generateReflection(trajectory, makeRouting(), playbooks);
|
|
250
|
+
expect(obs.memoryUsage.retrievalQuality).toBe('helpful');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should be misleading when guidance followed but failed', async () => {
|
|
254
|
+
const trajectory = makeTrajectory({
|
|
255
|
+
success: false,
|
|
256
|
+
steps: [
|
|
257
|
+
{ action: 'read the file carefully' },
|
|
258
|
+
{ action: 'edit the function body' },
|
|
259
|
+
],
|
|
260
|
+
});
|
|
261
|
+
const playbooks = [makePlaybookMatch('fix', ['read the file', 'edit the function'])];
|
|
262
|
+
const obs = await learner.generateReflection(trajectory, makeRouting(), playbooks);
|
|
263
|
+
expect(obs.memoryUsage.retrievalQuality).toBe('misleading');
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('learn', () => {
|
|
268
|
+
it('should return empty when too few observations', async () => {
|
|
269
|
+
const strategies = await learner.learn();
|
|
270
|
+
expect(strategies).toEqual([]);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should generate strategies after enough observations', async () => {
|
|
274
|
+
// Feed 10+ observations to reach threshold
|
|
275
|
+
for (let i = 0; i < 12; i++) {
|
|
276
|
+
const trajectory = makeTrajectory({ success: i % 3 !== 0 });
|
|
277
|
+
await learner.generateReflection(trajectory, makeRouting(), []);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const strategies = await learner.learn();
|
|
281
|
+
// Should have generated at least meta-strategies from MetaMemory
|
|
282
|
+
expect(strategies).toBeDefined();
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('adjustRouting', () => {
|
|
287
|
+
it('should return base routing when no strategies exist', async () => {
|
|
288
|
+
const base = makeRouting();
|
|
289
|
+
const adjusted = await learner.adjustRouting(base, [], []);
|
|
290
|
+
expect(adjusted).toEqual(base);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should adjust confidence based on routing bias', async () => {
|
|
294
|
+
// Manually add a strategy
|
|
295
|
+
const { createMetaStrategy } = await import('../../src/types/meta.js');
|
|
296
|
+
await metaMemory.addStrategy(
|
|
297
|
+
createMetaStrategy({
|
|
298
|
+
name: 'boost-direct',
|
|
299
|
+
condition: { taskCharacteristics: [], memoryState: [] },
|
|
300
|
+
adjustment: {
|
|
301
|
+
routingBias: { direct: 0.15 },
|
|
302
|
+
retrievalModification: '',
|
|
303
|
+
executionHint: '',
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const base = makeRouting({ confidence: 0.5 });
|
|
309
|
+
const adjusted = await learner.adjustRouting(base, [], []);
|
|
310
|
+
expect(adjusted.confidence).toBeCloseTo(0.65, 1);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should clamp adjusted confidence to [0.1, 0.95]', async () => {
|
|
314
|
+
const { createMetaStrategy } = await import('../../src/types/meta.js');
|
|
315
|
+
await metaMemory.addStrategy(
|
|
316
|
+
createMetaStrategy({
|
|
317
|
+
name: 'mega-boost',
|
|
318
|
+
condition: { taskCharacteristics: [], memoryState: [] },
|
|
319
|
+
adjustment: {
|
|
320
|
+
routingBias: { direct: 0.9 },
|
|
321
|
+
retrievalModification: '',
|
|
322
|
+
executionHint: '',
|
|
323
|
+
},
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const base = makeRouting({ confidence: 0.9 });
|
|
328
|
+
const adjusted = await learner.adjustRouting(base, [], []);
|
|
329
|
+
expect(adjusted.confidence).toBeLessThanOrEqual(0.95);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should switch strategy when bias strongly favors alternative and base confidence low', async () => {
|
|
333
|
+
const { createMetaStrategy } = await import('../../src/types/meta.js');
|
|
334
|
+
await metaMemory.addStrategy(
|
|
335
|
+
createMetaStrategy({
|
|
336
|
+
name: 'prefer-explore',
|
|
337
|
+
condition: { taskCharacteristics: [], memoryState: [] },
|
|
338
|
+
adjustment: {
|
|
339
|
+
routingBias: { explore: 0.5 },
|
|
340
|
+
retrievalModification: '',
|
|
341
|
+
executionHint: 'try exploration',
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
const base = makeRouting({ strategy: 'direct', confidence: 0.3 });
|
|
347
|
+
const adjusted = await learner.adjustRouting(base, [], []);
|
|
348
|
+
expect(adjusted.strategy).toBe('explore');
|
|
349
|
+
expect(adjusted.reasoning).toContain('Meta-strategy');
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should match strategies by task characteristics', async () => {
|
|
353
|
+
const { createMetaStrategy } = await import('../../src/types/meta.js');
|
|
354
|
+
await metaMemory.addStrategy(
|
|
355
|
+
createMetaStrategy({
|
|
356
|
+
name: 'multi-file-strategy',
|
|
357
|
+
condition: {
|
|
358
|
+
taskCharacteristics: ['multi-file change'],
|
|
359
|
+
memoryState: [],
|
|
360
|
+
},
|
|
361
|
+
adjustment: {
|
|
362
|
+
routingBias: { direct: 0.1 },
|
|
363
|
+
retrievalModification: '',
|
|
364
|
+
executionHint: 'use decomposition',
|
|
365
|
+
},
|
|
366
|
+
})
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
// Should match because "multi-file change" is included
|
|
370
|
+
const adjusted = await learner.adjustRouting(
|
|
371
|
+
makeRouting({ confidence: 0.5 }),
|
|
372
|
+
['This involves a multi-file change'],
|
|
373
|
+
[]
|
|
374
|
+
);
|
|
375
|
+
expect(adjusted.reasoning).toContain('decomposition');
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('generateStrategiesFromAnalysis (via learn)', () => {
|
|
380
|
+
it('should generate avoid strategy for low success rate decisions', async () => {
|
|
381
|
+
// Create observations where "explore" has < 40% success rate
|
|
382
|
+
for (let i = 0; i < 10; i++) {
|
|
383
|
+
const trajectory = makeTrajectory({ success: i < 2 }); // 20% success
|
|
384
|
+
const routing = makeRouting({ strategy: 'explore' });
|
|
385
|
+
await learner.generateReflection(trajectory, routing, []);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const strategies = await learner.learn();
|
|
389
|
+
const avoidStrategy = strategies.find((s) => s.name.includes('avoid'));
|
|
390
|
+
// May or may not appear depending on MetaMemory's own strategy generation
|
|
391
|
+
if (avoidStrategy) {
|
|
392
|
+
expect(avoidStrategy.name).toContain('explore');
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe('strategy trigger interval', () => {
|
|
398
|
+
it('should auto-generate strategies at configured interval', async () => {
|
|
399
|
+
const intervalLearner = createMetaLearner(metaMemory, {
|
|
400
|
+
strategyGenerationInterval: 5,
|
|
401
|
+
minObservationsForStrategies: 3,
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Feed 5 observations to trigger auto-learn
|
|
405
|
+
for (let i = 0; i < 5; i++) {
|
|
406
|
+
await intervalLearner.generateReflection(
|
|
407
|
+
makeTrajectory({ success: i % 2 === 0 }),
|
|
408
|
+
makeRouting(),
|
|
409
|
+
[]
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// After 5 observations (interval=5), learn() should have been called
|
|
414
|
+
const stats = await metaMemory.getStats();
|
|
415
|
+
expect(stats.totalObservations).toBe(5);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
});
|