@tobilu/qmd 2.0.1 → 2.5.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/CHANGELOG.md +177 -0
- package/README.md +64 -1
- package/bin/qmd +49 -4
- package/dist/ast.d.ts +65 -0
- package/dist/ast.js +334 -0
- package/dist/bench/bench.d.ts +23 -0
- package/dist/bench/bench.js +280 -0
- package/dist/bench/score.d.ts +33 -0
- package/dist/bench/score.js +88 -0
- package/dist/bench/types.d.ts +80 -0
- package/dist/bench/types.js +8 -0
- package/dist/cli/formatter.js +5 -1
- package/dist/cli/qmd.d.ts +27 -0
- package/dist/cli/qmd.js +1328 -115
- package/dist/collections.d.ts +20 -0
- package/dist/collections.js +32 -7
- package/dist/db.d.ts +14 -3
- package/dist/db.js +45 -4
- package/dist/index.d.ts +11 -1
- package/dist/index.js +18 -5
- package/dist/llm.d.ts +77 -6
- package/dist/llm.js +445 -62
- package/dist/mcp/server.d.ts +6 -3
- package/dist/mcp/server.js +68 -29
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +4 -0
- package/dist/store.d.ts +148 -23
- package/dist/store.js +1018 -255
- package/package.json +48 -20
- package/scripts/build.mjs +29 -0
- package/scripts/check-package-grammars.mjs +29 -0
- package/scripts/package-smoke.mjs +65 -0
- package/scripts/test-all.mjs +27 -0
- package/skills/qmd/SKILL.md +203 -0
- package/skills/qmd/references/mcp-setup.md +102 -0
- package/skills/release/SKILL.md +139 -0
- package/skills/release/scripts/install-hooks.sh +38 -0
- package/dist/embedded-skills.d.ts +0 -6
- package/dist/embedded-skills.js +0 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tobilu/qmd",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.1",
|
|
4
4
|
"description": "Query Markup Documents - On-device hybrid search for markdown files with BM25, vector search, and LLM reranking",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,13 +17,23 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"bin/",
|
|
19
19
|
"dist/",
|
|
20
|
+
"skills/",
|
|
21
|
+
"scripts/build.mjs",
|
|
22
|
+
"scripts/check-package-grammars.mjs",
|
|
23
|
+
"scripts/package-smoke.mjs",
|
|
24
|
+
"scripts/test-all.mjs",
|
|
20
25
|
"LICENSE",
|
|
21
26
|
"CHANGELOG.md"
|
|
22
27
|
],
|
|
23
28
|
"scripts": {
|
|
24
29
|
"prepare": "[ -d .git ] && ./scripts/install-hooks.sh || true",
|
|
25
|
-
"build": "
|
|
26
|
-
"test": "
|
|
30
|
+
"build": "node scripts/build.mjs",
|
|
31
|
+
"test": "node scripts/test-all.mjs",
|
|
32
|
+
"test:types": "node ./node_modules/typescript/bin/tsc -p tsconfig.build.json --noEmit",
|
|
33
|
+
"test:node": "node ./node_modules/vitest/vitest.mjs run --reporter=verbose --testTimeout 60000",
|
|
34
|
+
"test:bun": "bun test --timeout 60000 --preload ./src/test-preload.ts",
|
|
35
|
+
"test:unit": "CI=true node ./node_modules/vitest/vitest.mjs run --reporter=verbose --testTimeout 60000 test/ && CI=true bun test --timeout 60000 --preload ./src/test-preload.ts test/",
|
|
36
|
+
"test:package": "node scripts/package-smoke.mjs",
|
|
27
37
|
"qmd": "tsx src/cli/qmd.ts",
|
|
28
38
|
"index": "tsx src/cli/qmd.ts index",
|
|
29
39
|
"vector": "tsx src/cli/qmd.ts vector",
|
|
@@ -31,7 +41,8 @@
|
|
|
31
41
|
"vsearch": "tsx src/cli/qmd.ts vsearch",
|
|
32
42
|
"rerank": "tsx src/cli/qmd.ts rerank",
|
|
33
43
|
"inspector": "npx @modelcontextprotocol/inspector tsx src/cli/qmd.ts mcp",
|
|
34
|
-
"release": "./scripts/release.sh"
|
|
44
|
+
"release": "./scripts/release.sh",
|
|
45
|
+
"smoke:package-grammars": "node scripts/check-package-grammars.mjs"
|
|
35
46
|
},
|
|
36
47
|
"publishConfig": {
|
|
37
48
|
"access": "public"
|
|
@@ -45,26 +56,43 @@
|
|
|
45
56
|
"url": "https://github.com/tobi/qmd/issues"
|
|
46
57
|
},
|
|
47
58
|
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "
|
|
49
|
-
"better-sqlite3": "
|
|
50
|
-
"fast-glob": "
|
|
51
|
-
"node-llama-cpp": "
|
|
52
|
-
"picomatch": "
|
|
53
|
-
"sqlite-vec": "
|
|
54
|
-
"
|
|
55
|
-
"
|
|
59
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
60
|
+
"better-sqlite3": "12.10.0",
|
|
61
|
+
"fast-glob": "3.3.3",
|
|
62
|
+
"node-llama-cpp": "3.18.1",
|
|
63
|
+
"picomatch": "4.0.4",
|
|
64
|
+
"sqlite-vec": "0.1.9",
|
|
65
|
+
"tree-sitter-go": "0.25.0",
|
|
66
|
+
"tree-sitter-python": "0.25.0",
|
|
67
|
+
"tree-sitter-rust": "0.24.0",
|
|
68
|
+
"tree-sitter-typescript": "0.23.2",
|
|
69
|
+
"web-tree-sitter": "0.26.8",
|
|
70
|
+
"yaml": "2.9.0",
|
|
71
|
+
"zod": "4.2.1"
|
|
56
72
|
},
|
|
57
73
|
"optionalDependencies": {
|
|
58
|
-
"sqlite-vec-darwin-arm64": "
|
|
59
|
-
"sqlite-vec-darwin-x64": "
|
|
60
|
-
"sqlite-vec-linux-arm64": "
|
|
61
|
-
"sqlite-vec-linux-x64": "
|
|
62
|
-
"sqlite-vec-windows-x64": "
|
|
74
|
+
"sqlite-vec-darwin-arm64": "0.1.9",
|
|
75
|
+
"sqlite-vec-darwin-x64": "0.1.9",
|
|
76
|
+
"sqlite-vec-linux-arm64": "0.1.9",
|
|
77
|
+
"sqlite-vec-linux-x64": "0.1.9",
|
|
78
|
+
"sqlite-vec-windows-x64": "0.1.9"
|
|
63
79
|
},
|
|
64
80
|
"devDependencies": {
|
|
65
|
-
"@types/better-sqlite3": "
|
|
66
|
-
"tsx": "
|
|
67
|
-
"vitest": "
|
|
81
|
+
"@types/better-sqlite3": "7.6.13",
|
|
82
|
+
"tsx": "4.21.0",
|
|
83
|
+
"vitest": "3.2.4"
|
|
84
|
+
},
|
|
85
|
+
"pnpm": {
|
|
86
|
+
"onlyBuiltDependencies": [
|
|
87
|
+
"better-sqlite3",
|
|
88
|
+
"esbuild",
|
|
89
|
+
"node-llama-cpp",
|
|
90
|
+
"tree-sitter-go",
|
|
91
|
+
"tree-sitter-javascript",
|
|
92
|
+
"tree-sitter-python",
|
|
93
|
+
"tree-sitter-rust",
|
|
94
|
+
"tree-sitter-typescript"
|
|
95
|
+
]
|
|
68
96
|
},
|
|
69
97
|
"peerDependencies": {
|
|
70
98
|
"typescript": "^5.9.3"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { chmodSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = join(fileURLToPath(new URL("..", import.meta.url)));
|
|
8
|
+
|
|
9
|
+
function run(command, args, options = {}) {
|
|
10
|
+
const result = spawnSync(command, args, {
|
|
11
|
+
cwd: root,
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
shell: process.platform === "win32",
|
|
14
|
+
...options,
|
|
15
|
+
});
|
|
16
|
+
if (result.status !== 0) {
|
|
17
|
+
process.exit(result.status ?? 1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
run(process.execPath, [join(root, "node_modules", "typescript", "bin", "tsc"), "-p", "tsconfig.build.json"]);
|
|
22
|
+
|
|
23
|
+
const cliPath = join(root, "dist", "cli", "qmd.js");
|
|
24
|
+
const tmpPath = `${cliPath}.tmp`;
|
|
25
|
+
const built = readFileSync(cliPath, "utf8");
|
|
26
|
+
const withoutExistingShebang = built.startsWith("#!") ? built.slice(built.indexOf("\n") + 1) : built;
|
|
27
|
+
writeFileSync(tmpPath, `#!/usr/bin/env node\n${withoutExistingShebang}`);
|
|
28
|
+
renameSync(tmpPath, cliPath);
|
|
29
|
+
chmodSync(cliPath, 0o755);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
|
|
6
|
+
const grammars = [
|
|
7
|
+
"tree-sitter-typescript/tree-sitter-typescript.wasm",
|
|
8
|
+
"tree-sitter-typescript/tree-sitter-tsx.wasm",
|
|
9
|
+
"tree-sitter-python/tree-sitter-python.wasm",
|
|
10
|
+
"tree-sitter-go/tree-sitter-go.wasm",
|
|
11
|
+
"tree-sitter-rust/tree-sitter-rust.wasm",
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
let ok = true;
|
|
15
|
+
for (const grammar of grammars) {
|
|
16
|
+
try {
|
|
17
|
+
const resolved = require.resolve(grammar);
|
|
18
|
+
console.log(`ok ${grammar} -> ${resolved}`);
|
|
19
|
+
} catch (err) {
|
|
20
|
+
ok = false;
|
|
21
|
+
console.error(`missing ${grammar}`);
|
|
22
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!ok) {
|
|
27
|
+
console.error("\nAST grammar package smoke check failed. Run `bun install` locally or repair a broken global install with the matching `bun add tree-sitter-...@<version>` command shown by `qmd status`.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
8
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
9
|
+
|
|
10
|
+
function run(label, command, args, options = {}) {
|
|
11
|
+
console.log(`==> ${label}`);
|
|
12
|
+
const { quiet, ...spawnOptions } = options;
|
|
13
|
+
const result = spawnSync(command, args, {
|
|
14
|
+
cwd: root,
|
|
15
|
+
stdio: quiet ? "pipe" : "inherit",
|
|
16
|
+
shell: process.platform === "win32",
|
|
17
|
+
...spawnOptions,
|
|
18
|
+
});
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
console.error(`Package smoke failed: ${label}`);
|
|
21
|
+
if (quiet) {
|
|
22
|
+
if (result.stdout) process.stderr.write(result.stdout);
|
|
23
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
24
|
+
}
|
|
25
|
+
process.exit(result.status ?? 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assertPath(path, label = path) {
|
|
30
|
+
const full = join(root, path);
|
|
31
|
+
if (!existsSync(full)) {
|
|
32
|
+
console.error(`Package smoke failed: missing ${label} (${path})`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
return full;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
run("build compiled package", process.execPath, ["scripts/build.mjs"]);
|
|
39
|
+
run("AST grammar runtime packages", process.execPath, ["scripts/check-package-grammars.mjs"]);
|
|
40
|
+
|
|
41
|
+
for (const entry of pkg.files ?? []) {
|
|
42
|
+
assertPath(entry.replace(/\/$/, ""), `package.json files[] entry ${entry}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const [name, binPath] of Object.entries(pkg.bin ?? {})) {
|
|
46
|
+
const full = assertPath(binPath, `bin ${name}`);
|
|
47
|
+
const mode = statSync(full).mode;
|
|
48
|
+
if ((mode & 0o111) === 0) {
|
|
49
|
+
console.error(`Package smoke failed: bin ${name} is not executable (${binPath})`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
assertPath("dist/index.js", "compiled main export");
|
|
55
|
+
assertPath("dist/index.d.ts", "compiled type export");
|
|
56
|
+
assertPath("dist/cli/qmd.js", "compiled CLI");
|
|
57
|
+
|
|
58
|
+
run("compiled CLI under Node", process.execPath, ["dist/cli/qmd.js", "--help"], { quiet: true });
|
|
59
|
+
run("package wrapper", "sh", ["bin/qmd", "--help"], { quiet: true });
|
|
60
|
+
|
|
61
|
+
if (process.env.QMD_SKIP_BUN_SMOKE === "1") {
|
|
62
|
+
console.log("==> compiled CLI under Bun (skipped by QMD_SKIP_BUN_SMOKE=1)");
|
|
63
|
+
} else {
|
|
64
|
+
run("compiled CLI under Bun", "bun", ["dist/cli/qmd.js", "--help"], { quiet: true });
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = fileURLToPath(new URL("..", import.meta.url));
|
|
7
|
+
|
|
8
|
+
function run(label, command, args, options = {}) {
|
|
9
|
+
console.log(`==> ${label}`);
|
|
10
|
+
const { env: extraEnv, ...spawnOptions } = options;
|
|
11
|
+
const result = spawnSync(command, args, {
|
|
12
|
+
cwd: root,
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
shell: process.platform === "win32",
|
|
15
|
+
env: { ...process.env, ...(extraEnv ?? {}) },
|
|
16
|
+
...spawnOptions,
|
|
17
|
+
});
|
|
18
|
+
if (result.status !== 0) {
|
|
19
|
+
console.error(`Test task failed: ${label}`);
|
|
20
|
+
process.exit(result.status ?? 1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
run("TypeScript build typecheck", process.execPath, [join(root, "node_modules", "typescript", "bin", "tsc"), "-p", "tsconfig.build.json", "--noEmit"]);
|
|
25
|
+
run("Vitest suite under Node", process.execPath, [join(root, "node_modules", "vitest", "vitest.mjs"), "run", "--reporter=verbose", "--testTimeout", "60000", "test/"], { env: { CI: "true" } });
|
|
26
|
+
run("Bun test suite", "bun", ["test", "--timeout", "60000", "--preload", "./src/test-preload.ts", "test/"], { env: { CI: "true" } });
|
|
27
|
+
run("Package smoke", process.execPath, ["scripts/package-smoke.mjs"]);
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: qmd
|
|
3
|
+
description: Search local markdown knowledge bases, notes, docs, and wikis with QMD. Use when users ask to find notes, retrieve documents, inspect a wiki, answer from indexed markdown, or set up QMD access.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires qmd CLI or MCP server. Install via `npm install -g @tobilu/qmd`.
|
|
6
|
+
metadata:
|
|
7
|
+
author: tobi
|
|
8
|
+
version: "2.1.0"
|
|
9
|
+
allowed-tools: Bash(qmd:*), mcp__qmd__*
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# QMD - Query Markdown Documents
|
|
13
|
+
|
|
14
|
+
## How search works
|
|
15
|
+
|
|
16
|
+
QMD searches local markdown collections: notes, docs, wikis, transcripts, and
|
|
17
|
+
project knowledge bases. Use it before web search when the answer may already be
|
|
18
|
+
in indexed local files.
|
|
19
|
+
|
|
20
|
+
The workflow is always:
|
|
21
|
+
|
|
22
|
+
1. Search for candidate documents.
|
|
23
|
+
2. Retrieve the full source with `qmd get` or `qmd multi-get`.
|
|
24
|
+
3. Answer from retrieved text, citing paths or docids.
|
|
25
|
+
|
|
26
|
+
Do not answer from snippets alone when the user needs facts, decisions, quotes,
|
|
27
|
+
or nuance. Snippets are only leads.
|
|
28
|
+
|
|
29
|
+
Typical loop:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
qmd search "merchant reality support interviews" -n 5
|
|
33
|
+
# leads: #abc123 concepts/customer-proximity.md; #def432 sources/merchant-call.md
|
|
34
|
+
qmd multi-get "#abc123,#def432" --md
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For harder searches, use `qmd query` structured queries with `intent:`, `lex:`,
|
|
38
|
+
`vec:`, and `hyde:` fields.
|
|
39
|
+
|
|
40
|
+
When reporting what you retrieved, a compact note is enough; do not paste whole
|
|
41
|
+
files unless needed:
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
Retrieved:
|
|
45
|
+
- #abc123 concepts/customer-proximity.md
|
|
46
|
+
- #def432 sources/merchant-call.md
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Pick the right search mode
|
|
50
|
+
|
|
51
|
+
Use **BM25 lexical search** when you know exact words, titles, names, code
|
|
52
|
+
symbols, or rare phrases:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
qmd search "cockpit OKR Goodhart" -n 10
|
|
56
|
+
qmd search '"AI Before Headcount"' -c concepts -n 5
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Use **hybrid semantic search** when the user describes an idea indirectly, uses
|
|
60
|
+
different wording than the source, or needs conceptual recall:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
qmd query "decision quality depends on surfacing assumptions and context" -n 10
|
|
64
|
+
qmd query --json --explain "metrics as cockpit instruments but not OKRs"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Use **structured queries** for hard searches. They combine exact anchors with
|
|
68
|
+
semantic recall:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
qmd query $'intent: Find the concept note about metrics as instruments without letting OKRs replace judgment.\nlex: cockpit instruments OKR Goodhart metrics judgment\nvec: data informed not metric driven product judgment\nhyde: A concept note says metrics are useful like cockpit instruments, but leaders should remain data-informed rather than metric-driven because OKRs and dashboards can Goodhart product judgment.'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Structured query fields:
|
|
75
|
+
|
|
76
|
+
- `intent:` states what you are trying to find and what to avoid.
|
|
77
|
+
- `lex:` uses exact terms, aliases, titles, and rare words.
|
|
78
|
+
- `vec:` paraphrases the idea in natural language.
|
|
79
|
+
- `hyde:` describes the document or answer that would satisfy the request.
|
|
80
|
+
|
|
81
|
+
If `qmd query` is slow or model/GPU setup fails, fall back to `qmd search` with
|
|
82
|
+
better lexical terms.
|
|
83
|
+
|
|
84
|
+
## Retrieve sources
|
|
85
|
+
|
|
86
|
+
Search results include docids like `#abc123` and `qmd://...` paths. Fetch them:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
qmd get "#abc123"
|
|
90
|
+
qmd get qmd://concepts/ai-before-headcount.md --full
|
|
91
|
+
qmd multi-get "#abc123,#def432" --md
|
|
92
|
+
qmd multi-get 'concepts/{ai-before-headcount.md,data-informed-not-metric-driven.md}' --md
|
|
93
|
+
qmd multi-get 'sources/podcast-2025-*.md' -l 80
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use `multi-get` when comparing several hits or gathering context across pages.
|
|
97
|
+
Use `--full` when the exact source matters.
|
|
98
|
+
|
|
99
|
+
## Discover what is indexed
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
qmd collection list
|
|
103
|
+
qmd ls
|
|
104
|
+
qmd status
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Add collection filters when broad searches drift into the wrong corpus:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
qmd search "headcount autonomous agents" -c concepts -n 10
|
|
111
|
+
qmd query "merchant support product reality" -c concepts -c sources -n 10
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Omit `-c` to search everything.
|
|
115
|
+
|
|
116
|
+
## MCP Tool: `query`
|
|
117
|
+
|
|
118
|
+
When using the MCP server, prefer structured searches:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"searches": [
|
|
123
|
+
{ "type": "lex", "query": "cockpit OKR Goodhart" },
|
|
124
|
+
{ "type": "vec", "query": "data informed not metric driven product judgment" },
|
|
125
|
+
{ "type": "hyde", "query": "A concept note explains that metrics are useful as instruments, but leaders should not let OKRs or dashboards replace judgment." }
|
|
126
|
+
],
|
|
127
|
+
"intent": "Find the concept note about using metrics as instruments without becoming metric-driven.",
|
|
128
|
+
"collections": ["concepts"],
|
|
129
|
+
"limit": 10
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Query types:
|
|
134
|
+
|
|
135
|
+
- `lex` — BM25 keyword search. Best for exact terms, names, titles, and code.
|
|
136
|
+
- `vec` — vector semantic search. Best for natural-language concepts.
|
|
137
|
+
- `hyde` — vector search using a hypothetical answer/document passage.
|
|
138
|
+
|
|
139
|
+
## Query craft
|
|
140
|
+
|
|
141
|
+
Good QMD searches mix three things:
|
|
142
|
+
|
|
143
|
+
1. **Title/alias anchors:** exact page titles, named entities, phrases.
|
|
144
|
+
2. **Semantic paraphrase:** how a human would describe the idea.
|
|
145
|
+
3. **Negative space:** enough intent to avoid nearby-but-wrong concepts.
|
|
146
|
+
|
|
147
|
+
Examples:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
# Exact-ish title lookup
|
|
151
|
+
qmd search '"arm the rebels" merchants tools big companies' -c concepts
|
|
152
|
+
|
|
153
|
+
# Semantic concept lookup
|
|
154
|
+
qmd query $'intent: Find the customer proximity concept, not generic customer delight.\nlex: support pseudonymous merchant customer interviews\nvec: founder stays close to merchant reality through support and product use'
|
|
155
|
+
|
|
156
|
+
# Source lookup
|
|
157
|
+
qmd search "six-week cadence WhatsApp merchant relationships Shawn Ryan" -c sources -n 10
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Setup and maintenance
|
|
161
|
+
|
|
162
|
+
Only mutate indexes when the user asked for setup or maintenance. Searching and
|
|
163
|
+
retrieving are safe; collection/index mutation is not a casual first step.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npm install -g @tobilu/qmd
|
|
167
|
+
qmd collection add ~/notes --name notes
|
|
168
|
+
qmd update
|
|
169
|
+
qmd embed
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Health and diagnostics:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
qmd doctor
|
|
176
|
+
qmd status
|
|
177
|
+
qmd pull
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
`qmd doctor` checks config, model cache, device/GPU setup, vector fingerprints,
|
|
181
|
+
and common environment overrides. If a model-backed command fails, run it before
|
|
182
|
+
changing configuration.
|
|
183
|
+
|
|
184
|
+
## MCP setup
|
|
185
|
+
|
|
186
|
+
See `references/mcp-setup.md` for Claude Code, Claude Desktop, OpenClaw, and HTTP
|
|
187
|
+
server configuration.
|
|
188
|
+
|
|
189
|
+
## Pitfalls
|
|
190
|
+
|
|
191
|
+
- **Do not stop at snippets.** Fetch documents before making claims.
|
|
192
|
+
- **Do not overuse semantic search.** If you know exact titles or terms, BM25 is
|
|
193
|
+
faster and often better.
|
|
194
|
+
- **Do not mutate indexes casually.** `qmd collection add`, `qmd update`, and
|
|
195
|
+
`qmd embed` change local state and can be expensive.
|
|
196
|
+
- **Model-backed commands can be environment-sensitive.** If `qmd query`,
|
|
197
|
+
`qmd vsearch`, or reranking fails because local models/GPU are unavailable,
|
|
198
|
+
use `qmd search` and stronger lexical/structured terms.
|
|
199
|
+
- **Ambiguous user wording needs intent.** Add `intent:` rather than hoping query
|
|
200
|
+
expansion guesses the right domain.
|
|
201
|
+
- **Collection names matter.** Search `concepts` for synthesized wiki pages,
|
|
202
|
+
`sources` for transcripts/raw source pages, and docs collections for code or
|
|
203
|
+
project documentation.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# QMD MCP Server Setup
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install -g @tobilu/qmd
|
|
7
|
+
qmd collection add ~/path/to/markdown --name myknowledge
|
|
8
|
+
qmd embed
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configure MCP Client
|
|
12
|
+
|
|
13
|
+
**Claude Code** (`~/.claude/settings.json`):
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"qmd": { "command": "qmd", "args": ["mcp"] }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"qmd": { "command": "qmd", "args": ["mcp"] }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**OpenClaw** (`~/.openclaw/openclaw.json`):
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcp": {
|
|
35
|
+
"servers": {
|
|
36
|
+
"qmd": { "command": "qmd", "args": ["mcp"] }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## HTTP Mode
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
qmd mcp --http # Port 8181
|
|
46
|
+
qmd mcp --http --daemon # Background
|
|
47
|
+
qmd mcp stop # Stop daemon
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Tools
|
|
51
|
+
|
|
52
|
+
### structured_search
|
|
53
|
+
|
|
54
|
+
Search with pre-expanded queries.
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"searches": [
|
|
59
|
+
{ "type": "lex", "query": "keyword phrases" },
|
|
60
|
+
{ "type": "vec", "query": "natural language question" },
|
|
61
|
+
{ "type": "hyde", "query": "hypothetical answer passage..." }
|
|
62
|
+
],
|
|
63
|
+
"limit": 10,
|
|
64
|
+
"collection": "optional",
|
|
65
|
+
"minScore": 0.0
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
| Type | Method | Input |
|
|
70
|
+
|------|--------|-------|
|
|
71
|
+
| `lex` | BM25 | Keywords (2-5 terms) |
|
|
72
|
+
| `vec` | Vector | Question |
|
|
73
|
+
| `hyde` | Vector | Answer passage (50-100 words) |
|
|
74
|
+
|
|
75
|
+
### get
|
|
76
|
+
|
|
77
|
+
Retrieve document by path or `#docid`.
|
|
78
|
+
|
|
79
|
+
| Param | Type | Description |
|
|
80
|
+
|-------|------|-------------|
|
|
81
|
+
| `path` | string | File path or `#docid` |
|
|
82
|
+
| `full` | bool? | Return full content |
|
|
83
|
+
| `lineNumbers` | bool? | Add line numbers |
|
|
84
|
+
|
|
85
|
+
### multi_get
|
|
86
|
+
|
|
87
|
+
Retrieve multiple documents.
|
|
88
|
+
|
|
89
|
+
| Param | Type | Description |
|
|
90
|
+
|-------|------|-------------|
|
|
91
|
+
| `pattern` | string | Glob or comma-separated list |
|
|
92
|
+
| `maxBytes` | number? | Skip large files (default 10KB) |
|
|
93
|
+
|
|
94
|
+
### status
|
|
95
|
+
|
|
96
|
+
Index health and collections. No params.
|
|
97
|
+
|
|
98
|
+
## Troubleshooting
|
|
99
|
+
|
|
100
|
+
- **Not starting**: `which qmd`, `qmd mcp` manually
|
|
101
|
+
- **No results**: `qmd collection list`, `qmd embed`
|
|
102
|
+
- **Slow first search**: Normal, models loading (~3GB)
|