dino-spec 17.4.3 → 17.5.0
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/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +10 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/core/agents/__tests__/confidence-gate.test.d.ts +5 -0
- package/dist/core/agents/__tests__/confidence-gate.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/confidence-gate.test.js +329 -0
- package/dist/core/agents/__tests__/confidence-gate.test.js.map +1 -0
- package/dist/core/agents/__tests__/gap-detector.test.d.ts +5 -0
- package/dist/core/agents/__tests__/gap-detector.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/gap-detector.test.js +609 -0
- package/dist/core/agents/__tests__/gap-detector.test.js.map +1 -0
- package/dist/core/agents/__tests__/learning-loop.test.d.ts +5 -0
- package/dist/core/agents/__tests__/learning-loop.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/learning-loop.test.js +663 -0
- package/dist/core/agents/__tests__/learning-loop.test.js.map +1 -0
- package/dist/core/agents/__tests__/parallel-executor.test.d.ts +5 -0
- package/dist/core/agents/__tests__/parallel-executor.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/parallel-executor.test.js +610 -0
- package/dist/core/agents/__tests__/parallel-executor.test.js.map +1 -0
- package/dist/core/agents/__tests__/research-validator.test.js +5 -5
- package/dist/core/agents/__tests__/research-validator.test.js.map +1 -1
- package/dist/core/agents/__tests__/skill-creator.test.d.ts +5 -0
- package/dist/core/agents/__tests__/skill-creator.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/skill-creator.test.js +443 -0
- package/dist/core/agents/__tests__/skill-creator.test.js.map +1 -0
- package/dist/core/agents/__tests__/state-persistence.test.d.ts +2 -0
- package/dist/core/agents/__tests__/state-persistence.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/state-persistence.test.js +270 -0
- package/dist/core/agents/__tests__/state-persistence.test.js.map +1 -0
- package/dist/core/agents/__tests__/validation-cache.test.d.ts +2 -0
- package/dist/core/agents/__tests__/validation-cache.test.d.ts.map +1 -0
- package/dist/core/agents/__tests__/validation-cache.test.js +181 -0
- package/dist/core/agents/__tests__/validation-cache.test.js.map +1 -0
- package/dist/core/agents/blackboard.d.ts +22 -3
- package/dist/core/agents/blackboard.d.ts.map +1 -1
- package/dist/core/agents/blackboard.js +52 -2
- package/dist/core/agents/blackboard.js.map +1 -1
- package/dist/core/agents/circuit-breaker.d.ts +79 -1
- package/dist/core/agents/circuit-breaker.d.ts.map +1 -1
- package/dist/core/agents/circuit-breaker.js +175 -1
- package/dist/core/agents/circuit-breaker.js.map +1 -1
- package/dist/core/agents/clarification-phase.d.ts +128 -0
- package/dist/core/agents/clarification-phase.d.ts.map +1 -0
- package/dist/core/agents/clarification-phase.js +375 -0
- package/dist/core/agents/clarification-phase.js.map +1 -0
- package/dist/core/agents/confidence-gate.d.ts +116 -3
- package/dist/core/agents/confidence-gate.d.ts.map +1 -1
- package/dist/core/agents/confidence-gate.js +363 -3
- package/dist/core/agents/confidence-gate.js.map +1 -1
- package/dist/core/agents/correction-tracker.d.ts +197 -0
- package/dist/core/agents/correction-tracker.d.ts.map +1 -0
- package/dist/core/agents/correction-tracker.js +366 -0
- package/dist/core/agents/correction-tracker.js.map +1 -0
- package/dist/core/agents/dependency-graph.d.ts +193 -0
- package/dist/core/agents/dependency-graph.d.ts.map +1 -0
- package/dist/core/agents/dependency-graph.js +462 -0
- package/dist/core/agents/dependency-graph.js.map +1 -0
- package/dist/core/agents/gap-detector.d.ts +277 -0
- package/dist/core/agents/gap-detector.d.ts.map +1 -0
- package/dist/core/agents/gap-detector.js +540 -0
- package/dist/core/agents/gap-detector.js.map +1 -0
- package/dist/core/agents/index.d.ts +14 -1
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +26 -1
- package/dist/core/agents/index.js.map +1 -1
- package/dist/core/agents/message-protocol.d.ts +13 -2
- package/dist/core/agents/message-protocol.d.ts.map +1 -1
- package/dist/core/agents/message-protocol.js +20 -1
- package/dist/core/agents/message-protocol.js.map +1 -1
- package/dist/core/agents/parallel-executor.d.ts +182 -0
- package/dist/core/agents/parallel-executor.d.ts.map +1 -0
- package/dist/core/agents/parallel-executor.js +474 -0
- package/dist/core/agents/parallel-executor.js.map +1 -0
- package/dist/core/agents/pattern-detector.d.ts +157 -0
- package/dist/core/agents/pattern-detector.d.ts.map +1 -0
- package/dist/core/agents/pattern-detector.js +370 -0
- package/dist/core/agents/pattern-detector.js.map +1 -0
- package/dist/core/agents/registry-client.d.ts +6 -0
- package/dist/core/agents/registry-client.d.ts.map +1 -1
- package/dist/core/agents/registry-client.js +3 -0
- package/dist/core/agents/registry-client.js.map +1 -1
- package/dist/core/agents/research-phase.d.ts +114 -0
- package/dist/core/agents/research-phase.d.ts.map +1 -0
- package/dist/core/agents/research-phase.js +355 -0
- package/dist/core/agents/research-phase.js.map +1 -0
- package/dist/core/agents/research-validator.d.ts +13 -2
- package/dist/core/agents/research-validator.d.ts.map +1 -1
- package/dist/core/agents/research-validator.js +63 -31
- package/dist/core/agents/research-validator.js.map +1 -1
- package/dist/core/agents/skill-creator.d.ts +174 -0
- package/dist/core/agents/skill-creator.d.ts.map +1 -0
- package/dist/core/agents/skill-creator.js +570 -0
- package/dist/core/agents/skill-creator.js.map +1 -0
- package/dist/core/agents/state-persistence.d.ts +184 -0
- package/dist/core/agents/state-persistence.d.ts.map +1 -0
- package/dist/core/agents/state-persistence.js +394 -0
- package/dist/core/agents/state-persistence.js.map +1 -0
- package/dist/core/agents/validation-cache.d.ts +122 -0
- package/dist/core/agents/validation-cache.d.ts.map +1 -0
- package/dist/core/agents/validation-cache.js +280 -0
- package/dist/core/agents/validation-cache.js.map +1 -0
- package/dist/core/agents/validators/__tests__/validators.test.d.ts +5 -0
- package/dist/core/agents/validators/__tests__/validators.test.d.ts.map +1 -0
- package/dist/core/agents/validators/__tests__/validators.test.js +321 -0
- package/dist/core/agents/validators/__tests__/validators.test.js.map +1 -0
- package/dist/core/agents/validators/base-validator.d.ts +191 -0
- package/dist/core/agents/validators/base-validator.d.ts.map +1 -0
- package/dist/core/agents/validators/base-validator.js +192 -0
- package/dist/core/agents/validators/base-validator.js.map +1 -0
- package/dist/core/agents/validators/index.d.ts +7 -0
- package/dist/core/agents/validators/index.d.ts.map +1 -0
- package/dist/core/agents/validators/index.js +7 -0
- package/dist/core/agents/validators/index.js.map +1 -0
- package/dist/core/agents/validators/npm-validator.d.ts +50 -0
- package/dist/core/agents/validators/npm-validator.d.ts.map +1 -0
- package/dist/core/agents/validators/npm-validator.js +211 -0
- package/dist/core/agents/validators/npm-validator.js.map +1 -0
- package/dist/core/agents/validators/pip-validator.d.ts +49 -0
- package/dist/core/agents/validators/pip-validator.d.ts.map +1 -0
- package/dist/core/agents/validators/pip-validator.js +191 -0
- package/dist/core/agents/validators/pip-validator.js.map +1 -0
- package/dist/core/agents/validators/validator-registry.d.ts +122 -0
- package/dist/core/agents/validators/validator-registry.d.ts.map +1 -0
- package/dist/core/agents/validators/validator-registry.js +321 -0
- package/dist/core/agents/validators/validator-registry.js.map +1 -0
- package/dist/core/context-repl/types.d.ts +4 -4
- package/dist/core/memory/index.d.ts +1 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +2 -0
- package/dist/core/memory/index.js.map +1 -1
- package/dist/core/memory/learning-store.d.ts +222 -0
- package/dist/core/memory/learning-store.d.ts.map +1 -0
- package/dist/core/memory/learning-store.js +477 -0
- package/dist/core/memory/learning-store.js.map +1 -0
- package/dist/core/spec-analyzer/__tests__/ambiguity-detector.test.d.ts +5 -0
- package/dist/core/spec-analyzer/__tests__/ambiguity-detector.test.d.ts.map +1 -0
- package/dist/core/spec-analyzer/__tests__/ambiguity-detector.test.js +401 -0
- package/dist/core/spec-analyzer/__tests__/ambiguity-detector.test.js.map +1 -0
- package/dist/core/spec-analyzer/ambiguity-detector.d.ts +99 -0
- package/dist/core/spec-analyzer/ambiguity-detector.d.ts.map +1 -0
- package/dist/core/spec-analyzer/ambiguity-detector.js +250 -0
- package/dist/core/spec-analyzer/ambiguity-detector.js.map +1 -0
- package/dist/core/spec-analyzer/clarification-generator.d.ts +76 -0
- package/dist/core/spec-analyzer/clarification-generator.d.ts.map +1 -0
- package/dist/core/spec-analyzer/clarification-generator.js +257 -0
- package/dist/core/spec-analyzer/clarification-generator.js.map +1 -0
- package/dist/core/spec-analyzer/index.d.ts +10 -1
- package/dist/core/spec-analyzer/index.d.ts.map +1 -1
- package/dist/core/spec-analyzer/index.js +13 -1
- package/dist/core/spec-analyzer/index.js.map +1 -1
- package/dist/core/spec-analyzer/patterns.d.ts +73 -0
- package/dist/core/spec-analyzer/patterns.d.ts.map +1 -0
- package/dist/core/spec-analyzer/patterns.js +412 -0
- package/dist/core/spec-analyzer/patterns.js.map +1 -0
- package/dist/hooks/__tests__/dynamic-generator.test.d.ts +5 -0
- package/dist/hooks/__tests__/dynamic-generator.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/dynamic-generator.test.js +425 -0
- package/dist/hooks/__tests__/dynamic-generator.test.js.map +1 -0
- package/dist/hooks/__tests__/hook-agent-bridge.test.d.ts +5 -0
- package/dist/hooks/__tests__/hook-agent-bridge.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/hook-agent-bridge.test.js +315 -0
- package/dist/hooks/__tests__/hook-agent-bridge.test.js.map +1 -0
- package/dist/hooks/dynamic-generator.d.ts +158 -0
- package/dist/hooks/dynamic-generator.d.ts.map +1 -0
- package/dist/hooks/dynamic-generator.js +448 -0
- package/dist/hooks/dynamic-generator.js.map +1 -0
- package/dist/hooks/hook-agent-bridge.d.ts +252 -0
- package/dist/hooks/hook-agent-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-agent-bridge.js +489 -0
- package/dist/hooks/hook-agent-bridge.js.map +1 -0
- package/dist/hooks/index.d.ts +3 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +5 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/mcp/tool-catalog.d.ts.map +1 -1
- package/dist/mcp/tool-catalog.js +47 -0
- package/dist/mcp/tool-catalog.js.map +1 -1
- package/dist/mcp/tool-tiers.d.ts.map +1 -1
- package/dist/mcp/tool-tiers.js +4 -0
- package/dist/mcp/tool-tiers.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +2 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +15 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/learning-store.d.ts +30 -0
- package/dist/mcp/tools/learning-store.d.ts.map +1 -0
- package/dist/mcp/tools/learning-store.js +286 -0
- package/dist/mcp/tools/learning-store.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Learning Loop from Corrections (MAC-9)
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
|
+
import { formatLearnedPattern, formatLearningStats, formatPatternsForSession, LearningStore, } from '../../memory/learning-store.js';
|
|
8
|
+
import { CorrectionTracker, detectCorrection, formatCluster, formatCorrection, } from '../correction-tracker.js';
|
|
9
|
+
import { calculateConfidenceAdjustment, formatDetectedPattern, formatSuggestionsForSession, PatternDetector, } from '../pattern-detector.js';
|
|
10
|
+
// =============================================================================
|
|
11
|
+
// Test Fixtures
|
|
12
|
+
// =============================================================================
|
|
13
|
+
const testDir = join(process.cwd(), '.test-learning-loop');
|
|
14
|
+
function createTestContext(original, corrected) {
|
|
15
|
+
return {
|
|
16
|
+
original,
|
|
17
|
+
corrected,
|
|
18
|
+
filePath: 'src/test.ts',
|
|
19
|
+
focusArea: 'src/',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// CorrectionTracker Tests
|
|
24
|
+
// =============================================================================
|
|
25
|
+
describe('CorrectionTracker', () => {
|
|
26
|
+
let tracker;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tracker = new CorrectionTracker();
|
|
29
|
+
});
|
|
30
|
+
describe('basic tracking', () => {
|
|
31
|
+
it('should track a correction', () => {
|
|
32
|
+
const context = createTestContext('const x = 1;', 'const x = 1');
|
|
33
|
+
const correction = tracker.track(context, 'explicit');
|
|
34
|
+
expect(correction.id).toBeDefined();
|
|
35
|
+
expect(correction.type).toBe('explicit');
|
|
36
|
+
expect(correction.context.original).toBe('const x = 1;');
|
|
37
|
+
expect(correction.context.corrected).toBe('const x = 1');
|
|
38
|
+
expect(correction.keywords.length).toBeGreaterThan(0);
|
|
39
|
+
});
|
|
40
|
+
it('should get all corrections', () => {
|
|
41
|
+
tracker.track(createTestContext('a', 'b'), 'explicit');
|
|
42
|
+
tracker.track(createTestContext('c', 'd'), 'implicit');
|
|
43
|
+
const corrections = tracker.getCorrections();
|
|
44
|
+
expect(corrections).toHaveLength(2);
|
|
45
|
+
});
|
|
46
|
+
it('should get correction by ID', () => {
|
|
47
|
+
const correction = tracker.track(createTestContext('x', 'y'), 'explicit');
|
|
48
|
+
const retrieved = tracker.getCorrection(correction.id);
|
|
49
|
+
expect(retrieved).toBeDefined();
|
|
50
|
+
expect(retrieved?.id).toBe(correction.id);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('correction detection', () => {
|
|
54
|
+
it('should detect "no, use X" pattern', () => {
|
|
55
|
+
const result = detectCorrection('no, use single quotes');
|
|
56
|
+
expect(result.detected).toBe(true);
|
|
57
|
+
expect(result.pattern).toBe('use single quotes');
|
|
58
|
+
});
|
|
59
|
+
it('should detect "actually, X" pattern', () => {
|
|
60
|
+
const result = detectCorrection('actually, it should be camelCase');
|
|
61
|
+
expect(result.detected).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
it('should detect "change X to Y" pattern', () => {
|
|
64
|
+
const result = detectCorrection('change tabs to spaces');
|
|
65
|
+
expect(result.detected).toBe(true);
|
|
66
|
+
expect(result.pattern).toBe('tabs');
|
|
67
|
+
expect(result.replacement).toBe('spaces');
|
|
68
|
+
});
|
|
69
|
+
it('should return false for non-corrections', () => {
|
|
70
|
+
const result = detectCorrection('looks good to me');
|
|
71
|
+
expect(result.detected).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe('clustering', () => {
|
|
75
|
+
it('should cluster similar corrections', () => {
|
|
76
|
+
// Track multiple similar corrections
|
|
77
|
+
tracker.track(createTestContext('var x', 'const x'), 'explicit');
|
|
78
|
+
tracker.track(createTestContext('var y', 'const y'), 'explicit');
|
|
79
|
+
tracker.track(createTestContext('var z', 'const z'), 'explicit');
|
|
80
|
+
const clusters = tracker.getClusters();
|
|
81
|
+
expect(clusters.length).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
it('should identify promotable clusters', () => {
|
|
84
|
+
// Create tracker with low threshold for testing
|
|
85
|
+
const testTracker = new CorrectionTracker({ automationThreshold: 2 });
|
|
86
|
+
testTracker.track(createTestContext('semicolon;', 'no semicolon'), 'explicit');
|
|
87
|
+
testTracker.track(createTestContext('add semicolon;', 'remove semicolon'), 'explicit');
|
|
88
|
+
const promotable = testTracker.getPromotableClusters();
|
|
89
|
+
// May or may not be promotable depending on similarity
|
|
90
|
+
expect(Array.isArray(promotable)).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('should find similar corrections', () => {
|
|
93
|
+
tracker.track(createTestContext('use npm', 'use bun'), 'explicit');
|
|
94
|
+
tracker.track(createTestContext('npm install', 'bun install'), 'explicit');
|
|
95
|
+
const similar = tracker.findSimilar(createTestContext('npm run', 'bun run'), 0.3);
|
|
96
|
+
expect(similar.length).toBeGreaterThanOrEqual(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('statistics', () => {
|
|
100
|
+
it('should return correct stats', () => {
|
|
101
|
+
tracker.track(createTestContext('a', 'b'), 'explicit');
|
|
102
|
+
tracker.track(createTestContext('c', 'd'), 'implicit');
|
|
103
|
+
tracker.track(createTestContext('e', 'f'), 'style');
|
|
104
|
+
const stats = tracker.getStats();
|
|
105
|
+
expect(stats.totalCorrections).toBe(3);
|
|
106
|
+
expect(stats.byType.explicit).toBe(1);
|
|
107
|
+
expect(stats.byType.implicit).toBe(1);
|
|
108
|
+
expect(stats.byType.style).toBe(1);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('export/import', () => {
|
|
112
|
+
it('should export and import state', () => {
|
|
113
|
+
tracker.track(createTestContext('x', 'y'), 'explicit');
|
|
114
|
+
tracker.track(createTestContext('a', 'b'), 'implicit');
|
|
115
|
+
const exported = tracker.export();
|
|
116
|
+
expect(exported.corrections).toHaveLength(2);
|
|
117
|
+
const newTracker = new CorrectionTracker();
|
|
118
|
+
newTracker.import(exported);
|
|
119
|
+
expect(newTracker.getCorrections()).toHaveLength(2);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('formatting', () => {
|
|
123
|
+
it('should format correction', () => {
|
|
124
|
+
const correction = tracker.track(createTestContext('old', 'new'), 'explicit');
|
|
125
|
+
const formatted = formatCorrection(correction);
|
|
126
|
+
expect(formatted).toContain('Correction');
|
|
127
|
+
expect(formatted).toContain('old');
|
|
128
|
+
expect(formatted).toContain('new');
|
|
129
|
+
});
|
|
130
|
+
it('should format cluster', () => {
|
|
131
|
+
tracker.track(createTestContext('x', 'y'), 'explicit');
|
|
132
|
+
const clusters = tracker.getClusters();
|
|
133
|
+
if (clusters.length > 0) {
|
|
134
|
+
const formatted = formatCluster(clusters[0]);
|
|
135
|
+
expect(formatted).toContain('Cluster');
|
|
136
|
+
expect(formatted).toContain('occurrences');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// PatternDetector Tests
|
|
143
|
+
// =============================================================================
|
|
144
|
+
describe('PatternDetector', () => {
|
|
145
|
+
let detector;
|
|
146
|
+
let tracker;
|
|
147
|
+
beforeEach(() => {
|
|
148
|
+
detector = new PatternDetector();
|
|
149
|
+
tracker = new CorrectionTracker({ automationThreshold: 2 });
|
|
150
|
+
});
|
|
151
|
+
describe('pattern detection', () => {
|
|
152
|
+
it('should detect pattern from cluster', () => {
|
|
153
|
+
// Create enough similar corrections
|
|
154
|
+
tracker.track(createTestContext('use semicolons;', 'no semicolons'), 'explicit');
|
|
155
|
+
tracker.track(createTestContext('add semicolon;', 'remove semicolon'), 'explicit');
|
|
156
|
+
tracker.track(createTestContext('with semicolon;', 'without semicolon'), 'explicit');
|
|
157
|
+
const clusters = tracker.getClusters().filter((c) => c.count >= 2);
|
|
158
|
+
if (clusters.length > 0) {
|
|
159
|
+
const pattern = detector.detectFromCluster(clusters[0]);
|
|
160
|
+
if (pattern) {
|
|
161
|
+
expect(pattern.trigger).toBeDefined();
|
|
162
|
+
expect(pattern.preference).toBeDefined();
|
|
163
|
+
expect(pattern.type).toBeDefined();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
it('should detect patterns from multiple clusters', () => {
|
|
168
|
+
tracker.track(createTestContext('double quotes', 'single quotes'), 'explicit');
|
|
169
|
+
tracker.track(createTestContext('"hello"', "'hello'"), 'explicit');
|
|
170
|
+
const clusters = tracker.getClusters();
|
|
171
|
+
const patterns = detector.detectFromClusters(clusters);
|
|
172
|
+
expect(Array.isArray(patterns)).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
it('should classify pattern types correctly', () => {
|
|
175
|
+
// Create style-related corrections
|
|
176
|
+
const styleTracker = new CorrectionTracker({ automationThreshold: 1 });
|
|
177
|
+
styleTracker.track(createTestContext('2 space indent', '4 space indent'), 'style');
|
|
178
|
+
styleTracker.track(createTestContext('tabs', 'spaces'), 'style');
|
|
179
|
+
styleTracker.track(createTestContext('tabs indent', 'space indent'), 'style');
|
|
180
|
+
const clusters = styleTracker.getClusters();
|
|
181
|
+
if (clusters.length > 0) {
|
|
182
|
+
const pattern = detector.detectFromCluster(clusters[0]);
|
|
183
|
+
if (pattern) {
|
|
184
|
+
expect(['style', 'preference']).toContain(pattern.type);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('suggestions', () => {
|
|
190
|
+
it('should provide context-aware suggestions', () => {
|
|
191
|
+
// Add a pattern manually
|
|
192
|
+
const mockPattern = {
|
|
193
|
+
id: 'test-1',
|
|
194
|
+
type: 'style',
|
|
195
|
+
trigger: 'semicolons',
|
|
196
|
+
preference: 'no semicolons',
|
|
197
|
+
confidence: 'high',
|
|
198
|
+
occurrences: 5,
|
|
199
|
+
examples: [],
|
|
200
|
+
keywords: ['semicolon', 'style'],
|
|
201
|
+
sourceClusterId: 'cluster-1',
|
|
202
|
+
detectedAt: new Date(),
|
|
203
|
+
};
|
|
204
|
+
detector.import([mockPattern]);
|
|
205
|
+
const suggestions = detector.getSuggestions({
|
|
206
|
+
focusArea: 'src/',
|
|
207
|
+
currentTask: 'fix semicolon issues',
|
|
208
|
+
});
|
|
209
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
210
|
+
expect(suggestions[0].pattern.id).toBe('test-1');
|
|
211
|
+
});
|
|
212
|
+
it('should return empty for no relevant patterns', () => {
|
|
213
|
+
const suggestions = detector.getSuggestions({
|
|
214
|
+
focusArea: 'test/',
|
|
215
|
+
currentTask: 'write tests',
|
|
216
|
+
});
|
|
217
|
+
expect(suggestions).toHaveLength(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('confidence adjustment', () => {
|
|
221
|
+
it('should calculate positive adjustment for patterns', () => {
|
|
222
|
+
const patterns = [
|
|
223
|
+
{
|
|
224
|
+
id: 'p1',
|
|
225
|
+
type: 'style',
|
|
226
|
+
trigger: 't',
|
|
227
|
+
preference: 'p',
|
|
228
|
+
confidence: 'high',
|
|
229
|
+
occurrences: 5,
|
|
230
|
+
examples: [],
|
|
231
|
+
keywords: [],
|
|
232
|
+
sourceClusterId: 'c1',
|
|
233
|
+
detectedAt: new Date(),
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'p2',
|
|
237
|
+
type: 'naming',
|
|
238
|
+
trigger: 't2',
|
|
239
|
+
preference: 'p2',
|
|
240
|
+
confidence: 'medium',
|
|
241
|
+
occurrences: 3,
|
|
242
|
+
examples: [],
|
|
243
|
+
keywords: [],
|
|
244
|
+
sourceClusterId: 'c2',
|
|
245
|
+
detectedAt: new Date(),
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
const adjustment = calculateConfidenceAdjustment(patterns);
|
|
249
|
+
expect(adjustment).toBeGreaterThan(0);
|
|
250
|
+
expect(adjustment).toBeLessThanOrEqual(0.1);
|
|
251
|
+
});
|
|
252
|
+
it('should return 0 for no patterns', () => {
|
|
253
|
+
const adjustment = calculateConfidenceAdjustment([]);
|
|
254
|
+
expect(adjustment).toBe(0);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
describe('formatting', () => {
|
|
258
|
+
it('should format detected pattern', () => {
|
|
259
|
+
const pattern = {
|
|
260
|
+
id: 'test',
|
|
261
|
+
type: 'style',
|
|
262
|
+
trigger: 'semicolons',
|
|
263
|
+
preference: 'no semicolons',
|
|
264
|
+
confidence: 'high',
|
|
265
|
+
occurrences: 5,
|
|
266
|
+
examples: [],
|
|
267
|
+
keywords: ['semi', 'style'],
|
|
268
|
+
sourceClusterId: 'c1',
|
|
269
|
+
detectedAt: new Date(),
|
|
270
|
+
};
|
|
271
|
+
const formatted = formatDetectedPattern(pattern);
|
|
272
|
+
expect(formatted).toContain('style');
|
|
273
|
+
expect(formatted).toContain('5 occurrences');
|
|
274
|
+
expect(formatted).toContain('semicolons');
|
|
275
|
+
});
|
|
276
|
+
it('should format suggestions for session', () => {
|
|
277
|
+
const suggestions = [
|
|
278
|
+
{
|
|
279
|
+
pattern: {
|
|
280
|
+
id: 'test',
|
|
281
|
+
type: 'style',
|
|
282
|
+
trigger: 'tabs',
|
|
283
|
+
preference: 'spaces',
|
|
284
|
+
confidence: 'high',
|
|
285
|
+
occurrences: 3,
|
|
286
|
+
examples: [],
|
|
287
|
+
keywords: [],
|
|
288
|
+
sourceClusterId: 'c1',
|
|
289
|
+
detectedAt: new Date(),
|
|
290
|
+
},
|
|
291
|
+
relevance: 0.8,
|
|
292
|
+
reason: 'frequently corrected',
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
const formatted = formatSuggestionsForSession(suggestions);
|
|
296
|
+
expect(formatted).toContain('Learned Patterns');
|
|
297
|
+
expect(formatted).toContain('tabs');
|
|
298
|
+
expect(formatted).toContain('spaces');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
// =============================================================================
|
|
303
|
+
// LearningStore Tests
|
|
304
|
+
// =============================================================================
|
|
305
|
+
describe('LearningStore', () => {
|
|
306
|
+
let store;
|
|
307
|
+
beforeEach(() => {
|
|
308
|
+
// Clean up test directory
|
|
309
|
+
if (existsSync(testDir)) {
|
|
310
|
+
rmSync(testDir, { recursive: true });
|
|
311
|
+
}
|
|
312
|
+
mkdirSync(testDir, { recursive: true });
|
|
313
|
+
store = new LearningStore(testDir, { autoSave: false });
|
|
314
|
+
});
|
|
315
|
+
afterEach(() => {
|
|
316
|
+
if (existsSync(testDir)) {
|
|
317
|
+
rmSync(testDir, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
describe('pattern storage', () => {
|
|
321
|
+
it('should add and retrieve patterns', () => {
|
|
322
|
+
const pattern = {
|
|
323
|
+
id: 'test-pattern',
|
|
324
|
+
type: 'style',
|
|
325
|
+
trigger: 'semicolons',
|
|
326
|
+
preference: 'no semicolons',
|
|
327
|
+
confidence: 'high',
|
|
328
|
+
occurrences: 5,
|
|
329
|
+
examples: [{ original: 'x;', corrected: 'x' }],
|
|
330
|
+
keywords: ['semicolon'],
|
|
331
|
+
sourceClusterId: 'cluster-1',
|
|
332
|
+
detectedAt: new Date(),
|
|
333
|
+
};
|
|
334
|
+
const learned = store.addPattern(pattern);
|
|
335
|
+
expect(learned.id).toBe('test-pattern');
|
|
336
|
+
expect(learned.active).toBe(true);
|
|
337
|
+
expect(learned.applyCount).toBe(0);
|
|
338
|
+
const retrieved = store.getPattern('test-pattern');
|
|
339
|
+
expect(retrieved).toBeDefined();
|
|
340
|
+
expect(retrieved?.trigger).toBe('semicolons');
|
|
341
|
+
});
|
|
342
|
+
it('should get active patterns only', () => {
|
|
343
|
+
const pattern1 = {
|
|
344
|
+
id: 'p1',
|
|
345
|
+
type: 'style',
|
|
346
|
+
trigger: 't1',
|
|
347
|
+
preference: 'p1',
|
|
348
|
+
confidence: 'high',
|
|
349
|
+
occurrences: 3,
|
|
350
|
+
examples: [],
|
|
351
|
+
keywords: [],
|
|
352
|
+
sourceClusterId: 'c1',
|
|
353
|
+
detectedAt: new Date(),
|
|
354
|
+
};
|
|
355
|
+
const pattern2 = {
|
|
356
|
+
id: 'p2',
|
|
357
|
+
type: 'naming',
|
|
358
|
+
trigger: 't2',
|
|
359
|
+
preference: 'p2',
|
|
360
|
+
confidence: 'medium',
|
|
361
|
+
occurrences: 2,
|
|
362
|
+
examples: [],
|
|
363
|
+
keywords: [],
|
|
364
|
+
sourceClusterId: 'c2',
|
|
365
|
+
detectedAt: new Date(),
|
|
366
|
+
};
|
|
367
|
+
store.addPattern(pattern1);
|
|
368
|
+
store.addPattern(pattern2);
|
|
369
|
+
store.deactivatePattern('p2');
|
|
370
|
+
const active = store.getActivePatterns();
|
|
371
|
+
expect(active).toHaveLength(1);
|
|
372
|
+
expect(active[0].id).toBe('p1');
|
|
373
|
+
});
|
|
374
|
+
it('should record pattern application', () => {
|
|
375
|
+
const pattern = {
|
|
376
|
+
id: 'apply-test',
|
|
377
|
+
type: 'tool',
|
|
378
|
+
trigger: 'npm',
|
|
379
|
+
preference: 'bun',
|
|
380
|
+
confidence: 'high',
|
|
381
|
+
occurrences: 5,
|
|
382
|
+
examples: [],
|
|
383
|
+
keywords: [],
|
|
384
|
+
sourceClusterId: 'c1',
|
|
385
|
+
detectedAt: new Date(),
|
|
386
|
+
};
|
|
387
|
+
store.addPattern(pattern);
|
|
388
|
+
store.recordApplication('apply-test');
|
|
389
|
+
store.recordApplication('apply-test');
|
|
390
|
+
const retrieved = store.getPattern('apply-test');
|
|
391
|
+
expect(retrieved?.applyCount).toBe(2);
|
|
392
|
+
expect(retrieved?.lastApplied).toBeDefined();
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
describe('persistence', () => {
|
|
396
|
+
it('should save and load state', () => {
|
|
397
|
+
const pattern = {
|
|
398
|
+
id: 'persist-test',
|
|
399
|
+
type: 'workflow',
|
|
400
|
+
trigger: 'before commit',
|
|
401
|
+
preference: 'run tests',
|
|
402
|
+
confidence: 'high',
|
|
403
|
+
occurrences: 4,
|
|
404
|
+
examples: [],
|
|
405
|
+
keywords: ['test', 'commit'],
|
|
406
|
+
sourceClusterId: 'c1',
|
|
407
|
+
detectedAt: new Date(),
|
|
408
|
+
};
|
|
409
|
+
store.addPattern(pattern);
|
|
410
|
+
store.save();
|
|
411
|
+
// Create new store and load
|
|
412
|
+
const newStore = new LearningStore(testDir);
|
|
413
|
+
newStore.load();
|
|
414
|
+
const retrieved = newStore.getPattern('persist-test');
|
|
415
|
+
expect(retrieved).toBeDefined();
|
|
416
|
+
expect(retrieved?.trigger).toBe('before commit');
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
describe('suggestions', () => {
|
|
420
|
+
it('should provide context-relevant suggestions', () => {
|
|
421
|
+
const pattern = {
|
|
422
|
+
id: 'suggest-test',
|
|
423
|
+
type: 'style',
|
|
424
|
+
trigger: 'formatting',
|
|
425
|
+
preference: 'use prettier',
|
|
426
|
+
confidence: 'high',
|
|
427
|
+
occurrences: 5,
|
|
428
|
+
examples: [],
|
|
429
|
+
keywords: ['format', 'prettier', 'style'],
|
|
430
|
+
sourceClusterId: 'c1',
|
|
431
|
+
detectedAt: new Date(),
|
|
432
|
+
};
|
|
433
|
+
store.addPattern(pattern);
|
|
434
|
+
const suggestions = store.getSuggestions({
|
|
435
|
+
focusArea: 'src/',
|
|
436
|
+
currentTask: 'fix formatting issues',
|
|
437
|
+
});
|
|
438
|
+
expect(suggestions.length).toBeGreaterThan(0);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
describe('confidence boost', () => {
|
|
442
|
+
it('should calculate confidence boost from patterns', () => {
|
|
443
|
+
const patterns = [
|
|
444
|
+
{
|
|
445
|
+
id: 'boost1',
|
|
446
|
+
type: 'style',
|
|
447
|
+
trigger: 't1',
|
|
448
|
+
preference: 'p1',
|
|
449
|
+
confidence: 'high',
|
|
450
|
+
occurrences: 5,
|
|
451
|
+
examples: [],
|
|
452
|
+
keywords: ['test', 'style'],
|
|
453
|
+
sourceClusterId: 'c1',
|
|
454
|
+
detectedAt: new Date(),
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
id: 'boost2',
|
|
458
|
+
type: 'naming',
|
|
459
|
+
trigger: 't2',
|
|
460
|
+
preference: 'p2',
|
|
461
|
+
confidence: 'medium',
|
|
462
|
+
occurrences: 3,
|
|
463
|
+
examples: [],
|
|
464
|
+
keywords: ['name', 'convention'],
|
|
465
|
+
sourceClusterId: 'c2',
|
|
466
|
+
detectedAt: new Date(),
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
for (const p of patterns) {
|
|
470
|
+
store.addPattern(p);
|
|
471
|
+
}
|
|
472
|
+
const boost = store.calculateConfidenceBoost({
|
|
473
|
+
focusArea: 'src/',
|
|
474
|
+
currentTask: 'test style improvements',
|
|
475
|
+
});
|
|
476
|
+
expect(boost).toBeGreaterThanOrEqual(0);
|
|
477
|
+
expect(boost).toBeLessThanOrEqual(0.1);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
describe('statistics', () => {
|
|
481
|
+
it('should return correct stats', () => {
|
|
482
|
+
const patterns = [
|
|
483
|
+
{
|
|
484
|
+
id: 's1',
|
|
485
|
+
type: 'style',
|
|
486
|
+
trigger: 't1',
|
|
487
|
+
preference: 'p1',
|
|
488
|
+
confidence: 'high',
|
|
489
|
+
occurrences: 5,
|
|
490
|
+
examples: [],
|
|
491
|
+
keywords: [],
|
|
492
|
+
sourceClusterId: 'c1',
|
|
493
|
+
detectedAt: new Date(),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 's2',
|
|
497
|
+
type: 'style',
|
|
498
|
+
trigger: 't2',
|
|
499
|
+
preference: 'p2',
|
|
500
|
+
confidence: 'medium',
|
|
501
|
+
occurrences: 3,
|
|
502
|
+
examples: [],
|
|
503
|
+
keywords: [],
|
|
504
|
+
sourceClusterId: 'c2',
|
|
505
|
+
detectedAt: new Date(),
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
id: 'n1',
|
|
509
|
+
type: 'naming',
|
|
510
|
+
trigger: 't3',
|
|
511
|
+
preference: 'p3',
|
|
512
|
+
confidence: 'low',
|
|
513
|
+
occurrences: 2,
|
|
514
|
+
examples: [],
|
|
515
|
+
keywords: [],
|
|
516
|
+
sourceClusterId: 'c3',
|
|
517
|
+
detectedAt: new Date(),
|
|
518
|
+
},
|
|
519
|
+
];
|
|
520
|
+
for (const p of patterns) {
|
|
521
|
+
store.addPattern(p);
|
|
522
|
+
}
|
|
523
|
+
const stats = store.getStats();
|
|
524
|
+
expect(stats.totalPatterns).toBe(3);
|
|
525
|
+
expect(stats.activePatterns).toBe(3);
|
|
526
|
+
expect(stats.patternsByType.style).toBe(2);
|
|
527
|
+
expect(stats.patternsByType.naming).toBe(1);
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
describe('formatting', () => {
|
|
531
|
+
it('should format learned pattern', () => {
|
|
532
|
+
const pattern = {
|
|
533
|
+
id: 'fmt-test',
|
|
534
|
+
type: 'tool',
|
|
535
|
+
trigger: 'npm',
|
|
536
|
+
preference: 'bun',
|
|
537
|
+
confidence: 'high',
|
|
538
|
+
occurrences: 5,
|
|
539
|
+
examples: [],
|
|
540
|
+
keywords: [],
|
|
541
|
+
sourceClusterId: 'c1',
|
|
542
|
+
detectedAt: new Date(),
|
|
543
|
+
};
|
|
544
|
+
const learned = store.addPattern(pattern);
|
|
545
|
+
const formatted = formatLearnedPattern(learned);
|
|
546
|
+
expect(formatted).toContain('tool');
|
|
547
|
+
expect(formatted).toContain('npm');
|
|
548
|
+
expect(formatted).toContain('bun');
|
|
549
|
+
});
|
|
550
|
+
it('should format patterns for session', () => {
|
|
551
|
+
const patterns = [
|
|
552
|
+
{
|
|
553
|
+
id: 'sess1',
|
|
554
|
+
type: 'style',
|
|
555
|
+
trigger: 'tabs',
|
|
556
|
+
preference: 'spaces',
|
|
557
|
+
confidence: 'high',
|
|
558
|
+
occurrences: 5,
|
|
559
|
+
examples: [],
|
|
560
|
+
keywords: [],
|
|
561
|
+
sourceClusterId: 'c1',
|
|
562
|
+
detectedAt: new Date(),
|
|
563
|
+
},
|
|
564
|
+
];
|
|
565
|
+
for (const p of patterns) {
|
|
566
|
+
store.addPattern(p);
|
|
567
|
+
}
|
|
568
|
+
const formatted = formatPatternsForSession(store.getActivePatterns());
|
|
569
|
+
expect(formatted).toContain('Learned Preferences');
|
|
570
|
+
expect(formatted).toContain('tabs');
|
|
571
|
+
expect(formatted).toContain('spaces');
|
|
572
|
+
});
|
|
573
|
+
it('should format stats', () => {
|
|
574
|
+
const pattern = {
|
|
575
|
+
id: 'stat-fmt',
|
|
576
|
+
type: 'workflow',
|
|
577
|
+
trigger: 't',
|
|
578
|
+
preference: 'p',
|
|
579
|
+
confidence: 'high',
|
|
580
|
+
occurrences: 3,
|
|
581
|
+
examples: [],
|
|
582
|
+
keywords: [],
|
|
583
|
+
sourceClusterId: 'c1',
|
|
584
|
+
detectedAt: new Date(),
|
|
585
|
+
};
|
|
586
|
+
store.addPattern(pattern);
|
|
587
|
+
const formatted = formatLearningStats(store.getStats());
|
|
588
|
+
expect(formatted).toContain('Learning Store Stats');
|
|
589
|
+
expect(formatted).toContain('Patterns');
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
// =============================================================================
|
|
594
|
+
// Integration Tests
|
|
595
|
+
// =============================================================================
|
|
596
|
+
describe('Learning Loop Integration', () => {
|
|
597
|
+
let tracker;
|
|
598
|
+
let detector;
|
|
599
|
+
let store;
|
|
600
|
+
beforeEach(() => {
|
|
601
|
+
if (existsSync(testDir)) {
|
|
602
|
+
rmSync(testDir, { recursive: true });
|
|
603
|
+
}
|
|
604
|
+
mkdirSync(testDir, { recursive: true });
|
|
605
|
+
tracker = new CorrectionTracker({ automationThreshold: 2 });
|
|
606
|
+
detector = new PatternDetector({ minClusterSize: 2 });
|
|
607
|
+
store = new LearningStore(testDir, { autoSave: false });
|
|
608
|
+
});
|
|
609
|
+
afterEach(() => {
|
|
610
|
+
if (existsSync(testDir)) {
|
|
611
|
+
rmSync(testDir, { recursive: true });
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
it('should complete full learning loop', () => {
|
|
615
|
+
// Step 1: Track corrections
|
|
616
|
+
tracker.track(createTestContext('use var', 'use const'), 'explicit');
|
|
617
|
+
tracker.track(createTestContext('var x', 'const x'), 'explicit');
|
|
618
|
+
tracker.track(createTestContext('declare var', 'declare const'), 'explicit');
|
|
619
|
+
// Step 2: Get promotable clusters
|
|
620
|
+
const clusters = tracker.getClusters();
|
|
621
|
+
expect(clusters.length).toBeGreaterThan(0);
|
|
622
|
+
// Step 3: Detect patterns from clusters
|
|
623
|
+
const patterns = detector.detectFromClusters(clusters);
|
|
624
|
+
// Step 4: Store patterns
|
|
625
|
+
for (const pattern of patterns) {
|
|
626
|
+
store.addPattern(pattern);
|
|
627
|
+
}
|
|
628
|
+
// Step 5: Verify patterns are stored
|
|
629
|
+
const storedPatterns = store.getAllPatterns();
|
|
630
|
+
expect(storedPatterns.length).toBeGreaterThanOrEqual(0);
|
|
631
|
+
// Step 6: Get suggestions for context
|
|
632
|
+
const suggestions = store.getSuggestions({
|
|
633
|
+
focusArea: 'src/',
|
|
634
|
+
currentTask: 'refactor variables',
|
|
635
|
+
});
|
|
636
|
+
// Suggestions may or may not be returned depending on keyword matching
|
|
637
|
+
expect(Array.isArray(suggestions)).toBe(true);
|
|
638
|
+
});
|
|
639
|
+
it('should integrate with confidence scoring', () => {
|
|
640
|
+
// Add some patterns
|
|
641
|
+
const pattern = {
|
|
642
|
+
id: 'conf-int',
|
|
643
|
+
type: 'style',
|
|
644
|
+
trigger: 'formatting',
|
|
645
|
+
preference: 'prettier',
|
|
646
|
+
confidence: 'high',
|
|
647
|
+
occurrences: 5,
|
|
648
|
+
examples: [],
|
|
649
|
+
keywords: ['format', 'style'],
|
|
650
|
+
sourceClusterId: 'c1',
|
|
651
|
+
detectedAt: new Date(),
|
|
652
|
+
};
|
|
653
|
+
store.addPattern(pattern);
|
|
654
|
+
// Calculate confidence boost
|
|
655
|
+
const boost = store.calculateConfidenceBoost({
|
|
656
|
+
focusArea: 'src/',
|
|
657
|
+
currentTask: 'format code',
|
|
658
|
+
});
|
|
659
|
+
expect(boost).toBeGreaterThanOrEqual(0);
|
|
660
|
+
expect(boost).toBeLessThanOrEqual(0.1);
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
//# sourceMappingURL=learning-loop.test.js.map
|