pi-vault-mind 0.14.2 → 0.16.3
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/CHANGELOG.md +293 -0
- package/README.md +5 -5
- package/agents/broadcaster.agent.md +2 -2
- package/agents/heavy-lifter.agent.md +1 -1
- package/agents/main.agent.md +29 -0
- package/agents/manager.agent.md +1 -1
- package/agents/miner.agent.md +1 -1
- package/dist/packages/obsidian/src/client.js +331 -0
- package/dist/packages/obsidian/src/types.js +1 -0
- package/dist/src/activity.d.ts +49 -0
- package/dist/src/activity.js +175 -0
- package/dist/src/agent-bus/Agent.d.ts +9 -1
- package/dist/src/agent-bus/Agent.js +18 -2
- package/dist/src/agent-bus/index.d.ts +5 -5
- package/dist/src/agent-bus/index.js +1 -1
- package/dist/src/agent-queue.js +12 -3
- package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
- package/dist/src/agents/BroadcasterAgent.js +2 -2
- package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
- package/dist/src/agents/HeavyLifterAgent.js +2 -2
- package/dist/src/agents/ManagerAgent.js +1 -1
- package/dist/src/agents/MinerAgent.d.ts +1 -1
- package/dist/src/agents/MinerAgent.js +2 -2
- package/dist/src/agents/index.d.ts +2 -2
- package/dist/src/agents/index.js +2 -2
- package/dist/src/auth.d.ts +17 -15
- package/dist/src/auth.js +26 -37
- package/dist/src/autosync.js +2 -2
- package/dist/src/commands.d.ts +2 -19
- package/dist/src/commands.js +301 -231
- package/dist/src/config-keys.d.ts +16 -0
- package/dist/src/config-keys.js +45 -0
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/edits.d.ts +21 -0
- package/dist/src/edits.js +75 -0
- package/dist/src/embedding-probe.d.ts +34 -0
- package/dist/src/embedding-probe.js +142 -0
- package/dist/src/embedding-providers.d.ts +133 -0
- package/dist/src/embedding-providers.js +173 -0
- package/dist/src/embedding-secrets.d.ts +38 -0
- package/dist/src/embedding-secrets.js +291 -0
- package/dist/src/engine.d.ts +6 -0
- package/dist/src/engine.js +78 -6
- package/dist/src/events.js +7 -10
- package/dist/src/extension-packages.d.ts +9 -0
- package/dist/src/extension-packages.js +14 -0
- package/dist/src/git.d.ts +19 -0
- package/dist/src/git.js +78 -0
- package/dist/src/graph.d.ts +2 -1
- package/dist/src/graph.js +47 -13
- package/dist/src/identities-config.d.ts +6 -0
- package/dist/src/identities-config.js +39 -0
- package/dist/src/identity-injector.js +1 -1
- package/dist/src/index.js +9 -26
- package/dist/src/lance.d.ts +9 -0
- package/dist/src/lance.js +143 -203
- package/dist/src/modal-client.d.ts +16 -0
- package/dist/src/modal-client.js +16 -0
- package/dist/src/modal-config.d.ts +37 -14
- package/dist/src/modal-config.js +59 -32
- package/dist/src/model-router.d.ts +27 -0
- package/dist/src/model-router.js +67 -0
- package/dist/src/models.d.ts +42 -0
- package/dist/src/models.js +78 -0
- package/dist/src/personalize.js +17 -3
- package/dist/src/scaffold.d.ts +48 -0
- package/dist/src/scaffold.js +246 -0
- package/dist/src/server.d.ts +47 -4
- package/dist/src/server.js +988 -221
- package/dist/src/session-search.d.ts +63 -0
- package/dist/src/session-search.js +379 -0
- package/dist/src/settings-ui.d.ts +2 -2
- package/dist/src/settings-ui.js +76 -135
- package/dist/src/sync.d.ts +3 -3
- package/dist/src/sync.js +19 -15
- package/dist/src/tool-catalog.d.ts +18 -0
- package/dist/src/tool-catalog.js +232 -0
- package/dist/src/tools/marksman.js +1 -1
- package/dist/src/tools.js +111 -65
- package/dist/src/types.d.ts +127 -71
- package/dist/src/utils.d.ts +57 -6
- package/dist/src/utils.js +114 -28
- package/dist/src/vault-tools.d.ts +34 -0
- package/dist/src/vault-tools.js +99 -0
- package/dist/src/vault-writer.js +4 -1
- package/dist/src/vm-handlers.js +140 -43
- package/dist/src/watcher.js +9 -9
- package/dist/test/activity-tool.test.js +54 -0
- package/dist/test/agent-bus/Agent.test.js +174 -0
- package/dist/test/agent-bus/AgentLoader.test.js +180 -0
- package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
- package/dist/test/agent-bus/LLMProvider.test.js +162 -0
- package/dist/test/agent-bus/TaskQueue.test.js +256 -0
- package/dist/test/agent-dispatch-context.test.js +73 -0
- package/dist/test/agent-queue.test.js +1 -1
- package/dist/test/agents/Agents.test.js +282 -0
- package/dist/test/auth.test.js +57 -56
- package/dist/test/bridge-identity.test.js +34 -3
- package/dist/test/classify-search-mode.test.js +51 -0
- package/dist/test/commands.test.js +410 -0
- package/dist/test/config-merge.test.js +98 -0
- package/dist/test/context-capture.test.js +56 -0
- package/dist/test/discover-schema.test.js +8 -5
- package/dist/test/dispatch.test.js +45 -91
- package/dist/test/edits.test.js +144 -0
- package/dist/test/embedding-probe.test.js +424 -0
- package/dist/test/embedding-providers.test.js +157 -0
- package/dist/test/embedding-secrets.test.js +210 -0
- package/dist/test/engine-identity.test.js +1 -1
- package/dist/test/events.test.js +675 -0
- package/dist/test/git-bridge.test.js +160 -0
- package/dist/test/graph.test.js +464 -0
- package/dist/test/helpers/server.js +81 -0
- package/dist/test/identity-injector.test.js +197 -0
- package/dist/test/index.test.js +5 -7
- package/dist/test/lance-modal.test.js +67 -2
- package/dist/test/lance.test.js +429 -0
- package/dist/test/modal-client.test.js +1 -1
- package/dist/test/modal-config.test.js +92 -79
- package/dist/test/model-readiness.test.js +122 -0
- package/dist/test/model-router.test.js +99 -0
- package/dist/test/obsidian-client.test.js +42 -0
- package/dist/test/personalize.test.js +1 -1
- package/dist/test/rest-models.test.js +153 -0
- package/dist/test/rest-queue.test.js +135 -87
- package/dist/test/rest-setup.test.js +625 -115
- package/dist/test/rest-vm.test.js +355 -94
- package/dist/test/server-cors.test.js +48 -0
- package/dist/test/server-ws.test.js +61 -34
- package/dist/test/session-crud.test.js +323 -0
- package/dist/test/session-search.test.js +139 -0
- package/dist/test/settings-ui.test.js +113 -4
- package/dist/test/sync.test.js +12 -12
- package/dist/test/tombstone.test.js +8 -10
- package/dist/test/tool-catalog.test.js +86 -0
- package/dist/test/utils.test.js +588 -0
- package/dist/test/vault-tools.test.js +103 -12
- package/dist/test/vault-writer.test.js +371 -0
- package/dist/test/vm-handlers.test.js +620 -0
- package/extension-packages.json +9 -0
- package/package.json +107 -90
- package/pi-vault-mind.config.example.json +24 -2
- package/scripts/check-deps.sh +35 -32
- package/scripts/cli-e2e.mjs +215 -0
- package/scripts/configuration-e2e.mjs +783 -0
- package/scripts/e2e-commands.mjs +391 -0
- package/scripts/e2e-ws.mjs +3 -2
- package/scripts/fetch-modal-token.sh +4 -3
- package/scripts/generate-config-keys-json.mjs +45 -0
- package/scripts/generate-extension-packages-json.mjs +55 -0
- package/scripts/pi-session-query +0 -0
- package/scripts/publish-obsidian-plugin.sh +0 -0
- package/scripts/reset-test-vault.sh +2 -2
- package/scripts/setup-test-config.sh +0 -0
- package/scripts/setup-vault-pi.sh +48 -9
- package/scripts/test-slash-commands.mjs +2 -2
- package/scripts/vault-pi.sh +12 -3
- package/skills/github-release-notes-file/SKILL.md +42 -0
- package/skills/obsidian-guided-test-session/SKILL.md +110 -0
- package/skills/obsidian-interactive-test/SKILL.md +88 -0
- package/skills/obsidian-plugin-publish/SKILL.md +58 -0
- package/skills/pi-vault-mind-release/SKILL.md +141 -0
- package/skills/pre-test-release/SKILL.md +116 -0
- package/dist/test/auto-indexer.test.js +0 -2
|
@@ -3,9 +3,10 @@ import * as fs from "node:fs";
|
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { afterEach, describe, it } from "node:test";
|
|
6
|
+
import { registerCommands } from "../src/commands.js";
|
|
6
7
|
import { modalUrl } from "../src/modal-config.js";
|
|
7
8
|
import { buildPluginInstallGuidance, detectVaultFromCwd, getMissingOnboardingPlugins, setupWizard, } from "../src/settings-ui.js";
|
|
8
|
-
import {
|
|
9
|
+
import { deriveCollectionNameFromVaultPath, getConfigPath } from "../src/utils.js";
|
|
9
10
|
const savedHome = process.env.HOME;
|
|
10
11
|
const mkTmpDir = (prefix) => fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
11
12
|
const makeCtx = (cwd) => ({
|
|
@@ -24,6 +25,19 @@ afterEach(() => {
|
|
|
24
25
|
process.env.HOME = savedHome;
|
|
25
26
|
}
|
|
26
27
|
});
|
|
28
|
+
// registerCommands(pi) registers "vm" (and "intake"); capture the real handler so
|
|
29
|
+
// tests exercise the actual /vm command dispatch (args tokenizer, handleSetup
|
|
30
|
+
// ordering, etc.), not a hand-built cliArgs object that would bypass a bug entirely.
|
|
31
|
+
const captureVmHandler = () => {
|
|
32
|
+
const handlers = [];
|
|
33
|
+
const pi = {
|
|
34
|
+
registerCommand: (name, spec) => {
|
|
35
|
+
handlers.push({ name, handler: spec.handler });
|
|
36
|
+
},
|
|
37
|
+
getAllTools: () => [],
|
|
38
|
+
};
|
|
39
|
+
return { pi, handlers };
|
|
40
|
+
};
|
|
27
41
|
describe("settings-ui vault detection", () => {
|
|
28
42
|
it("detectVaultFromCwd returns cwd when .obsidian exists", () => {
|
|
29
43
|
const vault = mkTmpDir("pvm-vault-detect-");
|
|
@@ -68,7 +82,7 @@ describe("setupWizard CLI defaults", () => {
|
|
|
68
82
|
remoteUrl: "https://example.com/v1",
|
|
69
83
|
model: "embeddinggemma",
|
|
70
84
|
});
|
|
71
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
85
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(vault), "utf-8"));
|
|
72
86
|
const stored = cfg.vaultMind?.vaults?.default?.path;
|
|
73
87
|
assert.ok(stored, "default vault path should be written");
|
|
74
88
|
assert.equal(expandMaybeHome(stored, home), vault);
|
|
@@ -84,9 +98,104 @@ describe("setupWizard CLI defaults", () => {
|
|
|
84
98
|
workspace: "acme-workspace",
|
|
85
99
|
model: "embeddinggemma",
|
|
86
100
|
});
|
|
87
|
-
const cfg = JSON.parse(fs.readFileSync(
|
|
88
|
-
assert.equal(cfg.vaultMind?.embedding?.
|
|
101
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(vault), "utf-8"));
|
|
102
|
+
assert.equal(cfg.vaultMind?.embedding?.workspace, "acme-workspace");
|
|
89
103
|
assert.equal(cfg.vaultMind?.embedding?.remoteUrl, modalUrl("acme-workspace"));
|
|
90
104
|
fs.rmSync(home, { recursive: true, force: true });
|
|
91
105
|
});
|
|
92
106
|
});
|
|
107
|
+
describe("/vm setup CLI arg tokenizer (regression)", () => {
|
|
108
|
+
it("captures --vault as the leading flag in the CLI-mode args string", async () => {
|
|
109
|
+
// cwd is deliberately a plain, non-Obsidian directory and distinct from the
|
|
110
|
+
// --vault target: if detectVaultFromCwd(cwd) could return a fallback vault,
|
|
111
|
+
// a broken tokenizer that drops --vault would be masked by that fallback.
|
|
112
|
+
const home = mkTmpDir("pvm-home-");
|
|
113
|
+
const cwd = mkTmpDir("pvm-cwd-");
|
|
114
|
+
const vault = mkTmpDir("pvm-vault-");
|
|
115
|
+
fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
|
|
116
|
+
process.env.HOME = home;
|
|
117
|
+
const { pi, handlers } = captureVmHandler();
|
|
118
|
+
registerCommands(pi);
|
|
119
|
+
const vm = handlers.find((h) => h.name === "vm");
|
|
120
|
+
assert.ok(vm, "/vm command should be registered");
|
|
121
|
+
// Exact invocation documented in docs/getting-started/CLI_ONLY_WALKTHROUGH.md —
|
|
122
|
+
// --vault has no leading whitespace before its `--`, so a tokenizer that only
|
|
123
|
+
// strips `--` when preceded by whitespace drops it from cliArgs entirely.
|
|
124
|
+
await vm?.handler(`setup --vault ${vault} --remoteUrl https://example.com/v1 --model embeddinggemma`, makeCtx(cwd));
|
|
125
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(cwd), "utf-8"));
|
|
126
|
+
const stored = cfg.vaultMind?.vaults?.default?.path;
|
|
127
|
+
assert.ok(stored, "leading --vault flag should populate vaults.default.path");
|
|
128
|
+
assert.equal(expandMaybeHome(stored, home), vault);
|
|
129
|
+
assert.equal(cfg.vaultMind?.embedding?.remoteUrl, "https://example.com/v1");
|
|
130
|
+
assert.equal(cfg.vaultMind?.embedding?.model, "embeddinggemma");
|
|
131
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
132
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
133
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
134
|
+
});
|
|
135
|
+
it("parses whichever flag comes first, not only --vault", async () => {
|
|
136
|
+
const home = mkTmpDir("pvm-home-");
|
|
137
|
+
const cwd = mkTmpDir("pvm-cwd-");
|
|
138
|
+
const vault = mkTmpDir("pvm-vault-");
|
|
139
|
+
fs.mkdirSync(path.join(vault, ".obsidian"), { recursive: true });
|
|
140
|
+
process.env.HOME = home;
|
|
141
|
+
const { pi, handlers } = captureVmHandler();
|
|
142
|
+
registerCommands(pi);
|
|
143
|
+
const vm = handlers.find((h) => h.name === "vm");
|
|
144
|
+
assert.ok(vm, "/vm command should be registered");
|
|
145
|
+
// --model leads here instead of --vault; the fix strips leading `--` from
|
|
146
|
+
// whichever flag is first, so this must not regress to a --vault-only fix.
|
|
147
|
+
await vm?.handler(`setup --model embeddinggemma --vault ${vault} --remoteUrl https://example.com/v1`, makeCtx(cwd));
|
|
148
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(cwd), "utf-8"));
|
|
149
|
+
assert.equal(cfg.vaultMind?.embedding?.model, "embeddinggemma");
|
|
150
|
+
assert.equal(expandMaybeHome(cfg.vaultMind?.vaults?.default?.path, home), vault);
|
|
151
|
+
fs.rmSync(home, { recursive: true, force: true });
|
|
152
|
+
fs.rmSync(cwd, { recursive: true, force: true });
|
|
153
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe("/vm setup collection scaffolding (regression)", () => {
|
|
157
|
+
it("populates collections.main/pending/context_events + draft-context injector on a fresh vault", async () => {
|
|
158
|
+
const vault = mkTmpDir("pvm-setup-scaffold-");
|
|
159
|
+
const { pi, handlers } = captureVmHandler();
|
|
160
|
+
registerCommands(pi);
|
|
161
|
+
const vm = handlers.find((h) => h.name === "vm");
|
|
162
|
+
assert.ok(vm, "/vm command should be registered");
|
|
163
|
+
await vm?.handler(`setup --vault ${vault} --remoteUrl https://example.test --model embeddinggemma --collection customname`, makeCtx(vault));
|
|
164
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(vault), "utf-8"));
|
|
165
|
+
// This is exactly the regression handleSetup shipped with: setupWizard() used to
|
|
166
|
+
// run before handleInit(), and setupWizard always writes the config file first —
|
|
167
|
+
// so handleInit's "file doesn't exist yet" branch (which populates these defaults)
|
|
168
|
+
// never fired, and every /vm setup run ended with "collections": {}.
|
|
169
|
+
assert.ok(cfg.collections?.main, "collections.main should be scaffolded");
|
|
170
|
+
assert.ok(cfg.collections.main.path.endsWith(path.join("collections", "customname.jsonl")), `expected collections.main.path to end with collections/customname.jsonl, got ${cfg.collections.main.path}`);
|
|
171
|
+
assert.ok(cfg.collections?.pending, "collections.pending should be scaffolded");
|
|
172
|
+
assert.ok(cfg.collections?.context_events, "collections.context_events should be scaffolded");
|
|
173
|
+
assert.ok((cfg.injectors ?? []).some((ij) => ij.name === "draft-context"), "draft-context injector should be scaffolded");
|
|
174
|
+
for (const rel of [
|
|
175
|
+
path.join("collections", "customname.jsonl"),
|
|
176
|
+
path.join("collections", "pending.jsonl"),
|
|
177
|
+
path.join("collections", "context_events.jsonl"),
|
|
178
|
+
path.join("collections", "artifact.md"),
|
|
179
|
+
]) {
|
|
180
|
+
assert.ok(fs.existsSync(path.join(vault, rel)), `${rel} should exist on disk`);
|
|
181
|
+
}
|
|
182
|
+
assert.equal(cfg.vaultMind?.embedding?.remoteUrl, "https://example.test");
|
|
183
|
+
assert.equal(cfg.vaultMind?.embedding?.model, "embeddinggemma");
|
|
184
|
+
assert.ok(cfg.vaultMind?.vaults?.default?.path, "vaults.default.path should be written");
|
|
185
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
186
|
+
});
|
|
187
|
+
it("derives the default collection name from the vault directory when --collection is omitted", async () => {
|
|
188
|
+
const vault = mkTmpDir("pvm-setup-scaffold-noflag-");
|
|
189
|
+
const { pi, handlers } = captureVmHandler();
|
|
190
|
+
registerCommands(pi);
|
|
191
|
+
const vm = handlers.find((h) => h.name === "vm");
|
|
192
|
+
assert.ok(vm, "/vm command should be registered");
|
|
193
|
+
await vm?.handler(`setup --vault ${vault} --remoteUrl https://example.test --model embeddinggemma`, makeCtx(vault));
|
|
194
|
+
const cfg = JSON.parse(fs.readFileSync(getConfigPath(vault), "utf-8"));
|
|
195
|
+
const expectedCollection = deriveCollectionNameFromVaultPath(vault);
|
|
196
|
+
assert.ok(cfg.collections?.main, "collections.main should be scaffolded");
|
|
197
|
+
assert.ok(cfg.collections.main.path.endsWith(path.join("collections", `${expectedCollection}.jsonl`)));
|
|
198
|
+
assert.ok(fs.existsSync(path.join(vault, "collections", `${expectedCollection}.jsonl`)), "derived collection file should exist on disk");
|
|
199
|
+
fs.rmSync(vault, { recursive: true, force: true });
|
|
200
|
+
});
|
|
201
|
+
});
|
package/dist/test/sync.test.js
CHANGED
|
@@ -23,8 +23,10 @@ const makeCfg = (dataDir) => ({
|
|
|
23
23
|
dataDir,
|
|
24
24
|
embedding: {
|
|
25
25
|
remoteUrl: "https://pvm.modal.run",
|
|
26
|
-
|
|
26
|
+
model: MODEL,
|
|
27
|
+
dim: DIM,
|
|
27
28
|
},
|
|
29
|
+
vaults: { default: { path: dataDir, collectionPrefix: "test" } },
|
|
28
30
|
ftsEnabled: true,
|
|
29
31
|
},
|
|
30
32
|
});
|
|
@@ -41,12 +43,10 @@ function exportPagesFetch(pages) {
|
|
|
41
43
|
count: page.rows.length,
|
|
42
44
|
done: page.done,
|
|
43
45
|
};
|
|
44
|
-
return {
|
|
45
|
-
ok: true,
|
|
46
|
+
return new Response(JSON.stringify(body), {
|
|
46
47
|
status: 200,
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
};
|
|
48
|
+
headers: { "Content-Type": "application/json" },
|
|
49
|
+
});
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
const row = (id, seq, vector) => ({
|
|
@@ -94,10 +94,10 @@ describe("Modal sync engine", () => {
|
|
|
94
94
|
0: { rows: [row("1", 100, [1, 0, 0]), row("2", 150, [0, 1, 0])], next: 150, done: false },
|
|
95
95
|
150: { rows: [row("3", 200, [0, 0, 1])], next: 200, done: true },
|
|
96
96
|
});
|
|
97
|
-
const res = await syncCollection(cfg, "main");
|
|
97
|
+
const res = await syncCollection(cfg, "main", dir);
|
|
98
98
|
assert.equal(res.rows, 3);
|
|
99
99
|
assert.equal(res.watermark, 200);
|
|
100
|
-
assert.equal(getWatermark(cfg, "
|
|
100
|
+
assert.equal(getWatermark(cfg, "test_main", MODEL, DIM), 200);
|
|
101
101
|
const conn = await connect(dir);
|
|
102
102
|
const table = await conn.openTable(`col_main__${MODEL}__${DIM}`);
|
|
103
103
|
assert.equal(await table.countRows(), 3);
|
|
@@ -109,8 +109,8 @@ describe("Modal sync engine", () => {
|
|
|
109
109
|
100: { rows: [row("2", 200, [0, 1, 0])], next: 200, done: true },
|
|
110
110
|
200: { rows: [], next: 200, done: true },
|
|
111
111
|
});
|
|
112
|
-
await syncCollection(cfg, "main");
|
|
113
|
-
const second = await syncCollection(cfg, "main"); // since=200 → empty, done
|
|
112
|
+
await syncCollection(cfg, "main", dir);
|
|
113
|
+
const second = await syncCollection(cfg, "main", dir); // since=200 → empty, done
|
|
114
114
|
assert.equal(second.rows, 0);
|
|
115
115
|
assert.equal(second.watermark, 200);
|
|
116
116
|
const conn = await connect(dir);
|
|
@@ -123,8 +123,8 @@ describe("Modal sync engine", () => {
|
|
|
123
123
|
0: { rows: [row("1", 100, [1, 0, 0])], next: 100, done: false },
|
|
124
124
|
100: { rows: [row("1", 200, [1, 0, 0])], next: 200, done: true },
|
|
125
125
|
});
|
|
126
|
-
await syncCollection(cfg, "main"); // inserts id=1
|
|
127
|
-
await syncCollection(cfg, "main", { full: true }); // re-fetches id=1 → merge by id
|
|
126
|
+
await syncCollection(cfg, "main", dir); // inserts id=1
|
|
127
|
+
await syncCollection(cfg, "main", dir, { full: true }); // re-fetches id=1 → merge by id
|
|
128
128
|
const conn = await connect(dir);
|
|
129
129
|
const table = await conn.openTable(`col_main__${MODEL}__${DIM}`);
|
|
130
130
|
assert.equal(await table.countRows(), 1);
|
|
@@ -5,7 +5,9 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
7
7
|
const setupConfig = (dir) => {
|
|
8
|
-
|
|
8
|
+
const configDir = path.join(dir, ".vault-mind");
|
|
9
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
10
|
+
fs.writeFileSync(path.join(configDir, "vault-mind.config.json"), JSON.stringify({
|
|
9
11
|
version: 2,
|
|
10
12
|
collections: {
|
|
11
13
|
main: {
|
|
@@ -17,7 +19,7 @@ const setupConfig = (dir) => {
|
|
|
17
19
|
injectors: [],
|
|
18
20
|
vaultMind: {
|
|
19
21
|
dataDir: ".lancedb",
|
|
20
|
-
embedding: {
|
|
22
|
+
embedding: {},
|
|
21
23
|
ftsEnabled: false,
|
|
22
24
|
graph: { enabled: false },
|
|
23
25
|
},
|
|
@@ -27,21 +29,17 @@ const setupConfig = (dir) => {
|
|
|
27
29
|
describe("tombstone_entry", () => {
|
|
28
30
|
let testDir;
|
|
29
31
|
let collectionPath;
|
|
30
|
-
let
|
|
32
|
+
let savedCwd;
|
|
31
33
|
beforeEach(() => {
|
|
32
34
|
testDir = path.join(tmpdir(), `pi-vault-mind-tombstone-test-${randomUUID()}`);
|
|
33
35
|
fs.mkdirSync(testDir, { recursive: true });
|
|
34
36
|
setupConfig(testDir);
|
|
35
37
|
collectionPath = path.join(testDir, "collections", "main.jsonl");
|
|
36
|
-
|
|
37
|
-
process.
|
|
38
|
-
process.env.USERPROFILE = testDir;
|
|
38
|
+
savedCwd = process.cwd();
|
|
39
|
+
process.chdir(testDir);
|
|
39
40
|
});
|
|
40
41
|
afterEach(() => {
|
|
41
|
-
|
|
42
|
-
process.env.HOME = savedHome;
|
|
43
|
-
else
|
|
44
|
-
process.env.HOME = undefined;
|
|
42
|
+
process.chdir(savedCwd);
|
|
45
43
|
if (fs.existsSync(testDir))
|
|
46
44
|
fs.rmSync(testDir, { recursive: true });
|
|
47
45
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { listAllTools, listBuiltinTools, listExtensionTools } from "../src/tool-catalog.js";
|
|
4
|
+
import { registerTools } from "../src/tools.js";
|
|
5
|
+
describe("listAllTools", () => {
|
|
6
|
+
it("returns 30 entries", () => {
|
|
7
|
+
const tools = listAllTools();
|
|
8
|
+
assert.equal(tools.length, 30);
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
describe("listBuiltinTools", () => {
|
|
12
|
+
it("returns 7 entries, all kind: builtin", () => {
|
|
13
|
+
const tools = listBuiltinTools();
|
|
14
|
+
assert.equal(tools.length, 7);
|
|
15
|
+
for (const t of tools) {
|
|
16
|
+
assert.equal(t.kind, "builtin", `tool ${t.name} has kind ${t.kind}, expected builtin`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("listExtensionTools", () => {
|
|
21
|
+
it("returns 23 entries, all kind starts with vm_", () => {
|
|
22
|
+
const tools = listExtensionTools();
|
|
23
|
+
assert.equal(tools.length, 23);
|
|
24
|
+
for (const t of tools) {
|
|
25
|
+
assert.ok(t.kind.startsWith("vm_"), `tool ${t.name} has kind ${t.kind}, expected vm_*`);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
describe("tool catalog integrity", () => {
|
|
30
|
+
it("has no duplicate names across all tools", () => {
|
|
31
|
+
const tools = listAllTools();
|
|
32
|
+
const names = tools.map((t) => t.name);
|
|
33
|
+
const unique = new Set(names);
|
|
34
|
+
assert.equal(unique.size, names.length, `duplicate names: ${names}`);
|
|
35
|
+
});
|
|
36
|
+
it("every entry has a non-empty name and description", () => {
|
|
37
|
+
const tools = listAllTools();
|
|
38
|
+
for (const t of tools) {
|
|
39
|
+
assert.ok(t.name.length > 0, "tool has empty name");
|
|
40
|
+
assert.ok(t.description.length > 0, `tool ${t.name} has empty description`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
it("listAllTools equals listBuiltinTools + listExtensionTools", () => {
|
|
44
|
+
const all = listAllTools();
|
|
45
|
+
const builtin = listBuiltinTools();
|
|
46
|
+
const ext = listExtensionTools();
|
|
47
|
+
assert.equal(all.length, builtin.length + ext.length);
|
|
48
|
+
const allNames = new Set(all.map((t) => t.name));
|
|
49
|
+
const combinedNames = new Set([...builtin.map((t) => t.name), ...ext.map((t) => t.name)]);
|
|
50
|
+
assert.deepStrictEqual(allNames, combinedNames);
|
|
51
|
+
});
|
|
52
|
+
it("vm_search has mode parameter schema", () => {
|
|
53
|
+
const ext = listExtensionTools();
|
|
54
|
+
const vmSearch = ext.find((t) => t.name === "vm_search");
|
|
55
|
+
assert.ok(vmSearch, "vm_search not found");
|
|
56
|
+
assert.ok(vmSearch?.params, "vm_search missing params schema");
|
|
57
|
+
const params = vmSearch?.params;
|
|
58
|
+
const properties = params.properties;
|
|
59
|
+
assert.deepStrictEqual(properties.mode, {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: ["auto", "hybrid", "semantic", "fts", "graph", "vault", "full"],
|
|
62
|
+
default: "hybrid",
|
|
63
|
+
});
|
|
64
|
+
assert.deepStrictEqual(params.required, ["query"]);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe("tool catalog registry drift guard", () => {
|
|
68
|
+
it("extension tools in the catalog exactly match registerTools output", () => {
|
|
69
|
+
const captured = new Set();
|
|
70
|
+
const capturedList = [];
|
|
71
|
+
const mock = {
|
|
72
|
+
registerTool(tool) {
|
|
73
|
+
captured.add(tool.name);
|
|
74
|
+
capturedList.push({ name: tool.name });
|
|
75
|
+
},
|
|
76
|
+
getAllTools() {
|
|
77
|
+
return capturedList;
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
registerTools(mock);
|
|
81
|
+
const catalogExtension = new Set(listAllTools()
|
|
82
|
+
.filter((t) => t.kind !== "builtin")
|
|
83
|
+
.map((t) => t.name));
|
|
84
|
+
assert.deepStrictEqual(captured, catalogExtension, "catalog must equal the registered tool set");
|
|
85
|
+
});
|
|
86
|
+
});
|