vault-go 0.29.0 → 0.29.2

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 CHANGED
@@ -150,6 +150,8 @@ Capture and recall: `vault_go_event`, `vault_go_remember`, `vault_go_forget`, `v
150
150
 
151
151
  Projects are matched by path first and then by repository: vault-go reads the checkout's `origin` remote, normalizes it to `host/owner/repo` and records it as the project's `repoKey`. A clone on another machine or user joins the oldest project with the same `repoKey` instead of creating a new one, and projects created before this are backfilled the first time a capture matches them by path.
152
152
 
153
+ A folder without git joins the nearest existing project above it instead of becoming a project of its own; a git checkout always keeps its own project.
154
+
153
155
  Read tools (`vault_go_search`, `vault_go_search_index`, `vault_go_feed`, `vault_go_retrieve` and query-anchored `vault_go_timeline`) are scoped to the project of the MCP server's working directory when no `projectId` is given; a git worktree maps to its main checkout. Pass `scope: "account"` to search every project of the account. Outside a known project they fall back to the account, and no project is created. Memories marked private stay out of search and feed results unless `includePrivate: true` is set.
154
156
 
155
157
  `vault_go_retrieve` lets the central memory engine choose a bounded retrieval path while every explicit retrieval tool remains available. `vault_go_decision_status` reports sanitized rollout/circuit state. Jev and its credential remain in the engine; a healthy deployment does not by itself prove provider availability or decision quality. `vault_go_decide` sends a caller-authored state plus up to 8 typed questions (`noul`, `choice`, `score`) to the central Jev and returns probabilities; it runs only in `full` rollout, one call at a time, refuses secrets, e-mails, identifiers, URLs and absolute local paths before anything leaves Vault, and persists nothing. The caller still owns the decision.
@@ -23,7 +23,15 @@ export async function resolveCrystalProjectFolder(cloud, projectId) {
23
23
  if (!Array.isArray(projects))
24
24
  return null;
25
25
  const found = projects.find((item) => item && typeof item === 'object' && item.id === projectId);
26
- return crystalProjectFolder(found);
26
+ const folder = crystalProjectFolder(found);
27
+ if (!folder)
28
+ return null;
29
+ // Why: same-named projects used to share one Crystal folder and mix their
30
+ // memories. The oldest keeps the plain name (existing folders stay put); newer
31
+ // namesakes get a stable suffix from their id. Projects arrive newest first.
32
+ const namesakes = projects.filter((item) => crystalProjectFolder(item)?.toLowerCase() === folder.toLowerCase());
33
+ const oldest = namesakes.at(-1);
34
+ return oldest?.id === projectId ? folder : `${folder}-${projectId.slice(0, 8)}`;
27
35
  }
28
36
  export async function mirrorCrystalMemory(input) {
29
37
  if (!shouldMirrorCrystalMemory(input.job))
package/dist/hooks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { redact } from './capture-privacy.js';
2
2
  export { redact } from './capture-privacy.js';
3
3
  import { createHash, randomUUID } from "node:crypto";
4
- import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
5
5
  import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
6
6
  import { applyCustomModeContext } from './custom-modes.js';
7
7
  import { VaultCloudClient } from "./cloud.js";
@@ -67,7 +67,7 @@ function projectRoots(workspace) {
67
67
  return fallback;
68
68
  }
69
69
  }
70
- function matchWorkspaceProject(projects, roots, key) {
70
+ function matchWorkspaceProject(projects, roots, key, inGit = true) {
71
71
  if (!Array.isArray(projects))
72
72
  throw new Error("Resposta de projetos inválida");
73
73
  const byPath = roots.map((cwd) => projects.find((item) => item &&
@@ -75,16 +75,34 @@ function matchWorkspaceProject(projects, roots, key) {
75
75
  typeof item.rootPath === "string" &&
76
76
  Boolean(String(item.rootPath).trim()) &&
77
77
  resolvedPath(String(item.rootPath)) === cwd)).find(Boolean);
78
- if (byPath || !key)
78
+ if (byPath)
79
79
  return byPath;
80
80
  // Why: a clone on another machine or user shares the repository, not the path.
81
81
  // Projects arrive newest first; the oldest one with this key is the canonical one.
82
- return projects.filter((item) => item && typeof item === "object" && item.repoKey === key).at(-1);
82
+ const byRepo = key
83
+ ? projects.filter((item) => item && typeof item === "object" && item.repoKey === key).at(-1)
84
+ : undefined;
85
+ if (byRepo || inGit)
86
+ return byRepo;
87
+ // Why: a folder without git used to become its own project, splitting memories
88
+ // between an umbrella folder and its subfolders. Prefer the nearest existing ancestor.
89
+ const cwd = roots[0];
90
+ return projects
91
+ .filter((item) => {
92
+ if (!item || typeof item !== "object" || typeof item.rootPath !== "string")
93
+ return false;
94
+ const path = String(item.rootPath).trim();
95
+ if (!path)
96
+ return false;
97
+ const ancestor = relative(resolvedPath(path), cwd);
98
+ return Boolean(ancestor) && ancestor !== ".." && !ancestor.startsWith(`..${sep}`) && !isAbsolute(ancestor);
99
+ })
100
+ .sort((a, b) => resolvedPath(String(b.rootPath)).length - resolvedPath(String(a.rootPath)).length)[0];
83
101
  }
84
102
  /** The existing project for a working directory (worktrees map to their main checkout); never creates one. */
85
103
  export async function findWorkspaceProject(cloud, cwd) {
86
104
  const workspace = root(cwd);
87
- return matchWorkspaceProject(await cloud.projects(), projectRoots(workspace), repoKey(workspace));
105
+ return matchWorkspaceProject(await cloud.projects(), projectRoots(workspace), repoKey(workspace), existsSync(resolve(workspace, ".git")));
88
106
  }
89
107
  function canonicalFile(file, workspace, project) {
90
108
  const absolute = resolve(workspace, file);
@@ -194,7 +212,7 @@ export async function executeHook(adapter, event, value, cloud = new VaultCloudC
194
212
  if (!Array.isArray(projects))
195
213
  throw new Error("Resposta de projetos inválida");
196
214
  const key = repoKey(workspace);
197
- let project = matchWorkspaceProject(projects, roots, key);
215
+ let project = matchWorkspaceProject(projects, roots, key, existsSync(resolve(workspace, ".git")));
198
216
  const cwd = project ? resolvedPath(String(project.rootPath)) : roots[0];
199
217
  if (!project && reading)
200
218
  return hookOutput(event, "", adapter);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vault-go",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "MCP server and installer for Vault Memory: search, capture, and use account memory across AI clients.",
5
5
  "type": "module",
6
6
  "bin": {