@orquentra/codebuddy 2.5.0 → 2.6.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.
Files changed (33) hide show
  1. package/README.md +8 -0
  2. package/dist/{chunk-KYK6RPAJ.js → chunk-JFDRBPYF.js} +2 -2
  3. package/dist/{chunk-JMWJ7V6Y.js → chunk-JPHJBCZB.js} +2 -2
  4. package/dist/{chunk-JMWJ7V6Y.js.map → chunk-JPHJBCZB.js.map} +1 -1
  5. package/dist/{chunk-ROQTT5ZO.js → chunk-N34XHWBA.js} +2 -2
  6. package/dist/{chunk-CQPCHMBS.js → chunk-SYWJG5NA.js} +2 -2
  7. package/dist/{chunk-3XYQGFPB.js → chunk-U4HAJLIB.js} +2 -2
  8. package/dist/{chunk-JDZ3VK26.js → chunk-UN57FKQX.js} +32 -5
  9. package/dist/chunk-UN57FKQX.js.map +1 -0
  10. package/dist/{chunk-Z5QB367W.js → chunk-VAKBJV26.js} +106 -22
  11. package/dist/chunk-VAKBJV26.js.map +1 -0
  12. package/dist/{chunk-4BYGTEJT.js → chunk-XBJQKCLU.js} +2 -2
  13. package/dist/cli/index.js +2 -2
  14. package/dist/{cloud-GP6G3EAK.js → cloud-7WM3YLI4.js} +3 -3
  15. package/dist/index.js +7 -7
  16. package/dist/{lean-command-DA4ZNKG3.js → lean-command-C5YPFKHP.js} +5 -5
  17. package/dist/{local-3NGNESNB.js → local-OTOY56B4.js} +3 -3
  18. package/dist/{main-BDVQAGAS.js → main-HFHLGID4.js} +201 -55
  19. package/dist/main-HFHLGID4.js.map +1 -0
  20. package/dist/{operations-EQGOFCBD.js → operations-LOBAQRLO.js} +6 -6
  21. package/package.json +1 -1
  22. package/dist/chunk-JDZ3VK26.js.map +0 -1
  23. package/dist/chunk-Z5QB367W.js.map +0 -1
  24. package/dist/main-BDVQAGAS.js.map +0 -1
  25. /package/dist/{chunk-KYK6RPAJ.js.map → chunk-JFDRBPYF.js.map} +0 -0
  26. /package/dist/{chunk-ROQTT5ZO.js.map → chunk-N34XHWBA.js.map} +0 -0
  27. /package/dist/{chunk-CQPCHMBS.js.map → chunk-SYWJG5NA.js.map} +0 -0
  28. /package/dist/{chunk-3XYQGFPB.js.map → chunk-U4HAJLIB.js.map} +0 -0
  29. /package/dist/{chunk-4BYGTEJT.js.map → chunk-XBJQKCLU.js.map} +0 -0
  30. /package/dist/{cloud-GP6G3EAK.js.map → cloud-7WM3YLI4.js.map} +0 -0
  31. /package/dist/{lean-command-DA4ZNKG3.js.map → lean-command-C5YPFKHP.js.map} +0 -0
  32. /package/dist/{local-3NGNESNB.js.map → local-OTOY56B4.js.map} +0 -0
  33. /package/dist/{operations-EQGOFCBD.js.map → operations-LOBAQRLO.js.map} +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  detectTestCommand,
8
8
  estimateTokens,
9
9
  findCoveringTest
10
- } from "./chunk-CQPCHMBS.js";
10
+ } from "./chunk-SYWJG5NA.js";
11
11
  import {
12
12
  TASK_PATH_LIMIT,
13
13
  buildArchitectureMap,
@@ -36,8 +36,9 @@ import {
36
36
  createRuntime,
37
37
  isEligibleMemory,
38
38
  lexicalTerms,
39
- listFactsPage
40
- } from "./chunk-JDZ3VK26.js";
39
+ listFactsPage,
40
+ termOverlap
41
+ } from "./chunk-UN57FKQX.js";
41
42
  import {
42
43
  claimText,
43
44
  redactPersisted,
@@ -47,16 +48,17 @@ import {
47
48
  } from "./chunk-GDJH7XJC.js";
48
49
  import {
49
50
  generateEntityId
50
- } from "./chunk-4BYGTEJT.js";
51
+ } from "./chunk-XBJQKCLU.js";
51
52
  import {
52
53
  DEFAULT_RERANK_MODELS
53
- } from "./chunk-JMWJ7V6Y.js";
54
+ } from "./chunk-JPHJBCZB.js";
54
55
  import {
55
56
  refreshZvecIndex
56
57
  } from "./chunk-QDBDYCM2.js";
57
58
  import {
58
59
  assertNoSymlinkBetween,
59
60
  composeMarkdown,
61
+ configPath,
60
62
  loadPlanPolicy,
61
63
  loadRuntimeConfig,
62
64
  parseFrontMatter,
@@ -411,7 +413,7 @@ function formatIssues(error) {
411
413
  });
412
414
  }
413
415
  function delay(ms) {
414
- return new Promise((resolve13) => setTimeout(resolve13, ms));
416
+ return new Promise((resolve14) => setTimeout(resolve14, ms));
415
417
  }
416
418
  async function releaseOwnedLock(lockPath) {
417
419
  try {
@@ -610,7 +612,7 @@ var PlanLifecycle = class {
610
612
  // package.json
611
613
  var package_default = {
612
614
  name: "@orquentra/codebuddy",
613
- version: "2.5.0",
615
+ version: "2.6.0",
614
616
  description: "Local-first project context and memory for AI coding agents.",
615
617
  type: "module",
616
618
  license: "MIT",
@@ -745,7 +747,7 @@ var RiskAssessor = class {
745
747
  const byPath = /* @__PURE__ */ new Map();
746
748
  const perSignal = /* @__PURE__ */ new Map();
747
749
  for (const { signal, contributions } of settled) {
748
- const stat5 = perSignal.get(signal.id) ?? {
750
+ const stat6 = perSignal.get(signal.id) ?? {
749
751
  id: signal.id,
750
752
  category: signal.category,
751
753
  contributions: 0,
@@ -756,10 +758,10 @@ var RiskAssessor = class {
756
758
  const list = byPath.get(contribution.path) ?? [];
757
759
  list.push({ signal, contribution });
758
760
  byPath.set(contribution.path, list);
759
- stat5.contributions += 1;
760
- stat5.totalWeight += contribution.weight;
761
+ stat6.contributions += 1;
762
+ stat6.totalWeight += contribution.weight;
761
763
  }
762
- perSignal.set(signal.id, stat5);
764
+ perSignal.set(signal.id, stat6);
763
765
  }
764
766
  const items = [];
765
767
  for (const [path, contributions] of byPath) {
@@ -891,7 +893,7 @@ async function gitLogForPath(repositoryRoot, path, since) {
891
893
  }).filter((record) => record.sha.length > 0);
892
894
  }
893
895
  function runGit(cwd, args) {
894
- return new Promise((resolve13, reject) => {
896
+ return new Promise((resolve14, reject) => {
895
897
  const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
896
898
  let stdout = "";
897
899
  let stderr = "";
@@ -902,7 +904,7 @@ function runGit(cwd, args) {
902
904
  stderr += String(chunk);
903
905
  });
904
906
  child.on("error", reject);
905
- child.on("close", (code) => resolve13({ code: code ?? 0, stdout, stderr }));
907
+ child.on("close", (code) => resolve14({ code: code ?? 0, stdout, stderr }));
906
908
  });
907
909
  }
908
910
 
@@ -2115,7 +2117,16 @@ async function rankRelevantFacts(facts, namespace, task, targetPaths, semanticRa
2115
2117
  } catch {
2116
2118
  }
2117
2119
  }
2118
- return fused.slice(0, LIMITS2.relevantFacts).map(({ fact, taskScore, pathScore, score }) => {
2120
+ const selected = [];
2121
+ for (const candidate of fused) {
2122
+ if (selected.length >= LIMITS2.relevantFacts) break;
2123
+ const text = candidate.fact.content || candidate.fact.object;
2124
+ if (selected.some((kept) => termOverlap(text, kept.fact.content || kept.fact.object) >= 0.8)) {
2125
+ continue;
2126
+ }
2127
+ selected.push(candidate);
2128
+ }
2129
+ return selected.map(({ fact, taskScore, pathScore, score }) => {
2119
2130
  const evidence = [
2120
2131
  pathScore > 0 ? "matches a target path" : "",
2121
2132
  taskScore > 0 ? `shares ${taskScore / 3} task term(s)` : "",
@@ -2835,8 +2846,36 @@ async function gate(args) {
2835
2846
  ref: match.incident.id
2836
2847
  };
2837
2848
  }
2838
- const ref = await saveDurable(args);
2839
2849
  const superseded = candidate.content.match(/\bsupersedes\s+(fact_[a-z0-9]+)\b/i)?.[1];
2850
+ if (candidate.class !== "incident" && !superseded) {
2851
+ const similar = await nearestFact(
2852
+ args.memoryStore,
2853
+ args.input.namespace,
2854
+ candidate,
2855
+ fingerprint
2856
+ );
2857
+ if (similar && similar.overlap >= DUPLICATE_OVERLAP) {
2858
+ return {
2859
+ ...base,
2860
+ outcome: "ignored",
2861
+ reason: `near-duplicate of ${similar.id} (${Math.round(similar.overlap * 100)}% shared words)`,
2862
+ ref: similar.id
2863
+ };
2864
+ }
2865
+ if (similar && similar.overlap >= UPDATE_OVERLAP) {
2866
+ const existing = (await args.reviewStore.list()).find(
2867
+ (item) => item.fingerprint === fingerprint
2868
+ );
2869
+ const ref2 = existing?.id ?? await queue(args, `possible update of ${similar.id}`);
2870
+ return {
2871
+ ...base,
2872
+ outcome: "queued",
2873
+ reason: `overlaps ${similar.id} (${Math.round(similar.overlap * 100)}% shared words): review as a possible update`,
2874
+ ref: ref2
2875
+ };
2876
+ }
2877
+ }
2878
+ const ref = await saveDurable(args);
2840
2879
  if (superseded && superseded !== ref) {
2841
2880
  try {
2842
2881
  await args.memoryStore.markFactSuperseded(superseded, ref);
@@ -2845,6 +2884,19 @@ async function gate(args) {
2845
2884
  }
2846
2885
  return { ...base, outcome: "saved", reason: `confident ${candidate.class} auto-saved`, ref };
2847
2886
  }
2887
+ var DUPLICATE_OVERLAP = 0.8;
2888
+ var UPDATE_OVERLAP = 0.5;
2889
+ async function nearestFact(memoryStore, namespace, candidate, fingerprint) {
2890
+ let best = null;
2891
+ for (const fact of await memoryStore.listFacts()) {
2892
+ if (fact.fingerprint === fingerprint || fact.category === "incident") continue;
2893
+ if (fact.predicate === "rule" || fact.predicate === "procedure") continue;
2894
+ if (!isEligibleMemory(fact, { namespace })) continue;
2895
+ const overlap = termOverlap(candidate.content, fact.content || fact.object);
2896
+ if (!best || overlap > best.overlap) best = { id: fact.id, overlap };
2897
+ }
2898
+ return best;
2899
+ }
2848
2900
  async function saveDurable(args) {
2849
2901
  const { candidate, input, memoryStore, now } = args;
2850
2902
  const fingerprint = candidateFingerprint(input.namespace, candidate);
@@ -3028,7 +3080,7 @@ async function configuredReranker(repositoryRoot) {
3028
3080
  if (provider === "local") {
3029
3081
  const revision = model === DEFAULT_RERANK_MODELS.local.id ? DEFAULT_RERANK_MODELS.local.revision : void 0;
3030
3082
  return async (task, documents) => {
3031
- const { rerankLocally } = await import("./local-3NGNESNB.js");
3083
+ const { rerankLocally } = await import("./local-OTOY56B4.js");
3032
3084
  return bounded(
3033
3085
  rerankLocally({ id: model, ...revision ? { revision } : {} }, task, documents)
3034
3086
  );
@@ -3036,7 +3088,7 @@ async function configuredReranker(repositoryRoot) {
3036
3088
  }
3037
3089
  const apiKey = provider === "openrouter" ? process.env.OPENROUTER_API_KEY : process.env.HF_TOKEN ?? file.provider?.apiKey;
3038
3090
  if (!apiKey) return void 0;
3039
- const { rerankHuggingFace, rerankOpenRouter } = await import("./cloud-GP6G3EAK.js");
3091
+ const { rerankHuggingFace, rerankOpenRouter } = await import("./cloud-7WM3YLI4.js");
3040
3092
  const call = provider === "openrouter" ? rerankOpenRouter : rerankHuggingFace;
3041
3093
  return (task, documents) => call({ apiKey, model, timeoutMs: RERANK_BUDGET_MS }, task, documents);
3042
3094
  }
@@ -3045,7 +3097,7 @@ async function openHookSemantics(repositoryRoot) {
3045
3097
  const connect = async () => {
3046
3098
  const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
3047
3099
  if (!config.postgresUrl) return null;
3048
- const { createRuntime: createRuntime2 } = await import("./operations-EQGOFCBD.js");
3100
+ const { createRuntime: createRuntime2 } = await import("./operations-LOBAQRLO.js");
3049
3101
  const opening = createRuntime2(config, {
3050
3102
  repositoryRoot,
3051
3103
  startWorker: false,
@@ -3406,7 +3458,7 @@ var SYNC_BUDGET_MS = 5 * 6e4;
3406
3458
  async function runSyncHook(repositoryRoot) {
3407
3459
  const config = await loadRuntimeConfig(repositoryRoot, { requireDatabase: false });
3408
3460
  if (!config.postgresUrl) return null;
3409
- const { createRuntime: createRuntime2 } = await import("./operations-EQGOFCBD.js");
3461
+ const { createRuntime: createRuntime2 } = await import("./operations-LOBAQRLO.js");
3410
3462
  const started = Date.now();
3411
3463
  const runtime = await createRuntime2(config, { repositoryRoot });
3412
3464
  try {
@@ -4858,6 +4910,33 @@ function startProjectSync(options) {
4858
4910
  };
4859
4911
  }
4860
4912
 
4913
+ // src/core/project-root.ts
4914
+ import { realpath as realpath2, stat as stat5 } from "fs/promises";
4915
+ import { homedir } from "os";
4916
+ import { isAbsolute as isAbsolute2, parse, resolve as resolve13 } from "path";
4917
+ async function resolveInitializedProjectRoot(configuredRoot, cwd = process.cwd()) {
4918
+ if (configuredRoot !== void 0 && !isAbsolute2(configuredRoot)) {
4919
+ throw new Error("CODEBUDDY_PROJECT_ROOT must be an absolute path to an initialized project.");
4920
+ }
4921
+ const selected = resolve13(configuredRoot ?? cwd);
4922
+ const root = await realpath2(selected);
4923
+ if (root === parse(root).root || root === await realpath2(homedir())) {
4924
+ throw new Error(
4925
+ "Select an initialized project folder, not the filesystem root or home folder."
4926
+ );
4927
+ }
4928
+ const initialized = await stat5(configPath(root)).then(
4929
+ (info) => info.isFile(),
4930
+ () => false
4931
+ );
4932
+ if (!initialized) {
4933
+ throw new Error(
4934
+ `CodeBuddy will not scan ${root}: run codebuddy init in your project folder first. For a desktop client, re-register CodeBuddy from that folder to save its project path.`
4935
+ );
4936
+ }
4937
+ return root;
4938
+ }
4939
+
4861
4940
  // src/mcp/database.ts
4862
4941
  function createLazyDatabase(connect, options = {}) {
4863
4942
  const retryMs = options.retryMs ?? 5e3;
@@ -4921,10 +5000,15 @@ function describeError(error) {
4921
5000
  // src/mcp/server.ts
4922
5001
  async function startMcpServer(options = {}) {
4923
5002
  const logger = pino({ level: process.env.LOG_LEVEL ?? "info" }, process.stderr);
4924
- const config = options.config ?? await loadRuntimeConfig(process.cwd(), { requireDatabase: false });
5003
+ const projectRoot = await resolveInitializedProjectRoot(
5004
+ options.config?.projectRoot ?? process.env.CODEBUDDY_PROJECT_ROOT
5005
+ );
5006
+ const config = {
5007
+ ...options.config ?? await loadRuntimeConfig(projectRoot, { requireDatabase: false }),
5008
+ projectRoot
5009
+ };
4925
5010
  (config.postgresUrl ? codeBuddyConfigSchema : codeBuddyConfigSchema.omit({ postgresUrl: true })).parse(config);
4926
5011
  const toolProfile = resolveToolProfile();
4927
- const projectRoot = config.projectRoot ?? process.env.CODEBUDDY_PROJECT_ROOT ?? process.cwd();
4928
5012
  const importMemory = async (memory) => {
4929
5013
  try {
4930
5014
  const result = await memory.reindexMemory();
@@ -5025,4 +5109,4 @@ export {
5025
5109
  registerCodeBuddyTools,
5026
5110
  startMcpServer
5027
5111
  };
5028
- //# sourceMappingURL=chunk-Z5QB367W.js.map
5112
+ //# sourceMappingURL=chunk-VAKBJV26.js.map