@orquentra/codebuddy 2.4.1 → 2.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/README.md +17 -2
- package/dist/chunk-3XYQGFPB.js +91 -0
- package/dist/chunk-3XYQGFPB.js.map +1 -0
- package/dist/chunk-4BYGTEJT.js +103 -0
- package/dist/chunk-4BYGTEJT.js.map +1 -0
- package/dist/{chunk-ITGRANYW.js → chunk-6MKEIDX5.js} +2 -2
- package/dist/{chunk-K63EOA4B.js → chunk-776EQXDV.js} +25 -6
- package/dist/chunk-776EQXDV.js.map +1 -0
- package/dist/{chunk-5OWZVZSO.js → chunk-CQPCHMBS.js} +8 -165
- package/dist/chunk-CQPCHMBS.js.map +1 -0
- package/dist/chunk-JDZ3VK26.js +4006 -0
- package/dist/chunk-JDZ3VK26.js.map +1 -0
- package/dist/chunk-JMWJ7V6Y.js +99 -0
- package/dist/chunk-JMWJ7V6Y.js.map +1 -0
- package/dist/chunk-KYK6RPAJ.js +99 -0
- package/dist/chunk-KYK6RPAJ.js.map +1 -0
- package/dist/{chunk-IBMJYIEP.js → chunk-L5AYZC3F.js} +7 -7
- package/dist/{chunk-3ZELDCR3.js → chunk-PCE4STAY.js} +7 -7
- package/dist/{chunk-TI5TQD6D.js → chunk-QDBDYCM2.js} +2 -2
- package/dist/{chunk-DSN5CEHO.js → chunk-ROQTT5ZO.js} +3 -3
- package/dist/{chunk-IE2OUO54.js → chunk-UFZD3JAA.js} +3 -3
- package/dist/chunk-Z5QB367W.js +5028 -0
- package/dist/chunk-Z5QB367W.js.map +1 -0
- package/dist/cli/index.js +2 -2
- package/dist/cloud-GP6G3EAK.js +17 -0
- package/dist/{codebuddy-D2xJ6hFk.d.ts → codebuddy-DM-i0Fu5.d.ts} +49 -18
- package/dist/{config-S2TULOMK.js → config-KFEHLV6W.js} +3 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +34 -25
- package/dist/langgraph/index.d.ts +1 -1
- package/dist/lean-command-DA4ZNKG3.js +15 -0
- package/dist/local-3NGNESNB.js +13 -0
- package/dist/{main-BFDVQHKO.js → main-BDVQAGAS.js} +1261 -242
- package/dist/main-BDVQAGAS.js.map +1 -0
- package/dist/{memory-NIBFAMY4.js → memory-4RGVLPKG.js} +6 -6
- package/dist/operations-EQGOFCBD.js +24 -0
- package/dist/operations-EQGOFCBD.js.map +1 -0
- package/dist/{rerank-Q7MIFNKA.js → rerank-3P3C2UHN.js} +4 -4
- package/dist/{summaries-TSE44KUQ.js → summaries-ZBV5IN7Y.js} +6 -6
- package/dist/summaries-ZBV5IN7Y.js.map +1 -0
- package/dist/{zvec-F27AR357.js → zvec-MAPSJQSR.js} +3 -3
- package/dist/zvec-MAPSJQSR.js.map +1 -0
- package/package.json +6 -2
- package/dist/chunk-5OWZVZSO.js.map +0 -1
- package/dist/chunk-K63EOA4B.js.map +0 -1
- package/dist/chunk-OLFCOR4U.js +0 -8580
- package/dist/chunk-OLFCOR4U.js.map +0 -1
- package/dist/lean-command-D6TZJEFX.js +0 -13
- package/dist/main-BFDVQHKO.js.map +0 -1
- /package/dist/{chunk-ITGRANYW.js.map → chunk-6MKEIDX5.js.map} +0 -0
- /package/dist/{chunk-IBMJYIEP.js.map → chunk-L5AYZC3F.js.map} +0 -0
- /package/dist/{chunk-3ZELDCR3.js.map → chunk-PCE4STAY.js.map} +0 -0
- /package/dist/{chunk-TI5TQD6D.js.map → chunk-QDBDYCM2.js.map} +0 -0
- /package/dist/{chunk-DSN5CEHO.js.map → chunk-ROQTT5ZO.js.map} +0 -0
- /package/dist/{chunk-IE2OUO54.js.map → chunk-UFZD3JAA.js.map} +0 -0
- /package/dist/{config-S2TULOMK.js.map → cloud-GP6G3EAK.js.map} +0 -0
- /package/dist/{lean-command-D6TZJEFX.js.map → config-KFEHLV6W.js.map} +0 -0
- /package/dist/{summaries-TSE44KUQ.js.map → lean-command-DA4ZNKG3.js.map} +0 -0
- /package/dist/{zvec-F27AR357.js.map → local-3NGNESNB.js.map} +0 -0
- /package/dist/{memory-NIBFAMY4.js.map → memory-4RGVLPKG.js.map} +0 -0
- /package/dist/{rerank-Q7MIFNKA.js.map → rerank-3P3C2UHN.js.map} +0 -0
package/README.md
CHANGED
|
@@ -18,17 +18,32 @@ codebuddy
|
|
|
18
18
|
|
|
19
19
|
The setup wizard configures the project and agent integration. Project context
|
|
20
20
|
works without a database; semantic memory recall optionally uses PostgreSQL
|
|
21
|
-
with pgvector and a
|
|
21
|
+
with pgvector and a local Hugging Face embedding model (no token needed).
|
|
22
22
|
|
|
23
23
|
## What it does
|
|
24
24
|
|
|
25
25
|
- **Task context:** relevant files, symbols, code excerpts, nearby dependencies,
|
|
26
26
|
and covering tests within a token budget.
|
|
27
|
-
- **Project memory:** decisions, rules, and handoffs stored as readable Markdown
|
|
27
|
+
- **Project memory:** decisions, rules, and handoffs stored as readable Markdown,
|
|
28
|
+
recalled on every prompt by hybrid lexical + vector search
|
|
29
|
+
([how it works](docs/semantic-memory.md)).
|
|
28
30
|
- **Change guidance:** plans, policies, incident history, and risk signals.
|
|
29
31
|
- **Optional retrieval:** zvec semantic search, Graphify architecture maps, and
|
|
30
32
|
a bring-your-own LLM layer.
|
|
31
33
|
|
|
34
|
+
## See what it does
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
codebuddy dashboard
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
This opens a live local page showing:
|
|
41
|
+
- every prompt and the memories recalled for it;
|
|
42
|
+
- MCP tool calls with their latency;
|
|
43
|
+
- the tokens delivered to your agent.
|
|
44
|
+
|
|
45
|
+
It updates as you work, and the traces never leave your machine.
|
|
46
|
+
|
|
32
47
|
## Try it without a database
|
|
33
48
|
|
|
34
49
|
```bash
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOCAL_EMBEDDING_MODEL
|
|
3
|
+
} from "./chunk-JMWJ7V6Y.js";
|
|
4
|
+
|
|
5
|
+
// src/providers/local.ts
|
|
6
|
+
import { homedir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
var MAX_INPUT_CHARS = 8e3;
|
|
9
|
+
var transformers = null;
|
|
10
|
+
var extractor = null;
|
|
11
|
+
function modelCacheDir() {
|
|
12
|
+
return process.env.CODEBUDDY_MODEL_CACHE ?? join(homedir(), ".cache", "codebuddy", "models");
|
|
13
|
+
}
|
|
14
|
+
function loadTransformers() {
|
|
15
|
+
transformers ??= import("@huggingface/transformers").then((module) => {
|
|
16
|
+
module.env.cacheDir = modelCacheDir();
|
|
17
|
+
module.env.logLevel = module.LogLevel.ERROR;
|
|
18
|
+
return module;
|
|
19
|
+
});
|
|
20
|
+
transformers.catch(() => {
|
|
21
|
+
transformers = null;
|
|
22
|
+
});
|
|
23
|
+
return transformers;
|
|
24
|
+
}
|
|
25
|
+
var LocalModelProvider = class {
|
|
26
|
+
space = {
|
|
27
|
+
model: LOCAL_EMBEDDING_MODEL.id,
|
|
28
|
+
revision: LOCAL_EMBEDDING_MODEL.revision,
|
|
29
|
+
schema: LOCAL_EMBEDDING_MODEL.schema,
|
|
30
|
+
dimensions: LOCAL_EMBEDDING_MODEL.dimensions
|
|
31
|
+
};
|
|
32
|
+
/** `request.model` is ignored: one provider is one embedding space. */
|
|
33
|
+
async embed(request) {
|
|
34
|
+
const inputs = (typeof request.input === "string" ? [request.input] : request.input).map(
|
|
35
|
+
(text) => text.slice(0, MAX_INPUT_CHARS)
|
|
36
|
+
);
|
|
37
|
+
extractor ??= loadTransformers().then(
|
|
38
|
+
async (module) => await module.pipeline("feature-extraction", this.space.model, {
|
|
39
|
+
revision: this.space.revision,
|
|
40
|
+
dtype: "q8"
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
extractor.catch(() => {
|
|
44
|
+
extractor = null;
|
|
45
|
+
});
|
|
46
|
+
const extract = await extractor;
|
|
47
|
+
const output = await extract(inputs, { pooling: "cls", normalize: true });
|
|
48
|
+
return { ...this.space, vectors: output.tolist() };
|
|
49
|
+
}
|
|
50
|
+
async generateText() {
|
|
51
|
+
throw new Error("The local provider only embeds; text generation uses the BYOK LLM layer.");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var rerankers = /* @__PURE__ */ new Map();
|
|
55
|
+
async function rerankLocally(model, query, documents) {
|
|
56
|
+
if (documents.length === 0) return [];
|
|
57
|
+
let pending = rerankers.get(model.id);
|
|
58
|
+
if (!pending) {
|
|
59
|
+
pending = loadTransformers().then(async (module) => {
|
|
60
|
+
const options = model.revision ? { revision: model.revision } : {};
|
|
61
|
+
const [tokenizer2, classifier2] = await Promise.all([
|
|
62
|
+
module.AutoTokenizer.from_pretrained(model.id, options),
|
|
63
|
+
module.AutoModelForSequenceClassification.from_pretrained(model.id, {
|
|
64
|
+
...options,
|
|
65
|
+
dtype: "q8"
|
|
66
|
+
})
|
|
67
|
+
]);
|
|
68
|
+
return { tokenizer: tokenizer2, model: classifier2 };
|
|
69
|
+
});
|
|
70
|
+
rerankers.set(model.id, pending);
|
|
71
|
+
pending.catch(() => rerankers.delete(model.id));
|
|
72
|
+
}
|
|
73
|
+
const { tokenizer, model: classifier } = await pending;
|
|
74
|
+
const inputs = tokenizer(
|
|
75
|
+
documents.map(() => query.slice(0, MAX_INPUT_CHARS)),
|
|
76
|
+
{
|
|
77
|
+
text_pair: documents.map((document) => document.slice(0, MAX_INPUT_CHARS)),
|
|
78
|
+
padding: true,
|
|
79
|
+
truncation: true
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
const { logits } = await classifier(inputs);
|
|
83
|
+
return logits.tolist().map((row) => row[0] ?? Number.NEGATIVE_INFINITY);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export {
|
|
87
|
+
modelCacheDir,
|
|
88
|
+
LocalModelProvider,
|
|
89
|
+
rerankLocally
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=chunk-3XYQGFPB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/providers/local.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type {\n EmbeddingRequest,\n EmbeddingResponse,\n ModelProvider,\n TextGenerationResponse,\n} from \"./adapter.js\";\nimport type { EmbeddingSpace } from \"./embedding-space.js\";\nimport { LOCAL_EMBEDDING_MODEL } from \"./models.js\";\n\n/**\n * Local Hugging Face models through transformers.js (ONNX Runtime, CPU). The\n * weights download once into `~/.cache/codebuddy/models` (override with\n * `CODEBUDDY_MODEL_CACHE`); afterwards nothing leaves the machine and no token\n * is needed. `@huggingface/transformers` is an optional dependency, loaded on\n * first use, so a missing install only disables semantic ranking.\n */\n\ntype Transformers = typeof import(\"@huggingface/transformers\");\n/** The slice of transformers.js' FeatureExtractionPipeline used here. */\ntype Extractor = (\n texts: string[],\n options: { pooling: \"cls\"; normalize: boolean },\n) => Promise<{ tolist(): number[][] }>;\n\n/** Hard cap on a single input; ModernBERT reads 8k tokens, memories are far shorter. */\nconst MAX_INPUT_CHARS = 8_000;\n\nlet transformers: Promise<Transformers> | null = null;\nlet extractor: Promise<Extractor> | null = null;\n\nexport function modelCacheDir(): string {\n return process.env.CODEBUDDY_MODEL_CACHE ?? join(homedir(), \".cache\", \"codebuddy\", \"models\");\n}\n\nfunction loadTransformers(): Promise<Transformers> {\n transformers ??= import(\"@huggingface/transformers\").then((module) => {\n module.env.cacheDir = modelCacheDir();\n // Info/debug logs go to console.log, i.e. stdout: the MCP JSON-RPC channel\n // under `codebuddy serve`. Errors only (they use stderr), here and in ONNX Runtime.\n module.env.logLevel = module.LogLevel.ERROR;\n return module;\n });\n // A failed import (optional dependency missing) is retried on the next call.\n transformers.catch(() => {\n transformers = null;\n });\n return transformers;\n}\n\nexport class LocalModelProvider implements ModelProvider {\n readonly space: EmbeddingSpace = {\n model: LOCAL_EMBEDDING_MODEL.id,\n revision: LOCAL_EMBEDDING_MODEL.revision,\n schema: LOCAL_EMBEDDING_MODEL.schema,\n dimensions: LOCAL_EMBEDDING_MODEL.dimensions,\n };\n\n /** `request.model` is ignored: one provider is one embedding space. */\n async embed(request: EmbeddingRequest): Promise<EmbeddingResponse> {\n const inputs = (typeof request.input === \"string\" ? [request.input] : request.input).map(\n (text) => text.slice(0, MAX_INPUT_CHARS),\n );\n extractor ??= loadTransformers().then(\n async (module) =>\n (await module.pipeline(\"feature-extraction\", this.space.model, {\n revision: this.space.revision,\n dtype: \"q8\",\n })) as unknown as Extractor,\n );\n // A failed load (no network on first use) is retried on the next call.\n extractor.catch(() => {\n extractor = null;\n });\n const extract = await extractor;\n // Granite r2 is trained with CLS pooling; L2-normalized so cosine = dot.\n const output = await extract(inputs, { pooling: \"cls\", normalize: true });\n return { ...this.space, vectors: output.tolist() };\n }\n\n async generateText(): Promise<TextGenerationResponse> {\n throw new Error(\"The local provider only embeds; text generation uses the BYOK LLM layer.\");\n }\n}\n\ntype Reranker = {\n tokenizer: (\n texts: string[],\n options: { text_pair: string[]; padding: boolean; truncation: boolean },\n ) => unknown;\n model: (inputs: unknown) => Promise<{ logits: { tolist(): number[][] } }>;\n};\nconst rerankers = new Map<string, Promise<Reranker>>();\n\n/**\n * Local cross-encoder logits for (query, document) pairs, in input order.\n * Higher is more relevant; values order documents for one query and are not\n * calibrated across queries, so callers order by them and never threshold.\n */\nexport async function rerankLocally(\n model: { id: string; revision?: string },\n query: string,\n documents: string[],\n): Promise<number[]> {\n if (documents.length === 0) return [];\n let pending = rerankers.get(model.id);\n if (!pending) {\n pending = loadTransformers().then(async (module) => {\n const options = model.revision ? { revision: model.revision } : {};\n const [tokenizer, classifier] = await Promise.all([\n module.AutoTokenizer.from_pretrained(model.id, options),\n module.AutoModelForSequenceClassification.from_pretrained(model.id, {\n ...options,\n dtype: \"q8\",\n }),\n ]);\n return { tokenizer, model: classifier } as unknown as Reranker;\n });\n rerankers.set(model.id, pending);\n pending.catch(() => rerankers.delete(model.id));\n }\n const { tokenizer, model: classifier } = await pending;\n const inputs = tokenizer(\n documents.map(() => query.slice(0, MAX_INPUT_CHARS)),\n {\n text_pair: documents.map((document) => document.slice(0, MAX_INPUT_CHARS)),\n padding: true,\n truncation: true,\n },\n );\n const { logits } = await classifier(inputs);\n return logits.tolist().map((row) => row[0] ?? Number.NEGATIVE_INFINITY);\n}\n"],"mappings":";;;;;AAAA,SAAS,eAAe;AACxB,SAAS,YAAY;AA0BrB,IAAM,kBAAkB;AAExB,IAAI,eAA6C;AACjD,IAAI,YAAuC;AAEpC,SAAS,gBAAwB;AACtC,SAAO,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,GAAG,UAAU,aAAa,QAAQ;AAC7F;AAEA,SAAS,mBAA0C;AACjD,mBAAiB,OAAO,2BAA2B,EAAE,KAAK,CAAC,WAAW;AACpE,WAAO,IAAI,WAAW,cAAc;AAGpC,WAAO,IAAI,WAAW,OAAO,SAAS;AACtC,WAAO;AAAA,EACT,CAAC;AAED,eAAa,MAAM,MAAM;AACvB,mBAAe;AAAA,EACjB,CAAC;AACD,SAAO;AACT;AAEO,IAAM,qBAAN,MAAkD;AAAA,EAC9C,QAAwB;AAAA,IAC/B,OAAO,sBAAsB;AAAA,IAC7B,UAAU,sBAAsB;AAAA,IAChC,QAAQ,sBAAsB;AAAA,IAC9B,YAAY,sBAAsB;AAAA,EACpC;AAAA;AAAA,EAGA,MAAM,MAAM,SAAuD;AACjE,UAAM,UAAU,OAAO,QAAQ,UAAU,WAAW,CAAC,QAAQ,KAAK,IAAI,QAAQ,OAAO;AAAA,MACnF,CAAC,SAAS,KAAK,MAAM,GAAG,eAAe;AAAA,IACzC;AACA,kBAAc,iBAAiB,EAAE;AAAA,MAC/B,OAAO,WACJ,MAAM,OAAO,SAAS,sBAAsB,KAAK,MAAM,OAAO;AAAA,QAC7D,UAAU,KAAK,MAAM;AAAA,QACrB,OAAO;AAAA,MACT,CAAC;AAAA,IACL;AAEA,cAAU,MAAM,MAAM;AACpB,kBAAY;AAAA,IACd,CAAC;AACD,UAAM,UAAU,MAAM;AAEtB,UAAM,SAAS,MAAM,QAAQ,QAAQ,EAAE,SAAS,OAAO,WAAW,KAAK,CAAC;AACxE,WAAO,EAAE,GAAG,KAAK,OAAO,SAAS,OAAO,OAAO,EAAE;AAAA,EACnD;AAAA,EAEA,MAAM,eAAgD;AACpD,UAAM,IAAI,MAAM,0EAA0E;AAAA,EAC5F;AACF;AASA,IAAM,YAAY,oBAAI,IAA+B;AAOrD,eAAsB,cACpB,OACA,OACA,WACmB;AACnB,MAAI,UAAU,WAAW,EAAG,QAAO,CAAC;AACpC,MAAI,UAAU,UAAU,IAAI,MAAM,EAAE;AACpC,MAAI,CAAC,SAAS;AACZ,cAAU,iBAAiB,EAAE,KAAK,OAAO,WAAW;AAClD,YAAM,UAAU,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AACjE,YAAM,CAACA,YAAWC,WAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,QAChD,OAAO,cAAc,gBAAgB,MAAM,IAAI,OAAO;AAAA,QACtD,OAAO,mCAAmC,gBAAgB,MAAM,IAAI;AAAA,UAClE,GAAG;AAAA,UACH,OAAO;AAAA,QACT,CAAC;AAAA,MACH,CAAC;AACD,aAAO,EAAE,WAAAD,YAAW,OAAOC,YAAW;AAAA,IACxC,CAAC;AACD,cAAU,IAAI,MAAM,IAAI,OAAO;AAC/B,YAAQ,MAAM,MAAM,UAAU,OAAO,MAAM,EAAE,CAAC;AAAA,EAChD;AACA,QAAM,EAAE,WAAW,OAAO,WAAW,IAAI,MAAM;AAC/C,QAAM,SAAS;AAAA,IACb,UAAU,IAAI,MAAM,MAAM,MAAM,GAAG,eAAe,CAAC;AAAA,IACnD;AAAA,MACE,WAAW,UAAU,IAAI,CAAC,aAAa,SAAS,MAAM,GAAG,eAAe,CAAC;AAAA,MACzE,SAAS;AAAA,MACT,YAAY;AAAA,IACd;AAAA,EACF;AACA,QAAM,EAAE,OAAO,IAAI,MAAM,WAAW,MAAM;AAC1C,SAAO,OAAO,OAAO,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC,KAAK,OAAO,iBAAiB;AACxE;","names":["tokenizer","classifier"]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getModelContextWindow
|
|
3
|
+
} from "./chunk-JMWJ7V6Y.js";
|
|
4
|
+
|
|
5
|
+
// src/planner/budget.ts
|
|
6
|
+
import { Tiktoken } from "js-tiktoken/lite";
|
|
7
|
+
import cl100kBase from "js-tiktoken/ranks/cl100k_base";
|
|
8
|
+
var encoder = null;
|
|
9
|
+
function getEncoder() {
|
|
10
|
+
if (!encoder) {
|
|
11
|
+
encoder = new Tiktoken(cl100kBase);
|
|
12
|
+
}
|
|
13
|
+
return encoder;
|
|
14
|
+
}
|
|
15
|
+
var PIECE = new RegExp(cl100kBase.pat_str, "gu");
|
|
16
|
+
var SPECIAL_TOKENS = Object.keys(cl100kBase.special_tokens);
|
|
17
|
+
var MAX_CACHED_PIECES = 2e5;
|
|
18
|
+
var pieceTokens = /* @__PURE__ */ new Map();
|
|
19
|
+
function countTokens(text) {
|
|
20
|
+
if (!text) return 0;
|
|
21
|
+
try {
|
|
22
|
+
if (SPECIAL_TOKENS.some((token) => text.includes(token))) throw new Error("special token");
|
|
23
|
+
let total = 0;
|
|
24
|
+
for (const [piece] of text.matchAll(PIECE)) {
|
|
25
|
+
let count = pieceTokens.get(piece);
|
|
26
|
+
if (count === void 0) {
|
|
27
|
+
count = getEncoder().encode(piece).length;
|
|
28
|
+
if (pieceTokens.size >= MAX_CACHED_PIECES) pieceTokens.clear();
|
|
29
|
+
pieceTokens.set(piece, count);
|
|
30
|
+
}
|
|
31
|
+
total += count;
|
|
32
|
+
}
|
|
33
|
+
return total;
|
|
34
|
+
} catch {
|
|
35
|
+
return Math.max(1, Math.ceil(text.length / 4));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function clampBudgetForCallerModel(requestedBudget, callerModel) {
|
|
39
|
+
if (!callerModel) {
|
|
40
|
+
return { budget: requestedBudget, clamped: false };
|
|
41
|
+
}
|
|
42
|
+
const ctx = getModelContextWindow(callerModel);
|
|
43
|
+
if (!ctx) {
|
|
44
|
+
return { budget: requestedBudget, clamped: false };
|
|
45
|
+
}
|
|
46
|
+
const headroom = Math.floor(ctx * 0.75);
|
|
47
|
+
if (requestedBudget > headroom) {
|
|
48
|
+
return {
|
|
49
|
+
budget: headroom,
|
|
50
|
+
clamped: true,
|
|
51
|
+
reason: `budget ${requestedBudget} exceeds 75% of ${callerModel} context window (${ctx}); clamped to ${headroom}.`
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return { budget: requestedBudget, clamped: false };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/core/ids.ts
|
|
58
|
+
import { createHash, randomBytes } from "crypto";
|
|
59
|
+
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
60
|
+
function generateUlid(now = Date.now()) {
|
|
61
|
+
let timePart = "";
|
|
62
|
+
let ts = now;
|
|
63
|
+
for (let i = 0; i < 10; i++) {
|
|
64
|
+
timePart = (CROCKFORD[ts % 32] ?? "0") + timePart;
|
|
65
|
+
ts = Math.floor(ts / 32);
|
|
66
|
+
}
|
|
67
|
+
const bytes = randomBytes(10);
|
|
68
|
+
let randPart = "";
|
|
69
|
+
for (let i = 0; i < 16; i++) {
|
|
70
|
+
const bitOffset = i * 5;
|
|
71
|
+
const byteIndex = Math.floor(bitOffset / 8);
|
|
72
|
+
const bitInByte = bitOffset % 8;
|
|
73
|
+
const hi = bytes[byteIndex] ?? 0;
|
|
74
|
+
const lo = bytes[byteIndex + 1] ?? 0;
|
|
75
|
+
const combined = (hi << 8 | lo) >>> 0;
|
|
76
|
+
const shift = 16 - bitInByte - 5;
|
|
77
|
+
const value = combined >>> shift & 31;
|
|
78
|
+
randPart += CROCKFORD[value] ?? "0";
|
|
79
|
+
}
|
|
80
|
+
return timePart + randPart;
|
|
81
|
+
}
|
|
82
|
+
function generateSessionId(now) {
|
|
83
|
+
return `sess_${generateUlid(now).toLowerCase()}`;
|
|
84
|
+
}
|
|
85
|
+
function generateEntityId(prefix, now) {
|
|
86
|
+
return `${prefix}_${generateUlid(now).toLowerCase()}`;
|
|
87
|
+
}
|
|
88
|
+
function computeContentHash(namespace, sessionId, content) {
|
|
89
|
+
return createHash("sha256").update(`${namespace}${sessionId}${content}`).digest("hex");
|
|
90
|
+
}
|
|
91
|
+
function computeFactHash(namespace, content) {
|
|
92
|
+
return createHash("sha256").update(`${namespace}fact${content}`).digest("hex");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
countTokens,
|
|
97
|
+
clampBudgetForCallerModel,
|
|
98
|
+
generateSessionId,
|
|
99
|
+
generateEntityId,
|
|
100
|
+
computeContentHash,
|
|
101
|
+
computeFactHash
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=chunk-4BYGTEJT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/planner/budget.ts","../src/core/ids.ts"],"sourcesContent":["import { Tiktoken } from \"js-tiktoken/lite\";\nimport cl100kBase from \"js-tiktoken/ranks/cl100k_base\";\nimport { getModelContextWindow } from \"../providers/models.js\";\n\nlet encoder: Tiktoken | null = null;\n\nfunction getEncoder(): Tiktoken {\n if (!encoder) {\n encoder = new Tiktoken(cl100kBase);\n }\n return encoder;\n}\n\n/** cl100k pre-tokenizer: BPE merges never cross these pieces, so counts add up. */\nconst PIECE = new RegExp(cl100kBase.pat_str, \"gu\");\nconst SPECIAL_TOKENS = Object.keys(cl100kBase.special_tokens);\n// ponytail: cleared when full rather than LRU; source and JSON pieces repeat\n// heavily, so a simple bound keeps the hit rate high.\nconst MAX_CACHED_PIECES = 200_000;\nconst pieceTokens = new Map<string, number>();\n\n/**\n * Exact cl100k token count. A context request tokenizes nearly the same JSON\n * several times (stats, stages, budget fixed point, delivery), so BPE results\n * are memoized per pre-tokenized piece; the sum equals `encode(text).length`.\n */\nexport function countTokens(text: string): number {\n if (!text) return 0;\n try {\n // `encode` rejects special-token text; keep the same fallback for it.\n if (SPECIAL_TOKENS.some((token) => text.includes(token))) throw new Error(\"special token\");\n let total = 0;\n for (const [piece] of text.matchAll(PIECE)) {\n let count = pieceTokens.get(piece);\n if (count === undefined) {\n count = getEncoder().encode(piece).length;\n if (pieceTokens.size >= MAX_CACHED_PIECES) pieceTokens.clear();\n pieceTokens.set(piece, count);\n }\n total += count;\n }\n return total;\n } catch {\n return Math.max(1, Math.ceil(text.length / 4));\n }\n}\n\nexport type BudgetClampResult = {\n budget: number;\n clamped: boolean;\n reason?: string;\n};\n\nexport function clampBudgetForCallerModel(\n requestedBudget: number,\n callerModel: string | undefined,\n): BudgetClampResult {\n if (!callerModel) {\n return { budget: requestedBudget, clamped: false };\n }\n const ctx = getModelContextWindow(callerModel);\n if (!ctx) {\n return { budget: requestedBudget, clamped: false };\n }\n const headroom = Math.floor(ctx * 0.75);\n if (requestedBudget > headroom) {\n return {\n budget: headroom,\n clamped: true,\n reason: `budget ${requestedBudget} exceeds 75% of ${callerModel} context window (${ctx}); clamped to ${headroom}.`,\n };\n }\n return { budget: requestedBudget, clamped: false };\n}\n","import { createHash, randomBytes } from \"node:crypto\";\n\nconst CROCKFORD = \"0123456789ABCDEFGHJKMNPQRSTVWXYZ\";\n\nexport function generateUlid(now: number = Date.now()): string {\n let timePart = \"\";\n let ts = now;\n for (let i = 0; i < 10; i++) {\n timePart = (CROCKFORD[ts % 32] ?? \"0\") + timePart;\n ts = Math.floor(ts / 32);\n }\n\n const bytes = randomBytes(10);\n let randPart = \"\";\n for (let i = 0; i < 16; i++) {\n const bitOffset = i * 5;\n const byteIndex = Math.floor(bitOffset / 8);\n const bitInByte = bitOffset % 8;\n const hi = bytes[byteIndex] ?? 0;\n const lo = bytes[byteIndex + 1] ?? 0;\n const combined = ((hi << 8) | lo) >>> 0;\n const shift = 16 - bitInByte - 5;\n const value = (combined >>> shift) & 0x1f;\n randPart += CROCKFORD[value] ?? \"0\";\n }\n\n return timePart + randPart;\n}\n\nexport function generateSessionId(now?: number): string {\n return `sess_${generateUlid(now).toLowerCase()}`;\n}\n\nexport function generateEntityId(prefix: string, now?: number): string {\n return `${prefix}_${generateUlid(now).toLowerCase()}`;\n}\n\nexport function computeContentHash(namespace: string, sessionId: string, content: string): string {\n return createHash(\"sha256\").update(`${namespace}\\x1f${sessionId}\\x1f${content}`).digest(\"hex\");\n}\n\nexport function computeFactHash(namespace: string, content: string): string {\n return createHash(\"sha256\").update(`${namespace}\\x1ffact\\x1f${content}`).digest(\"hex\");\n}\n"],"mappings":";;;;;AAAA,SAAS,gBAAgB;AACzB,OAAO,gBAAgB;AAGvB,IAAI,UAA2B;AAE/B,SAAS,aAAuB;AAC9B,MAAI,CAAC,SAAS;AACZ,cAAU,IAAI,SAAS,UAAU;AAAA,EACnC;AACA,SAAO;AACT;AAGA,IAAM,QAAQ,IAAI,OAAO,WAAW,SAAS,IAAI;AACjD,IAAM,iBAAiB,OAAO,KAAK,WAAW,cAAc;AAG5D,IAAM,oBAAoB;AAC1B,IAAM,cAAc,oBAAI,IAAoB;AAOrC,SAAS,YAAY,MAAsB;AAChD,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI;AAEF,QAAI,eAAe,KAAK,CAAC,UAAU,KAAK,SAAS,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,eAAe;AACzF,QAAI,QAAQ;AACZ,eAAW,CAAC,KAAK,KAAK,KAAK,SAAS,KAAK,GAAG;AAC1C,UAAI,QAAQ,YAAY,IAAI,KAAK;AACjC,UAAI,UAAU,QAAW;AACvB,gBAAQ,WAAW,EAAE,OAAO,KAAK,EAAE;AACnC,YAAI,YAAY,QAAQ,kBAAmB,aAAY,MAAM;AAC7D,oBAAY,IAAI,OAAO,KAAK;AAAA,MAC9B;AACA,eAAS;AAAA,IACX;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO,KAAK,IAAI,GAAG,KAAK,KAAK,KAAK,SAAS,CAAC,CAAC;AAAA,EAC/C;AACF;AAQO,SAAS,0BACd,iBACA,aACmB;AACnB,MAAI,CAAC,aAAa;AAChB,WAAO,EAAE,QAAQ,iBAAiB,SAAS,MAAM;AAAA,EACnD;AACA,QAAM,MAAM,sBAAsB,WAAW;AAC7C,MAAI,CAAC,KAAK;AACR,WAAO,EAAE,QAAQ,iBAAiB,SAAS,MAAM;AAAA,EACnD;AACA,QAAM,WAAW,KAAK,MAAM,MAAM,IAAI;AACtC,MAAI,kBAAkB,UAAU;AAC9B,WAAO;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,QAAQ,UAAU,eAAe,mBAAmB,WAAW,oBAAoB,GAAG,iBAAiB,QAAQ;AAAA,IACjH;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,iBAAiB,SAAS,MAAM;AACnD;;;ACzEA,SAAS,YAAY,mBAAmB;AAExC,IAAM,YAAY;AAEX,SAAS,aAAa,MAAc,KAAK,IAAI,GAAW;AAC7D,MAAI,WAAW;AACf,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,gBAAY,UAAU,KAAK,EAAE,KAAK,OAAO;AACzC,SAAK,KAAK,MAAM,KAAK,EAAE;AAAA,EACzB;AAEA,QAAM,QAAQ,YAAY,EAAE;AAC5B,MAAI,WAAW;AACf,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,YAAY,IAAI;AACtB,UAAM,YAAY,KAAK,MAAM,YAAY,CAAC;AAC1C,UAAM,YAAY,YAAY;AAC9B,UAAM,KAAK,MAAM,SAAS,KAAK;AAC/B,UAAM,KAAK,MAAM,YAAY,CAAC,KAAK;AACnC,UAAM,YAAa,MAAM,IAAK,QAAQ;AACtC,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,QAAS,aAAa,QAAS;AACrC,gBAAY,UAAU,KAAK,KAAK;AAAA,EAClC;AAEA,SAAO,WAAW;AACpB;AAEO,SAAS,kBAAkB,KAAsB;AACtD,SAAO,QAAQ,aAAa,GAAG,EAAE,YAAY,CAAC;AAChD;AAEO,SAAS,iBAAiB,QAAgB,KAAsB;AACrE,SAAO,GAAG,MAAM,IAAI,aAAa,GAAG,EAAE,YAAY,CAAC;AACrD;AAEO,SAAS,mBAAmB,WAAmB,WAAmB,SAAyB;AAChG,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,SAAS,IAAO,SAAS,IAAO,OAAO,EAAE,EAAE,OAAO,KAAK;AAC/F;AAEO,SAAS,gBAAgB,WAAmB,SAAyB;AAC1E,SAAO,WAAW,QAAQ,EAAE,OAAO,GAAG,SAAS,SAAe,OAAO,EAAE,EAAE,OAAO,KAAK;AACvF;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
readConfigFile
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-776EQXDV.js";
|
|
4
4
|
|
|
5
5
|
// src/llm/config.ts
|
|
6
6
|
var BASE_URL = {
|
|
@@ -60,4 +60,4 @@ export {
|
|
|
60
60
|
resolveLlmConfig,
|
|
61
61
|
redactLlmKeys
|
|
62
62
|
};
|
|
63
|
-
//# sourceMappingURL=chunk-
|
|
63
|
+
//# sourceMappingURL=chunk-6MKEIDX5.js.map
|
|
@@ -95,8 +95,15 @@ var fileSchema = z.object({
|
|
|
95
95
|
namespace: z.string().min(1).optional(),
|
|
96
96
|
tokenBudget: z.number().int().positive().optional(),
|
|
97
97
|
provider: z.object({
|
|
98
|
-
type: z.
|
|
99
|
-
apiKey: z.string().min(1).optional()
|
|
98
|
+
type: z.enum(["local", "huggingface", "openrouter"]).default("local"),
|
|
99
|
+
apiKey: z.string().min(1).optional(),
|
|
100
|
+
model: z.string().min(1).optional(),
|
|
101
|
+
relevanceFloor: z.number().min(-1).max(1).optional()
|
|
102
|
+
}).optional(),
|
|
103
|
+
/** Optional cross-encoder that orders recalled facts (off by default). */
|
|
104
|
+
rerank: z.object({
|
|
105
|
+
provider: z.enum(["off", "local", "huggingface", "openrouter"]).default("off"),
|
|
106
|
+
model: z.string().min(1).optional()
|
|
100
107
|
}).optional(),
|
|
101
108
|
plan: z.object({
|
|
102
109
|
policy: z.enum(PLAN_POLICIES).default("suggest")
|
|
@@ -175,7 +182,7 @@ async function initConfigFile(cwd = process.cwd()) {
|
|
|
175
182
|
// Default to the folder name (sanitized) so a fresh scaffold agrees with
|
|
176
183
|
// doctor's expected default instead of warning immediately after init.
|
|
177
184
|
namespace: defaultNamespaceForCwd(cwd),
|
|
178
|
-
provider: { type: "
|
|
185
|
+
provider: { type: "local" },
|
|
179
186
|
tokenBudget: 4e3
|
|
180
187
|
};
|
|
181
188
|
await writeFile(path, `${JSON.stringify(template, null, 2)}
|
|
@@ -227,8 +234,10 @@ async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
|
|
|
227
234
|
namespace: process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),
|
|
228
235
|
projectRoot,
|
|
229
236
|
provider: {
|
|
230
|
-
type: "
|
|
231
|
-
...hfToken ? { apiKey: hfToken } : {}
|
|
237
|
+
type: file.provider?.type ?? "local",
|
|
238
|
+
...hfToken ? { apiKey: hfToken } : {},
|
|
239
|
+
...file.provider?.model ? { model: file.provider.model } : {},
|
|
240
|
+
...file.provider?.relevanceFloor !== void 0 ? { relevanceFloor: file.provider.relevanceFloor } : {}
|
|
232
241
|
},
|
|
233
242
|
...file.tokenBudget ? { tokenBudget: file.tokenBudget } : {},
|
|
234
243
|
...file.graphify ? { graphify: file.graphify } : {}
|
|
@@ -355,6 +364,15 @@ async function setLlmEnabled(enabled, options = {}, cwd = process.cwd()) {
|
|
|
355
364
|
await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
356
365
|
`, { mode: 384 });
|
|
357
366
|
}
|
|
367
|
+
async function setConfigBlock(key, value, cwd = process.cwd()) {
|
|
368
|
+
await initConfigFile(cwd);
|
|
369
|
+
const path = configPath(cwd);
|
|
370
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
371
|
+
raw[key] = value;
|
|
372
|
+
fileSchema.parse(raw);
|
|
373
|
+
await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
374
|
+
`, { mode: 384 });
|
|
375
|
+
}
|
|
358
376
|
function redactSecrets(value) {
|
|
359
377
|
if (typeof value === "string") {
|
|
360
378
|
let redacted = value;
|
|
@@ -411,6 +429,7 @@ export {
|
|
|
411
429
|
setPlanPolicy,
|
|
412
430
|
setZvecEnabled,
|
|
413
431
|
setLlmEnabled,
|
|
432
|
+
setConfigBlock,
|
|
414
433
|
redactSecrets
|
|
415
434
|
};
|
|
416
|
-
//# sourceMappingURL=chunk-
|
|
435
|
+
//# sourceMappingURL=chunk-776EQXDV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/markdown-store-fs.ts","../src/core/config-file.ts"],"sourcesContent":["import { lstat, open, rename } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\n\n/**\n * Filesystem helpers shared by the Markdown-backed stores\n * (`MemoryFileStore`, `PlanFileStore`). These enforce the security\n * invariants that keep `.codebuddy/` writes inside the repository root:\n * no path traversal, no symlinked path components, and crash-safe atomic\n * writes. Keep this module small and audited; both stores depend on it.\n */\n\n/**\n * Resolve `path` and assert it stays inside `root`. Returns the absolute\n * path. Throws if the resolved path escapes the root.\n */\nexport function resolveWithinRoot(root: string, path: string): string {\n const absoluteRoot = resolve(root);\n const absolute = resolve(path);\n const relativePath = relative(absoluteRoot, absolute);\n if (relativePath === \"..\" || relativePath.startsWith(`..${sep}`) || relativePath === \"\") {\n throw new Error(`Path escapes repository root: ${path}`);\n }\n return absolute;\n}\n\n/**\n * Walk each path component between `root` and `target` and refuse if any\n * component is a symbolic link. Prevents a planted symlink from\n * redirecting a write outside the repository. Missing components are\n * fine — the caller creates them.\n */\nexport async function assertNoSymlinkBetween(root: string, target: string): Promise<void> {\n const absoluteRoot = resolve(root);\n const absoluteTarget = resolve(target);\n const relativeTarget = relative(absoluteRoot, absoluteTarget);\n if (relativeTarget === \"..\" || relativeTarget.startsWith(`..${sep}`)) {\n throw new Error(`Path escapes repository root: ${target}`);\n }\n\n let current = absoluteRoot;\n const parts = relativeTarget.split(sep).filter(Boolean);\n for (const part of parts) {\n current = join(current, part);\n try {\n const info = await lstat(current);\n if (info.isSymbolicLink()) {\n throw new Error(`Refusing to use symlinked path: ${current}`);\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return;\n throw error;\n }\n }\n}\n\n/**\n * Write `document` to `path` atomically: write to a unique temp file with\n * an exclusive (`wx`) create, fsync, then rename into place. A crash mid-\n * write leaves either the old file or nothing — never a partial file.\n *\n * `mode` sets permissions on NEWLY created files (an existing file keeps its\n * mode, matching rename semantics). Secrets-bearing configs should use 0o600.\n */\nexport async function writeAtomic(\n path: string,\n document: string,\n options?: { mode?: number },\n): Promise<void> {\n const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;\n const handle = await open(temporaryPath, \"wx\", options?.mode ?? 0o600);\n try {\n await handle.writeFile(document, \"utf8\");\n await handle.sync();\n } finally {\n await handle.close();\n }\n await rename(temporaryPath, path);\n}\n\n/**\n * Split a Markdown document with leading YAML front matter into its\n * front-matter text and body. Throws on malformed or unterminated\n * front matter so hand-edited files fail loudly rather than silently\n * losing fields.\n */\nexport function parseFrontMatter(document: string): { frontMatter: string; content: string } {\n if (!document.startsWith(\"---\\n\")) {\n throw new Error(\"Markdown file must start with YAML front matter.\");\n }\n const closingIndex = document.indexOf(\"\\n---\\n\", 4);\n if (closingIndex === -1) {\n throw new Error(\"Markdown file has unterminated YAML front matter.\");\n }\n return {\n frontMatter: document.slice(4, closingIndex),\n content: document.slice(closingIndex + 5),\n };\n}\n\n/**\n * Compose a Markdown document from serialized front-matter YAML and a\n * body. Ensures exactly one trailing newline on the body so files are\n * stable across rewrites (minimal git diffs).\n */\nexport function composeMarkdown(frontMatterYaml: string, body: string): string {\n const trimmedFrontMatter = frontMatterYaml.trimEnd();\n const content = body.endsWith(\"\\n\") ? body : `${body}\\n`;\n return `---\\n${trimmedFrontMatter}\\n---\\n${content}`;\n}\n","import { constants } from \"node:fs\";\nimport { access, chmod, mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { z } from \"zod\";\nimport { writeAtomic } from \"./markdown-store-fs.js\";\nimport type { CodeBuddyConfig } from \"./types.js\";\n\nexport const CONFIG_DIR = \".codebuddy\";\nexport const CONFIG_FILE = \"config.json\";\nexport const POLICY_FILE = \"policies.yaml\";\n\nconst PROJECT_GITIGNORE = [\n \"# CodeBuddy local-only files\",\n \"/config.json\",\n \"/cache/\",\n \"/memory/review/\",\n \"/memory/review-archive/\",\n \"/plans/.locks/\",\n \"/memory/.locks/\",\n \"\",\n \"# Keep these reviewable project artifacts shareable by default:\",\n \"# /memory/facts/*.md\",\n \"# /memory/summaries/*.md\",\n \"# /plans/*.md\",\n \"# If your project memory is private, ignore /memory/ in the repo root .gitignore.\",\n \"\",\n].join(\"\\n\");\n\nconst DEFAULT_POLICY_FILE = [\n \"rules:\",\n \" - id: auth-sensitive\",\n ' pattern: \"src/auth/**\"',\n ' message: \"Auth changes need careful review.\"',\n \" weight: 70\",\n \"\",\n].join(\"\\n\");\n\n/**\n * Plan policy controls how strongly CodeBuddy nudges agents toward writing\n * a plan before substantive work:\n * - off: plans are never suggested or required.\n * - suggest: agents are reminded to plan, but small tasks stay lightweight (default).\n * - required: agents should refuse substantive changes without an active plan.\n */\nexport const PLAN_POLICIES = [\"off\", \"suggest\", \"required\"] as const;\nexport type PlanPolicy = (typeof PLAN_POLICIES)[number];\n\nconst fileSchema = z.object({\n postgresUrl: z.string().min(1).optional(),\n namespace: z.string().min(1).optional(),\n tokenBudget: z.number().int().positive().optional(),\n provider: z\n .object({\n type: z.enum([\"local\", \"huggingface\", \"openrouter\"]).default(\"local\"),\n apiKey: z.string().min(1).optional(),\n model: z.string().min(1).optional(),\n relevanceFloor: z.number().min(-1).max(1).optional(),\n })\n .optional(),\n /** Optional cross-encoder that orders recalled facts (off by default). */\n rerank: z\n .object({\n provider: z.enum([\"off\", \"local\", \"huggingface\", \"openrouter\"]).default(\"off\"),\n model: z.string().min(1).optional(),\n })\n .optional(),\n plan: z\n .object({\n policy: z.enum(PLAN_POLICIES).default(\"suggest\"),\n })\n .optional(),\n graphify: z\n .object({\n enabled: z.boolean(),\n graphPath: z.string().min(1).optional(),\n })\n .optional(),\n /**\n * Optional hosted small-model layer (bring your own key). Keys are read from\n * the environment only, never from this file (`src/llm/config.ts`).\n */\n llm: z\n .object({\n enabled: z.boolean(),\n provider: z.enum([\"groq\", \"openrouter\"]).optional(),\n model: z.string().min(1).optional(),\n fallback: z\n .object({ provider: z.enum([\"groq\", \"openrouter\"]), model: z.string().min(1) })\n .optional(),\n timeoutMs: z.number().int().min(100).max(5_000).optional(),\n dailyTokenBudget: z.number().int().positive().optional(),\n features: z\n .object({\n summaries: z.boolean().optional(),\n rerank: z.boolean().optional(),\n memory: z.boolean().optional(),\n })\n .optional(),\n })\n .optional(),\n /** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */\n zvec: z\n .object({\n enabled: z.boolean(),\n binary: z.string().min(1).optional(),\n timeoutMs: z.number().int().positive().max(10_000).optional(),\n })\n .optional(),\n});\n\nexport type CodeBuddyFileConfig = z.infer<typeof fileSchema>;\n\nexport type ConfigPermissionReport = {\n path: string;\n exists: boolean;\n mode: string | null;\n ok: boolean;\n warning?: string;\n};\n\nexport type ProjectScaffoldReport = {\n codebuddyDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memoryFactsDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n memorySummariesDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n plansDir: { path: string; exists: boolean; ok: boolean; warning?: string };\n gitignore: { path: string; exists: boolean; ok: boolean; warning?: string };\n policies: { path: string; exists: boolean; ok: boolean; warning?: string };\n};\n\nexport function configPath(cwd = process.cwd()): string {\n return join(cwd, CONFIG_DIR, CONFIG_FILE);\n}\n\nexport async function initProjectScaffold(cwd = process.cwd()): Promise<{\n codebuddyDir: string;\n gitignorePath: string;\n policyPath: string;\n}> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n await mkdir(join(codebuddyDir, \"memory\", \"facts\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"memory\", \"summaries\"), { recursive: true, mode: 0o700 });\n await mkdir(join(codebuddyDir, \"plans\"), { recursive: true, mode: 0o700 });\n\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n try {\n await access(gitignorePath, constants.F_OK);\n let gitignore = await readFile(gitignorePath, \"utf8\");\n const lines = new Set(gitignore.split(/\\r?\\n/));\n const missing = [\"/memory/review/\", \"/memory/review-archive/\"].filter(\n (line) => !lines.has(line),\n );\n if (missing.length > 0) {\n gitignore = `${gitignore.trimEnd()}\\n${missing.join(\"\\n\")}\\n`;\n await writeFile(gitignorePath, gitignore, { mode: 0o644 });\n }\n } catch {\n await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 0o644 });\n }\n\n const policyPath = join(codebuddyDir, POLICY_FILE);\n try {\n await access(policyPath, constants.F_OK);\n } catch {\n await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 0o644 });\n }\n\n return { codebuddyDir, gitignorePath, policyPath };\n}\n\nexport async function initConfigFile(\n cwd = process.cwd(),\n): Promise<{ path: string; created: boolean }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n try {\n await access(path, constants.F_OK);\n await chmod(path, 0o600);\n return { path, created: false };\n } catch {\n const template: CodeBuddyFileConfig = {\n postgresUrl: process.env.DATABASE_URL ?? \"postgres://localhost:5432/codebuddy\",\n // Default to the folder name (sanitized) so a fresh scaffold agrees with\n // doctor's expected default instead of warning immediately after init.\n namespace: defaultNamespaceForCwd(cwd),\n provider: { type: \"local\" },\n tokenBudget: 4000,\n };\n await writeFile(path, `${JSON.stringify(template, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path, created: true };\n }\n}\n\n/** Persist a complete project config without changing its private permissions. */\nexport async function writeConfigFile(\n config: CodeBuddyFileConfig,\n cwd = process.cwd(),\n): Promise<{ path: string }> {\n const path = configPath(cwd);\n await initProjectScaffold(cwd);\n await writeFile(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n await chmod(path, 0o600);\n return { path };\n}\n\nexport async function readConfigFile(cwd = process.cwd()): Promise<CodeBuddyFileConfig> {\n const path = configPath(cwd);\n try {\n const raw = await readFile(path, \"utf8\");\n return fileSchema.parse(JSON.parse(raw));\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n throw error;\n }\n}\n\n/**\n * The ONE namespace-resolution rule, shared by the DB/MCP runtime, every\n * file-based CLI command, and doctor. Precedence:\n * 1. CODEBUDDY_NAMESPACE env\n * 2. `.codebuddy/config.json` `namespace`\n * 3. the current folder name (sanitized) — matching what doctor reports as\n * the expected default.\n */\nexport function sanitizeNamespace(value: string): string {\n return value.replace(/[^a-zA-Z0-9_.:-]/g, \"-\").slice(0, 128);\n}\n\n/** Folder-name fallback used when neither env nor config specify a namespace. */\nexport function defaultNamespaceForCwd(cwd = process.cwd()): string {\n return sanitizeNamespace(basename(cwd)) || \"default\";\n}\n\nexport async function resolveNamespaceFrom(cwd = process.cwd()): Promise<string> {\n if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;\n const file = await readConfigFile(cwd);\n return file.namespace ?? defaultNamespaceForCwd(cwd);\n}\n\nexport const MISSING_DATABASE_URL =\n \"DATABASE_URL or .codebuddy/config.json postgresUrl is required.\";\n\n/**\n * `requireDatabase: false` (the MCP server) returns an empty `postgresUrl`\n * instead of throwing, so file-based tools still run without PostgreSQL;\n * `createRuntime` rejects the empty URL with the same message.\n */\nexport async function loadRuntimeConfig(\n cwd = process.cwd(),\n options: { requireDatabase?: boolean } = {},\n): Promise<CodeBuddyConfig> {\n const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;\n const file = await readConfigFile(projectRoot);\n const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? \"\";\n if (!postgresUrl && options.requireDatabase !== false) {\n throw new Error(MISSING_DATABASE_URL);\n }\n const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;\n return {\n postgresUrl,\n namespace:\n process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),\n projectRoot,\n provider: {\n type: file.provider?.type ?? \"local\",\n ...(hfToken ? { apiKey: hfToken } : {}),\n ...(file.provider?.model ? { model: file.provider.model } : {}),\n ...(file.provider?.relevanceFloor !== undefined\n ? { relevanceFloor: file.provider.relevanceFloor }\n : {}),\n },\n ...(file.tokenBudget ? { tokenBudget: file.tokenBudget } : {}),\n ...(file.graphify ? { graphify: file.graphify } : {}),\n };\n}\n\nexport async function checkConfigPermissions(cwd = process.cwd()): Promise<ConfigPermissionReport> {\n const path = configPath(cwd);\n try {\n const info = await stat(path);\n const modeNumber = info.mode & 0o777;\n const mode = modeNumber.toString(8).padStart(4, \"0\");\n const ok = modeNumber === 0o600;\n return {\n path,\n exists: true,\n mode,\n ok,\n ...(ok ? {} : { warning: \".codebuddy/config.json should be chmod 0600.\" }),\n };\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { path, exists: false, mode: null, ok: true };\n }\n throw error;\n }\n}\n\nexport async function checkProjectScaffold(cwd = process.cwd()): Promise<ProjectScaffoldReport> {\n const codebuddyDir = join(cwd, CONFIG_DIR);\n const memoryFactsDir = join(codebuddyDir, \"memory\", \"facts\");\n const memorySummariesDir = join(codebuddyDir, \"memory\", \"summaries\");\n const plansDir = join(codebuddyDir, \"plans\");\n const gitignorePath = join(codebuddyDir, \".gitignore\");\n const policyPath = join(codebuddyDir, POLICY_FILE);\n\n const [\n codebuddyDirExists,\n memoryFactsDirExists,\n memorySummariesDirExists,\n plansDirExists,\n gitignoreExists,\n policiesExist,\n ] = await Promise.all([\n pathExists(codebuddyDir),\n pathExists(memoryFactsDir),\n pathExists(memorySummariesDir),\n pathExists(plansDir),\n pathExists(gitignorePath),\n pathExists(policyPath),\n ]);\n\n return {\n codebuddyDir: {\n path: codebuddyDir,\n exists: codebuddyDirExists,\n ok: codebuddyDirExists,\n ...(codebuddyDirExists\n ? {}\n : { warning: \"Run `codebuddy use` or `codebuddy init` to create the project scaffold.\" }),\n },\n memoryFactsDir: {\n path: memoryFactsDir,\n exists: memoryFactsDirExists,\n ok: memoryFactsDirExists,\n ...(memoryFactsDirExists ? {} : { warning: \"Missing .codebuddy/memory/facts directory.\" }),\n },\n memorySummariesDir: {\n path: memorySummariesDir,\n exists: memorySummariesDirExists,\n ok: memorySummariesDirExists,\n ...(memorySummariesDirExists\n ? {}\n : { warning: \"Missing .codebuddy/memory/summaries directory.\" }),\n },\n plansDir: {\n path: plansDir,\n exists: plansDirExists,\n ok: plansDirExists,\n ...(plansDirExists ? {} : { warning: \"Missing .codebuddy/plans directory.\" }),\n },\n gitignore: {\n path: gitignorePath,\n exists: gitignoreExists,\n ok: gitignoreExists,\n ...(gitignoreExists ? {} : { warning: \"Missing .codebuddy/.gitignore.\" }),\n },\n policies: {\n path: policyPath,\n exists: policiesExist,\n ok: policiesExist,\n ...(policiesExist\n ? {}\n : {\n warning:\n \"Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it.\",\n }),\n },\n };\n}\n\n/** Read the plan policy, honoring CODEBUDDY_PLAN_POLICY then the config file. */\nexport async function loadPlanPolicy(cwd = process.cwd()): Promise<PlanPolicy> {\n const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;\n if (fromEnv && (PLAN_POLICIES as readonly string[]).includes(fromEnv)) {\n return fromEnv as PlanPolicy;\n }\n const file = await readConfigFile(cwd);\n return file.plan?.policy ?? \"suggest\";\n}\n\n/** Persist the plan policy into `.codebuddy/config.json`, creating it if needed. */\nexport async function setPlanPolicy(policy: PlanPolicy, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.plan = { ...(raw.plan as Record<string, unknown> | undefined), policy };\n // Atomic rewrite: this file holds the postgresUrl and provider apiKey.\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn optional zvec-grep retrieval on or off in `.codebuddy/config.json`. */\nexport async function setZvecEnabled(enabled: boolean, cwd = process.cwd()): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.zvec = { ...(raw.zvec as Record<string, unknown> | undefined), enabled };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/** Turn the BYOK LLM layer on or off; never writes a key (keys live in the environment). */\nexport async function setLlmEnabled(\n enabled: boolean,\n options: { provider?: \"groq\" | \"openrouter\"; model?: string } = {},\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw.llm = {\n ...(raw.llm as Record<string, unknown> | undefined),\n enabled,\n ...(options.provider ? { provider: options.provider } : {}),\n ...(options.model ? { model: options.model } : {}),\n };\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\n/**\n * Replace one top-level block of `.codebuddy/config.json` (`provider`,\n * `rerank`), keeping every other key and the private file mode. The result\n * must still parse, so a bad value never reaches disk.\n */\nexport async function setConfigBlock(\n key: \"provider\" | \"rerank\",\n value: Record<string, unknown>,\n cwd = process.cwd(),\n): Promise<void> {\n await initConfigFile(cwd);\n const path = configPath(cwd);\n const raw = JSON.parse(await readFile(path, \"utf8\")) as Record<string, unknown>;\n raw[key] = value;\n fileSchema.parse(raw);\n await writeAtomic(path, `${JSON.stringify(raw, null, 2)}\\n`, { mode: 0o600 });\n}\n\nexport function redactSecrets(value: unknown): unknown {\n if (typeof value === \"string\") {\n let redacted = value;\n // Provider keys (HF, BYOK LLM) literally anywhere in a string.\n for (const key of [\n process.env.HF_TOKEN,\n process.env.GROQ_API_KEY,\n process.env.OPENROUTER_API_KEY,\n process.env.CODEBUDDY_LLM_API_KEY,\n ]) {\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n // Credentials embedded in connection URLs (postgres://user:pass@host…).\n // The password — not the username — is the secret.\n redacted = redacted.replace(\n /(\\b[a-z][a-z0-9+.-]*:\\/\\/[^:/\\s@]+:)[^/\\s@]+(@)/gi,\n \"$1[REDACTED]$2\",\n );\n return redacted;\n }\n if (Array.isArray(value)) return value.map(redactSecrets);\n if (!value || typeof value !== \"object\") return value;\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).map(([key, nested]) => [\n key,\n /token|apiKey|authorization|secret/i.test(key) ? \"[REDACTED]\" : redactSecrets(nested),\n ]),\n );\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["join","join"]}
|
|
@@ -1,157 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
countTokens,
|
|
3
|
+
generateEntityId
|
|
4
|
+
} from "./chunk-4BYGTEJT.js";
|
|
1
5
|
import {
|
|
2
6
|
writeAtomic
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
|
|
5
|
-
// src/planner/budget.ts
|
|
6
|
-
import { Tiktoken } from "js-tiktoken/lite";
|
|
7
|
-
import cl100kBase from "js-tiktoken/ranks/cl100k_base";
|
|
8
|
-
|
|
9
|
-
// src/providers/models.ts
|
|
10
|
-
var EMBEDDING_DIMENSIONS = 384;
|
|
11
|
-
var DEFAULT_EMBEDDING_MODELS = [
|
|
12
|
-
{
|
|
13
|
-
id: "sentence-transformers/all-MiniLM-L6-v2",
|
|
14
|
-
role: "primary",
|
|
15
|
-
kind: "embedding",
|
|
16
|
-
dimensions: EMBEDDING_DIMENSIONS,
|
|
17
|
-
dailyCallBudget: 1e3,
|
|
18
|
-
supportsBatch: true
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
id: "BAAI/bge-small-en-v1.5",
|
|
22
|
-
role: "fallback",
|
|
23
|
-
kind: "embedding",
|
|
24
|
-
dimensions: EMBEDDING_DIMENSIONS,
|
|
25
|
-
dailyCallBudget: 1e3,
|
|
26
|
-
supportsBatch: true
|
|
27
|
-
}
|
|
28
|
-
];
|
|
29
|
-
var DEFAULT_LLM_MODELS = [
|
|
30
|
-
{
|
|
31
|
-
id: "meta-llama/Llama-3.2-3B-Instruct",
|
|
32
|
-
role: "primary",
|
|
33
|
-
kind: "llm",
|
|
34
|
-
contextWindow: 131072,
|
|
35
|
-
dailyCallBudget: 300
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "Qwen/Qwen2.5-7B-Instruct",
|
|
39
|
-
role: "fallback",
|
|
40
|
-
kind: "llm",
|
|
41
|
-
contextWindow: 32768,
|
|
42
|
-
dailyCallBudget: 300
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: "mistralai/Mistral-7B-Instruct-v0.3",
|
|
46
|
-
role: "last_resort",
|
|
47
|
-
kind: "llm",
|
|
48
|
-
contextWindow: 32768,
|
|
49
|
-
dailyCallBudget: 300
|
|
50
|
-
}
|
|
51
|
-
];
|
|
52
|
-
var MODEL_REGISTRY = [...DEFAULT_EMBEDDING_MODELS, ...DEFAULT_LLM_MODELS];
|
|
53
|
-
function getModelDefinition(modelId) {
|
|
54
|
-
return MODEL_REGISTRY.find((model) => model.id === modelId);
|
|
55
|
-
}
|
|
56
|
-
function getModelContextWindow(modelId) {
|
|
57
|
-
return getModelDefinition(modelId)?.contextWindow;
|
|
58
|
-
}
|
|
59
|
-
function assertCompatibleEmbeddingModels(models) {
|
|
60
|
-
const mismatchedModel = models.find((model) => model.dimensions !== EMBEDDING_DIMENSIONS);
|
|
61
|
-
if (mismatchedModel) {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Embedding model ${mismatchedModel.id} has ${mismatchedModel.dimensions ?? "unknown"} dimensions; expected ${EMBEDDING_DIMENSIONS}.`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// src/planner/budget.ts
|
|
69
|
-
var encoder = null;
|
|
70
|
-
function getEncoder() {
|
|
71
|
-
if (!encoder) {
|
|
72
|
-
encoder = new Tiktoken(cl100kBase);
|
|
73
|
-
}
|
|
74
|
-
return encoder;
|
|
75
|
-
}
|
|
76
|
-
var PIECE = new RegExp(cl100kBase.pat_str, "gu");
|
|
77
|
-
var SPECIAL_TOKENS = Object.keys(cl100kBase.special_tokens);
|
|
78
|
-
var MAX_CACHED_PIECES = 2e5;
|
|
79
|
-
var pieceTokens = /* @__PURE__ */ new Map();
|
|
80
|
-
function countTokens(text) {
|
|
81
|
-
if (!text) return 0;
|
|
82
|
-
try {
|
|
83
|
-
if (SPECIAL_TOKENS.some((token) => text.includes(token))) throw new Error("special token");
|
|
84
|
-
let total = 0;
|
|
85
|
-
for (const [piece] of text.matchAll(PIECE)) {
|
|
86
|
-
let count = pieceTokens.get(piece);
|
|
87
|
-
if (count === void 0) {
|
|
88
|
-
count = getEncoder().encode(piece).length;
|
|
89
|
-
if (pieceTokens.size >= MAX_CACHED_PIECES) pieceTokens.clear();
|
|
90
|
-
pieceTokens.set(piece, count);
|
|
91
|
-
}
|
|
92
|
-
total += count;
|
|
93
|
-
}
|
|
94
|
-
return total;
|
|
95
|
-
} catch {
|
|
96
|
-
return Math.max(1, Math.ceil(text.length / 4));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
function clampBudgetForCallerModel(requestedBudget, callerModel) {
|
|
100
|
-
if (!callerModel) {
|
|
101
|
-
return { budget: requestedBudget, clamped: false };
|
|
102
|
-
}
|
|
103
|
-
const ctx = getModelContextWindow(callerModel);
|
|
104
|
-
if (!ctx) {
|
|
105
|
-
return { budget: requestedBudget, clamped: false };
|
|
106
|
-
}
|
|
107
|
-
const headroom = Math.floor(ctx * 0.75);
|
|
108
|
-
if (requestedBudget > headroom) {
|
|
109
|
-
return {
|
|
110
|
-
budget: headroom,
|
|
111
|
-
clamped: true,
|
|
112
|
-
reason: `budget ${requestedBudget} exceeds 75% of ${callerModel} context window (${ctx}); clamped to ${headroom}.`
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
return { budget: requestedBudget, clamped: false };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// src/core/ids.ts
|
|
119
|
-
import { createHash, randomBytes } from "crypto";
|
|
120
|
-
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
121
|
-
function generateUlid(now = Date.now()) {
|
|
122
|
-
let timePart = "";
|
|
123
|
-
let ts = now;
|
|
124
|
-
for (let i = 0; i < 10; i++) {
|
|
125
|
-
timePart = (CROCKFORD[ts % 32] ?? "0") + timePart;
|
|
126
|
-
ts = Math.floor(ts / 32);
|
|
127
|
-
}
|
|
128
|
-
const bytes = randomBytes(10);
|
|
129
|
-
let randPart = "";
|
|
130
|
-
for (let i = 0; i < 16; i++) {
|
|
131
|
-
const bitOffset = i * 5;
|
|
132
|
-
const byteIndex = Math.floor(bitOffset / 8);
|
|
133
|
-
const bitInByte = bitOffset % 8;
|
|
134
|
-
const hi = bytes[byteIndex] ?? 0;
|
|
135
|
-
const lo = bytes[byteIndex + 1] ?? 0;
|
|
136
|
-
const combined = (hi << 8 | lo) >>> 0;
|
|
137
|
-
const shift = 16 - bitInByte - 5;
|
|
138
|
-
const value = combined >>> shift & 31;
|
|
139
|
-
randPart += CROCKFORD[value] ?? "0";
|
|
140
|
-
}
|
|
141
|
-
return timePart + randPart;
|
|
142
|
-
}
|
|
143
|
-
function generateSessionId(now) {
|
|
144
|
-
return `sess_${generateUlid(now).toLowerCase()}`;
|
|
145
|
-
}
|
|
146
|
-
function generateEntityId(prefix, now) {
|
|
147
|
-
return `${prefix}_${generateUlid(now).toLowerCase()}`;
|
|
148
|
-
}
|
|
149
|
-
function computeContentHash(namespace, sessionId, content) {
|
|
150
|
-
return createHash("sha256").update(`${namespace}${sessionId}${content}`).digest("hex");
|
|
151
|
-
}
|
|
152
|
-
function computeFactHash(namespace, content) {
|
|
153
|
-
return createHash("sha256").update(`${namespace}fact${content}`).digest("hex");
|
|
154
|
-
}
|
|
7
|
+
} from "./chunk-776EQXDV.js";
|
|
155
8
|
|
|
156
9
|
// src/savings/tokens.ts
|
|
157
10
|
function estimateTokens(value) {
|
|
@@ -162,7 +15,7 @@ function estimateTokens(value) {
|
|
|
162
15
|
}
|
|
163
16
|
|
|
164
17
|
// src/context/session-ledger.ts
|
|
165
|
-
import { createHash
|
|
18
|
+
import { createHash } from "crypto";
|
|
166
19
|
import { mkdir, readdir, readFile, rm, stat } from "fs/promises";
|
|
167
20
|
import { join, resolve } from "path";
|
|
168
21
|
var VERSION = 2;
|
|
@@ -406,7 +259,7 @@ function empty(now) {
|
|
|
406
259
|
return { version: VERSION, updatedAt: now.toISOString(), records: {} };
|
|
407
260
|
}
|
|
408
261
|
function digest(text) {
|
|
409
|
-
return
|
|
262
|
+
return createHash("sha256").update(text).digest("hex");
|
|
410
263
|
}
|
|
411
264
|
|
|
412
265
|
// src/context/tests.ts
|
|
@@ -458,19 +311,9 @@ async function detectTestCommand(repositoryRoot) {
|
|
|
458
311
|
}
|
|
459
312
|
|
|
460
313
|
export {
|
|
461
|
-
EMBEDDING_DIMENSIONS,
|
|
462
|
-
DEFAULT_EMBEDDING_MODELS,
|
|
463
|
-
DEFAULT_LLM_MODELS,
|
|
464
|
-
assertCompatibleEmbeddingModels,
|
|
465
|
-
countTokens,
|
|
466
|
-
clampBudgetForCallerModel,
|
|
467
|
-
generateSessionId,
|
|
468
|
-
generateEntityId,
|
|
469
|
-
computeContentHash,
|
|
470
|
-
computeFactHash,
|
|
471
314
|
estimateTokens,
|
|
472
315
|
SessionCapsuleLedger,
|
|
473
316
|
findCoveringTest,
|
|
474
317
|
detectTestCommand
|
|
475
318
|
};
|
|
476
|
-
//# sourceMappingURL=chunk-
|
|
319
|
+
//# sourceMappingURL=chunk-CQPCHMBS.js.map
|