driftguard-mcp 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -5
- package/dist/bin.js +166 -153
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@ driftguard-mcp setup
|
|
|
24
24
|
{
|
|
25
25
|
"mcpServers": {
|
|
26
26
|
"driftguard": {
|
|
27
|
-
"command": "driftguard-mcp"
|
|
27
|
+
"command": "driftguard-mcp",
|
|
28
|
+
"env": { "DRIFTCLI_ADAPTER": "claude" }
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
}
|
|
@@ -36,7 +37,8 @@ driftguard-mcp setup
|
|
|
36
37
|
{
|
|
37
38
|
"mcpServers": {
|
|
38
39
|
"driftguard": {
|
|
39
|
-
"command": "driftguard-mcp"
|
|
40
|
+
"command": "driftguard-mcp",
|
|
41
|
+
"env": { "DRIFTCLI_ADAPTER": "gemini" }
|
|
40
42
|
}
|
|
41
43
|
}
|
|
42
44
|
}
|
|
@@ -48,7 +50,8 @@ driftguard-mcp setup
|
|
|
48
50
|
{
|
|
49
51
|
"mcpServers": {
|
|
50
52
|
"driftguard": {
|
|
51
|
-
"command": "driftguard-mcp"
|
|
53
|
+
"command": "driftguard-mcp",
|
|
54
|
+
"env": { "DRIFTCLI_ADAPTER": "codex" }
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
}
|
|
@@ -60,13 +63,16 @@ driftguard-mcp setup
|
|
|
60
63
|
{
|
|
61
64
|
"mcpServers": {
|
|
62
65
|
"driftguard": {
|
|
63
|
-
"command": "driftguard-mcp"
|
|
66
|
+
"command": "driftguard-mcp",
|
|
67
|
+
"env": { "DRIFTCLI_ADAPTER": "claude" }
|
|
64
68
|
}
|
|
65
69
|
}
|
|
66
70
|
}
|
|
67
71
|
```
|
|
68
72
|
|
|
69
|
-
> Note: Cursor drift is calculated from
|
|
73
|
+
> Note: Cursor drift is calculated from Claude Code sessions on your machine — not from Cursor's own conversation history.
|
|
74
|
+
|
|
75
|
+
> **`DRIFTCLI_ADAPTER`** tells driftguard-mcp which CLI's sessions to read. Without it, the server falls back to whichever session file was modified most recently, which may be from a different CLI. `driftguard-mcp setup` sets this automatically.
|
|
70
76
|
|
|
71
77
|
</details>
|
|
72
78
|
|
|
@@ -169,6 +175,14 @@ Both are plain JSON. All fields are optional.
|
|
|
169
175
|
| `storage.directory` | `~/.driftcli/history` | Override snapshot storage path |
|
|
170
176
|
| `sessionResolution.cacheTtlMs` | `5000` | How long to cache the resolved session file (ms) |
|
|
171
177
|
|
|
178
|
+
### Environment variables
|
|
179
|
+
|
|
180
|
+
| Variable | Description |
|
|
181
|
+
|----------|-------------|
|
|
182
|
+
| `DRIFTCLI_ADAPTER` | Pin session lookup to a specific CLI: `claude`, `gemini`, or `codex`. Set automatically by `driftguard-mcp setup`. |
|
|
183
|
+
| `DRIFTCLI_SESSION_ID` | Force a specific session UUID (Claude Code only). |
|
|
184
|
+
| `DRIFTCLI_HOME` | Override the home directory used for session file discovery. |
|
|
185
|
+
|
|
172
186
|
---
|
|
173
187
|
|
|
174
188
|
## CLI watcher
|
package/dist/bin.js
CHANGED
|
@@ -235,39 +235,65 @@ var init_gemini_adapter = __esm({
|
|
|
235
235
|
this.name = "gemini";
|
|
236
236
|
}
|
|
237
237
|
canParse(filePath) {
|
|
238
|
-
return filePath.includes(".gemini") && filePath.endsWith(".
|
|
238
|
+
return filePath.includes(".gemini") && filePath.endsWith(".json");
|
|
239
239
|
}
|
|
240
240
|
parse(filePath) {
|
|
241
241
|
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
242
|
-
|
|
242
|
+
let session;
|
|
243
|
+
try {
|
|
244
|
+
session = JSON.parse(raw);
|
|
245
|
+
} catch {
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
if (!Array.isArray(session.messages)) return [];
|
|
243
249
|
const messages = [];
|
|
244
250
|
let index = 0;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
251
|
+
let pendingToolTokens = 0;
|
|
252
|
+
let pendingInputTokens;
|
|
253
|
+
for (const msg of session.messages) {
|
|
254
|
+
if (msg.type !== "user" && msg.type !== "gemini") continue;
|
|
255
|
+
let content;
|
|
256
|
+
if (typeof msg.content === "string") {
|
|
257
|
+
content = msg.content.trim();
|
|
258
|
+
} else if (Array.isArray(msg.content)) {
|
|
259
|
+
content = msg.content.map((p) => p.text ?? "").join("\n").trim();
|
|
260
|
+
} else {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (!content && msg.type === "gemini") {
|
|
264
|
+
pendingToolTokens += msg.tokens?.tool ?? 0;
|
|
265
|
+
pendingToolTokens += msg.tokens?.thoughts ?? 0;
|
|
266
|
+
if ((msg.tokens?.tool ?? 0) === 0 && msg.toolCalls?.length) {
|
|
267
|
+
for (const tc of msg.toolCalls) {
|
|
268
|
+
if (tc.args) pendingToolTokens += Math.round(JSON.stringify(tc.args).length / 4);
|
|
269
|
+
if (tc.result) pendingToolTokens += Math.round(JSON.stringify(tc.result).length / 4);
|
|
259
270
|
}
|
|
260
271
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
if (msg.tokens?.input) pendingInputTokens = msg.tokens.input;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (!content) continue;
|
|
276
|
+
const role = msg.type === "gemini" ? "assistant" : "user";
|
|
277
|
+
const ts = msg.timestamp ? new Date(msg.timestamp).getTime() : Date.now();
|
|
278
|
+
let toolTokens = pendingToolTokens + (msg.tokens?.tool ?? 0);
|
|
279
|
+
pendingToolTokens = 0;
|
|
280
|
+
toolTokens += msg.tokens?.thoughts ?? 0;
|
|
281
|
+
if ((msg.tokens?.tool ?? 0) === 0 && msg.toolCalls?.length) {
|
|
282
|
+
for (const tc of msg.toolCalls) {
|
|
283
|
+
if (tc.args) toolTokens += Math.round(JSON.stringify(tc.args).length / 4);
|
|
284
|
+
if (tc.result) toolTokens += Math.round(JSON.stringify(tc.result).length / 4);
|
|
285
|
+
}
|
|
270
286
|
}
|
|
287
|
+
const inputTokens = msg.tokens?.input ?? pendingInputTokens;
|
|
288
|
+
pendingInputTokens = void 0;
|
|
289
|
+
messages.push({
|
|
290
|
+
id: `gemini-${index++}`,
|
|
291
|
+
role,
|
|
292
|
+
content,
|
|
293
|
+
timestamp: isFinite(ts) ? ts : Date.now(),
|
|
294
|
+
...toolTokens > 0 ? { toolTokens } : {},
|
|
295
|
+
...inputTokens !== void 0 ? { inputTokens } : {}
|
|
296
|
+
});
|
|
271
297
|
}
|
|
272
298
|
return messages;
|
|
273
299
|
}
|
|
@@ -289,10 +315,12 @@ var init_gemini_adapter = __esm({
|
|
|
289
315
|
}
|
|
290
316
|
});
|
|
291
317
|
for (const dir of sessionDirs) {
|
|
318
|
+
const chatsDir = path2.join(dir, "chats");
|
|
319
|
+
if (!fs2.existsSync(chatsDir)) continue;
|
|
292
320
|
try {
|
|
293
|
-
const files = fs2.readdirSync(
|
|
321
|
+
const files = fs2.readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
|
|
294
322
|
for (const file of files) {
|
|
295
|
-
const fullPath = path2.join(
|
|
323
|
+
const fullPath = path2.join(chatsDir, file);
|
|
296
324
|
try {
|
|
297
325
|
const mtime = fs2.statSync(fullPath).mtimeMs;
|
|
298
326
|
if (mtime > latestTime) {
|
|
@@ -337,49 +365,40 @@ var init_codex_adapter = __esm({
|
|
|
337
365
|
const messages = [];
|
|
338
366
|
let index = 0;
|
|
339
367
|
let pendingToolTokens = 0;
|
|
368
|
+
let pendingInputTokens;
|
|
340
369
|
for (const line of lines) {
|
|
341
370
|
try {
|
|
342
371
|
const entry = JSON.parse(line);
|
|
343
|
-
if (entry.
|
|
344
|
-
|
|
345
|
-
|
|
372
|
+
if (entry.type !== "event_msg" || !entry.payload) continue;
|
|
373
|
+
const payload = entry.payload;
|
|
374
|
+
if (payload.type === "token_count") {
|
|
375
|
+
const tc = payload;
|
|
376
|
+
const usage = tc.info?.total_token_usage;
|
|
377
|
+
if (usage?.input_tokens) pendingInputTokens = usage.input_tokens;
|
|
378
|
+
if (usage?.reasoning_output_tokens) pendingToolTokens += usage.reasoning_output_tokens;
|
|
346
379
|
continue;
|
|
347
380
|
}
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (typeof entry.content === "string") {
|
|
353
|
-
text = entry.content.trim();
|
|
354
|
-
} else if (Array.isArray(entry.content)) {
|
|
355
|
-
for (const block of entry.content) {
|
|
356
|
-
if (block.type === "text" && typeof block.text === "string") {
|
|
357
|
-
text += (text ? "\n" : "") + block.text.trim();
|
|
358
|
-
} else if (block.type === "tool_use" && block.input) {
|
|
359
|
-
toolTokens += Math.round(JSON.stringify(block.input).length / 4);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
text = text.trim();
|
|
363
|
-
}
|
|
364
|
-
if (entry.tool_calls) {
|
|
365
|
-
for (const tc of entry.tool_calls) {
|
|
366
|
-
if (tc.function?.arguments) {
|
|
367
|
-
toolTokens += Math.round(tc.function.arguments.length / 4);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
381
|
+
if (payload.type === "ExecCommandEnd") {
|
|
382
|
+
const output = payload.aggregated_output ?? "";
|
|
383
|
+
pendingToolTokens += Math.round(output.length / 4);
|
|
384
|
+
continue;
|
|
370
385
|
}
|
|
386
|
+
if (payload.type !== "user_message" && payload.type !== "agent_message") continue;
|
|
387
|
+
const text = (payload.message ?? "").trim();
|
|
371
388
|
if (!text) continue;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
389
|
+
const role = payload.type === "agent_message" ? "assistant" : "user";
|
|
390
|
+
const ts = entry.timestamp ? new Date(entry.timestamp).getTime() : Date.now();
|
|
391
|
+
const toolTokens = pendingToolTokens;
|
|
392
|
+
pendingToolTokens = 0;
|
|
393
|
+
const inputTokens = pendingInputTokens;
|
|
394
|
+
pendingInputTokens = void 0;
|
|
377
395
|
messages.push({
|
|
378
396
|
id: `codex-${index++}`,
|
|
379
|
-
role
|
|
397
|
+
role,
|
|
380
398
|
content: text,
|
|
381
|
-
timestamp: ts,
|
|
382
|
-
...toolTokens > 0 ? { toolTokens } : {}
|
|
399
|
+
timestamp: isFinite(ts) ? ts : Date.now(),
|
|
400
|
+
...toolTokens > 0 ? { toolTokens } : {},
|
|
401
|
+
...inputTokens !== void 0 ? { inputTokens } : {}
|
|
383
402
|
});
|
|
384
403
|
} catch {
|
|
385
404
|
}
|
|
@@ -424,6 +443,11 @@ var init_codex_adapter = __esm({
|
|
|
424
443
|
function detectAdapter(filePath) {
|
|
425
444
|
return ADAPTERS.find((a) => a.canParse(filePath)) ?? ADAPTERS[0];
|
|
426
445
|
}
|
|
446
|
+
function getPinnedAdapter() {
|
|
447
|
+
const name = process.env.DRIFTCLI_ADAPTER;
|
|
448
|
+
if (!name) return null;
|
|
449
|
+
return ADAPTERS.find((a) => a.name === name) ?? null;
|
|
450
|
+
}
|
|
427
451
|
var ADAPTERS;
|
|
428
452
|
var init_adapter_registry = __esm({
|
|
429
453
|
"src/watchers/adapter-registry.ts"() {
|
|
@@ -453,7 +477,8 @@ var init_session_resolver = __esm({
|
|
|
453
477
|
if (this.cached && this.cached.expiresAt > Date.now()) {
|
|
454
478
|
return this.cached.file;
|
|
455
479
|
}
|
|
456
|
-
const
|
|
480
|
+
const pinned = getPinnedAdapter();
|
|
481
|
+
const result = this.resolveFromEnv() ?? this.resolveFromCwd(pinned) ?? (pinned ? pinned.findLatest() : findLatestSession());
|
|
457
482
|
if (result) {
|
|
458
483
|
this.cached = { file: result, expiresAt: Date.now() + this.cacheTtlMs };
|
|
459
484
|
} else {
|
|
@@ -470,7 +495,8 @@ var init_session_resolver = __esm({
|
|
|
470
495
|
invalidate() {
|
|
471
496
|
this.cached = null;
|
|
472
497
|
}
|
|
473
|
-
resolveFromCwd() {
|
|
498
|
+
resolveFromCwd(pinned) {
|
|
499
|
+
if (pinned && pinned.name !== "claude") return null;
|
|
474
500
|
const file = findSessionByCwd();
|
|
475
501
|
if (file && process.env.DRIFTCLI_DEBUG) {
|
|
476
502
|
const slug = file.split(/[\\/]/).slice(-2, -1)[0];
|
|
@@ -718,15 +744,6 @@ function createWindows(messages, windowSize) {
|
|
|
718
744
|
}
|
|
719
745
|
return windows;
|
|
720
746
|
}
|
|
721
|
-
function extractTopTerms(messages, n = 5) {
|
|
722
|
-
const freq = /* @__PURE__ */ new Map();
|
|
723
|
-
for (const msg of messages) {
|
|
724
|
-
for (const term of tokenize(msg.content)) {
|
|
725
|
-
freq.set(term, (freq.get(term) ?? 0) + 1);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([term]) => term);
|
|
729
|
-
}
|
|
730
747
|
function extractNgrams(text, n = 3) {
|
|
731
748
|
const tokens = tokenize(text).filter((w) => !/^\d/.test(w));
|
|
732
749
|
if (tokens.length < n) return /* @__PURE__ */ new Set();
|
|
@@ -1473,11 +1490,16 @@ function calculateDrift(messages, weights = DEFAULT_WEIGHTS, userGoal) {
|
|
|
1473
1490
|
}
|
|
1474
1491
|
function calcMessageDecay(messages) {
|
|
1475
1492
|
let totalTokens = 0;
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1493
|
+
const latestInputTokens = [...messages].reverse().find((m) => (m.inputTokens ?? 0) > 0)?.inputTokens;
|
|
1494
|
+
if (latestInputTokens !== void 0) {
|
|
1495
|
+
totalTokens = latestInputTokens;
|
|
1496
|
+
} else {
|
|
1497
|
+
for (const msg of messages) {
|
|
1498
|
+
const words = msg.content.split(/\s+/).filter((w) => w.length > 0).length;
|
|
1499
|
+
const hasCode = msg.content.includes("```");
|
|
1500
|
+
const tokenEstimate = Math.round(words * 1.3 * (hasCode ? 1.5 : 1));
|
|
1501
|
+
totalTokens += tokenEstimate + (msg.toolTokens ?? 0);
|
|
1502
|
+
}
|
|
1481
1503
|
}
|
|
1482
1504
|
if (totalTokens < 500) return 0;
|
|
1483
1505
|
let score = Math.min(100, Math.round(15 * Math.log(totalTokens / 1500)));
|
|
@@ -2059,38 +2081,57 @@ var setup_exports = {};
|
|
|
2059
2081
|
__export(setup_exports, {
|
|
2060
2082
|
setup: () => setup
|
|
2061
2083
|
});
|
|
2062
|
-
function
|
|
2084
|
+
function setupToml(target, update) {
|
|
2085
|
+
const existing = fs6.existsSync(target.filePath) ? fs6.readFileSync(target.filePath, "utf-8") : "";
|
|
2086
|
+
if (existing.includes("[mcp_servers.driftguard]")) {
|
|
2087
|
+
if (!update) {
|
|
2088
|
+
console.log(` ${target.name}: already configured (use --update to overwrite)`);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
const stripped = existing.replace(/\n\[mcp_servers\.driftguard\][^\[]*/s, "");
|
|
2092
|
+
fs6.writeFileSync(target.filePath, stripped.trimEnd() + TOML_ENTRY(target.adapter), "utf-8");
|
|
2093
|
+
} else {
|
|
2094
|
+
fs6.writeFileSync(target.filePath, existing.trimEnd() + TOML_ENTRY(target.adapter), "utf-8");
|
|
2095
|
+
}
|
|
2096
|
+
console.log(` ${target.name}: configured (${target.filePath})`);
|
|
2097
|
+
}
|
|
2098
|
+
function setupJson(target, update) {
|
|
2099
|
+
let config2 = {};
|
|
2100
|
+
if (fs6.existsSync(target.filePath)) {
|
|
2101
|
+
try {
|
|
2102
|
+
config2 = JSON.parse(fs6.readFileSync(target.filePath, "utf-8"));
|
|
2103
|
+
} catch {
|
|
2104
|
+
console.log(` ${target.name}: skipped \u2014 could not parse ${target.filePath}`);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
const servers = config2.mcpServers ?? {};
|
|
2109
|
+
if (servers.driftguard && !update) {
|
|
2110
|
+
console.log(` ${target.name}: already configured (use --update to overwrite)`);
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
config2.mcpServers = { ...servers, driftguard: { command: "driftguard-mcp", env: { DRIFTCLI_ADAPTER: target.adapter } } };
|
|
2114
|
+
const dir = path8.dirname(target.filePath);
|
|
2115
|
+
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
2116
|
+
fs6.writeFileSync(target.filePath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
2117
|
+
console.log(` ${target.name}: configured (${target.filePath})`);
|
|
2118
|
+
}
|
|
2119
|
+
function setup(update = false) {
|
|
2063
2120
|
console.log("driftguard-mcp setup\n");
|
|
2064
2121
|
for (const target of TARGETS) {
|
|
2065
2122
|
try {
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
config2 = JSON.parse(fs6.readFileSync(target.filePath, "utf-8"));
|
|
2071
|
-
} catch {
|
|
2072
|
-
console.log(` ${target.name}: skipped \u2014 could not parse ${target.filePath}`);
|
|
2073
|
-
continue;
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
const servers = config2.mcpServers ?? {};
|
|
2077
|
-
if (servers.driftguard) {
|
|
2078
|
-
console.log(` ${target.name}: already configured`);
|
|
2079
|
-
continue;
|
|
2123
|
+
if (target.format === "toml") {
|
|
2124
|
+
setupToml(target, update);
|
|
2125
|
+
} else {
|
|
2126
|
+
setupJson(target, update);
|
|
2080
2127
|
}
|
|
2081
|
-
config2.mcpServers = { ...servers, driftguard: MCP_ENTRY };
|
|
2082
|
-
if (!fs6.existsSync(dir)) {
|
|
2083
|
-
fs6.mkdirSync(dir, { recursive: true });
|
|
2084
|
-
}
|
|
2085
|
-
fs6.writeFileSync(target.filePath, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
2086
|
-
console.log(` ${target.name}: configured (${target.filePath})`);
|
|
2087
2128
|
} catch (err) {
|
|
2088
2129
|
console.log(` ${target.name}: failed \u2014 ${err instanceof Error ? err.message : err}`);
|
|
2089
2130
|
}
|
|
2090
2131
|
}
|
|
2091
2132
|
console.log("\nDone. Restart your AI CLI to activate the tools.");
|
|
2092
2133
|
}
|
|
2093
|
-
var fs6, path8, os6, TARGETS,
|
|
2134
|
+
var fs6, path8, os6, TARGETS, TOML_ENTRY;
|
|
2094
2135
|
var init_setup = __esm({
|
|
2095
2136
|
"src/setup.ts"() {
|
|
2096
2137
|
"use strict";
|
|
@@ -2098,14 +2139,16 @@ var init_setup = __esm({
|
|
|
2098
2139
|
path8 = __toESM(require("path"));
|
|
2099
2140
|
os6 = __toESM(require("os"));
|
|
2100
2141
|
TARGETS = [
|
|
2101
|
-
{ name: "Claude Code", filePath: path8.join(os6.homedir(), ".claude.json") },
|
|
2102
|
-
{ name: "Gemini CLI", filePath: path8.join(os6.homedir(), ".gemini", "settings.json") },
|
|
2103
|
-
{ name: "Codex CLI", filePath: path8.join(os6.homedir(), ".codex", "config.
|
|
2104
|
-
{ name: "Cursor", filePath: path8.join(os6.homedir(), ".cursor", "mcp.json") }
|
|
2142
|
+
{ name: "Claude Code", filePath: path8.join(os6.homedir(), ".claude.json"), adapter: "claude", format: "json" },
|
|
2143
|
+
{ name: "Gemini CLI", filePath: path8.join(os6.homedir(), ".gemini", "settings.json"), adapter: "gemini", format: "json" },
|
|
2144
|
+
{ name: "Codex CLI", filePath: path8.join(os6.homedir(), ".codex", "config.toml"), adapter: "codex", format: "toml" },
|
|
2145
|
+
{ name: "Cursor", filePath: path8.join(os6.homedir(), ".cursor", "mcp.json"), adapter: "claude", format: "json" }
|
|
2105
2146
|
];
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2147
|
+
TOML_ENTRY = (adapter) => `
|
|
2148
|
+
[mcp_servers.driftguard]
|
|
2149
|
+
command = "driftguard-mcp"
|
|
2150
|
+
env.DRIFTCLI_ADAPTER = "${adapter}"
|
|
2151
|
+
`;
|
|
2109
2152
|
}
|
|
2110
2153
|
});
|
|
2111
2154
|
|
|
@@ -2114,52 +2157,27 @@ var mcp_server_exports = {};
|
|
|
2114
2157
|
__export(mcp_server_exports, {
|
|
2115
2158
|
main: () => main
|
|
2116
2159
|
});
|
|
2117
|
-
function buildHandoff(
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
const durationStr = durationMin !== null ? durationMin >= 60 ? `${Math.floor(durationMin / 60)}h ${durationMin % 60}m` : `${durationMin}m` : "unknown duration";
|
|
2121
|
-
const topTerms = extractTopTerms(chatMessages, 6);
|
|
2122
|
-
const topicsStr = topTerms.length > 0 ? topTerms.join(", ") : "not enough content to determine";
|
|
2123
|
-
const recentUserMessages = messages.filter((m) => m.role === "user").slice(-3).map((m, i) => `${i + 1}. ${m.content.slice(0, 200)}${m.content.length > 200 ? "\u2026" : ""}`).join("\n");
|
|
2124
|
-
let lastCodeSnippet = "";
|
|
2125
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
2126
|
-
const match = [...messages[i].content.matchAll(/```(\w*)\n([\s\S]*?)```/g)].pop();
|
|
2127
|
-
if (match) {
|
|
2128
|
-
const lang = match[1] || "";
|
|
2129
|
-
const code = match[2].split("\n").slice(0, 20).join("\n");
|
|
2130
|
-
lastCodeSnippet = `\`\`\`${lang}
|
|
2131
|
-
${code}
|
|
2132
|
-
\`\`\``;
|
|
2133
|
-
break;
|
|
2134
|
-
}
|
|
2135
|
-
}
|
|
2136
|
-
const lines = [
|
|
2137
|
-
`## Context Handoff`,
|
|
2160
|
+
function buildHandoff() {
|
|
2161
|
+
return [
|
|
2162
|
+
`Please write a \`handoff.md\` file in the current working directory with the following structure:`,
|
|
2138
2163
|
``,
|
|
2139
|
-
|
|
2164
|
+
`## What we accomplished`,
|
|
2165
|
+
`A clear summary of everything completed this session.`,
|
|
2140
2166
|
``,
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
`**Recent focus (last 3 user messages):**`,
|
|
2144
|
-
recentUserMessages
|
|
2145
|
-
];
|
|
2146
|
-
if (lastCodeSnippet) {
|
|
2147
|
-
lines.push(``, `**Last code context:**`, lastCodeSnippet);
|
|
2148
|
-
}
|
|
2149
|
-
lines.push(
|
|
2167
|
+
`## Current state`,
|
|
2168
|
+
`Where things stand right now \u2014 what's working, what's broken, what's in progress.`,
|
|
2150
2169
|
``,
|
|
2151
|
-
|
|
2152
|
-
|
|
2170
|
+
`## Files modified`,
|
|
2171
|
+
`List of files changed this session and what changed in each.`,
|
|
2153
2172
|
``,
|
|
2154
|
-
|
|
2155
|
-
`
|
|
2173
|
+
`## Open questions / next steps`,
|
|
2174
|
+
`Anything unresolved, pending decisions, or what should be done next.`,
|
|
2156
2175
|
``,
|
|
2157
|
-
|
|
2158
|
-
|
|
2176
|
+
`## Context for next session`,
|
|
2177
|
+
`Key decisions, constraints, or background a fresh session needs to continue without losing context.`,
|
|
2159
2178
|
``,
|
|
2160
|
-
`
|
|
2161
|
-
);
|
|
2162
|
-
return lines.join("\n");
|
|
2179
|
+
`Write the file now.`
|
|
2180
|
+
].join("\n");
|
|
2163
2181
|
}
|
|
2164
2182
|
async function main() {
|
|
2165
2183
|
const transport = new import_stdio.StdioServerTransport();
|
|
@@ -2176,7 +2194,6 @@ var init_mcp_server = __esm({
|
|
|
2176
2194
|
init_session_resolver();
|
|
2177
2195
|
init_drift_calculator();
|
|
2178
2196
|
init_types();
|
|
2179
|
-
init_topic_analyzer();
|
|
2180
2197
|
init_config();
|
|
2181
2198
|
init_storage();
|
|
2182
2199
|
init_ui();
|
|
@@ -2270,16 +2287,12 @@ var init_mcp_server = __esm({
|
|
|
2270
2287
|
];
|
|
2271
2288
|
if (trendLine) lines.push(``, trendLine);
|
|
2272
2289
|
if (isDegrading) {
|
|
2273
|
-
lines.push(
|
|
2274
|
-
``,
|
|
2275
|
-
`---`,
|
|
2276
|
-
buildHandoff(analysis, messages, chatMessages, emoji, level)
|
|
2277
|
-
);
|
|
2290
|
+
lines.push(``, `---`, buildHandoff());
|
|
2278
2291
|
}
|
|
2279
2292
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2280
2293
|
}
|
|
2281
2294
|
if (request.params.name === "get_handoff") {
|
|
2282
|
-
return { content: [{ type: "text", text: buildHandoff(
|
|
2295
|
+
return { content: [{ type: "text", text: buildHandoff() }] };
|
|
2283
2296
|
}
|
|
2284
2297
|
if (request.params.name === "get_trend") {
|
|
2285
2298
|
if (!storage) {
|
|
@@ -2306,7 +2319,7 @@ if (command === "watch") {
|
|
|
2306
2319
|
run2();
|
|
2307
2320
|
} else if (command === "setup") {
|
|
2308
2321
|
const { setup: setup2 } = (init_setup(), __toCommonJS(setup_exports));
|
|
2309
|
-
setup2();
|
|
2322
|
+
setup2(process.argv.includes("--update"));
|
|
2310
2323
|
} else {
|
|
2311
2324
|
const { main: main2 } = (init_mcp_server(), __toCommonJS(mcp_server_exports));
|
|
2312
2325
|
main2().catch(console.error);
|