evrex-mcp 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/dist/account.js +210 -0
- package/dist/capture.js +2604 -166
- package/dist/continuity.js +83 -10
- package/dist/hook.js +106 -11
- package/dist/import.js +2336 -350
- package/dist/index.js +597 -88
- package/dist/pretool.js +67 -16
- package/dist/tickets.js +24 -1
- package/package.json +4 -2
package/dist/pretool.js
CHANGED
|
@@ -49,7 +49,14 @@ var evrexApi = {
|
|
|
49
49
|
// Evidence-only retrieval (BM25 + embedding), no LLM synthesis call — see
|
|
50
50
|
// apps/backend/src/query/query.service.ts#search. Used by evrex_search,
|
|
51
51
|
// which wants ranked hits fast, not a synthesized paragraph.
|
|
52
|
-
search: (repoPath, text, filePaths) => post("/search", { repoPath, text, filePaths })
|
|
52
|
+
search: (repoPath, text, filePaths) => post("/search", { repoPath, text, filePaths }),
|
|
53
|
+
// Everything that happened in a repo, newest first, bounded by days — the
|
|
54
|
+
// same query the desktop Timeline screen makes. Sessions and commits
|
|
55
|
+
// interleaved, each with the handle evrex_expand takes.
|
|
56
|
+
feedback: (body) => post("/feedback", body),
|
|
57
|
+
timeline: (repoPath, days) => get(
|
|
58
|
+
`/timeline?repoPath=${encodeURIComponent(repoPath)}&days=${encodeURIComponent(String(days))}`
|
|
59
|
+
)
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
// src/hook-runtime.ts
|
|
@@ -91,6 +98,9 @@ function confidenceLabel(p) {
|
|
|
91
98
|
if (p.status === "inferred") return ` ${Math.round((p.confidence ?? 0) * 100)}%`;
|
|
92
99
|
return ` ${p.status}`;
|
|
93
100
|
}
|
|
101
|
+
function handleFor(e) {
|
|
102
|
+
return e.kind === "commit" ? `commit:${e.refId.slice(0, 12)}` : `${e.kind}:${e.refId}`;
|
|
103
|
+
}
|
|
94
104
|
function evidenceLabel(e) {
|
|
95
105
|
return e.kind === "session" ? e.sourceKind ?? "session" : e.kind;
|
|
96
106
|
}
|
|
@@ -125,14 +135,18 @@ function datedEvidence(items, root) {
|
|
|
125
135
|
);
|
|
126
136
|
}
|
|
127
137
|
|
|
128
|
-
// src/
|
|
138
|
+
// src/hook-state.ts
|
|
129
139
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
130
140
|
import { homedir } from "node:os";
|
|
131
141
|
import { dirname, join } from "node:path";
|
|
132
142
|
var MAX_SESSIONS = 40;
|
|
133
143
|
var MAX_FILES = 200;
|
|
144
|
+
var MAX_SHOWN = 400;
|
|
134
145
|
function statePath(home = homedir()) {
|
|
135
|
-
return join(home, ".evrex", "
|
|
146
|
+
return join(home, ".evrex", "hook-state.json");
|
|
147
|
+
}
|
|
148
|
+
function recordKey(e) {
|
|
149
|
+
return `${e.kind}:${e.refId}`;
|
|
136
150
|
}
|
|
137
151
|
function readState(path) {
|
|
138
152
|
try {
|
|
@@ -140,7 +154,16 @@ function readState(path) {
|
|
|
140
154
|
if (!parsed || typeof parsed !== "object" || typeof parsed.sessions !== "object") {
|
|
141
155
|
return { sessions: {} };
|
|
142
156
|
}
|
|
143
|
-
|
|
157
|
+
const sessions = {};
|
|
158
|
+
for (const [id, s] of Object.entries(parsed.sessions ?? {})) {
|
|
159
|
+
if (!s || typeof s !== "object") continue;
|
|
160
|
+
sessions[id] = {
|
|
161
|
+
at: typeof s.at === "string" ? s.at : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
162
|
+
files: Array.isArray(s.files) ? s.files.filter((f) => typeof f === "string") : [],
|
|
163
|
+
shown: Array.isArray(s.shown) ? s.shown.filter((f) => typeof f === "string") : []
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return { sessions };
|
|
144
167
|
} catch {
|
|
145
168
|
return { sessions: {} };
|
|
146
169
|
}
|
|
@@ -148,18 +171,35 @@ function readState(path) {
|
|
|
148
171
|
function alreadySeen(state, sessionId, file) {
|
|
149
172
|
return state.sessions[sessionId]?.files.includes(file) ?? false;
|
|
150
173
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
...state.sessions,
|
|
156
|
-
[sessionId]: { at: now.toISOString(), files }
|
|
157
|
-
};
|
|
174
|
+
function shownIn(state, sessionId) {
|
|
175
|
+
return new Set(state.sessions[sessionId]?.shown ?? []);
|
|
176
|
+
}
|
|
177
|
+
function prune(sessions) {
|
|
158
178
|
const ordered = Object.entries(sessions).sort(
|
|
159
179
|
(a, b) => Date.parse(b[1].at) - Date.parse(a[1].at)
|
|
160
180
|
);
|
|
161
181
|
return { sessions: Object.fromEntries(ordered.slice(0, MAX_SESSIONS)) };
|
|
162
182
|
}
|
|
183
|
+
function memoryOf(state, sessionId) {
|
|
184
|
+
const existing = state.sessions[sessionId];
|
|
185
|
+
return { at: existing?.at ?? "", files: existing?.files ?? [], shown: existing?.shown ?? [] };
|
|
186
|
+
}
|
|
187
|
+
function remember(state, sessionId, file, now = /* @__PURE__ */ new Date()) {
|
|
188
|
+
const m = memoryOf(state, sessionId);
|
|
189
|
+
const files = m.files.includes(file) ? m.files : [...m.files, file].slice(-MAX_FILES);
|
|
190
|
+
return prune({
|
|
191
|
+
...state.sessions,
|
|
192
|
+
[sessionId]: { ...m, at: now.toISOString(), files }
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function rememberShown(state, sessionId, keys, now = /* @__PURE__ */ new Date()) {
|
|
196
|
+
const m = memoryOf(state, sessionId);
|
|
197
|
+
const shown = [.../* @__PURE__ */ new Set([...m.shown, ...keys])].slice(-MAX_SHOWN);
|
|
198
|
+
return prune({
|
|
199
|
+
...state.sessions,
|
|
200
|
+
[sessionId]: { ...m, at: now.toISOString(), shown }
|
|
201
|
+
});
|
|
202
|
+
}
|
|
163
203
|
function writeState(path, state) {
|
|
164
204
|
try {
|
|
165
205
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -212,7 +252,7 @@ function formatBlock(relPath, evidence, sessions) {
|
|
|
212
252
|
parts.push(
|
|
213
253
|
"EVIDENCE:\n" + evidence.map((e) => {
|
|
214
254
|
const excerpt = e.excerpt.replace(/\s+/g, " ").trim().slice(0, MAX_EXCERPT);
|
|
215
|
-
return `-
|
|
255
|
+
return `- ${handleFor(e)} [${evidenceLabel(e)}${confidenceLabel(e.provenance)}${dateLabel(e.at)}] ${excerpt}`;
|
|
216
256
|
}).join("\n")
|
|
217
257
|
);
|
|
218
258
|
parts.push(
|
|
@@ -256,13 +296,24 @@ async function main() {
|
|
|
256
296
|
evrexApi.search(match.id, questionFor(relPath), [relPath]),
|
|
257
297
|
left()
|
|
258
298
|
);
|
|
259
|
-
const
|
|
299
|
+
const relevant = datedEvidence(found?.evidence ?? [], match.root).filter(
|
|
260
300
|
(e) => (e.provenance?.confidence ?? 1) >= MIN_CONFIDENCE
|
|
261
301
|
);
|
|
262
|
-
|
|
263
|
-
|
|
302
|
+
const shown = shownIn(state, sessionId);
|
|
303
|
+
const evidence = relevant.filter((e) => !shown.has(recordKey(e)));
|
|
304
|
+
if (sessionId) {
|
|
305
|
+
writeState(
|
|
306
|
+
path,
|
|
307
|
+
rememberShown(
|
|
308
|
+
remember(state, sessionId, file),
|
|
309
|
+
sessionId,
|
|
310
|
+
evidence.slice(0, MAX_ITEMS).map(recordKey)
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
if (relevant.length === 0) return;
|
|
264
315
|
let sessions = [];
|
|
265
|
-
const ids = [...new Set(
|
|
316
|
+
const ids = [...new Set(relevant.filter((e) => e.kind === "session").map((e) => e.refId))].slice(
|
|
266
317
|
0,
|
|
267
318
|
MAX_SESSIONS2
|
|
268
319
|
);
|
package/dist/tickets.js
CHANGED
|
@@ -63,7 +63,14 @@ var init_client = __esm({
|
|
|
63
63
|
// Evidence-only retrieval (BM25 + embedding), no LLM synthesis call — see
|
|
64
64
|
// apps/backend/src/query/query.service.ts#search. Used by evrex_search,
|
|
65
65
|
// which wants ranked hits fast, not a synthesized paragraph.
|
|
66
|
-
search: (repoPath, text, filePaths) => post("/search", { repoPath, text, filePaths })
|
|
66
|
+
search: (repoPath, text, filePaths) => post("/search", { repoPath, text, filePaths }),
|
|
67
|
+
// Everything that happened in a repo, newest first, bounded by days — the
|
|
68
|
+
// same query the desktop Timeline screen makes. Sessions and commits
|
|
69
|
+
// interleaved, each with the handle evrex_expand takes.
|
|
70
|
+
feedback: (body) => post("/feedback", body),
|
|
71
|
+
timeline: (repoPath, days) => get(
|
|
72
|
+
`/timeline?repoPath=${encodeURIComponent(repoPath)}&days=${encodeURIComponent(String(days))}`
|
|
73
|
+
)
|
|
67
74
|
};
|
|
68
75
|
}
|
|
69
76
|
});
|
|
@@ -253,9 +260,22 @@ import { fileURLToPath } from "node:url";
|
|
|
253
260
|
// ../../packages/ingest-core/src/types.ts
|
|
254
261
|
var CONVERSATION_KINDS = [
|
|
255
262
|
"claude-code",
|
|
263
|
+
// Xcode's coding assistant — the Claude Agent SDK embedded, writing the
|
|
264
|
+
// identical transcript format under ~/Library/Developer/Xcode. A separate
|
|
265
|
+
// kind because a conversation in an IDE panel is not a CLI session, and the
|
|
266
|
+
// Slack mislabel already taught this list what an absent entry costs.
|
|
267
|
+
"claude-xcode",
|
|
256
268
|
"cursor",
|
|
257
269
|
"codex",
|
|
258
270
|
"gemini",
|
|
271
|
+
// OpenCode keeps its sessions in a SQLite store rather than files; the
|
|
272
|
+
// parser reads the store, so this kind arrives through the same importer
|
|
273
|
+
// as Cursor's.
|
|
274
|
+
"opencode",
|
|
275
|
+
// GitHub Copilot CLI writes an events file per session under ~/.copilot;
|
|
276
|
+
// the coding agent on github.com is a different thing and arrives as a
|
|
277
|
+
// reference through the Agent-Logs-Url trailer, not as this kind.
|
|
278
|
+
"copilot",
|
|
259
279
|
"slack"
|
|
260
280
|
];
|
|
261
281
|
var REFERENCE_KINDS = ["linear", "jira", "confluence"];
|
|
@@ -264,6 +284,9 @@ var SOURCE_KINDS = [
|
|
|
264
284
|
...REFERENCE_KINDS
|
|
265
285
|
];
|
|
266
286
|
|
|
287
|
+
// ../../packages/ingest-core/src/claude-sessions.ts
|
|
288
|
+
var MAX_TRANSCRIPT_BYTES = 256 * 1024 * 1024;
|
|
289
|
+
|
|
267
290
|
// ../../packages/ingest-core/src/sanitize.ts
|
|
268
291
|
var NUL = String.fromCharCode(0);
|
|
269
292
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evrex-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "MCP server that gives coding agents the recorded reasoning behind a repo: prior decisions, hard constraints, and approaches already rejected.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mcp",
|
|
@@ -19,13 +19,15 @@
|
|
|
19
19
|
"evrex-import": "dist/import.js",
|
|
20
20
|
"evrex-tickets": "dist/tickets.js",
|
|
21
21
|
"evrex-pretool": "dist/pretool.js",
|
|
22
|
-
"evrex-continuity": "dist/continuity.js"
|
|
22
|
+
"evrex-continuity": "dist/continuity.js",
|
|
23
|
+
"evrex-account": "dist/account.js"
|
|
23
24
|
},
|
|
24
25
|
"files": [
|
|
25
26
|
"dist/index.js",
|
|
26
27
|
"dist/hook.js",
|
|
27
28
|
"dist/pretool.js",
|
|
28
29
|
"dist/continuity.js",
|
|
30
|
+
"dist/account.js",
|
|
29
31
|
"dist/capture.js",
|
|
30
32
|
"dist/import.js",
|
|
31
33
|
"dist/tickets.js",
|