@dotdotgod/claude-code 0.6.4 → 0.6.5
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/mcp/cli.mjs +46 -11
- package/package.json +2 -2
package/mcp/cli.mjs
CHANGED
|
@@ -855,6 +855,15 @@ import { homedir } from "node:os";
|
|
|
855
855
|
import { join } from "node:path";
|
|
856
856
|
var DEFAULT_EMBEDDING_PROFILE = Object.freeze({ provider: "local", model: "Xenova/multilingual-e5-small" });
|
|
857
857
|
var EMBEDDING_PROVIDERS = Object.freeze(["local", "openai-compatible", "ollama"]);
|
|
858
|
+
var DEFAULT_INPUT_FORMAT = Object.freeze({ queryPrefix: "query: ", documentPrefix: "passage: " });
|
|
859
|
+
function embeddingInputFormat(profile, label = "embedding") {
|
|
860
|
+
if (profile.inputFormat === void 0) return { ...DEFAULT_INPUT_FORMAT };
|
|
861
|
+
const format = profile.inputFormat;
|
|
862
|
+
if (!format || typeof format !== "object" || Array.isArray(format) || Object.keys(format).some((key) => !Object.hasOwn(DEFAULT_INPUT_FORMAT, key)) || Object.keys(DEFAULT_INPUT_FORMAT).some((key) => typeof format[key] !== "string")) {
|
|
863
|
+
throw new Error(`${label}.inputFormat must contain exactly queryPrefix and documentPrefix strings.`);
|
|
864
|
+
}
|
|
865
|
+
return { queryPrefix: format.queryPrefix, documentPrefix: format.documentPrefix };
|
|
866
|
+
}
|
|
858
867
|
function readJson(path, label) {
|
|
859
868
|
if (!existsSync(path)) return null;
|
|
860
869
|
try {
|
|
@@ -865,7 +874,7 @@ function readJson(path, label) {
|
|
|
865
874
|
}
|
|
866
875
|
function validateEmbeddingProfile(profile, label = "embedding") {
|
|
867
876
|
if (!profile || typeof profile !== "object" || Array.isArray(profile)) throw new Error(`${label} must be an object.`);
|
|
868
|
-
const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv"]);
|
|
877
|
+
const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv", "inputFormat"]);
|
|
869
878
|
const unknown = Object.keys(profile).find((key) => !allowed.has(key));
|
|
870
879
|
if (unknown) throw new Error(`${label}.${unknown} is not supported.`);
|
|
871
880
|
if (!EMBEDDING_PROVIDERS.includes(profile.provider)) throw new Error(`${label}.provider must be local, openai-compatible, or ollama.`);
|
|
@@ -883,6 +892,7 @@ function validateEmbeddingProfile(profile, label = "embedding") {
|
|
|
883
892
|
if (!["http:", "https:"].includes(url.protocol)) throw new Error(`${label}.baseUrl must use HTTP or HTTPS.`);
|
|
884
893
|
if (url.username || url.password) throw new Error(`${label}.baseUrl must not contain credentials.`);
|
|
885
894
|
}
|
|
895
|
+
embeddingInputFormat(profile, label);
|
|
886
896
|
return { ...profile, model: profile.model.trim(), ...profile.baseUrl ? { baseUrl: profile.baseUrl.replace(/\/$/, "") } : {} };
|
|
887
897
|
}
|
|
888
898
|
function sanitizeEmbeddingProfile(profile) {
|
|
@@ -890,8 +900,13 @@ function sanitizeEmbeddingProfile(profile) {
|
|
|
890
900
|
return { ...safe, ...apiKey ? { apiKey: "[redacted]" } : {} };
|
|
891
901
|
}
|
|
892
902
|
function embeddingProfileIdentity(profile) {
|
|
893
|
-
|
|
894
|
-
|
|
903
|
+
return {
|
|
904
|
+
provider: profile.provider,
|
|
905
|
+
model: profile.model,
|
|
906
|
+
...profile.baseUrl !== void 0 ? { baseUrl: profile.baseUrl } : {},
|
|
907
|
+
inputFormatVersion: 1,
|
|
908
|
+
inputFormat: embeddingInputFormat(profile)
|
|
909
|
+
};
|
|
895
910
|
}
|
|
896
911
|
function resolveEmbeddingProfile(root = ".", options = {}) {
|
|
897
912
|
const home = options.home ?? homedir();
|
|
@@ -3944,7 +3959,12 @@ async function postJson(url, body, profile) {
|
|
|
3944
3959
|
const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json", ...key ? { authorization: `Bearer ${key}` } : {} }, body: JSON.stringify(body), signal: controller.signal });
|
|
3945
3960
|
if (!response.ok) {
|
|
3946
3961
|
const category = response.status === 401 || response.status === 403 ? "authentication" : response.status === 429 ? "rate limit" : `HTTP ${response.status}`;
|
|
3947
|
-
|
|
3962
|
+
const error = new Error(`Remote embedding ${category} failure.`);
|
|
3963
|
+
if (response.status === 400 || response.status === 413) {
|
|
3964
|
+
const body2 = (await response.text()).slice(0, 8192);
|
|
3965
|
+
error.tokenLimit = /(?:exceed\w*[\s\S]{0,120}tokens?|tokens?[\s\S]{0,120}(?:exceed|limit|too (?:long|many))|maximum context length)/i.test(body2);
|
|
3966
|
+
}
|
|
3967
|
+
throw error;
|
|
3948
3968
|
}
|
|
3949
3969
|
try {
|
|
3950
3970
|
return await response.json();
|
|
@@ -3971,20 +3991,35 @@ function remoteEmbedder(profile) {
|
|
|
3971
3991
|
return ordered.map(normalize);
|
|
3972
3992
|
};
|
|
3973
3993
|
}
|
|
3994
|
+
function withInputFormat(embed, profile) {
|
|
3995
|
+
const format = embeddingInputFormat(profile);
|
|
3996
|
+
return (texts, role) => {
|
|
3997
|
+
if (role !== void 0 && role !== "query" && role !== "document") throw new Error("Embedding role must be query or document.");
|
|
3998
|
+
return embed(role === void 0 ? texts : texts.map((text) => format[`${role}Prefix`] + text));
|
|
3999
|
+
};
|
|
4000
|
+
}
|
|
3974
4001
|
async function createEmbedder(profile) {
|
|
3975
4002
|
const embed = profile.provider === "local" ? await localEmbedder(profile) : remoteEmbedder(profile);
|
|
3976
|
-
|
|
4003
|
+
async function embedBatch(texts) {
|
|
3977
4004
|
if (texts.length === 0) return [];
|
|
3978
|
-
|
|
4005
|
+
let rows;
|
|
4006
|
+
try {
|
|
4007
|
+
rows = await embed(texts);
|
|
4008
|
+
} catch (error) {
|
|
4009
|
+
if (!error.tokenLimit || texts.length === 1) throw error;
|
|
4010
|
+
const middle = Math.ceil(texts.length / 2);
|
|
4011
|
+
rows = [...await embedBatch(texts.slice(0, middle)), ...await embedBatch(texts.slice(middle))];
|
|
4012
|
+
}
|
|
3979
4013
|
if (!Array.isArray(rows) || rows.length !== texts.length) throw new Error("Embedding provider returned an unexpected vector count.");
|
|
3980
4014
|
const dimensions = rows[0]?.length;
|
|
3981
4015
|
if (!dimensions || rows.some((row) => row.length !== dimensions)) throw new Error("Embedding provider returned inconsistent vector dimensions.");
|
|
3982
4016
|
return rows;
|
|
3983
|
-
}
|
|
4017
|
+
}
|
|
4018
|
+
return withInputFormat(embedBatch, profile);
|
|
3984
4019
|
}
|
|
3985
4020
|
async function resolveEmbedder(root, options = {}) {
|
|
3986
4021
|
const resolved = options.profile ? { profile: options.profile, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
|
|
3987
|
-
return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed
|
|
4022
|
+
return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed ? withInputFormat(options.embed, resolved.profile) : await createEmbedder(resolved.profile) };
|
|
3988
4023
|
}
|
|
3989
4024
|
|
|
3990
4025
|
// packages/cli/src/query/store.mjs
|
|
@@ -4190,7 +4225,7 @@ async function buildVectorIndex(root, embedOrOptions = {}) {
|
|
|
4190
4225
|
});
|
|
4191
4226
|
for (let offset = 0; offset < missing.length; offset += EMBED_BATCH_SIZE) {
|
|
4192
4227
|
const batch = missing.slice(offset, offset + EMBED_BATCH_SIZE);
|
|
4193
|
-
const embedded = await resolved.embed(batch.map(({ chunk }) =>
|
|
4228
|
+
const embedded = await resolved.embed(batch.map(({ chunk }) => chunk.passage), "document");
|
|
4194
4229
|
embedded.forEach((vector, index) => {
|
|
4195
4230
|
rows[batch[index].index] = vector;
|
|
4196
4231
|
});
|
|
@@ -4220,7 +4255,7 @@ async function queryProject(root, query, options = {}) {
|
|
|
4220
4255
|
try {
|
|
4221
4256
|
assertCodeEmbeddingConsent(root, options, corpus.chunks);
|
|
4222
4257
|
index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks });
|
|
4223
|
-
const [queryVector] = await index.embedding.embed([
|
|
4258
|
+
const [queryVector] = await index.embedding.embed([query], "query");
|
|
4224
4259
|
vector = index.chunks.map((chunk, position) => ({ ...chunk, vectorScore: cosineScore(queryVector, index.vectors, position * index.manifest.dimensions, index.manifest.dimensions) })).sort((a, b) => b.vectorScore - a.vectorScore || a.id.localeCompare(b.id));
|
|
4225
4260
|
} catch (error) {
|
|
4226
4261
|
if (search === "vector") throw error;
|
|
@@ -5996,7 +6031,7 @@ async function buildVectorImpactOverlay(root, index, changedPaths, options = {})
|
|
|
5996
6031
|
if (!profile) continue;
|
|
5997
6032
|
const embed = vectorIndex.embedding?.embed ?? options.embed;
|
|
5998
6033
|
if (!embed) throw new Error("Vector index did not provide an embedder.");
|
|
5999
|
-
const [queryVector] = await embed([
|
|
6034
|
+
const [queryVector] = await embed([profile.text], "query");
|
|
6000
6035
|
if (!queryVector) throw new Error("Changed-file embedding was empty.");
|
|
6001
6036
|
const matches = rankVectorFiles(queryVector, vectorIndex).filter((match) => match.path !== changed && match.vectorScore >= threshold).slice(0, topK);
|
|
6002
6037
|
for (const match of matches) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdotgod/claude-code",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "Claude Code adapter for dotdotgod project memory workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Elastic-2.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"esbuild": "0.27.3",
|
|
28
|
-
"@dotdotgod/context": "0.6.
|
|
28
|
+
"@dotdotgod/context": "0.6.5"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
".claude-plugin",
|