driftdetect-mcp 0.4.0 → 0.4.3
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/LICENSE +21 -0
- package/dist/bin/server.d.ts +12 -2
- package/dist/bin/server.d.ts.map +1 -1
- package/dist/bin/server.js +25 -5
- package/dist/bin/server.js.map +1 -1
- package/dist/enterprise-server.d.ts +78 -0
- package/dist/enterprise-server.d.ts.map +1 -0
- package/dist/enterprise-server.js +201 -0
- package/dist/enterprise-server.js.map +1 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/cache.d.ts +86 -0
- package/dist/infrastructure/cache.d.ts.map +1 -0
- package/dist/infrastructure/cache.js +271 -0
- package/dist/infrastructure/cache.js.map +1 -0
- package/dist/infrastructure/cursor-manager.d.ts +86 -0
- package/dist/infrastructure/cursor-manager.d.ts.map +1 -0
- package/dist/infrastructure/cursor-manager.js +175 -0
- package/dist/infrastructure/cursor-manager.js.map +1 -0
- package/dist/infrastructure/error-handler.d.ts +82 -0
- package/dist/infrastructure/error-handler.d.ts.map +1 -0
- package/dist/infrastructure/error-handler.js +226 -0
- package/dist/infrastructure/error-handler.js.map +1 -0
- package/dist/infrastructure/index.d.ts +19 -0
- package/dist/infrastructure/index.d.ts.map +1 -0
- package/dist/infrastructure/index.js +26 -0
- package/dist/infrastructure/index.js.map +1 -0
- package/dist/infrastructure/metrics.d.ts +104 -0
- package/dist/infrastructure/metrics.d.ts.map +1 -0
- package/dist/infrastructure/metrics.js +291 -0
- package/dist/infrastructure/metrics.js.map +1 -0
- package/dist/infrastructure/rate-limiter.d.ts +59 -0
- package/dist/infrastructure/rate-limiter.d.ts.map +1 -0
- package/dist/infrastructure/rate-limiter.js +132 -0
- package/dist/infrastructure/rate-limiter.js.map +1 -0
- package/dist/infrastructure/response-builder.d.ts +104 -0
- package/dist/infrastructure/response-builder.d.ts.map +1 -0
- package/dist/infrastructure/response-builder.js +207 -0
- package/dist/infrastructure/response-builder.js.map +1 -0
- package/dist/infrastructure/token-estimator.d.ts +48 -0
- package/dist/infrastructure/token-estimator.d.ts.map +1 -0
- package/dist/infrastructure/token-estimator.js +131 -0
- package/dist/infrastructure/token-estimator.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1074 -17
- package/dist/server.js.map +1 -1
- package/dist/tools/detail/code-examples.d.ts +33 -0
- package/dist/tools/detail/code-examples.d.ts.map +1 -0
- package/dist/tools/detail/code-examples.js +126 -0
- package/dist/tools/detail/code-examples.js.map +1 -0
- package/dist/tools/detail/dna-check.d.ts +32 -0
- package/dist/tools/detail/dna-check.d.ts.map +1 -0
- package/dist/tools/detail/dna-check.js +231 -0
- package/dist/tools/detail/dna-check.js.map +1 -0
- package/dist/tools/detail/dna-profile.d.ts +37 -0
- package/dist/tools/detail/dna-profile.d.ts.map +1 -0
- package/dist/tools/detail/dna-profile.js +101 -0
- package/dist/tools/detail/dna-profile.js.map +1 -0
- package/dist/tools/detail/file-patterns.d.ts +39 -0
- package/dist/tools/detail/file-patterns.d.ts.map +1 -0
- package/dist/tools/detail/file-patterns.js +103 -0
- package/dist/tools/detail/file-patterns.js.map +1 -0
- package/dist/tools/detail/files-list.d.ts +30 -0
- package/dist/tools/detail/files-list.d.ts.map +1 -0
- package/dist/tools/detail/files-list.js +99 -0
- package/dist/tools/detail/files-list.js.map +1 -0
- package/dist/tools/detail/impact-analysis.d.ts +53 -0
- package/dist/tools/detail/impact-analysis.d.ts.map +1 -0
- package/dist/tools/detail/impact-analysis.js +130 -0
- package/dist/tools/detail/impact-analysis.js.map +1 -0
- package/dist/tools/detail/index.d.ts +23 -0
- package/dist/tools/detail/index.d.ts.map +1 -0
- package/dist/tools/detail/index.js +200 -0
- package/dist/tools/detail/index.js.map +1 -0
- package/dist/tools/detail/pattern-get.d.ts +45 -0
- package/dist/tools/detail/pattern-get.d.ts.map +1 -0
- package/dist/tools/detail/pattern-get.js +87 -0
- package/dist/tools/detail/pattern-get.js.map +1 -0
- package/dist/tools/detail/reachability.d.ts +60 -0
- package/dist/tools/detail/reachability.d.ts.map +1 -0
- package/dist/tools/detail/reachability.js +168 -0
- package/dist/tools/detail/reachability.js.map +1 -0
- package/dist/tools/discovery/capabilities.d.ts +28 -0
- package/dist/tools/discovery/capabilities.d.ts.map +1 -0
- package/dist/tools/discovery/capabilities.js +112 -0
- package/dist/tools/discovery/capabilities.js.map +1 -0
- package/dist/tools/discovery/index.d.ts +13 -0
- package/dist/tools/discovery/index.d.ts.map +1 -0
- package/dist/tools/discovery/index.js +30 -0
- package/dist/tools/discovery/index.js.map +1 -0
- package/dist/tools/discovery/projects.d.ts +26 -0
- package/dist/tools/discovery/projects.d.ts.map +1 -0
- package/dist/tools/discovery/projects.js +210 -0
- package/dist/tools/discovery/projects.js.map +1 -0
- package/dist/tools/discovery/status.d.ts +42 -0
- package/dist/tools/discovery/status.d.ts.map +1 -0
- package/dist/tools/discovery/status.js +157 -0
- package/dist/tools/discovery/status.js.map +1 -0
- package/dist/tools/exploration/contracts-list.d.ts +35 -0
- package/dist/tools/exploration/contracts-list.d.ts.map +1 -0
- package/dist/tools/exploration/contracts-list.js +106 -0
- package/dist/tools/exploration/contracts-list.js.map +1 -0
- package/dist/tools/exploration/files-list.d.ts +29 -0
- package/dist/tools/exploration/files-list.d.ts.map +1 -0
- package/dist/tools/exploration/files-list.js +94 -0
- package/dist/tools/exploration/files-list.js.map +1 -0
- package/dist/tools/exploration/index.d.ts +17 -0
- package/dist/tools/exploration/index.d.ts.map +1 -0
- package/dist/tools/exploration/index.js +126 -0
- package/dist/tools/exploration/index.js.map +1 -0
- package/dist/tools/exploration/patterns-list.d.ts +40 -0
- package/dist/tools/exploration/patterns-list.d.ts.map +1 -0
- package/dist/tools/exploration/patterns-list.js +172 -0
- package/dist/tools/exploration/patterns-list.js.map +1 -0
- package/dist/tools/exploration/security-summary.d.ts +49 -0
- package/dist/tools/exploration/security-summary.d.ts.map +1 -0
- package/dist/tools/exploration/security-summary.js +111 -0
- package/dist/tools/exploration/security-summary.js.map +1 -0
- package/dist/tools/exploration/trends.d.ts +49 -0
- package/dist/tools/exploration/trends.d.ts.map +1 -0
- package/dist/tools/exploration/trends.js +147 -0
- package/dist/tools/exploration/trends.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/orchestration/context.d.ts +72 -0
- package/dist/tools/orchestration/context.d.ts.map +1 -0
- package/dist/tools/orchestration/context.js +499 -0
- package/dist/tools/orchestration/context.js.map +1 -0
- package/dist/tools/orchestration/index.d.ts +11 -0
- package/dist/tools/orchestration/index.d.ts.map +1 -0
- package/dist/tools/orchestration/index.js +56 -0
- package/dist/tools/orchestration/index.js.map +1 -0
- package/dist/tools/registry.d.ts +41 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/registry.js.map +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Cache
|
|
3
|
+
*
|
|
4
|
+
* Multi-level caching for MCP responses:
|
|
5
|
+
* - L1: In-memory LRU cache (fast, limited size)
|
|
6
|
+
* - L2: File-based cache (slower, larger, persistent)
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Automatic invalidation on pattern changes
|
|
10
|
+
* - TTL-based expiration
|
|
11
|
+
* - Cache key generation from tool + args
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'crypto';
|
|
14
|
+
import * as fs from 'fs/promises';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
const DEFAULT_CONFIG = {
|
|
17
|
+
l1MaxSize: 100,
|
|
18
|
+
l1TtlMs: 300000, // 5 minutes
|
|
19
|
+
l2Enabled: false,
|
|
20
|
+
l2TtlMs: 3600000, // 1 hour
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Simple LRU Cache implementation
|
|
24
|
+
*/
|
|
25
|
+
class LRUCache {
|
|
26
|
+
maxSize;
|
|
27
|
+
cache = new Map();
|
|
28
|
+
constructor(maxSize) {
|
|
29
|
+
this.maxSize = maxSize;
|
|
30
|
+
}
|
|
31
|
+
get(key) {
|
|
32
|
+
const value = this.cache.get(key);
|
|
33
|
+
if (value !== undefined) {
|
|
34
|
+
// Move to end (most recently used)
|
|
35
|
+
this.cache.delete(key);
|
|
36
|
+
this.cache.set(key, value);
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
set(key, value) {
|
|
41
|
+
// Delete if exists to update position
|
|
42
|
+
this.cache.delete(key);
|
|
43
|
+
// Evict oldest if at capacity
|
|
44
|
+
if (this.cache.size >= this.maxSize) {
|
|
45
|
+
const firstKey = this.cache.keys().next().value;
|
|
46
|
+
if (firstKey !== undefined) {
|
|
47
|
+
this.cache.delete(firstKey);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
this.cache.set(key, value);
|
|
51
|
+
}
|
|
52
|
+
delete(key) {
|
|
53
|
+
return this.cache.delete(key);
|
|
54
|
+
}
|
|
55
|
+
clear() {
|
|
56
|
+
this.cache.clear();
|
|
57
|
+
}
|
|
58
|
+
has(key) {
|
|
59
|
+
return this.cache.has(key);
|
|
60
|
+
}
|
|
61
|
+
*entries() {
|
|
62
|
+
yield* this.cache.entries();
|
|
63
|
+
}
|
|
64
|
+
get size() {
|
|
65
|
+
return this.cache.size;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export class ResponseCache {
|
|
69
|
+
l1;
|
|
70
|
+
config;
|
|
71
|
+
invalidationIndex = new Map();
|
|
72
|
+
constructor(config = {}) {
|
|
73
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
74
|
+
this.l1 = new LRUCache(this.config.l1MaxSize);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get cached response
|
|
78
|
+
*/
|
|
79
|
+
async get(key) {
|
|
80
|
+
// Try L1 first
|
|
81
|
+
const l1Result = this.l1.get(key);
|
|
82
|
+
if (l1Result && !this.isExpired(l1Result)) {
|
|
83
|
+
return { ...l1Result, source: 'l1' };
|
|
84
|
+
}
|
|
85
|
+
// Try L2 if enabled
|
|
86
|
+
if (this.config.l2Enabled && this.config.l2CacheDir) {
|
|
87
|
+
const l2Result = await this.getFromL2(key);
|
|
88
|
+
if (l2Result && !this.isExpired(l2Result)) {
|
|
89
|
+
// Promote to L1
|
|
90
|
+
this.l1.set(key, l2Result);
|
|
91
|
+
return { ...l2Result, source: 'l2' };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Set cached response
|
|
98
|
+
*/
|
|
99
|
+
async set(key, data, options = {}) {
|
|
100
|
+
const ttlMs = options.ttlMs ?? this.config.l1TtlMs;
|
|
101
|
+
const now = Date.now();
|
|
102
|
+
const cached = {
|
|
103
|
+
data,
|
|
104
|
+
createdAt: now,
|
|
105
|
+
expiresAt: now + ttlMs,
|
|
106
|
+
invalidationKeys: options.invalidationKeys,
|
|
107
|
+
};
|
|
108
|
+
// Store in L1
|
|
109
|
+
this.l1.set(key, cached);
|
|
110
|
+
// Update invalidation index
|
|
111
|
+
if (options.invalidationKeys) {
|
|
112
|
+
for (const invKey of options.invalidationKeys) {
|
|
113
|
+
if (!this.invalidationIndex.has(invKey)) {
|
|
114
|
+
this.invalidationIndex.set(invKey, new Set());
|
|
115
|
+
}
|
|
116
|
+
this.invalidationIndex.get(invKey).add(key);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Store in L2 if enabled
|
|
120
|
+
if (this.config.l2Enabled && this.config.l2CacheDir) {
|
|
121
|
+
await this.setToL2(key, {
|
|
122
|
+
...cached,
|
|
123
|
+
expiresAt: now + this.config.l2TtlMs,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Delete cached response
|
|
129
|
+
*/
|
|
130
|
+
async delete(key) {
|
|
131
|
+
this.l1.delete(key);
|
|
132
|
+
if (this.config.l2Enabled && this.config.l2CacheDir) {
|
|
133
|
+
await this.deleteFromL2(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Invalidate all caches matching an invalidation key
|
|
138
|
+
*/
|
|
139
|
+
async invalidate(invalidationKey) {
|
|
140
|
+
const cacheKeys = this.invalidationIndex.get(invalidationKey);
|
|
141
|
+
if (!cacheKeys) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
let count = 0;
|
|
145
|
+
for (const key of cacheKeys) {
|
|
146
|
+
await this.delete(key);
|
|
147
|
+
count++;
|
|
148
|
+
}
|
|
149
|
+
this.invalidationIndex.delete(invalidationKey);
|
|
150
|
+
return count;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Invalidate caches for specific categories
|
|
154
|
+
*/
|
|
155
|
+
async invalidateCategories(categories) {
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (const category of categories) {
|
|
158
|
+
count += await this.invalidate(`category:${category}`);
|
|
159
|
+
}
|
|
160
|
+
return count;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Invalidate all pattern-related caches
|
|
164
|
+
*/
|
|
165
|
+
async invalidatePatterns() {
|
|
166
|
+
return this.invalidate('patterns');
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Clear all caches
|
|
170
|
+
*/
|
|
171
|
+
async clear() {
|
|
172
|
+
this.l1.clear();
|
|
173
|
+
this.invalidationIndex.clear();
|
|
174
|
+
if (this.config.l2Enabled && this.config.l2CacheDir) {
|
|
175
|
+
await this.clearL2();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Generate cache key from tool name and arguments
|
|
180
|
+
*/
|
|
181
|
+
generateKey(tool, args) {
|
|
182
|
+
// Remove undefined values and sort keys for consistency
|
|
183
|
+
const normalized = Object.keys(args)
|
|
184
|
+
.filter(k => args[k] !== undefined)
|
|
185
|
+
.sort()
|
|
186
|
+
.reduce((acc, key) => {
|
|
187
|
+
acc[key] = args[key];
|
|
188
|
+
return acc;
|
|
189
|
+
}, {});
|
|
190
|
+
const hash = createHash('sha256')
|
|
191
|
+
.update(`${tool}:${JSON.stringify(normalized)}`)
|
|
192
|
+
.digest('hex')
|
|
193
|
+
.slice(0, 16);
|
|
194
|
+
return `drift:${tool}:${hash}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get cache statistics
|
|
198
|
+
*/
|
|
199
|
+
getStats() {
|
|
200
|
+
return {
|
|
201
|
+
l1Size: this.l1.size,
|
|
202
|
+
l1MaxSize: this.config.l1MaxSize,
|
|
203
|
+
invalidationKeys: this.invalidationIndex.size,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Private methods
|
|
207
|
+
isExpired(cached) {
|
|
208
|
+
return Date.now() > cached.expiresAt;
|
|
209
|
+
}
|
|
210
|
+
async getFromL2(key) {
|
|
211
|
+
if (!this.config.l2CacheDir)
|
|
212
|
+
return null;
|
|
213
|
+
try {
|
|
214
|
+
const filePath = this.getL2Path(key);
|
|
215
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
216
|
+
return JSON.parse(content);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async setToL2(key, cached) {
|
|
223
|
+
if (!this.config.l2CacheDir)
|
|
224
|
+
return;
|
|
225
|
+
try {
|
|
226
|
+
const filePath = this.getL2Path(key);
|
|
227
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
228
|
+
await fs.writeFile(filePath, JSON.stringify(cached));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Ignore L2 write errors
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async deleteFromL2(key) {
|
|
235
|
+
if (!this.config.l2CacheDir)
|
|
236
|
+
return;
|
|
237
|
+
try {
|
|
238
|
+
const filePath = this.getL2Path(key);
|
|
239
|
+
await fs.unlink(filePath);
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
// Ignore if file doesn't exist
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async clearL2() {
|
|
246
|
+
if (!this.config.l2CacheDir)
|
|
247
|
+
return;
|
|
248
|
+
try {
|
|
249
|
+
await fs.rm(this.config.l2CacheDir, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// Ignore errors
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
getL2Path(key) {
|
|
256
|
+
const hash = createHash('sha256').update(key).digest('hex');
|
|
257
|
+
return path.join(this.config.l2CacheDir, hash.slice(0, 2), `${hash}.json`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Create a cache instance with project-specific L2 directory
|
|
262
|
+
*/
|
|
263
|
+
export function createCache(projectRoot, config = {}) {
|
|
264
|
+
return new ResponseCache({
|
|
265
|
+
...config,
|
|
266
|
+
l2CacheDir: config.l2Enabled
|
|
267
|
+
? path.join(projectRoot, '.drift', 'cache', 'mcp')
|
|
268
|
+
: undefined,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/infrastructure/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides stable, opaque cursor-based pagination.
|
|
5
|
+
* Cursors are:
|
|
6
|
+
* - Base64url encoded for URL safety
|
|
7
|
+
* - Versioned for forward compatibility
|
|
8
|
+
* - Time-limited to prevent stale pagination
|
|
9
|
+
* - Query-bound to prevent misuse
|
|
10
|
+
*/
|
|
11
|
+
export interface CursorData {
|
|
12
|
+
lastId?: string;
|
|
13
|
+
lastScore?: number;
|
|
14
|
+
lastTimestamp?: string;
|
|
15
|
+
offset?: number;
|
|
16
|
+
queryHash: string;
|
|
17
|
+
createdAt: number;
|
|
18
|
+
version: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CursorConfig {
|
|
21
|
+
version: number;
|
|
22
|
+
maxAgeMs: number;
|
|
23
|
+
secret?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare class CursorManager {
|
|
26
|
+
private readonly config;
|
|
27
|
+
constructor(config?: Partial<CursorConfig>);
|
|
28
|
+
/**
|
|
29
|
+
* Encode cursor data to opaque string
|
|
30
|
+
*/
|
|
31
|
+
encode(data: Omit<CursorData, 'createdAt' | 'version'>): string;
|
|
32
|
+
/**
|
|
33
|
+
* Decode cursor string to data
|
|
34
|
+
*/
|
|
35
|
+
decode(cursor: string): CursorData | null;
|
|
36
|
+
/**
|
|
37
|
+
* Create a query hash for cursor validation
|
|
38
|
+
*/
|
|
39
|
+
createQueryHash(params: Record<string, unknown>): string;
|
|
40
|
+
/**
|
|
41
|
+
* Validate that a cursor matches the current query
|
|
42
|
+
*/
|
|
43
|
+
validateQueryMatch(cursor: CursorData, params: Record<string, unknown>): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Create cursor for pattern-based pagination
|
|
46
|
+
*/
|
|
47
|
+
createPatternCursor(lastPattern: {
|
|
48
|
+
id: string;
|
|
49
|
+
confidence: {
|
|
50
|
+
score: number;
|
|
51
|
+
};
|
|
52
|
+
}, queryParams: Record<string, unknown>): string;
|
|
53
|
+
/**
|
|
54
|
+
* Create cursor for offset-based pagination (fallback)
|
|
55
|
+
*/
|
|
56
|
+
createOffsetCursor(offset: number, queryParams: Record<string, unknown>): string;
|
|
57
|
+
/**
|
|
58
|
+
* Create cursor for timestamp-based pagination
|
|
59
|
+
*/
|
|
60
|
+
createTimestampCursor(lastTimestamp: string | Date, lastId: string, queryParams: Record<string, unknown>): string;
|
|
61
|
+
/**
|
|
62
|
+
* Sign data with HMAC
|
|
63
|
+
*/
|
|
64
|
+
private sign;
|
|
65
|
+
/**
|
|
66
|
+
* Verify HMAC signature
|
|
67
|
+
*/
|
|
68
|
+
private verify;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Singleton instance for convenience
|
|
72
|
+
*/
|
|
73
|
+
export declare const cursorManager: CursorManager;
|
|
74
|
+
/**
|
|
75
|
+
* Simple cursor creation for offset-based pagination
|
|
76
|
+
* Creates a lightweight cursor without query validation
|
|
77
|
+
*/
|
|
78
|
+
export declare function createCursor(offset: number, limit: number): string;
|
|
79
|
+
/**
|
|
80
|
+
* Parse a simple cursor
|
|
81
|
+
*/
|
|
82
|
+
export declare function parseCursor(cursor: string): {
|
|
83
|
+
offset: number;
|
|
84
|
+
limit: number;
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=cursor-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-manager.d.ts","sourceRoot":"","sources":["../../src/infrastructure/cursor-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,WAAW,UAAU;IAEzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,SAAS,EAAE,MAAM,CAAC;IAGlB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAOD,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;gBAE1B,MAAM,GAAE,OAAO,CAAC,YAAY,CAAM;IAI9C;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM;IAmB/D;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAsCzC;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAexD;;OAEG;IACH,kBAAkB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAKhF;;OAEG;IACH,mBAAmB,CACjB,WAAW,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE;YAAE,KAAK,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,EAC1D,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM;IAQT;;OAEG;IACH,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM;IAOT;;OAEG;IACH,qBAAqB,CACnB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM;IAUT;;OAEG;IACH,OAAO,CAAC,IAAI;IAWZ;;OAEG;IACH,OAAO,CAAC,MAAM;CAIf;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,eAAsB,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAW7E"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor Manager
|
|
3
|
+
*
|
|
4
|
+
* Provides stable, opaque cursor-based pagination.
|
|
5
|
+
* Cursors are:
|
|
6
|
+
* - Base64url encoded for URL safety
|
|
7
|
+
* - Versioned for forward compatibility
|
|
8
|
+
* - Time-limited to prevent stale pagination
|
|
9
|
+
* - Query-bound to prevent misuse
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
version: 1,
|
|
14
|
+
maxAgeMs: 3600000, // 1 hour
|
|
15
|
+
};
|
|
16
|
+
export class CursorManager {
|
|
17
|
+
config;
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Encode cursor data to opaque string
|
|
23
|
+
*/
|
|
24
|
+
encode(data) {
|
|
25
|
+
const fullData = {
|
|
26
|
+
...data,
|
|
27
|
+
createdAt: Date.now(),
|
|
28
|
+
version: this.config.version,
|
|
29
|
+
};
|
|
30
|
+
const json = JSON.stringify(fullData);
|
|
31
|
+
const encoded = Buffer.from(json).toString('base64url');
|
|
32
|
+
// Optionally add HMAC signature
|
|
33
|
+
if (this.config.secret) {
|
|
34
|
+
const signature = this.sign(encoded);
|
|
35
|
+
return `${encoded}.${signature}`;
|
|
36
|
+
}
|
|
37
|
+
return encoded;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decode cursor string to data
|
|
41
|
+
*/
|
|
42
|
+
decode(cursor) {
|
|
43
|
+
try {
|
|
44
|
+
let encoded = cursor;
|
|
45
|
+
// Verify signature if secret is configured
|
|
46
|
+
if (this.config.secret) {
|
|
47
|
+
const parts = cursor.split('.');
|
|
48
|
+
if (parts.length !== 2) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const [data, signature] = parts;
|
|
52
|
+
if (!data || !signature || !this.verify(data, signature)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
encoded = data;
|
|
56
|
+
}
|
|
57
|
+
const json = Buffer.from(encoded, 'base64url').toString();
|
|
58
|
+
const data = JSON.parse(json);
|
|
59
|
+
// Validate version
|
|
60
|
+
if (data.version !== this.config.version) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// Validate age
|
|
64
|
+
if (Date.now() - data.createdAt > this.config.maxAgeMs) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create a query hash for cursor validation
|
|
75
|
+
*/
|
|
76
|
+
createQueryHash(params) {
|
|
77
|
+
// Sort keys for consistent hashing
|
|
78
|
+
const sorted = Object.keys(params)
|
|
79
|
+
.sort()
|
|
80
|
+
.reduce((acc, key) => {
|
|
81
|
+
acc[key] = params[key];
|
|
82
|
+
return acc;
|
|
83
|
+
}, {});
|
|
84
|
+
return createHash('sha256')
|
|
85
|
+
.update(JSON.stringify(sorted))
|
|
86
|
+
.digest('hex')
|
|
87
|
+
.slice(0, 16);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Validate that a cursor matches the current query
|
|
91
|
+
*/
|
|
92
|
+
validateQueryMatch(cursor, params) {
|
|
93
|
+
const currentHash = this.createQueryHash(params);
|
|
94
|
+
return cursor.queryHash === currentHash;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Create cursor for pattern-based pagination
|
|
98
|
+
*/
|
|
99
|
+
createPatternCursor(lastPattern, queryParams) {
|
|
100
|
+
return this.encode({
|
|
101
|
+
lastId: lastPattern.id,
|
|
102
|
+
lastScore: lastPattern.confidence.score,
|
|
103
|
+
queryHash: this.createQueryHash(queryParams),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create cursor for offset-based pagination (fallback)
|
|
108
|
+
*/
|
|
109
|
+
createOffsetCursor(offset, queryParams) {
|
|
110
|
+
return this.encode({
|
|
111
|
+
offset,
|
|
112
|
+
queryHash: this.createQueryHash(queryParams),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Create cursor for timestamp-based pagination
|
|
117
|
+
*/
|
|
118
|
+
createTimestampCursor(lastTimestamp, lastId, queryParams) {
|
|
119
|
+
return this.encode({
|
|
120
|
+
lastTimestamp: typeof lastTimestamp === 'string'
|
|
121
|
+
? lastTimestamp
|
|
122
|
+
: lastTimestamp.toISOString(),
|
|
123
|
+
lastId,
|
|
124
|
+
queryHash: this.createQueryHash(queryParams),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Sign data with HMAC
|
|
129
|
+
*/
|
|
130
|
+
sign(data) {
|
|
131
|
+
if (!this.config.secret) {
|
|
132
|
+
throw new Error('Secret required for signing');
|
|
133
|
+
}
|
|
134
|
+
return createHash('sha256')
|
|
135
|
+
.update(data + this.config.secret)
|
|
136
|
+
.digest('hex')
|
|
137
|
+
.slice(0, 16);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Verify HMAC signature
|
|
141
|
+
*/
|
|
142
|
+
verify(data, signature) {
|
|
143
|
+
const expected = this.sign(data);
|
|
144
|
+
return expected === signature;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Singleton instance for convenience
|
|
149
|
+
*/
|
|
150
|
+
export const cursorManager = new CursorManager();
|
|
151
|
+
/**
|
|
152
|
+
* Simple cursor creation for offset-based pagination
|
|
153
|
+
* Creates a lightweight cursor without query validation
|
|
154
|
+
*/
|
|
155
|
+
export function createCursor(offset, limit) {
|
|
156
|
+
const data = { offset, limit, v: 1 };
|
|
157
|
+
return Buffer.from(JSON.stringify(data)).toString('base64url');
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Parse a simple cursor
|
|
161
|
+
*/
|
|
162
|
+
export function parseCursor(cursor) {
|
|
163
|
+
try {
|
|
164
|
+
const json = Buffer.from(cursor, 'base64url').toString();
|
|
165
|
+
const data = JSON.parse(json);
|
|
166
|
+
return {
|
|
167
|
+
offset: data.offset ?? 0,
|
|
168
|
+
limit: data.limit ?? 20,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { offset: 0, limit: 20 };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=cursor-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-manager.js","sourceRoot":"","sources":["../../src/infrastructure/cursor-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Error Handler
|
|
3
|
+
*
|
|
4
|
+
* Provides structured error responses with:
|
|
5
|
+
* - Consistent error codes
|
|
6
|
+
* - Recovery suggestions for AI
|
|
7
|
+
* - Request tracking
|
|
8
|
+
*/
|
|
9
|
+
export declare enum DriftErrorCode {
|
|
10
|
+
INVALID_ARGUMENT = "INVALID_ARGUMENT",
|
|
11
|
+
PATTERN_NOT_FOUND = "PATTERN_NOT_FOUND",
|
|
12
|
+
FILE_NOT_FOUND = "FILE_NOT_FOUND",
|
|
13
|
+
INVALID_CURSOR = "INVALID_CURSOR",
|
|
14
|
+
INVALID_CATEGORY = "INVALID_CATEGORY",
|
|
15
|
+
MISSING_REQUIRED_PARAM = "MISSING_REQUIRED_PARAM",
|
|
16
|
+
SCAN_REQUIRED = "SCAN_REQUIRED",
|
|
17
|
+
STORE_UNAVAILABLE = "STORE_UNAVAILABLE",
|
|
18
|
+
ANALYSIS_FAILED = "ANALYSIS_FAILED",
|
|
19
|
+
INTERNAL_ERROR = "INTERNAL_ERROR",
|
|
20
|
+
RATE_LIMITED = "RATE_LIMITED",
|
|
21
|
+
CALLGRAPH_NOT_BUILT = "CALLGRAPH_NOT_BUILT",
|
|
22
|
+
DNA_NOT_ANALYZED = "DNA_NOT_ANALYZED"
|
|
23
|
+
}
|
|
24
|
+
export interface RecoveryHint {
|
|
25
|
+
suggestion: string;
|
|
26
|
+
alternativeTools?: string[];
|
|
27
|
+
retryAfterMs?: number;
|
|
28
|
+
command?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DriftErrorDetails {
|
|
31
|
+
code: DriftErrorCode;
|
|
32
|
+
message: string;
|
|
33
|
+
details?: Record<string, unknown> | undefined;
|
|
34
|
+
recovery?: RecoveryHint | undefined;
|
|
35
|
+
}
|
|
36
|
+
export declare class DriftError extends Error {
|
|
37
|
+
readonly code: DriftErrorCode;
|
|
38
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
39
|
+
readonly recovery?: RecoveryHint | undefined;
|
|
40
|
+
constructor(errorDetails: DriftErrorDetails);
|
|
41
|
+
/**
|
|
42
|
+
* Convert to MCP error response format
|
|
43
|
+
*/
|
|
44
|
+
toMCPResponse(requestId?: string): {
|
|
45
|
+
content: Array<{
|
|
46
|
+
type: string;
|
|
47
|
+
text: string;
|
|
48
|
+
}>;
|
|
49
|
+
isError: true;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Error factory functions for common errors
|
|
54
|
+
*/
|
|
55
|
+
export declare const Errors: {
|
|
56
|
+
invalidArgument(param: string, reason: string, suggestion?: string): DriftError;
|
|
57
|
+
missingRequired(param: string): DriftError;
|
|
58
|
+
missingParameter(param: string): DriftError;
|
|
59
|
+
notFound(type: string, id: string): DriftError;
|
|
60
|
+
invalidParameter(param: string, reason: string): DriftError;
|
|
61
|
+
patternNotFound(patternId: string): DriftError;
|
|
62
|
+
fileNotFound(path: string): DriftError;
|
|
63
|
+
invalidCursor(): DriftError;
|
|
64
|
+
invalidCategory(category: string, validCategories: string[]): DriftError;
|
|
65
|
+
scanRequired(): DriftError;
|
|
66
|
+
callgraphNotBuilt(): DriftError;
|
|
67
|
+
dnaNotAnalyzed(): DriftError;
|
|
68
|
+
rateLimited(retryAfterMs: number): DriftError;
|
|
69
|
+
internal(message: string, details?: Record<string, unknown>): DriftError;
|
|
70
|
+
custom(code: string, message: string, alternativeTools?: string[]): DriftError;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Error handler middleware
|
|
74
|
+
*/
|
|
75
|
+
export declare function handleError(error: unknown, requestId?: string): {
|
|
76
|
+
content: Array<{
|
|
77
|
+
type: string;
|
|
78
|
+
text: string;
|
|
79
|
+
}>;
|
|
80
|
+
isError: true;
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/infrastructure/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oBAAY,cAAc;IAExB,gBAAgB,qBAAqB;IACrC,iBAAiB,sBAAsB;IACvC,cAAc,mBAAmB;IACjC,cAAc,mBAAmB;IACjC,gBAAgB,qBAAqB;IACrC,sBAAsB,2BAA2B;IAGjD,aAAa,kBAAkB;IAC/B,iBAAiB,sBAAsB;IACvC,eAAe,oBAAoB;IACnC,cAAc,mBAAmB;IAGjC,YAAY,iBAAiB;IAG7B,mBAAmB,wBAAwB;IAC3C,gBAAgB,qBAAqB;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACrC;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAgB,IAAI,EAAE,cAAc,CAAC;IACrC,SAAgB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAC9D,SAAgB,QAAQ,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;gBAExC,YAAY,EAAE,iBAAiB;IAQ3C;;OAEG;IACH,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG;QACjC,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAC/C,OAAO,EAAE,IAAI,CAAC;KACf;CAsBF;AAED;;GAEG;AACH,eAAO,MAAM,MAAM;2BACM,MAAM,UAAU,MAAM,eAAe,MAAM,GAAG,UAAU;2BASxD,MAAM,GAAG,UAAU;4BAWlB,MAAM,GAAG,UAAU;mBAI5B,MAAM,MAAM,MAAM,GAAG,UAAU;4BAiBtB,MAAM,UAAU,MAAM,GAAG,UAAU;+BAIhC,MAAM,GAAG,UAAU;uBAY3B,MAAM,GAAG,UAAU;qBAYrB,UAAU;8BAUD,MAAM,mBAAmB,MAAM,EAAE,GAAG,UAAU;oBAcxD,UAAU;yBAYL,UAAU;sBAWb,UAAU;8BAWF,MAAM,GAAG,UAAU;sBAW3B,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU;iBAW3D,MAAM,WAAW,MAAM,qBAAqB,MAAM,EAAE,GAAG,UAAU;CAU/E,CAAC;AAEF;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG;IAC/D,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/C,OAAO,EAAE,IAAI,CAAC;CACf,CAQA"}
|