@orquentra/codebuddy 0.0.0-stage → 2.4.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/ARCHITECTURE.md +269 -0
- package/CLAUDE_CODEX.md +433 -0
- package/LICENSE +21 -0
- package/README.md +55 -2
- package/WORKFLOW.md +152 -0
- package/dist/chunk-3ZELDCR3.js +204 -0
- package/dist/chunk-3ZELDCR3.js.map +1 -0
- package/dist/chunk-5OWZVZSO.js +476 -0
- package/dist/chunk-5OWZVZSO.js.map +1 -0
- package/dist/chunk-DSN5CEHO.js +175 -0
- package/dist/chunk-DSN5CEHO.js.map +1 -0
- package/dist/chunk-GDJH7XJC.js +119 -0
- package/dist/chunk-GDJH7XJC.js.map +1 -0
- package/dist/chunk-IBMJYIEP.js +1578 -0
- package/dist/chunk-IBMJYIEP.js.map +1 -0
- package/dist/chunk-IE2OUO54.js +199 -0
- package/dist/chunk-IE2OUO54.js.map +1 -0
- package/dist/chunk-IHQM2ZDV.js +19 -0
- package/dist/chunk-IHQM2ZDV.js.map +1 -0
- package/dist/chunk-ITGRANYW.js +63 -0
- package/dist/chunk-ITGRANYW.js.map +1 -0
- package/dist/chunk-K63EOA4B.js +416 -0
- package/dist/chunk-K63EOA4B.js.map +1 -0
- package/dist/chunk-OLFCOR4U.js +8580 -0
- package/dist/chunk-OLFCOR4U.js.map +1 -0
- package/dist/chunk-PQK3GKZH.js +98 -0
- package/dist/chunk-PQK3GKZH.js.map +1 -0
- package/dist/chunk-PYLH4MH5.js +34 -0
- package/dist/chunk-PYLH4MH5.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +10 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-TI5TQD6D.js +324 -0
- package/dist/chunk-TI5TQD6D.js.map +1 -0
- package/dist/chunk-WHZSVY2L.js +144 -0
- package/dist/chunk-WHZSVY2L.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +15 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/codebuddy-D2xJ6hFk.d.ts +841 -0
- package/dist/config-S2TULOMK.js +11 -0
- package/dist/config-S2TULOMK.js.map +1 -0
- package/dist/index.d.ts +2527 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/langgraph/index.d.ts +53 -0
- package/dist/langgraph/index.js +143 -0
- package/dist/langgraph/index.js.map +1 -0
- package/dist/lean-command-D6TZJEFX.js +13 -0
- package/dist/lean-command-D6TZJEFX.js.map +1 -0
- package/dist/main-BFDVQHKO.js +4575 -0
- package/dist/main-BFDVQHKO.js.map +1 -0
- package/dist/memory-NIBFAMY4.js +57 -0
- package/dist/memory-NIBFAMY4.js.map +1 -0
- package/dist/pretool-command-MQQ2UNQM.js +10 -0
- package/dist/pretool-command-MQQ2UNQM.js.map +1 -0
- package/dist/rerank-Q7MIFNKA.js +60 -0
- package/dist/rerank-Q7MIFNKA.js.map +1 -0
- package/dist/summaries-TSE44KUQ.js +18 -0
- package/dist/summaries-TSE44KUQ.js.map +1 -0
- package/dist/zvec-F27AR357.js +23 -0
- package/dist/zvec-F27AR357.js.map +1 -0
- package/docker-compose.yml +20 -0
- package/examples/claude-code/README.md +67 -0
- package/examples/claude-desktop/README.md +54 -0
- package/examples/claude-desktop/claude_desktop_config.json +13 -0
- package/examples/langgraph-agent/README.md +32 -0
- package/examples/langgraph-agent/index.ts +71 -0
- package/examples/multi-agent-handoff/README.md +35 -0
- package/examples/multi-agent-handoff/index.ts +68 -0
- package/package.json +108 -4
- package/src/db/migrations/0000_cultured_satana.sql +144 -0
- package/src/db/migrations/0001_embedding_lease.sql +3 -0
- package/src/db/migrations/0002_embedding_lease_token.sql +1 -0
- package/src/db/migrations/0003_embedding_space.sql +9 -0
- package/src/db/migrations/0004_markdown_reconcile.sql +5 -0
- package/src/db/migrations/0005_fact_propositions.sql +1 -0
- package/src/db/migrations/meta/0000_snapshot.json +1169 -0
- package/src/db/migrations/meta/0002_snapshot.json +1181 -0
- package/src/db/migrations/meta/0003_snapshot.json +1214 -0
- package/src/db/migrations/meta/0004_snapshot.json +1225 -0
- package/src/db/migrations/meta/0005_snapshot.json +1231 -0
- package/src/db/migrations/meta/_journal.json +48 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readConfigFile
|
|
3
|
+
} from "./chunk-K63EOA4B.js";
|
|
4
|
+
|
|
5
|
+
// src/llm/config.ts
|
|
6
|
+
var BASE_URL = {
|
|
7
|
+
groq: "https://api.groq.com/openai/v1",
|
|
8
|
+
openrouter: "https://openrouter.ai/api/v1"
|
|
9
|
+
};
|
|
10
|
+
var KEY_ENV = {
|
|
11
|
+
groq: "GROQ_API_KEY",
|
|
12
|
+
openrouter: "OPENROUTER_API_KEY"
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_MODEL = {
|
|
15
|
+
groq: "llama-3.1-8b-instant",
|
|
16
|
+
openrouter: "meta-llama/llama-3.3-70b-instruct:free"
|
|
17
|
+
};
|
|
18
|
+
var DEFAULTS = { timeoutMs: 700, dailyTokenBudget: 4e5 };
|
|
19
|
+
async function resolveLlmConfig(repositoryRoot, env = process.env) {
|
|
20
|
+
if (env.CODEBUDDY_LLM === "0") return { enabled: false, reason: "CODEBUDDY_LLM=0" };
|
|
21
|
+
const configured = (await readConfigFile(repositoryRoot)).llm;
|
|
22
|
+
const block = configured ?? (env.CODEBUDDY_LLM === "1" ? { enabled: true } : void 0);
|
|
23
|
+
if (!block?.enabled) return { enabled: false, reason: "llm not enabled" };
|
|
24
|
+
const provider = block.provider ?? "groq";
|
|
25
|
+
const apiKey = env.CODEBUDDY_LLM_API_KEY || env[KEY_ENV[provider]];
|
|
26
|
+
if (!apiKey) return { enabled: false, reason: `no API key in ${KEY_ENV[provider]}` };
|
|
27
|
+
const fallbackKey = block.fallback && env[KEY_ENV[block.fallback.provider]];
|
|
28
|
+
return {
|
|
29
|
+
enabled: true,
|
|
30
|
+
provider,
|
|
31
|
+
model: block.model ?? DEFAULT_MODEL[provider],
|
|
32
|
+
// Any OpenAI-compatible endpoint (a proxy, a test stub).
|
|
33
|
+
baseUrl: env.CODEBUDDY_LLM_BASE_URL?.replace(/\/+$/, "") || BASE_URL[provider],
|
|
34
|
+
apiKey,
|
|
35
|
+
timeoutMs: block.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
36
|
+
dailyTokenBudget: block.dailyTokenBudget ?? DEFAULTS.dailyTokenBudget,
|
|
37
|
+
features: {
|
|
38
|
+
summaries: block.features?.summaries ?? true,
|
|
39
|
+
rerank: block.features?.rerank ?? true,
|
|
40
|
+
memory: block.features?.memory ?? false
|
|
41
|
+
},
|
|
42
|
+
fallback: block.fallback && fallbackKey ? {
|
|
43
|
+
provider: block.fallback.provider,
|
|
44
|
+
model: block.fallback.model,
|
|
45
|
+
baseUrl: BASE_URL[block.fallback.provider],
|
|
46
|
+
apiKey: fallbackKey
|
|
47
|
+
} : null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function redactLlmKeys(text, env = process.env) {
|
|
51
|
+
let redacted = text;
|
|
52
|
+
for (const name of ["CODEBUDDY_LLM_API_KEY", ...Object.values(KEY_ENV)]) {
|
|
53
|
+
const key = env[name];
|
|
54
|
+
if (key) redacted = redacted.split(key).join("[REDACTED]");
|
|
55
|
+
}
|
|
56
|
+
return redacted;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
resolveLlmConfig,
|
|
61
|
+
redactLlmKeys
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=chunk-ITGRANYW.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/llm/config.ts"],"sourcesContent":["import { readConfigFile } from \"../core/config-file.js\";\n\n/**\n * Bring-your-own-key small-model layer. Off unless `.codebuddy/config.json`\n * has `llm.enabled` and the environment holds a key. Keys are read from the\n * environment only, never stored in config, and redacted from any error text.\n */\nexport type LlmProvider = \"groq\" | \"openrouter\";\nexport type LlmFeatures = { summaries: boolean; rerank: boolean; memory: boolean };\ntype Endpoint = { provider: LlmProvider; model: string; baseUrl: string; apiKey: string };\nexport type EnabledLlmConfig = Endpoint & {\n enabled: true;\n timeoutMs: number;\n dailyTokenBudget: number;\n features: LlmFeatures;\n /** Background work only; never on the prompt path. */\n fallback: Endpoint | null;\n};\nexport type LlmConfig = EnabledLlmConfig | { enabled: false; reason: string };\n\nconst BASE_URL: Record<LlmProvider, string> = {\n groq: \"https://api.groq.com/openai/v1\",\n openrouter: \"https://openrouter.ai/api/v1\",\n};\nconst KEY_ENV: Record<LlmProvider, string> = {\n groq: \"GROQ_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\nconst DEFAULT_MODEL: Record<LlmProvider, string> = {\n groq: \"llama-3.1-8b-instant\",\n openrouter: \"meta-llama/llama-3.3-70b-instruct:free\",\n};\nconst DEFAULTS = { timeoutMs: 700, dailyTokenBudget: 400_000 } as const;\n\nexport async function resolveLlmConfig(\n repositoryRoot: string,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<LlmConfig> {\n if (env.CODEBUDDY_LLM === \"0\") return { enabled: false, reason: \"CODEBUDDY_LLM=0\" };\n // CODEBUDDY_LLM=1 turns it on with defaults where no config says otherwise\n // (eval copies, one-off runs); an explicit `enabled: false` still wins.\n const configured = (await readConfigFile(repositoryRoot)).llm;\n const block = configured ?? (env.CODEBUDDY_LLM === \"1\" ? { enabled: true } : undefined);\n if (!block?.enabled) return { enabled: false, reason: \"llm not enabled\" };\n const provider = block.provider ?? \"groq\";\n const apiKey = env.CODEBUDDY_LLM_API_KEY || env[KEY_ENV[provider]];\n if (!apiKey) return { enabled: false, reason: `no API key in ${KEY_ENV[provider]}` };\n const fallbackKey = block.fallback && env[KEY_ENV[block.fallback.provider]];\n return {\n enabled: true,\n provider,\n model: block.model ?? DEFAULT_MODEL[provider],\n // Any OpenAI-compatible endpoint (a proxy, a test stub).\n baseUrl: env.CODEBUDDY_LLM_BASE_URL?.replace(/\\/+$/, \"\") || BASE_URL[provider],\n apiKey,\n timeoutMs: block.timeoutMs ?? DEFAULTS.timeoutMs,\n dailyTokenBudget: block.dailyTokenBudget ?? DEFAULTS.dailyTokenBudget,\n features: {\n summaries: block.features?.summaries ?? true,\n rerank: block.features?.rerank ?? true,\n memory: block.features?.memory ?? false,\n },\n fallback:\n block.fallback && fallbackKey\n ? {\n provider: block.fallback.provider,\n model: block.fallback.model,\n baseUrl: BASE_URL[block.fallback.provider],\n apiKey: fallbackKey,\n }\n : null,\n };\n}\n\n/** Replace every LLM key present in the environment with `[REDACTED]`. */\nexport function redactLlmKeys(text: string, env: NodeJS.ProcessEnv = process.env): string {\n let redacted = text;\n for (const name of [\"CODEBUDDY_LLM_API_KEY\", ...Object.values(KEY_ENV)]) {\n const key = env[name];\n if (key) redacted = redacted.split(key).join(\"[REDACTED]\");\n }\n return redacted;\n}\n"],"mappings":";;;;;AAoBA,IAAM,WAAwC;AAAA,EAC5C,MAAM;AAAA,EACN,YAAY;AACd;AACA,IAAM,UAAuC;AAAA,EAC3C,MAAM;AAAA,EACN,YAAY;AACd;AACA,IAAM,gBAA6C;AAAA,EACjD,MAAM;AAAA,EACN,YAAY;AACd;AACA,IAAM,WAAW,EAAE,WAAW,KAAK,kBAAkB,IAAQ;AAE7D,eAAsB,iBACpB,gBACA,MAAyB,QAAQ,KACb;AACpB,MAAI,IAAI,kBAAkB,IAAK,QAAO,EAAE,SAAS,OAAO,QAAQ,kBAAkB;AAGlF,QAAM,cAAc,MAAM,eAAe,cAAc,GAAG;AAC1D,QAAM,QAAQ,eAAe,IAAI,kBAAkB,MAAM,EAAE,SAAS,KAAK,IAAI;AAC7E,MAAI,CAAC,OAAO,QAAS,QAAO,EAAE,SAAS,OAAO,QAAQ,kBAAkB;AACxE,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,SAAS,IAAI,yBAAyB,IAAI,QAAQ,QAAQ,CAAC;AACjE,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,iBAAiB,QAAQ,QAAQ,CAAC,GAAG;AACnF,QAAM,cAAc,MAAM,YAAY,IAAI,QAAQ,MAAM,SAAS,QAAQ,CAAC;AAC1E,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,OAAO,MAAM,SAAS,cAAc,QAAQ;AAAA;AAAA,IAE5C,SAAS,IAAI,wBAAwB,QAAQ,QAAQ,EAAE,KAAK,SAAS,QAAQ;AAAA,IAC7E;AAAA,IACA,WAAW,MAAM,aAAa,SAAS;AAAA,IACvC,kBAAkB,MAAM,oBAAoB,SAAS;AAAA,IACrD,UAAU;AAAA,MACR,WAAW,MAAM,UAAU,aAAa;AAAA,MACxC,QAAQ,MAAM,UAAU,UAAU;AAAA,MAClC,QAAQ,MAAM,UAAU,UAAU;AAAA,IACpC;AAAA,IACA,UACE,MAAM,YAAY,cACd;AAAA,MACE,UAAU,MAAM,SAAS;AAAA,MACzB,OAAO,MAAM,SAAS;AAAA,MACtB,SAAS,SAAS,MAAM,SAAS,QAAQ;AAAA,MACzC,QAAQ;AAAA,IACV,IACA;AAAA,EACR;AACF;AAGO,SAAS,cAAc,MAAc,MAAyB,QAAQ,KAAa;AACxF,MAAI,WAAW;AACf,aAAW,QAAQ,CAAC,yBAAyB,GAAG,OAAO,OAAO,OAAO,CAAC,GAAG;AACvE,UAAM,MAAM,IAAI,IAAI;AACpB,QAAI,IAAK,YAAW,SAAS,MAAM,GAAG,EAAE,KAAK,YAAY;AAAA,EAC3D;AACA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// src/core/markdown-store-fs.ts
|
|
2
|
+
import { lstat, open, rename } from "fs/promises";
|
|
3
|
+
import { join, relative, resolve, sep } from "path";
|
|
4
|
+
async function assertNoSymlinkBetween(root, target) {
|
|
5
|
+
const absoluteRoot = resolve(root);
|
|
6
|
+
const absoluteTarget = resolve(target);
|
|
7
|
+
const relativeTarget = relative(absoluteRoot, absoluteTarget);
|
|
8
|
+
if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`)) {
|
|
9
|
+
throw new Error(`Path escapes repository root: ${target}`);
|
|
10
|
+
}
|
|
11
|
+
let current = absoluteRoot;
|
|
12
|
+
const parts = relativeTarget.split(sep).filter(Boolean);
|
|
13
|
+
for (const part of parts) {
|
|
14
|
+
current = join(current, part);
|
|
15
|
+
try {
|
|
16
|
+
const info = await lstat(current);
|
|
17
|
+
if (info.isSymbolicLink()) {
|
|
18
|
+
throw new Error(`Refusing to use symlinked path: ${current}`);
|
|
19
|
+
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === "ENOENT") return;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function writeAtomic(path, document, options) {
|
|
27
|
+
const temporaryPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
28
|
+
const handle = await open(temporaryPath, "wx", options?.mode ?? 384);
|
|
29
|
+
try {
|
|
30
|
+
await handle.writeFile(document, "utf8");
|
|
31
|
+
await handle.sync();
|
|
32
|
+
} finally {
|
|
33
|
+
await handle.close();
|
|
34
|
+
}
|
|
35
|
+
await rename(temporaryPath, path);
|
|
36
|
+
}
|
|
37
|
+
function parseFrontMatter(document) {
|
|
38
|
+
if (!document.startsWith("---\n")) {
|
|
39
|
+
throw new Error("Markdown file must start with YAML front matter.");
|
|
40
|
+
}
|
|
41
|
+
const closingIndex = document.indexOf("\n---\n", 4);
|
|
42
|
+
if (closingIndex === -1) {
|
|
43
|
+
throw new Error("Markdown file has unterminated YAML front matter.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
frontMatter: document.slice(4, closingIndex),
|
|
47
|
+
content: document.slice(closingIndex + 5)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function composeMarkdown(frontMatterYaml, body) {
|
|
51
|
+
const trimmedFrontMatter = frontMatterYaml.trimEnd();
|
|
52
|
+
const content = body.endsWith("\n") ? body : `${body}
|
|
53
|
+
`;
|
|
54
|
+
return `---
|
|
55
|
+
${trimmedFrontMatter}
|
|
56
|
+
---
|
|
57
|
+
${content}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/core/config-file.ts
|
|
61
|
+
import { constants } from "fs";
|
|
62
|
+
import { access, chmod, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
63
|
+
import { basename, join as join2 } from "path";
|
|
64
|
+
import { z } from "zod";
|
|
65
|
+
var CONFIG_DIR = ".codebuddy";
|
|
66
|
+
var CONFIG_FILE = "config.json";
|
|
67
|
+
var POLICY_FILE = "policies.yaml";
|
|
68
|
+
var PROJECT_GITIGNORE = [
|
|
69
|
+
"# CodeBuddy local-only files",
|
|
70
|
+
"/config.json",
|
|
71
|
+
"/cache/",
|
|
72
|
+
"/memory/review/",
|
|
73
|
+
"/memory/review-archive/",
|
|
74
|
+
"/plans/.locks/",
|
|
75
|
+
"/memory/.locks/",
|
|
76
|
+
"",
|
|
77
|
+
"# Keep these reviewable project artifacts shareable by default:",
|
|
78
|
+
"# /memory/facts/*.md",
|
|
79
|
+
"# /memory/summaries/*.md",
|
|
80
|
+
"# /plans/*.md",
|
|
81
|
+
"# If your project memory is private, ignore /memory/ in the repo root .gitignore.",
|
|
82
|
+
""
|
|
83
|
+
].join("\n");
|
|
84
|
+
var DEFAULT_POLICY_FILE = [
|
|
85
|
+
"rules:",
|
|
86
|
+
" - id: auth-sensitive",
|
|
87
|
+
' pattern: "src/auth/**"',
|
|
88
|
+
' message: "Auth changes need careful review."',
|
|
89
|
+
" weight: 70",
|
|
90
|
+
""
|
|
91
|
+
].join("\n");
|
|
92
|
+
var PLAN_POLICIES = ["off", "suggest", "required"];
|
|
93
|
+
var fileSchema = z.object({
|
|
94
|
+
postgresUrl: z.string().min(1).optional(),
|
|
95
|
+
namespace: z.string().min(1).optional(),
|
|
96
|
+
tokenBudget: z.number().int().positive().optional(),
|
|
97
|
+
provider: z.object({
|
|
98
|
+
type: z.literal("huggingface").default("huggingface"),
|
|
99
|
+
apiKey: z.string().min(1).optional()
|
|
100
|
+
}).optional(),
|
|
101
|
+
plan: z.object({
|
|
102
|
+
policy: z.enum(PLAN_POLICIES).default("suggest")
|
|
103
|
+
}).optional(),
|
|
104
|
+
graphify: z.object({
|
|
105
|
+
enabled: z.boolean(),
|
|
106
|
+
graphPath: z.string().min(1).optional()
|
|
107
|
+
}).optional(),
|
|
108
|
+
/**
|
|
109
|
+
* Optional hosted small-model layer (bring your own key). Keys are read from
|
|
110
|
+
* the environment only, never from this file (`src/llm/config.ts`).
|
|
111
|
+
*/
|
|
112
|
+
llm: z.object({
|
|
113
|
+
enabled: z.boolean(),
|
|
114
|
+
provider: z.enum(["groq", "openrouter"]).optional(),
|
|
115
|
+
model: z.string().min(1).optional(),
|
|
116
|
+
fallback: z.object({ provider: z.enum(["groq", "openrouter"]), model: z.string().min(1) }).optional(),
|
|
117
|
+
timeoutMs: z.number().int().min(100).max(5e3).optional(),
|
|
118
|
+
dailyTokenBudget: z.number().int().positive().optional(),
|
|
119
|
+
features: z.object({
|
|
120
|
+
summaries: z.boolean().optional(),
|
|
121
|
+
rerank: z.boolean().optional(),
|
|
122
|
+
memory: z.boolean().optional()
|
|
123
|
+
}).optional()
|
|
124
|
+
}).optional(),
|
|
125
|
+
/** Optional local semantic retrieval via zvec-grep (`zg`). Off unless enabled. */
|
|
126
|
+
zvec: z.object({
|
|
127
|
+
enabled: z.boolean(),
|
|
128
|
+
binary: z.string().min(1).optional(),
|
|
129
|
+
timeoutMs: z.number().int().positive().max(1e4).optional()
|
|
130
|
+
}).optional()
|
|
131
|
+
});
|
|
132
|
+
function configPath(cwd = process.cwd()) {
|
|
133
|
+
return join2(cwd, CONFIG_DIR, CONFIG_FILE);
|
|
134
|
+
}
|
|
135
|
+
async function initProjectScaffold(cwd = process.cwd()) {
|
|
136
|
+
const codebuddyDir = join2(cwd, CONFIG_DIR);
|
|
137
|
+
await mkdir(join2(codebuddyDir, "memory", "facts"), { recursive: true, mode: 448 });
|
|
138
|
+
await mkdir(join2(codebuddyDir, "memory", "summaries"), { recursive: true, mode: 448 });
|
|
139
|
+
await mkdir(join2(codebuddyDir, "plans"), { recursive: true, mode: 448 });
|
|
140
|
+
const gitignorePath = join2(codebuddyDir, ".gitignore");
|
|
141
|
+
try {
|
|
142
|
+
await access(gitignorePath, constants.F_OK);
|
|
143
|
+
let gitignore = await readFile(gitignorePath, "utf8");
|
|
144
|
+
const lines = new Set(gitignore.split(/\r?\n/));
|
|
145
|
+
const missing = ["/memory/review/", "/memory/review-archive/"].filter(
|
|
146
|
+
(line) => !lines.has(line)
|
|
147
|
+
);
|
|
148
|
+
if (missing.length > 0) {
|
|
149
|
+
gitignore = `${gitignore.trimEnd()}
|
|
150
|
+
${missing.join("\n")}
|
|
151
|
+
`;
|
|
152
|
+
await writeFile(gitignorePath, gitignore, { mode: 420 });
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
await writeFile(gitignorePath, PROJECT_GITIGNORE, { mode: 420 });
|
|
156
|
+
}
|
|
157
|
+
const policyPath = join2(codebuddyDir, POLICY_FILE);
|
|
158
|
+
try {
|
|
159
|
+
await access(policyPath, constants.F_OK);
|
|
160
|
+
} catch {
|
|
161
|
+
await writeFile(policyPath, DEFAULT_POLICY_FILE, { mode: 420 });
|
|
162
|
+
}
|
|
163
|
+
return { codebuddyDir, gitignorePath, policyPath };
|
|
164
|
+
}
|
|
165
|
+
async function initConfigFile(cwd = process.cwd()) {
|
|
166
|
+
const path = configPath(cwd);
|
|
167
|
+
await initProjectScaffold(cwd);
|
|
168
|
+
try {
|
|
169
|
+
await access(path, constants.F_OK);
|
|
170
|
+
await chmod(path, 384);
|
|
171
|
+
return { path, created: false };
|
|
172
|
+
} catch {
|
|
173
|
+
const template = {
|
|
174
|
+
postgresUrl: process.env.DATABASE_URL ?? "postgres://localhost:5432/codebuddy",
|
|
175
|
+
// Default to the folder name (sanitized) so a fresh scaffold agrees with
|
|
176
|
+
// doctor's expected default instead of warning immediately after init.
|
|
177
|
+
namespace: defaultNamespaceForCwd(cwd),
|
|
178
|
+
provider: { type: "huggingface" },
|
|
179
|
+
tokenBudget: 4e3
|
|
180
|
+
};
|
|
181
|
+
await writeFile(path, `${JSON.stringify(template, null, 2)}
|
|
182
|
+
`, { mode: 384 });
|
|
183
|
+
await chmod(path, 384);
|
|
184
|
+
return { path, created: true };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function writeConfigFile(config, cwd = process.cwd()) {
|
|
188
|
+
const path = configPath(cwd);
|
|
189
|
+
await initProjectScaffold(cwd);
|
|
190
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}
|
|
191
|
+
`, { mode: 384 });
|
|
192
|
+
await chmod(path, 384);
|
|
193
|
+
return { path };
|
|
194
|
+
}
|
|
195
|
+
async function readConfigFile(cwd = process.cwd()) {
|
|
196
|
+
const path = configPath(cwd);
|
|
197
|
+
try {
|
|
198
|
+
const raw = await readFile(path, "utf8");
|
|
199
|
+
return fileSchema.parse(JSON.parse(raw));
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error.code === "ENOENT") return {};
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function sanitizeNamespace(value) {
|
|
206
|
+
return value.replace(/[^a-zA-Z0-9_.:-]/g, "-").slice(0, 128);
|
|
207
|
+
}
|
|
208
|
+
function defaultNamespaceForCwd(cwd = process.cwd()) {
|
|
209
|
+
return sanitizeNamespace(basename(cwd)) || "default";
|
|
210
|
+
}
|
|
211
|
+
async function resolveNamespaceFrom(cwd = process.cwd()) {
|
|
212
|
+
if (process.env.CODEBUDDY_NAMESPACE) return process.env.CODEBUDDY_NAMESPACE;
|
|
213
|
+
const file = await readConfigFile(cwd);
|
|
214
|
+
return file.namespace ?? defaultNamespaceForCwd(cwd);
|
|
215
|
+
}
|
|
216
|
+
var MISSING_DATABASE_URL = "DATABASE_URL or .codebuddy/config.json postgresUrl is required.";
|
|
217
|
+
async function loadRuntimeConfig(cwd = process.cwd(), options = {}) {
|
|
218
|
+
const projectRoot = process.env.CODEBUDDY_PROJECT_ROOT ?? cwd;
|
|
219
|
+
const file = await readConfigFile(projectRoot);
|
|
220
|
+
const postgresUrl = process.env.DATABASE_URL ?? file.postgresUrl ?? "";
|
|
221
|
+
if (!postgresUrl && options.requireDatabase !== false) {
|
|
222
|
+
throw new Error(MISSING_DATABASE_URL);
|
|
223
|
+
}
|
|
224
|
+
const hfToken = process.env.HF_TOKEN ?? file.provider?.apiKey;
|
|
225
|
+
return {
|
|
226
|
+
postgresUrl,
|
|
227
|
+
namespace: process.env.CODEBUDDY_NAMESPACE ?? file.namespace ?? defaultNamespaceForCwd(projectRoot),
|
|
228
|
+
projectRoot,
|
|
229
|
+
provider: {
|
|
230
|
+
type: "huggingface",
|
|
231
|
+
...hfToken ? { apiKey: hfToken } : {}
|
|
232
|
+
},
|
|
233
|
+
...file.tokenBudget ? { tokenBudget: file.tokenBudget } : {},
|
|
234
|
+
...file.graphify ? { graphify: file.graphify } : {}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function checkConfigPermissions(cwd = process.cwd()) {
|
|
238
|
+
const path = configPath(cwd);
|
|
239
|
+
try {
|
|
240
|
+
const info = await stat(path);
|
|
241
|
+
const modeNumber = info.mode & 511;
|
|
242
|
+
const mode = modeNumber.toString(8).padStart(4, "0");
|
|
243
|
+
const ok = modeNumber === 384;
|
|
244
|
+
return {
|
|
245
|
+
path,
|
|
246
|
+
exists: true,
|
|
247
|
+
mode,
|
|
248
|
+
ok,
|
|
249
|
+
...ok ? {} : { warning: ".codebuddy/config.json should be chmod 0600." }
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
if (error.code === "ENOENT") {
|
|
253
|
+
return { path, exists: false, mode: null, ok: true };
|
|
254
|
+
}
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function checkProjectScaffold(cwd = process.cwd()) {
|
|
259
|
+
const codebuddyDir = join2(cwd, CONFIG_DIR);
|
|
260
|
+
const memoryFactsDir = join2(codebuddyDir, "memory", "facts");
|
|
261
|
+
const memorySummariesDir = join2(codebuddyDir, "memory", "summaries");
|
|
262
|
+
const plansDir = join2(codebuddyDir, "plans");
|
|
263
|
+
const gitignorePath = join2(codebuddyDir, ".gitignore");
|
|
264
|
+
const policyPath = join2(codebuddyDir, POLICY_FILE);
|
|
265
|
+
const [
|
|
266
|
+
codebuddyDirExists,
|
|
267
|
+
memoryFactsDirExists,
|
|
268
|
+
memorySummariesDirExists,
|
|
269
|
+
plansDirExists,
|
|
270
|
+
gitignoreExists,
|
|
271
|
+
policiesExist
|
|
272
|
+
] = await Promise.all([
|
|
273
|
+
pathExists(codebuddyDir),
|
|
274
|
+
pathExists(memoryFactsDir),
|
|
275
|
+
pathExists(memorySummariesDir),
|
|
276
|
+
pathExists(plansDir),
|
|
277
|
+
pathExists(gitignorePath),
|
|
278
|
+
pathExists(policyPath)
|
|
279
|
+
]);
|
|
280
|
+
return {
|
|
281
|
+
codebuddyDir: {
|
|
282
|
+
path: codebuddyDir,
|
|
283
|
+
exists: codebuddyDirExists,
|
|
284
|
+
ok: codebuddyDirExists,
|
|
285
|
+
...codebuddyDirExists ? {} : { warning: "Run `codebuddy use` or `codebuddy init` to create the project scaffold." }
|
|
286
|
+
},
|
|
287
|
+
memoryFactsDir: {
|
|
288
|
+
path: memoryFactsDir,
|
|
289
|
+
exists: memoryFactsDirExists,
|
|
290
|
+
ok: memoryFactsDirExists,
|
|
291
|
+
...memoryFactsDirExists ? {} : { warning: "Missing .codebuddy/memory/facts directory." }
|
|
292
|
+
},
|
|
293
|
+
memorySummariesDir: {
|
|
294
|
+
path: memorySummariesDir,
|
|
295
|
+
exists: memorySummariesDirExists,
|
|
296
|
+
ok: memorySummariesDirExists,
|
|
297
|
+
...memorySummariesDirExists ? {} : { warning: "Missing .codebuddy/memory/summaries directory." }
|
|
298
|
+
},
|
|
299
|
+
plansDir: {
|
|
300
|
+
path: plansDir,
|
|
301
|
+
exists: plansDirExists,
|
|
302
|
+
ok: plansDirExists,
|
|
303
|
+
...plansDirExists ? {} : { warning: "Missing .codebuddy/plans directory." }
|
|
304
|
+
},
|
|
305
|
+
gitignore: {
|
|
306
|
+
path: gitignorePath,
|
|
307
|
+
exists: gitignoreExists,
|
|
308
|
+
ok: gitignoreExists,
|
|
309
|
+
...gitignoreExists ? {} : { warning: "Missing .codebuddy/.gitignore." }
|
|
310
|
+
},
|
|
311
|
+
policies: {
|
|
312
|
+
path: policyPath,
|
|
313
|
+
exists: policiesExist,
|
|
314
|
+
ok: policiesExist,
|
|
315
|
+
...policiesExist ? {} : {
|
|
316
|
+
warning: "Missing .codebuddy/policies.yaml. Run `codebuddy use` or `codebuddy init` to scaffold it."
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
async function loadPlanPolicy(cwd = process.cwd()) {
|
|
322
|
+
const fromEnv = process.env.CODEBUDDY_PLAN_POLICY;
|
|
323
|
+
if (fromEnv && PLAN_POLICIES.includes(fromEnv)) {
|
|
324
|
+
return fromEnv;
|
|
325
|
+
}
|
|
326
|
+
const file = await readConfigFile(cwd);
|
|
327
|
+
return file.plan?.policy ?? "suggest";
|
|
328
|
+
}
|
|
329
|
+
async function setPlanPolicy(policy, cwd = process.cwd()) {
|
|
330
|
+
await initConfigFile(cwd);
|
|
331
|
+
const path = configPath(cwd);
|
|
332
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
333
|
+
raw.plan = { ...raw.plan, policy };
|
|
334
|
+
await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
335
|
+
`, { mode: 384 });
|
|
336
|
+
}
|
|
337
|
+
async function setZvecEnabled(enabled, cwd = process.cwd()) {
|
|
338
|
+
await initConfigFile(cwd);
|
|
339
|
+
const path = configPath(cwd);
|
|
340
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
341
|
+
raw.zvec = { ...raw.zvec, enabled };
|
|
342
|
+
await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
343
|
+
`, { mode: 384 });
|
|
344
|
+
}
|
|
345
|
+
async function setLlmEnabled(enabled, options = {}, cwd = process.cwd()) {
|
|
346
|
+
await initConfigFile(cwd);
|
|
347
|
+
const path = configPath(cwd);
|
|
348
|
+
const raw = JSON.parse(await readFile(path, "utf8"));
|
|
349
|
+
raw.llm = {
|
|
350
|
+
...raw.llm,
|
|
351
|
+
enabled,
|
|
352
|
+
...options.provider ? { provider: options.provider } : {},
|
|
353
|
+
...options.model ? { model: options.model } : {}
|
|
354
|
+
};
|
|
355
|
+
await writeAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
356
|
+
`, { mode: 384 });
|
|
357
|
+
}
|
|
358
|
+
function redactSecrets(value) {
|
|
359
|
+
if (typeof value === "string") {
|
|
360
|
+
let redacted = value;
|
|
361
|
+
for (const key of [
|
|
362
|
+
process.env.HF_TOKEN,
|
|
363
|
+
process.env.GROQ_API_KEY,
|
|
364
|
+
process.env.OPENROUTER_API_KEY,
|
|
365
|
+
process.env.CODEBUDDY_LLM_API_KEY
|
|
366
|
+
]) {
|
|
367
|
+
if (key) redacted = redacted.split(key).join("[REDACTED]");
|
|
368
|
+
}
|
|
369
|
+
redacted = redacted.replace(
|
|
370
|
+
/(\b[a-z][a-z0-9+.-]*:\/\/[^:/\s@]+:)[^/\s@]+(@)/gi,
|
|
371
|
+
"$1[REDACTED]$2"
|
|
372
|
+
);
|
|
373
|
+
return redacted;
|
|
374
|
+
}
|
|
375
|
+
if (Array.isArray(value)) return value.map(redactSecrets);
|
|
376
|
+
if (!value || typeof value !== "object") return value;
|
|
377
|
+
return Object.fromEntries(
|
|
378
|
+
Object.entries(value).map(([key, nested]) => [
|
|
379
|
+
key,
|
|
380
|
+
/token|apiKey|authorization|secret/i.test(key) ? "[REDACTED]" : redactSecrets(nested)
|
|
381
|
+
])
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
async function pathExists(path) {
|
|
385
|
+
try {
|
|
386
|
+
await access(path, constants.F_OK);
|
|
387
|
+
return true;
|
|
388
|
+
} catch {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export {
|
|
394
|
+
assertNoSymlinkBetween,
|
|
395
|
+
writeAtomic,
|
|
396
|
+
parseFrontMatter,
|
|
397
|
+
composeMarkdown,
|
|
398
|
+
PLAN_POLICIES,
|
|
399
|
+
configPath,
|
|
400
|
+
initProjectScaffold,
|
|
401
|
+
initConfigFile,
|
|
402
|
+
writeConfigFile,
|
|
403
|
+
readConfigFile,
|
|
404
|
+
defaultNamespaceForCwd,
|
|
405
|
+
resolveNamespaceFrom,
|
|
406
|
+
MISSING_DATABASE_URL,
|
|
407
|
+
loadRuntimeConfig,
|
|
408
|
+
checkConfigPermissions,
|
|
409
|
+
checkProjectScaffold,
|
|
410
|
+
loadPlanPolicy,
|
|
411
|
+
setPlanPolicy,
|
|
412
|
+
setZvecEnabled,
|
|
413
|
+
setLlmEnabled,
|
|
414
|
+
redactSecrets
|
|
415
|
+
};
|
|
416
|
+
//# sourceMappingURL=chunk-K63EOA4B.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.literal(\"huggingface\").default(\"huggingface\"),\n apiKey: 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: \"huggingface\" },\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: \"huggingface\",\n ...(hfToken ? { apiKey: hfToken } : {}),\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\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"]}
|