akm-cli 0.7.0-rc1 → 0.7.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/dist/{src/cli.js → cli.js} +100 -16
- package/dist/{src/commands → commands}/config-cli.js +42 -0
- package/dist/{src/commands → commands}/history.js +78 -7
- package/dist/{src/commands → commands}/registry-search.js +69 -6
- package/dist/{src/commands → commands}/search.js +30 -3
- package/dist/{src/commands → commands}/show.js +29 -0
- package/dist/{src/commands → commands}/source-add.js +5 -1
- package/dist/{src/commands → commands}/source-manage.js +7 -1
- package/dist/{src/core → core}/config.js +28 -0
- package/dist/{src/indexer → indexer}/db-search.js +1 -0
- package/dist/{src/indexer → indexer}/indexer.js +16 -2
- package/dist/{src/indexer → indexer}/matchers.js +1 -1
- package/dist/{src/indexer → indexer}/search-source.js +4 -2
- package/dist/{src/integrations → integrations}/agent/profiles.js +1 -1
- package/dist/{src/integrations → integrations}/agent/spawn.js +67 -16
- package/dist/{src/integrations → integrations}/github.js +9 -3
- package/dist/{src/llm → llm}/embedders/remote.js +37 -3
- package/dist/{src/output → output}/cli-hints.js +15 -2
- package/dist/{src/output → output}/renderers.js +3 -1
- package/dist/{src/output → output}/shapes.js +8 -1
- package/dist/{src/output → output}/text.js +156 -3
- package/dist/{src/registry → registry}/build-index.js +5 -4
- package/dist/{src/registry → registry}/providers/static-index.js +3 -1
- package/dist/{src/setup → setup}/setup.js +9 -0
- package/dist/{src/wiki → wiki}/wiki.js +54 -6
- package/dist/{src/workflows → workflows}/runs.js +37 -3
- package/package.json +8 -8
- package/dist/tests/add-website-source.test.js +0 -119
- package/dist/tests/agent/agent-config-loader.test.js +0 -70
- package/dist/tests/agent/agent-config.test.js +0 -221
- package/dist/tests/agent/agent-detect.test.js +0 -100
- package/dist/tests/agent/agent-spawn.test.js +0 -234
- package/dist/tests/agent-output.test.js +0 -186
- package/dist/tests/architecture/agent-no-llm-sdk-guard.test.js +0 -103
- package/dist/tests/architecture/agent-spawn-seam.test.js +0 -193
- package/dist/tests/architecture/llm-stateless-seam.test.js +0 -112
- package/dist/tests/asset-ref.test.js +0 -192
- package/dist/tests/asset-registry.test.js +0 -103
- package/dist/tests/asset-spec.test.js +0 -241
- package/dist/tests/bench/attribution.test.js +0 -995
- package/dist/tests/bench/cleanup-sigint.test.js +0 -83
- package/dist/tests/bench/cleanup.js +0 -203
- package/dist/tests/bench/cleanup.test.js +0 -166
- package/dist/tests/bench/cli.js +0 -683
- package/dist/tests/bench/cli.test.js +0 -177
- package/dist/tests/bench/compare.test.js +0 -556
- package/dist/tests/bench/corpus.js +0 -314
- package/dist/tests/bench/corpus.test.js +0 -258
- package/dist/tests/bench/driver.js +0 -346
- package/dist/tests/bench/driver.test.js +0 -443
- package/dist/tests/bench/evolve-metrics.js +0 -179
- package/dist/tests/bench/evolve-metrics.test.js +0 -187
- package/dist/tests/bench/evolve.js +0 -580
- package/dist/tests/bench/evolve.test.js +0 -616
- package/dist/tests/bench/failure-modes.test.js +0 -300
- package/dist/tests/bench/feedback-integrity.test.js +0 -456
- package/dist/tests/bench/leakage.test.js +0 -125
- package/dist/tests/bench/learning-curve.test.js +0 -133
- package/dist/tests/bench/metrics.js +0 -2319
- package/dist/tests/bench/metrics.test.js +0 -1144
- package/dist/tests/bench/no-os-tmpdir-invariant.test.js +0 -43
- package/dist/tests/bench/report.js +0 -1821
- package/dist/tests/bench/report.test.js +0 -989
- package/dist/tests/bench/runner.js +0 -536
- package/dist/tests/bench/runner.test.js +0 -958
- package/dist/tests/bench/search-bridge.test.js +0 -331
- package/dist/tests/bench/tmp.js +0 -41
- package/dist/tests/bench/trajectory.js +0 -116
- package/dist/tests/bench/trajectory.test.js +0 -127
- package/dist/tests/bench/verifier.js +0 -109
- package/dist/tests/bench/verifier.test.js +0 -118
- package/dist/tests/bench/workflow-evaluator.js +0 -557
- package/dist/tests/bench/workflow-evaluator.test.js +0 -421
- package/dist/tests/bench/workflow-spec.js +0 -358
- package/dist/tests/bench/workflow-spec.test.js +0 -363
- package/dist/tests/bench/workflow-trace.js +0 -438
- package/dist/tests/bench/workflow-trace.test.js +0 -254
- package/dist/tests/benchmark-search-quality.js +0 -536
- package/dist/tests/benchmark-suite.js +0 -1441
- package/dist/tests/capture-cli.test.js +0 -112
- package/dist/tests/cli-errors.test.js +0 -203
- package/dist/tests/commands/events.test.js +0 -370
- package/dist/tests/commands/history.test.js +0 -223
- package/dist/tests/commands/import.test.js +0 -103
- package/dist/tests/commands/proposal-cli.test.js +0 -209
- package/dist/tests/commands/reflect-propose-cli.test.js +0 -333
- package/dist/tests/commands/remember.test.js +0 -97
- package/dist/tests/commands/scope-flags.test.js +0 -300
- package/dist/tests/commands/search.test.js +0 -537
- package/dist/tests/commands/show-indexer-parity.test.js +0 -117
- package/dist/tests/commands/show.test.js +0 -294
- package/dist/tests/common.test.js +0 -266
- package/dist/tests/completions.test.js +0 -142
- package/dist/tests/config-cli.test.js +0 -193
- package/dist/tests/config-llm-features.test.js +0 -139
- package/dist/tests/config.test.js +0 -544
- package/dist/tests/contracts/migration-baseline.test.js +0 -43
- package/dist/tests/contracts/reflect-propose-envelope.test.js +0 -139
- package/dist/tests/contracts/spec-helpers.js +0 -46
- package/dist/tests/contracts/v1-spec-section-11-proposal-queue.test.js +0 -228
- package/dist/tests/contracts/v1-spec-section-12-agent-config.test.js +0 -56
- package/dist/tests/contracts/v1-spec-section-13-lesson-type.test.js +0 -34
- package/dist/tests/contracts/v1-spec-section-14-llm-features.test.js +0 -94
- package/dist/tests/contracts/v1-spec-section-4-1-asset-types.test.js +0 -39
- package/dist/tests/contracts/v1-spec-section-4-2-quality-rules.test.js +0 -44
- package/dist/tests/contracts/v1-spec-section-5-configuration.test.js +0 -47
- package/dist/tests/contracts/v1-spec-section-6-orchestration.test.js +0 -40
- package/dist/tests/contracts/v1-spec-section-7-module-layout.test.js +0 -58
- package/dist/tests/contracts/v1-spec-section-8-extension-points.test.js +0 -34
- package/dist/tests/contracts/v1-spec-section-9-4-cli-surface.test.js +0 -75
- package/dist/tests/contracts/v1-spec-section-9-7-llm-agent-boundary.test.js +0 -36
- package/dist/tests/core/write-source.test.js +0 -366
- package/dist/tests/curate-command.test.js +0 -87
- package/dist/tests/db-scoring.test.js +0 -201
- package/dist/tests/db.test.js +0 -654
- package/dist/tests/distill-cli-flag.test.js +0 -208
- package/dist/tests/distill.test.js +0 -515
- package/dist/tests/docker-install.test.js +0 -120
- package/dist/tests/e2e.test.js +0 -1398
- package/dist/tests/embedder.test.js +0 -340
- package/dist/tests/embedding-model-config.test.js +0 -379
- package/dist/tests/feedback-command.test.js +0 -172
- package/dist/tests/file-context.test.js +0 -552
- package/dist/tests/fixtures/scripts/git/summarize-diff.js +0 -9
- package/dist/tests/fixtures/scripts/lint/eslint-check.js +0 -7
- package/dist/tests/fixtures/stashes/load.js +0 -166
- package/dist/tests/fixtures/stashes/load.test.js +0 -88
- package/dist/tests/fixtures/stashes/ranking-baseline/scripts/mem0-search.js +0 -12
- package/dist/tests/frontmatter.test.js +0 -190
- package/dist/tests/fts-field-weighting.test.js +0 -254
- package/dist/tests/fuzzy-search.test.js +0 -230
- package/dist/tests/git-provider-clone.test.js +0 -45
- package/dist/tests/github.test.js +0 -161
- package/dist/tests/graph-boost-ranking.test.js +0 -305
- package/dist/tests/graph-extraction.test.js +0 -282
- package/dist/tests/helpers/usage-events.js +0 -8
- package/dist/tests/index-pass-llm.test.js +0 -161
- package/dist/tests/indexer.test.js +0 -559
- package/dist/tests/info-command.test.js +0 -166
- package/dist/tests/init.test.js +0 -69
- package/dist/tests/install-script.test.js +0 -246
- package/dist/tests/integration/agent-real-profile.test.js +0 -94
- package/dist/tests/issue-36-repro.test.js +0 -304
- package/dist/tests/issues-191-194.test.js +0 -160
- package/dist/tests/lesson-lint.test.js +0 -111
- package/dist/tests/llm-client.test.js +0 -115
- package/dist/tests/llm-feature-gate.test.js +0 -151
- package/dist/tests/llm.test.js +0 -139
- package/dist/tests/lockfile.test.js +0 -216
- package/dist/tests/manifest.test.js +0 -205
- package/dist/tests/markdown.test.js +0 -126
- package/dist/tests/matchers-unit.test.js +0 -189
- package/dist/tests/memory-inference.test.js +0 -299
- package/dist/tests/merge-scoring.test.js +0 -136
- package/dist/tests/metadata.test.js +0 -313
- package/dist/tests/migration-help.test.js +0 -89
- package/dist/tests/origin-resolve.test.js +0 -124
- package/dist/tests/output-baseline.test.js +0 -217
- package/dist/tests/output-shapes-unit.test.js +0 -476
- package/dist/tests/parallel-search.test.js +0 -272
- package/dist/tests/parameter-metadata.test.js +0 -365
- package/dist/tests/paths.test.js +0 -177
- package/dist/tests/progressive-disclosure.test.js +0 -280
- package/dist/tests/proposals.test.js +0 -279
- package/dist/tests/proposed-quality.test.js +0 -271
- package/dist/tests/provider-registry.test.js +0 -32
- package/dist/tests/ranking-regression.test.js +0 -548
- package/dist/tests/reflect-propose.test.js +0 -455
- package/dist/tests/registry-build-index.test.js +0 -378
- package/dist/tests/registry-cli.test.js +0 -290
- package/dist/tests/registry-index-v2.test.js +0 -430
- package/dist/tests/registry-install.test.js +0 -728
- package/dist/tests/registry-providers/parity.test.js +0 -189
- package/dist/tests/registry-providers/skills-sh.test.js +0 -309
- package/dist/tests/registry-providers/static-index.test.js +0 -204
- package/dist/tests/registry-resolve.test.js +0 -126
- package/dist/tests/registry-search.test.js +0 -723
- package/dist/tests/remember-frontmatter.test.js +0 -380
- package/dist/tests/remember-unit.test.js +0 -123
- package/dist/tests/ripgrep-install.test.js +0 -251
- package/dist/tests/ripgrep-resolve.test.js +0 -108
- package/dist/tests/ripgrep.test.js +0 -163
- package/dist/tests/save-command.test.js +0 -94
- package/dist/tests/save-trust-qa-fixes.test.js +0 -270
- package/dist/tests/scoring-pipeline.test.js +0 -648
- package/dist/tests/search-include-proposed-cli.test.js +0 -118
- package/dist/tests/self-update.test.js +0 -442
- package/dist/tests/semantic-search-e2e.test.js +0 -512
- package/dist/tests/semantic-status.test.js +0 -471
- package/dist/tests/setup-run.integration.js +0 -877
- package/dist/tests/setup-wizard.test.js +0 -198
- package/dist/tests/setup.test.js +0 -131
- package/dist/tests/source-add.test.js +0 -11
- package/dist/tests/source-clone.test.js +0 -254
- package/dist/tests/source-manage.test.js +0 -366
- package/dist/tests/source-providers/filesystem.test.js +0 -82
- package/dist/tests/source-providers/git.test.js +0 -252
- package/dist/tests/source-providers/website.test.js +0 -128
- package/dist/tests/source-qa-fixes.test.js +0 -268
- package/dist/tests/source-registry.test.js +0 -350
- package/dist/tests/source-resolve.test.js +0 -100
- package/dist/tests/source-source.test.js +0 -221
- package/dist/tests/source.test.js +0 -533
- package/dist/tests/tar-utils-scan.test.js +0 -73
- package/dist/tests/toggle-components.test.js +0 -73
- package/dist/tests/usage-telemetry.test.js +0 -265
- package/dist/tests/utility-scoring.test.js +0 -558
- package/dist/tests/vault-load-error.test.js +0 -78
- package/dist/tests/vault-qa-fixes.test.js +0 -194
- package/dist/tests/vault.test.js +0 -429
- package/dist/tests/vector-search.test.js +0 -608
- package/dist/tests/walker.test.js +0 -252
- package/dist/tests/wave2-cluster-bc.test.js +0 -228
- package/dist/tests/wave2-cluster-d.test.js +0 -180
- package/dist/tests/wave2-cluster-e.test.js +0 -179
- package/dist/tests/wiki-qa-fixes.test.js +0 -270
- package/dist/tests/wiki.test.js +0 -529
- package/dist/tests/workflow-cli.test.js +0 -271
- package/dist/tests/workflow-markdown.test.js +0 -171
- package/dist/tests/workflow-path-escape.test.js +0 -132
- package/dist/tests/workflow-qa-fixes.test.js +0 -377
- package/dist/tests/workflows/indexer-rejection.test.js +0 -213
- /package/dist/{src/commands → commands}/completions.js +0 -0
- /package/dist/{src/commands → commands}/curate.js +0 -0
- /package/dist/{src/commands → commands}/distill.js +0 -0
- /package/dist/{src/commands → commands}/events.js +0 -0
- /package/dist/{src/commands → commands}/info.js +0 -0
- /package/dist/{src/commands → commands}/init.js +0 -0
- /package/dist/{src/commands → commands}/install-audit.js +0 -0
- /package/dist/{src/commands → commands}/installed-stashes.js +0 -0
- /package/dist/{src/commands → commands}/migration-help.js +0 -0
- /package/dist/{src/commands → commands}/proposal.js +0 -0
- /package/dist/{src/commands → commands}/propose.js +0 -0
- /package/dist/{src/commands → commands}/reflect.js +0 -0
- /package/dist/{src/commands → commands}/remember.js +0 -0
- /package/dist/{src/commands → commands}/self-update.js +0 -0
- /package/dist/{src/commands → commands}/source-clone.js +0 -0
- /package/dist/{src/commands → commands}/vault.js +0 -0
- /package/dist/{src/core → core}/asset-ref.js +0 -0
- /package/dist/{src/core → core}/asset-registry.js +0 -0
- /package/dist/{src/core → core}/asset-spec.js +0 -0
- /package/dist/{src/core → core}/common.js +0 -0
- /package/dist/{src/core → core}/errors.js +0 -0
- /package/dist/{src/core → core}/events.js +0 -0
- /package/dist/{src/core → core}/frontmatter.js +0 -0
- /package/dist/{src/core → core}/lesson-lint.js +0 -0
- /package/dist/{src/core → core}/markdown.js +0 -0
- /package/dist/{src/core → core}/paths.js +0 -0
- /package/dist/{src/core → core}/proposals.js +0 -0
- /package/dist/{src/core → core}/warn.js +0 -0
- /package/dist/{src/core → core}/write-source.js +0 -0
- /package/dist/{src/indexer → indexer}/db.js +0 -0
- /package/dist/{src/indexer → indexer}/file-context.js +0 -0
- /package/dist/{src/indexer → indexer}/graph-boost.js +0 -0
- /package/dist/{src/indexer → indexer}/graph-extraction.js +0 -0
- /package/dist/{src/indexer → indexer}/manifest.js +0 -0
- /package/dist/{src/indexer → indexer}/memory-inference.js +0 -0
- /package/dist/{src/indexer → indexer}/metadata.js +0 -0
- /package/dist/{src/indexer → indexer}/search-fields.js +0 -0
- /package/dist/{src/indexer → indexer}/semantic-status.js +0 -0
- /package/dist/{src/indexer → indexer}/usage-events.js +0 -0
- /package/dist/{src/indexer → indexer}/walker.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/config.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/detect.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/index.js +0 -0
- /package/dist/{src/integrations → integrations}/agent/prompts.js +0 -0
- /package/dist/{src/integrations → integrations}/lockfile.js +0 -0
- /package/dist/{src/llm → llm}/client.js +0 -0
- /package/dist/{src/llm → llm}/embedder.js +0 -0
- /package/dist/{src/llm → llm}/embedders/cache.js +0 -0
- /package/dist/{src/llm → llm}/embedders/local.js +0 -0
- /package/dist/{src/llm → llm}/embedders/types.js +0 -0
- /package/dist/{src/llm → llm}/feature-gate.js +0 -0
- /package/dist/{src/llm → llm}/graph-extract.js +0 -0
- /package/dist/{src/llm → llm}/index-passes.js +0 -0
- /package/dist/{src/llm → llm}/memory-infer.js +0 -0
- /package/dist/{src/llm → llm}/metadata-enhance.js +0 -0
- /package/dist/{src/output → output}/context.js +0 -0
- /package/dist/{src/registry → registry}/create-provider-registry.js +0 -0
- /package/dist/{src/registry → registry}/factory.js +0 -0
- /package/dist/{src/registry → registry}/origin-resolve.js +0 -0
- /package/dist/{src/registry → registry}/providers/index.js +0 -0
- /package/dist/{src/registry → registry}/providers/skills-sh.js +0 -0
- /package/dist/{src/registry → registry}/providers/types.js +0 -0
- /package/dist/{src/registry → registry}/resolve.js +0 -0
- /package/dist/{src/registry → registry}/types.js +0 -0
- /package/dist/{src/setup → setup}/detect.js +0 -0
- /package/dist/{src/setup → setup}/ripgrep-install.js +0 -0
- /package/dist/{src/setup → setup}/ripgrep-resolve.js +0 -0
- /package/dist/{src/setup → setup}/steps.js +0 -0
- /package/dist/{src/sources → sources}/include.js +0 -0
- /package/dist/{src/sources → sources}/provider-factory.js +0 -0
- /package/dist/{src/sources → sources}/provider.js +0 -0
- /package/dist/{src/sources → sources}/providers/filesystem.js +0 -0
- /package/dist/{src/sources → sources}/providers/git.js +0 -0
- /package/dist/{src/sources → sources}/providers/index.js +0 -0
- /package/dist/{src/sources → sources}/providers/install-types.js +0 -0
- /package/dist/{src/sources → sources}/providers/npm.js +0 -0
- /package/dist/{src/sources → sources}/providers/provider-utils.js +0 -0
- /package/dist/{src/sources → sources}/providers/sync-from-ref.js +0 -0
- /package/dist/{src/sources → sources}/providers/tar-utils.js +0 -0
- /package/dist/{src/sources → sources}/providers/website.js +0 -0
- /package/dist/{src/sources → sources}/resolve.js +0 -0
- /package/dist/{src/sources → sources}/types.js +0 -0
- /package/dist/{src/templates → templates}/wiki-templates.js +0 -0
- /package/dist/{src/version.js → version.js} +0 -0
- /package/dist/{src/workflows → workflows}/authoring.js +0 -0
- /package/dist/{src/workflows → workflows}/cli.js +0 -0
- /package/dist/{src/workflows → workflows}/db.js +0 -0
- /package/dist/{src/workflows → workflows}/document-cache.js +0 -0
- /package/dist/{src/workflows → workflows}/parser.js +0 -0
- /package/dist/{src/workflows → workflows}/renderer.js +0 -0
- /package/dist/{src/workflows → workflows}/schema.js +0 -0
- /package/dist/{src/workflows → workflows}/validator.js +0 -0
package/dist/tests/db.test.js
DELETED
|
@@ -1,654 +0,0 @@
|
|
|
1
|
-
import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { closeDatabase, DB_VERSION, deleteEntriesByDir, getAllEntries, getEntriesByDir, getEntryById, getEntryCount, getMeta, isVecAvailable, openDatabase, rebuildFts, searchFts, searchVec, setMeta, upsertEmbedding, upsertEntry, } from "../src/indexer/db";
|
|
6
|
-
// ── Temp directory management ───────────────────────────────────────────────
|
|
7
|
-
const createdTmpDirs = [];
|
|
8
|
-
function tmpDir(label = "db") {
|
|
9
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `akm-${label}-`));
|
|
10
|
-
createdTmpDirs.push(dir);
|
|
11
|
-
return dir;
|
|
12
|
-
}
|
|
13
|
-
function tmpDbPath(label = "db") {
|
|
14
|
-
const dir = tmpDir(label);
|
|
15
|
-
return path.join(dir, "test.db");
|
|
16
|
-
}
|
|
17
|
-
afterAll(() => {
|
|
18
|
-
for (const dir of createdTmpDirs) {
|
|
19
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
// ── Environment isolation ───────────────────────────────────────────────────
|
|
23
|
-
const savedEnv = {};
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
savedEnv.XDG_CACHE_HOME = process.env.XDG_CACHE_HOME;
|
|
26
|
-
savedEnv.XDG_CONFIG_HOME = process.env.XDG_CONFIG_HOME;
|
|
27
|
-
process.env.XDG_CACHE_HOME = tmpDir("cache");
|
|
28
|
-
process.env.XDG_CONFIG_HOME = tmpDir("config");
|
|
29
|
-
});
|
|
30
|
-
afterEach(() => {
|
|
31
|
-
for (const [key, val] of Object.entries(savedEnv)) {
|
|
32
|
-
if (val === undefined) {
|
|
33
|
-
delete process.env[key];
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
process.env[key] = val;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
41
|
-
function makeEntry(overrides) {
|
|
42
|
-
return {
|
|
43
|
-
description: "A test entry",
|
|
44
|
-
...overrides,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function insertTestEntry(db, key, opts) {
|
|
48
|
-
const type = opts?.type ?? "script";
|
|
49
|
-
const entry = makeEntry({ name: key, type, description: opts?.description ?? `Description for ${key}` });
|
|
50
|
-
return upsertEntry(db, key, opts?.dirPath ?? "/test/dir", opts?.filePath ?? `/test/dir/${key}.ts`, opts?.stashDir ?? "/test/stash", entry, opts?.searchText ?? `${key} ${entry.description}`);
|
|
51
|
-
}
|
|
52
|
-
// ── Section 1.1: Schema ────────────────────────────────────────────────────
|
|
53
|
-
describe("Schema", () => {
|
|
54
|
-
test("openDatabase creates schema with correct version", () => {
|
|
55
|
-
const dbPath = tmpDbPath();
|
|
56
|
-
const db = openDatabase(dbPath);
|
|
57
|
-
try {
|
|
58
|
-
expect(getMeta(db, "version")).toBe(String(DB_VERSION));
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
closeDatabase(db);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
test("openDatabase with mismatched version drops and recreates tables", () => {
|
|
65
|
-
const dbPath = tmpDbPath();
|
|
66
|
-
// Open and insert some data, then tamper with the version
|
|
67
|
-
let db = openDatabase(dbPath);
|
|
68
|
-
insertTestEntry(db, "old-entry");
|
|
69
|
-
expect(getEntryCount(db)).toBe(1);
|
|
70
|
-
setMeta(db, "version", "0");
|
|
71
|
-
closeDatabase(db);
|
|
72
|
-
// Reopen — should detect mismatch, drop tables, recreate
|
|
73
|
-
db = openDatabase(dbPath);
|
|
74
|
-
try {
|
|
75
|
-
expect(getMeta(db, "version")).toBe(String(DB_VERSION));
|
|
76
|
-
expect(getEntryCount(db)).toBe(0);
|
|
77
|
-
}
|
|
78
|
-
finally {
|
|
79
|
-
closeDatabase(db);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
test("openDatabase creates FTS5 table", () => {
|
|
83
|
-
const dbPath = tmpDbPath();
|
|
84
|
-
const db = openDatabase(dbPath);
|
|
85
|
-
try {
|
|
86
|
-
const row = db.prepare("SELECT name FROM sqlite_master WHERE name = 'entries_fts'").get();
|
|
87
|
-
expect(row).toBeDefined();
|
|
88
|
-
expect(row?.name).toBe("entries_fts");
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
closeDatabase(db);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
test("isVecAvailable returns true when sqlite-vec is installed", () => {
|
|
95
|
-
const dbPath = tmpDbPath();
|
|
96
|
-
const db = openDatabase(dbPath);
|
|
97
|
-
try {
|
|
98
|
-
expect(isVecAvailable(db)).toBe(true);
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
closeDatabase(db);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
test("embeddingDim is stored and triggers vec table recreation", () => {
|
|
105
|
-
const dbPath = tmpDbPath();
|
|
106
|
-
let db = openDatabase(dbPath, { embeddingDim: 512 });
|
|
107
|
-
try {
|
|
108
|
-
if (isVecAvailable(db)) {
|
|
109
|
-
expect(getMeta(db, "embeddingDim")).toBe("512");
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
closeDatabase(db);
|
|
114
|
-
}
|
|
115
|
-
// Reopen with a different dimension
|
|
116
|
-
db = openDatabase(dbPath, { embeddingDim: 768 });
|
|
117
|
-
try {
|
|
118
|
-
if (isVecAvailable(db)) {
|
|
119
|
-
expect(getMeta(db, "embeddingDim")).toBe("768");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
finally {
|
|
123
|
-
closeDatabase(db);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
// ── Section 1.2: Entry CRUD ────────────────────────────────────────────────
|
|
128
|
-
describe("Entry CRUD", () => {
|
|
129
|
-
test("upsertEntry inserts a new entry and returns its id", () => {
|
|
130
|
-
const db = openDatabase(tmpDbPath());
|
|
131
|
-
try {
|
|
132
|
-
const id = insertTestEntry(db, "my-tool");
|
|
133
|
-
expect(id).toBeGreaterThan(0);
|
|
134
|
-
expect(getEntryCount(db)).toBe(1);
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
closeDatabase(db);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
test("upsertEntry updates on conflict (same entry_key)", () => {
|
|
141
|
-
const db = openDatabase(tmpDbPath());
|
|
142
|
-
try {
|
|
143
|
-
insertTestEntry(db, "my-tool", { description: "original description" });
|
|
144
|
-
expect(getEntryCount(db)).toBe(1);
|
|
145
|
-
// Upsert with updated description
|
|
146
|
-
insertTestEntry(db, "my-tool", { description: "updated description" });
|
|
147
|
-
expect(getEntryCount(db)).toBe(1);
|
|
148
|
-
// Verify the entry reflects the update
|
|
149
|
-
const entries = getAllEntries(db);
|
|
150
|
-
expect(entries).toHaveLength(1);
|
|
151
|
-
expect(entries[0].entry.description).toBe("updated description");
|
|
152
|
-
}
|
|
153
|
-
finally {
|
|
154
|
-
closeDatabase(db);
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
test("getEntryById returns the entry or undefined", () => {
|
|
158
|
-
const db = openDatabase(tmpDbPath());
|
|
159
|
-
try {
|
|
160
|
-
const id = insertTestEntry(db, "fetch-tool", { description: "Fetches data" });
|
|
161
|
-
const result = getEntryById(db, id);
|
|
162
|
-
expect(result).toBeDefined();
|
|
163
|
-
expect(result?.entry.name).toBe("fetch-tool");
|
|
164
|
-
expect(result?.entry.description).toBe("Fetches data");
|
|
165
|
-
expect(result?.filePath).toBe("/test/dir/fetch-tool.ts");
|
|
166
|
-
// Non-existent ID
|
|
167
|
-
const missing = getEntryById(db, 99999);
|
|
168
|
-
expect(missing).toBeUndefined();
|
|
169
|
-
}
|
|
170
|
-
finally {
|
|
171
|
-
closeDatabase(db);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
test("getEntriesByDir returns entries for a directory", () => {
|
|
175
|
-
const db = openDatabase(tmpDbPath());
|
|
176
|
-
try {
|
|
177
|
-
insertTestEntry(db, "tool-a", { dirPath: "/project/alpha" });
|
|
178
|
-
insertTestEntry(db, "tool-b", { dirPath: "/project/alpha" });
|
|
179
|
-
insertTestEntry(db, "tool-c", { dirPath: "/project/beta" });
|
|
180
|
-
const alphaEntries = getEntriesByDir(db, "/project/alpha");
|
|
181
|
-
expect(alphaEntries).toHaveLength(2);
|
|
182
|
-
const keys = alphaEntries.map((e) => e.entryKey).sort();
|
|
183
|
-
expect(keys).toEqual(["tool-a", "tool-b"]);
|
|
184
|
-
const betaEntries = getEntriesByDir(db, "/project/beta");
|
|
185
|
-
expect(betaEntries).toHaveLength(1);
|
|
186
|
-
expect(betaEntries[0].entryKey).toBe("tool-c");
|
|
187
|
-
}
|
|
188
|
-
finally {
|
|
189
|
-
closeDatabase(db);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
test("getAllEntries returns all entries", () => {
|
|
193
|
-
const db = openDatabase(tmpDbPath());
|
|
194
|
-
try {
|
|
195
|
-
insertTestEntry(db, "entry-1");
|
|
196
|
-
insertTestEntry(db, "entry-2");
|
|
197
|
-
insertTestEntry(db, "entry-3");
|
|
198
|
-
const all = getAllEntries(db);
|
|
199
|
-
expect(all).toHaveLength(3);
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
closeDatabase(db);
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
test("getAllEntries with type filter", () => {
|
|
206
|
-
const db = openDatabase(tmpDbPath());
|
|
207
|
-
try {
|
|
208
|
-
insertTestEntry(db, "script-1", { type: "script" });
|
|
209
|
-
insertTestEntry(db, "script-2", { type: "script" });
|
|
210
|
-
insertTestEntry(db, "skill-1", { type: "skill" });
|
|
211
|
-
const scripts = getAllEntries(db, "script");
|
|
212
|
-
expect(scripts).toHaveLength(2);
|
|
213
|
-
for (const t of scripts) {
|
|
214
|
-
expect(t.entry.type).toBe("script");
|
|
215
|
-
}
|
|
216
|
-
const skills = getAllEntries(db, "skill");
|
|
217
|
-
expect(skills).toHaveLength(1);
|
|
218
|
-
expect(skills[0].entry.type).toBe("skill");
|
|
219
|
-
}
|
|
220
|
-
finally {
|
|
221
|
-
closeDatabase(db);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
test("deleteEntriesByDir removes entries", () => {
|
|
225
|
-
const db = openDatabase(tmpDbPath());
|
|
226
|
-
try {
|
|
227
|
-
insertTestEntry(db, "del-1", { dirPath: "/to-delete" });
|
|
228
|
-
insertTestEntry(db, "del-2", { dirPath: "/to-delete" });
|
|
229
|
-
insertTestEntry(db, "keep-1", { dirPath: "/to-keep" });
|
|
230
|
-
expect(getEntryCount(db)).toBe(3);
|
|
231
|
-
deleteEntriesByDir(db, "/to-delete");
|
|
232
|
-
expect(getEntryCount(db)).toBe(1);
|
|
233
|
-
const remaining = getAllEntries(db);
|
|
234
|
-
expect(remaining[0].entryKey).toBe("keep-1");
|
|
235
|
-
}
|
|
236
|
-
finally {
|
|
237
|
-
closeDatabase(db);
|
|
238
|
-
}
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
// ── Section 1.3: FTS search ────────────────────────────────────────────────
|
|
242
|
-
describe("FTS search", () => {
|
|
243
|
-
test("searchFts returns results ranked by BM25", () => {
|
|
244
|
-
const db = openDatabase(tmpDbPath());
|
|
245
|
-
try {
|
|
246
|
-
insertTestEntry(db, "deploy-tool", {
|
|
247
|
-
description: "Deploy applications to production servers",
|
|
248
|
-
searchText: "deploy deploy deploy applications production servers deployment",
|
|
249
|
-
});
|
|
250
|
-
insertTestEntry(db, "infra-tool", {
|
|
251
|
-
description: "Cloud infrastructure for deploy pipelines",
|
|
252
|
-
searchText: "cloud infrastructure management scaling networking deploy pipelines automation",
|
|
253
|
-
});
|
|
254
|
-
rebuildFts(db);
|
|
255
|
-
const results = searchFts(db, "deploy", 10);
|
|
256
|
-
expect(results.length).toBe(2);
|
|
257
|
-
expect(results[0].entry.name).toBe("deploy-tool");
|
|
258
|
-
expect(results[1].entry.name).toBe("infra-tool");
|
|
259
|
-
}
|
|
260
|
-
finally {
|
|
261
|
-
closeDatabase(db);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
test("searchFts with type filter", () => {
|
|
265
|
-
const db = openDatabase(tmpDbPath());
|
|
266
|
-
try {
|
|
267
|
-
insertTestEntry(db, "build-script", {
|
|
268
|
-
type: "script",
|
|
269
|
-
description: "Build the project",
|
|
270
|
-
searchText: "build project compilation",
|
|
271
|
-
});
|
|
272
|
-
insertTestEntry(db, "build-skill", {
|
|
273
|
-
type: "skill",
|
|
274
|
-
description: "Build pipeline skill",
|
|
275
|
-
searchText: "build pipeline skill compilation",
|
|
276
|
-
});
|
|
277
|
-
rebuildFts(db);
|
|
278
|
-
const scriptResults = searchFts(db, "build", 10, "script");
|
|
279
|
-
expect(scriptResults).toHaveLength(1);
|
|
280
|
-
expect(scriptResults[0].entry.type).toBe("script");
|
|
281
|
-
const allResults = searchFts(db, "build", 10);
|
|
282
|
-
expect(allResults).toHaveLength(2);
|
|
283
|
-
}
|
|
284
|
-
finally {
|
|
285
|
-
closeDatabase(db);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
test("searchFts sanitizes query tokens", () => {
|
|
289
|
-
const db = openDatabase(tmpDbPath());
|
|
290
|
-
try {
|
|
291
|
-
insertTestEntry(db, "hello-tool", {
|
|
292
|
-
description: "hello world 123 greeting",
|
|
293
|
-
searchText: "hello world 123 greeting",
|
|
294
|
-
});
|
|
295
|
-
rebuildFts(db);
|
|
296
|
-
// Should not throw a SQL error despite special characters
|
|
297
|
-
const results = searchFts(db, "hello! world@123", 10);
|
|
298
|
-
expect(results[0].entry.name).toBe("hello-tool");
|
|
299
|
-
// "hello" and "world" and "123" are valid tokens after sanitization
|
|
300
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
301
|
-
}
|
|
302
|
-
finally {
|
|
303
|
-
closeDatabase(db);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
test("searchFts returns empty for garbage query", () => {
|
|
307
|
-
const db = openDatabase(tmpDbPath());
|
|
308
|
-
try {
|
|
309
|
-
insertTestEntry(db, "some-tool", { searchText: "some useful tool" });
|
|
310
|
-
rebuildFts(db);
|
|
311
|
-
const results = searchFts(db, "!@#$%", 10);
|
|
312
|
-
expect(results).toEqual([]);
|
|
313
|
-
}
|
|
314
|
-
finally {
|
|
315
|
-
closeDatabase(db);
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
// ── T5: sanitizeFtsQuery edge cases ──────────────────────────────────────
|
|
319
|
-
// sanitizeFtsQuery is private, so we test it indirectly through searchFts.
|
|
320
|
-
test("query that becomes empty after sanitization returns no results", () => {
|
|
321
|
-
const db = openDatabase(tmpDbPath());
|
|
322
|
-
try {
|
|
323
|
-
insertTestEntry(db, "target", { searchText: "some useful content" });
|
|
324
|
-
rebuildFts(db);
|
|
325
|
-
// "! @" contains only non-alphanumeric chars; after sanitization all
|
|
326
|
-
// tokens are stripped, leaving an empty FTS query.
|
|
327
|
-
const results = searchFts(db, "! @", 10);
|
|
328
|
-
expect(results).toEqual([]);
|
|
329
|
-
}
|
|
330
|
-
finally {
|
|
331
|
-
closeDatabase(db);
|
|
332
|
-
}
|
|
333
|
-
});
|
|
334
|
-
test("query with only 1-character tokens returns no results when content has no matching single-char terms", () => {
|
|
335
|
-
const db = openDatabase(tmpDbPath());
|
|
336
|
-
try {
|
|
337
|
-
insertTestEntry(db, "abc-tool", { searchText: "alpha bravo charlie" });
|
|
338
|
-
rebuildFts(db);
|
|
339
|
-
// "a b c" — single-char tokens are passed to FTS5 but don't match
|
|
340
|
-
// "alpha", "bravo", "charlie" because FTS5 doesn't do prefix matching.
|
|
341
|
-
const results = searchFts(db, "a b c", 10);
|
|
342
|
-
expect(results).toEqual([]);
|
|
343
|
-
}
|
|
344
|
-
finally {
|
|
345
|
-
closeDatabase(db);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
test("FTS5 syntax injection is neutralized", () => {
|
|
349
|
-
const db = openDatabase(tmpDbPath());
|
|
350
|
-
try {
|
|
351
|
-
insertTestEntry(db, "foo-tool", { description: "foo bar baz", searchText: "foo bar baz" });
|
|
352
|
-
insertTestEntry(db, "bar-tool", { description: "bar qux quux", searchText: "bar qux quux" });
|
|
353
|
-
rebuildFts(db);
|
|
354
|
-
// "NEAR(foo, bar)" is raw FTS5 syntax that should be sanitized.
|
|
355
|
-
// After sanitization, syntax chars and NEAR are stripped, leaving
|
|
356
|
-
// tokens "foo" "bar" (implicit AND) — should not throw and should
|
|
357
|
-
// return matches containing both foo and bar.
|
|
358
|
-
const results = searchFts(db, "NEAR(foo, bar)", 10);
|
|
359
|
-
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
360
|
-
// foo-tool has both "foo" and "bar" in its search text
|
|
361
|
-
const names = results.map((r) => r.entry.name);
|
|
362
|
-
expect(names).toContain("foo-tool");
|
|
363
|
-
}
|
|
364
|
-
finally {
|
|
365
|
-
closeDatabase(db);
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
test("normal multi-word query returns correct results", () => {
|
|
369
|
-
const db = openDatabase(tmpDbPath());
|
|
370
|
-
try {
|
|
371
|
-
insertTestEntry(db, "deploy-prod", {
|
|
372
|
-
description: "deploy application production servers",
|
|
373
|
-
searchText: "deploy application production servers",
|
|
374
|
-
});
|
|
375
|
-
insertTestEntry(db, "test-runner", {
|
|
376
|
-
description: "test runner unit integration",
|
|
377
|
-
searchText: "test runner unit integration",
|
|
378
|
-
});
|
|
379
|
-
rebuildFts(db);
|
|
380
|
-
const results = searchFts(db, "deploy production", 10);
|
|
381
|
-
expect(results).toHaveLength(1);
|
|
382
|
-
expect(results[0].entry.name).toBe("deploy-prod");
|
|
383
|
-
}
|
|
384
|
-
finally {
|
|
385
|
-
closeDatabase(db);
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
test("rebuildFts synchronizes FTS with entries table", () => {
|
|
389
|
-
const db = openDatabase(tmpDbPath());
|
|
390
|
-
try {
|
|
391
|
-
insertTestEntry(db, "alpha", { description: "alpha functionality", searchText: "alpha functionality" });
|
|
392
|
-
insertTestEntry(db, "beta", { description: "beta functionality", searchText: "beta functionality" });
|
|
393
|
-
insertTestEntry(db, "gamma", { description: "gamma functionality", searchText: "gamma functionality" });
|
|
394
|
-
rebuildFts(db);
|
|
395
|
-
const alphaResults = searchFts(db, "alpha", 10);
|
|
396
|
-
expect(alphaResults).toHaveLength(1);
|
|
397
|
-
expect(alphaResults[0].entry.name).toBe("alpha");
|
|
398
|
-
const allResults = searchFts(db, "functionality", 10);
|
|
399
|
-
expect(allResults).toHaveLength(3);
|
|
400
|
-
}
|
|
401
|
-
finally {
|
|
402
|
-
closeDatabase(db);
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
// ── Section 1.4: Meta helpers ──────────────────────────────────────────────
|
|
407
|
-
describe("Meta helpers", () => {
|
|
408
|
-
test("getMeta returns undefined for missing key", () => {
|
|
409
|
-
const db = openDatabase(tmpDbPath());
|
|
410
|
-
try {
|
|
411
|
-
const val = getMeta(db, "nonexistent-key");
|
|
412
|
-
expect(val).toBeUndefined();
|
|
413
|
-
}
|
|
414
|
-
finally {
|
|
415
|
-
closeDatabase(db);
|
|
416
|
-
}
|
|
417
|
-
});
|
|
418
|
-
test("setMeta and getMeta round-trip", () => {
|
|
419
|
-
const db = openDatabase(tmpDbPath());
|
|
420
|
-
try {
|
|
421
|
-
setMeta(db, "test-key", "test-value");
|
|
422
|
-
expect(getMeta(db, "test-key")).toBe("test-value");
|
|
423
|
-
}
|
|
424
|
-
finally {
|
|
425
|
-
closeDatabase(db);
|
|
426
|
-
}
|
|
427
|
-
});
|
|
428
|
-
test("setMeta overwrites existing key", () => {
|
|
429
|
-
const db = openDatabase(tmpDbPath());
|
|
430
|
-
try {
|
|
431
|
-
setMeta(db, "overwrite-key", "first");
|
|
432
|
-
expect(getMeta(db, "overwrite-key")).toBe("first");
|
|
433
|
-
setMeta(db, "overwrite-key", "second");
|
|
434
|
-
expect(getMeta(db, "overwrite-key")).toBe("second");
|
|
435
|
-
}
|
|
436
|
-
finally {
|
|
437
|
-
closeDatabase(db);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
// ── Section 1.5: Vector / Embedding integration ────────────────────────────
|
|
442
|
-
describe("Vector / Embedding integration", () => {
|
|
443
|
-
test("openDatabase creates vec table when extension available", () => {
|
|
444
|
-
const dbPath = tmpDbPath();
|
|
445
|
-
const db = openDatabase(dbPath);
|
|
446
|
-
try {
|
|
447
|
-
expect(isVecAvailable(db)).toBe(true);
|
|
448
|
-
const row = db.prepare("SELECT name FROM sqlite_master WHERE name = 'entries_vec'").get();
|
|
449
|
-
expect(row).toBeDefined();
|
|
450
|
-
expect(row?.name).toBe("entries_vec");
|
|
451
|
-
}
|
|
452
|
-
finally {
|
|
453
|
-
closeDatabase(db);
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
test("upsertEmbedding stores and searchVec retrieves by similarity", () => {
|
|
457
|
-
const dbPath = tmpDbPath();
|
|
458
|
-
const db = openDatabase(dbPath, { embeddingDim: 4 });
|
|
459
|
-
try {
|
|
460
|
-
expect(isVecAvailable(db)).toBe(true);
|
|
461
|
-
// Insert two entries with distinct embeddings
|
|
462
|
-
const id1 = insertTestEntry(db, "vec-tool-1", { searchText: "deployment" });
|
|
463
|
-
const id2 = insertTestEntry(db, "vec-tool-2", { searchText: "testing" });
|
|
464
|
-
// Embedding vectors: tool-1 points "north", tool-2 points "east"
|
|
465
|
-
upsertEmbedding(db, id1, [1, 0, 0, 0]);
|
|
466
|
-
upsertEmbedding(db, id2, [0, 1, 0, 0]);
|
|
467
|
-
// Query close to tool-1's embedding
|
|
468
|
-
const results = searchVec(db, [0.9, 0.1, 0, 0], 10);
|
|
469
|
-
expect(results.length).toBe(2);
|
|
470
|
-
// tool-1 should be the closest (smallest distance)
|
|
471
|
-
expect(results[0].id).toBe(id1);
|
|
472
|
-
expect(results[0].distance).toBeLessThan(results[1].distance);
|
|
473
|
-
}
|
|
474
|
-
finally {
|
|
475
|
-
closeDatabase(db);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
test("upsertEmbedding overwrites existing embedding for same entry", () => {
|
|
479
|
-
const dbPath = tmpDbPath();
|
|
480
|
-
const db = openDatabase(dbPath, { embeddingDim: 4 });
|
|
481
|
-
try {
|
|
482
|
-
const id = insertTestEntry(db, "vec-update", { searchText: "update test" });
|
|
483
|
-
upsertEmbedding(db, id, [1, 0, 0, 0]);
|
|
484
|
-
let results = searchVec(db, [1, 0, 0, 0], 10);
|
|
485
|
-
expect(results.length).toBe(1);
|
|
486
|
-
expect(results[0].distance).toBeCloseTo(0, 2);
|
|
487
|
-
// Overwrite with a completely different direction
|
|
488
|
-
upsertEmbedding(db, id, [0, 0, 0, 1]);
|
|
489
|
-
results = searchVec(db, [0, 0, 0, 1], 10);
|
|
490
|
-
expect(results.length).toBe(1);
|
|
491
|
-
expect(results[0].distance).toBeCloseTo(0, 2);
|
|
492
|
-
// Original direction should now be far
|
|
493
|
-
results = searchVec(db, [1, 0, 0, 0], 10);
|
|
494
|
-
expect(results[0].distance).toBeGreaterThan(1);
|
|
495
|
-
}
|
|
496
|
-
finally {
|
|
497
|
-
closeDatabase(db);
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
test("searchVec respects k limit", () => {
|
|
501
|
-
const dbPath = tmpDbPath();
|
|
502
|
-
const db = openDatabase(dbPath, { embeddingDim: 4 });
|
|
503
|
-
try {
|
|
504
|
-
// Insert 5 entries with embeddings
|
|
505
|
-
for (let i = 0; i < 5; i++) {
|
|
506
|
-
const id = insertTestEntry(db, `vec-k-${i}`, { searchText: `entry ${i}` });
|
|
507
|
-
const vec = [0, 0, 0, 0];
|
|
508
|
-
vec[i % 4] = 1;
|
|
509
|
-
upsertEmbedding(db, id, vec);
|
|
510
|
-
}
|
|
511
|
-
const results = searchVec(db, [1, 0, 0, 0], 2);
|
|
512
|
-
expect(results.length).toBe(2);
|
|
513
|
-
}
|
|
514
|
-
finally {
|
|
515
|
-
closeDatabase(db);
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
test("deleteEntriesByDir also removes vec rows", () => {
|
|
519
|
-
const dbPath = tmpDbPath();
|
|
520
|
-
const db = openDatabase(dbPath, { embeddingDim: 4 });
|
|
521
|
-
try {
|
|
522
|
-
const id1 = insertTestEntry(db, "vec-del-1", { dirPath: "/del-dir", searchText: "delete me" });
|
|
523
|
-
const id2 = insertTestEntry(db, "vec-del-2", { dirPath: "/keep-dir", searchText: "keep me" });
|
|
524
|
-
upsertEmbedding(db, id1, [1, 0, 0, 0]);
|
|
525
|
-
upsertEmbedding(db, id2, [0, 1, 0, 0]);
|
|
526
|
-
// Before delete, both should be searchable
|
|
527
|
-
let results = searchVec(db, [0.5, 0.5, 0, 0], 10);
|
|
528
|
-
expect(results.length).toBe(2);
|
|
529
|
-
deleteEntriesByDir(db, "/del-dir");
|
|
530
|
-
// After delete, only the kept entry should remain
|
|
531
|
-
results = searchVec(db, [0.5, 0.5, 0, 0], 10);
|
|
532
|
-
expect(results.length).toBe(1);
|
|
533
|
-
expect(results[0].id).toBe(id2);
|
|
534
|
-
}
|
|
535
|
-
finally {
|
|
536
|
-
closeDatabase(db);
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
test("embeddingDim change recreates vec table and clears old embeddings", () => {
|
|
540
|
-
const dbPath = tmpDbPath();
|
|
541
|
-
// Open with dim=4 and insert an embedding
|
|
542
|
-
let db = openDatabase(dbPath, { embeddingDim: 4 });
|
|
543
|
-
const id = insertTestEntry(db, "dim-change", { searchText: "dimension test" });
|
|
544
|
-
upsertEmbedding(db, id, [1, 0, 0, 0]);
|
|
545
|
-
let results = searchVec(db, [1, 0, 0, 0], 10);
|
|
546
|
-
expect(results.length).toBe(1);
|
|
547
|
-
closeDatabase(db);
|
|
548
|
-
// Reopen with dim=8 — vec table should be recreated, old embeddings gone
|
|
549
|
-
db = openDatabase(dbPath, { embeddingDim: 8 });
|
|
550
|
-
try {
|
|
551
|
-
expect(getMeta(db, "embeddingDim")).toBe("8");
|
|
552
|
-
// Old embedding was dim=4 and table was recreated for dim=8, so no results
|
|
553
|
-
results = searchVec(db, [1, 0, 0, 0, 0, 0, 0, 0], 10);
|
|
554
|
-
expect(results.length).toBe(0);
|
|
555
|
-
}
|
|
556
|
-
finally {
|
|
557
|
-
closeDatabase(db);
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
});
|
|
561
|
-
// ── Incremental rebuildFts (#177 perf finding) ──────────────────────────────
|
|
562
|
-
describe("rebuildFts incremental", () => {
|
|
563
|
-
function makeEntry(name, description = "") {
|
|
564
|
-
return {
|
|
565
|
-
name,
|
|
566
|
-
type: "skill",
|
|
567
|
-
description,
|
|
568
|
-
filename: `${name}.md`,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
function ftsCount(db) {
|
|
572
|
-
const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries_fts").get();
|
|
573
|
-
return row?.cnt ?? 0;
|
|
574
|
-
}
|
|
575
|
-
function dirtyCount(db) {
|
|
576
|
-
try {
|
|
577
|
-
const row = db.prepare("SELECT COUNT(*) AS cnt FROM entries_fts_dirty").get();
|
|
578
|
-
return row?.cnt ?? 0;
|
|
579
|
-
}
|
|
580
|
-
catch {
|
|
581
|
-
return 0;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
test("upsertEntry marks rows dirty; incremental rebuild only re-indexes them", () => {
|
|
585
|
-
const db = openDatabase(tmpDbPath("inc-fts"));
|
|
586
|
-
try {
|
|
587
|
-
upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha", "first"), "alpha first");
|
|
588
|
-
upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo", "second"), "bravo second");
|
|
589
|
-
upsertEntry(db, "k3", "/d", "/d/k3.md", "/stash", makeEntry("charlie", "third"), "charlie third");
|
|
590
|
-
rebuildFts(db, { incremental: false });
|
|
591
|
-
expect(ftsCount(db)).toBe(3);
|
|
592
|
-
expect(dirtyCount(db)).toBe(0);
|
|
593
|
-
// Touch only one entry — its row should be the only dirty one.
|
|
594
|
-
upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo", "second-updated"), "bravo second-updated");
|
|
595
|
-
expect(dirtyCount(db)).toBe(1);
|
|
596
|
-
rebuildFts(db, { incremental: true });
|
|
597
|
-
expect(ftsCount(db)).toBe(3);
|
|
598
|
-
expect(dirtyCount(db)).toBe(0);
|
|
599
|
-
const hits = db
|
|
600
|
-
.prepare("SELECT entry_id FROM entries_fts WHERE entries_fts MATCH ?")
|
|
601
|
-
.all(`"second-updated"`);
|
|
602
|
-
expect(hits.length).toBe(1);
|
|
603
|
-
}
|
|
604
|
-
finally {
|
|
605
|
-
closeDatabase(db);
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
test("incremental rebuild with empty dirty queue is a no-op", () => {
|
|
609
|
-
const db = openDatabase(tmpDbPath("inc-fts-empty"));
|
|
610
|
-
try {
|
|
611
|
-
upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
|
|
612
|
-
rebuildFts(db, { incremental: false });
|
|
613
|
-
expect(ftsCount(db)).toBe(1);
|
|
614
|
-
expect(dirtyCount(db)).toBe(0);
|
|
615
|
-
rebuildFts(db, { incremental: true });
|
|
616
|
-
expect(ftsCount(db)).toBe(1);
|
|
617
|
-
}
|
|
618
|
-
finally {
|
|
619
|
-
closeDatabase(db);
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
test("full rebuild also drains the dirty queue", () => {
|
|
623
|
-
const db = openDatabase(tmpDbPath("inc-fts-full"));
|
|
624
|
-
try {
|
|
625
|
-
upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
|
|
626
|
-
expect(dirtyCount(db)).toBe(1);
|
|
627
|
-
rebuildFts(db, { incremental: false });
|
|
628
|
-
expect(ftsCount(db)).toBe(1);
|
|
629
|
-
expect(dirtyCount(db)).toBe(0);
|
|
630
|
-
}
|
|
631
|
-
finally {
|
|
632
|
-
closeDatabase(db);
|
|
633
|
-
}
|
|
634
|
-
});
|
|
635
|
-
test("deleteEntriesByDir purges FTS rows + dirty markers immediately", () => {
|
|
636
|
-
const db = openDatabase(tmpDbPath("inc-fts-del"));
|
|
637
|
-
try {
|
|
638
|
-
upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha"), "alpha");
|
|
639
|
-
upsertEntry(db, "k2", "/d", "/d/k2.md", "/stash", makeEntry("bravo"), "bravo");
|
|
640
|
-
rebuildFts(db, { incremental: false });
|
|
641
|
-
expect(ftsCount(db)).toBe(2);
|
|
642
|
-
upsertEntry(db, "k1", "/d", "/d/k1.md", "/stash", makeEntry("alpha", "updated"), "alpha updated");
|
|
643
|
-
expect(dirtyCount(db)).toBe(1);
|
|
644
|
-
deleteEntriesByDir(db, "/d");
|
|
645
|
-
expect(ftsCount(db)).toBe(0);
|
|
646
|
-
expect(dirtyCount(db)).toBe(0);
|
|
647
|
-
rebuildFts(db, { incremental: true });
|
|
648
|
-
expect(ftsCount(db)).toBe(0);
|
|
649
|
-
}
|
|
650
|
-
finally {
|
|
651
|
-
closeDatabase(db);
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
});
|