@gorajing/zuun 0.1.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.
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.captureCommit = captureCommit;
4
+ const child_process_1 = require("child_process");
5
+ const db_1 = require("../lib/db");
6
+ const store_1 = require("../lib/store");
7
+ const entry_io_1 = require("../lib/entry-io");
8
+ const id_1 = require("../lib/id");
9
+ const tags_1 = require("../lib/tags");
10
+ const dedup_1 = require("../lib/dedup");
11
+ const log_1 = require("../lib/log");
12
+ function kindFromMessage(msg) {
13
+ const prefix = msg.trim().split(/[:(\s]/, 1)[0]?.toLowerCase() ?? "";
14
+ if (prefix === "feat")
15
+ return "pattern";
16
+ if (prefix === "fix")
17
+ return "observation";
18
+ if (prefix === "chore" || prefix === "refactor")
19
+ return "decision";
20
+ if (prefix === "docs" || prefix === "test")
21
+ return "reference";
22
+ return "observation";
23
+ }
24
+ function gitSafe(args) {
25
+ try {
26
+ return (0, child_process_1.execFileSync)("git", args, {
27
+ encoding: "utf8",
28
+ stdio: ["ignore", "pipe", "ignore"],
29
+ }).trim();
30
+ }
31
+ catch {
32
+ return null;
33
+ }
34
+ }
35
+ async function captureCommit(_args) {
36
+ const sha = gitSafe(["rev-parse", "HEAD"]);
37
+ if (!sha)
38
+ return 0;
39
+ const msg = gitSafe(["log", "-1", "--pretty=%B"]) ?? "";
40
+ if (msg.trim().length === 0)
41
+ return 0;
42
+ const branch = gitSafe(["rev-parse", "--abbrev-ref", "HEAD"]) ?? "unknown";
43
+ const files = gitSafe(["diff-tree", "--no-commit-id", "--name-only", "-r", "--root", sha]) ?? "";
44
+ const repoRoot = gitSafe(["rev-parse", "--show-toplevel"]);
45
+ const body = `${msg.trim()}\n\nFiles changed:\n${files}`;
46
+ const now = new Date();
47
+ const db = (0, db_1.openDb)();
48
+ try {
49
+ const existing = (0, dedup_1.findRecentDuplicate)(db, body, now);
50
+ if (existing) {
51
+ (0, log_1.appendLog)("capture_commit.dedup", { sha, id: existing });
52
+ return 0;
53
+ }
54
+ const id = (0, id_1.newEntryId)(body, now);
55
+ const entry = {
56
+ id,
57
+ created: now.toISOString(),
58
+ body,
59
+ kind: kindFromMessage(msg),
60
+ source: "git",
61
+ tags: (0, tags_1.normalizeTags)([branch]),
62
+ related: [],
63
+ origin: sha,
64
+ project: repoRoot ?? undefined,
65
+ };
66
+ (0, entry_io_1.writeEntry)(entry);
67
+ (0, store_1.upsertEntry)(db, entry);
68
+ (0, log_1.appendLog)("capture_commit", { id, sha, kind: entry.kind, project: entry.project });
69
+ process.stdout.write(`${id}\n`);
70
+ return 0;
71
+ }
72
+ finally {
73
+ db.close();
74
+ }
75
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.edit = edit;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const db_1 = require("../lib/db");
41
+ const entry_io_1 = require("../lib/entry-io");
42
+ const store_1 = require("../lib/store");
43
+ const paths_1 = require("../lib/paths");
44
+ const log_1 = require("../lib/log");
45
+ async function edit(args) {
46
+ const [id] = args;
47
+ if (!id) {
48
+ process.stderr.write("usage: zuun edit <id>\n");
49
+ return 1;
50
+ }
51
+ const db = (0, db_1.openDb)();
52
+ try {
53
+ if (!(0, store_1.getEntry)(db, id)) {
54
+ process.stderr.write(`edit: no entry with id ${id}\n`);
55
+ return 1;
56
+ }
57
+ const file = path.join((0, paths_1.entriesDir)(), `${id}.md`);
58
+ if (!fs.existsSync(file)) {
59
+ process.stderr.write(`edit: file missing on disk: ${file}\n`);
60
+ return 1;
61
+ }
62
+ const editor = process.env.EDITOR ?? "vi";
63
+ const result = (0, child_process_1.spawnSync)(editor, [file], { stdio: "inherit" });
64
+ if (result.status !== 0) {
65
+ process.stderr.write(`edit: editor exited with status ${result.status}\n`);
66
+ return 1;
67
+ }
68
+ try {
69
+ const parsed = (0, entry_io_1.readEntry)(id);
70
+ (0, store_1.upsertEntry)(db, parsed);
71
+ (0, log_1.appendLog)("edit", { id });
72
+ process.stdout.write(`updated ${id}\n`);
73
+ return 0;
74
+ }
75
+ catch (err) {
76
+ process.stderr.write(`edit: file fails schema after edit — db row left unchanged\n ${err.message}\n`);
77
+ return 1;
78
+ }
79
+ }
80
+ finally {
81
+ db.close();
82
+ }
83
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.explain = explain;
4
+ const db_1 = require("../lib/db");
5
+ const search_1 = require("../lib/search");
6
+ const embed_provider_1 = require("../lib/embed-provider");
7
+ async function explain(args) {
8
+ const query = args.join(" ");
9
+ if (!query) {
10
+ process.stderr.write("usage: zuun explain <query>\n");
11
+ return 1;
12
+ }
13
+ const db = (0, db_1.openDb)();
14
+ try {
15
+ const qVec = await embed_provider_1.defaultProvider.embed(query);
16
+ const results = (0, search_1.search)(db, { query, queryVec: qVec ?? undefined, limit: 10 });
17
+ if (results.length === 0) {
18
+ process.stdout.write("no results\n");
19
+ return 0;
20
+ }
21
+ for (const r of results) {
22
+ process.stdout.write(`${r.entry.id} · ${r.entry.kind} · ${r.entry.created}\n` +
23
+ ` fts: ${r.parts.fts.toFixed(3)} vec: ${r.parts.vec.toFixed(3)} recency: ${r.parts.recency.toFixed(3)} → score: ${r.score.toFixed(3)}\n` +
24
+ ` ${r.entry.body.replace(/\n/g, " ")}\n\n`);
25
+ }
26
+ return 0;
27
+ }
28
+ finally {
29
+ db.close();
30
+ }
31
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.forget = forget;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const db_1 = require("../lib/db");
40
+ const store_1 = require("../lib/store");
41
+ const paths_1 = require("../lib/paths");
42
+ const log_1 = require("../lib/log");
43
+ async function forget(args) {
44
+ const [id] = args;
45
+ if (!id) {
46
+ process.stderr.write("usage: zuun forget <id>\n");
47
+ return 1;
48
+ }
49
+ const db = (0, db_1.openDb)();
50
+ try {
51
+ const entry = (0, store_1.getEntry)(db, id);
52
+ if (!entry) {
53
+ process.stderr.write(`forget: no entry with id ${id}\n`);
54
+ return 1;
55
+ }
56
+ // Order matters: unlink the markdown file FIRST, then delete the DB row.
57
+ // Rationale: if we crash between steps, "file first" is self-healing via reindex
58
+ // (file missing → DB row will be dropped on next reindex). The reverse order
59
+ // would resurrect the entry — DB row gone, file present → next reindex re-reads
60
+ // the file and re-inserts the row. "Forget" must not be un-forgotten.
61
+ const file = path.join((0, paths_1.entriesDir)(), `${id}.md`);
62
+ if (fs.existsSync(file))
63
+ fs.rmSync(file);
64
+ (0, store_1.deleteEntry)(db, id);
65
+ (0, log_1.appendLog)("forget", { id });
66
+ process.stdout.write(`forgot ${id}\n`);
67
+ return 0;
68
+ }
69
+ finally {
70
+ db.close();
71
+ }
72
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.installGitHook = installGitHook;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const child_process_1 = require("child_process");
40
+ const ZUUN_MARKER = "# zuun:post-commit";
41
+ const SHEBANG = "#!/usr/bin/env sh\n";
42
+ /**
43
+ * Absolute path to the zuun.js we want the installed hook to invoke.
44
+ * Git hooks run outside Claude Code, so they cannot rely on PATH or CLAUDE_PLUGIN_ROOT
45
+ * at fire time. We resolve the plugin's install location NOW and embed it literally
46
+ * in the hook content. Resolution order:
47
+ *
48
+ * 1. ZUUN_BIN — set by bin/zuun.js when it spawns the tsx child (dev path).
49
+ * Authoritative in normal use.
50
+ * 2. CLAUDE_PLUGIN_ROOT — set by Claude Code when loaded as a plugin.
51
+ * 3. Walk up from process.argv[1] when it is src/cli.ts to find bin/zuun.js
52
+ * next door. Covers `tsx src/cli.ts install-git-hook` invocations.
53
+ * 4. Last resort: literal argv[1] — only correct when argv[1] is bin/zuun.js
54
+ * itself (compiled/direct-node path).
55
+ */
56
+ function resolveZuunBinPath() {
57
+ if (process.env.ZUUN_BIN)
58
+ return path.resolve(process.env.ZUUN_BIN);
59
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT;
60
+ if (pluginRoot)
61
+ return path.resolve(pluginRoot, "bin", "zuun.js");
62
+ const argv1 = process.argv[1];
63
+ if (argv1?.endsWith(".ts")) {
64
+ return path.resolve(path.dirname(argv1), "..", "bin", "zuun.js");
65
+ }
66
+ if (argv1)
67
+ return path.resolve(argv1);
68
+ throw new Error("install-git-hook: cannot resolve absolute path to zuun.js");
69
+ }
70
+ /** Resolve .git/hooks/post-commit honoring core.hooksPath and worktrees. */
71
+ function resolveHookPath() {
72
+ try {
73
+ const rel = (0, child_process_1.execFileSync)("git", ["rev-parse", "--git-path", "hooks/post-commit"], {
74
+ encoding: "utf8",
75
+ stdio: ["ignore", "pipe", "ignore"],
76
+ }).trim();
77
+ return path.resolve(process.cwd(), rel);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ async function installGitHook(_args) {
84
+ const hookPath = resolveHookPath();
85
+ if (!hookPath) {
86
+ process.stderr.write("install-git-hook: not inside a git repository\n");
87
+ return 1;
88
+ }
89
+ let zuunBin;
90
+ try {
91
+ zuunBin = resolveZuunBinPath();
92
+ }
93
+ catch (err) {
94
+ process.stderr.write(`install-git-hook: ${err.message}\n`);
95
+ return 1;
96
+ }
97
+ const zuunLine = `${ZUUN_MARKER}\nnode ${JSON.stringify(zuunBin)} capture-commit >/dev/null 2>&1 || true\n`;
98
+ fs.mkdirSync(path.dirname(hookPath), { recursive: true });
99
+ let content;
100
+ if (fs.existsSync(hookPath)) {
101
+ content = fs.readFileSync(hookPath, "utf8");
102
+ if (content.includes(ZUUN_MARKER)) {
103
+ process.stdout.write("post-commit hook already installed\n");
104
+ return 0;
105
+ }
106
+ if (!content.endsWith("\n"))
107
+ content += "\n";
108
+ content += "\n" + zuunLine;
109
+ }
110
+ else {
111
+ content = SHEBANG + zuunLine;
112
+ }
113
+ fs.writeFileSync(hookPath, content);
114
+ fs.chmodSync(hookPath, 0o755);
115
+ process.stdout.write(`installed post-commit hook at ${hookPath}\n invoking: ${zuunBin}\n`);
116
+ return 0;
117
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSessionStart = runSessionStart;
4
+ const db_1 = require("../lib/db");
5
+ const store_1 = require("../lib/store");
6
+ const project_1 = require("../lib/project");
7
+ const log_1 = require("../lib/log");
8
+ const MAX_CHARS = 2000;
9
+ const TOP_K = 3;
10
+ /**
11
+ * SessionStart only surfaces high-signal kinds. Observations get skipped because
12
+ * they're the noisy category — yesterday's debugging crumbs would drown out
13
+ * today's planning session if we included them. Decisions, patterns, commitments,
14
+ * and references are semantically stable across sessions and worth eager display.
15
+ */
16
+ const HIGH_SIGNAL_KINDS = ["decision", "pattern", "commitment", "reference"];
17
+ function entryInProject(entry, project) {
18
+ const p = entry.project;
19
+ // Entries with no project are global — included in every session's SessionStart.
20
+ if (!p)
21
+ return true;
22
+ return p === project || p.startsWith(project + "/") || project.startsWith(p + "/");
23
+ }
24
+ function relAge(ms) {
25
+ const d = Math.floor(ms / 86_400_000);
26
+ if (d < 1)
27
+ return "today";
28
+ if (d < 2)
29
+ return "yesterday";
30
+ if (d < 14)
31
+ return `${d}d ago`;
32
+ if (d < 60)
33
+ return `${Math.floor(d / 7)}w ago`;
34
+ return `${Math.floor(d / 30)}mo ago`;
35
+ }
36
+ async function runSessionStart(input) {
37
+ try {
38
+ const project = (0, project_1.resolveProject)(input.cwd);
39
+ if (!project) {
40
+ (0, log_1.appendLog)("session_start.miss", { cwd: input.cwd, reason: "no-project" });
41
+ return;
42
+ }
43
+ const db = (0, db_1.openDb)();
44
+ try {
45
+ // listEntries returns sorted by created DESC. Filter by project + kind, then take TOP_K.
46
+ const scoped = (0, store_1.listEntries)(db)
47
+ .filter((e) => HIGH_SIGNAL_KINDS.includes(e.kind))
48
+ .filter((e) => entryInProject(e, project))
49
+ .slice(0, TOP_K);
50
+ if (scoped.length === 0) {
51
+ (0, log_1.appendLog)("session_start.miss", { project, reason: "no-entries" });
52
+ return;
53
+ }
54
+ const now = Date.now();
55
+ const lines = ["Prior relevant context from Zuun:\n\n"];
56
+ for (const e of scoped) {
57
+ const body = e.body.replace(/\n/g, " ");
58
+ const age = relAge(now - new Date(e.created).getTime());
59
+ lines.push(`- ${e.id} (${e.kind}, ${age}): ${body}\n`);
60
+ }
61
+ let text = lines.join("");
62
+ if (text.length > MAX_CHARS)
63
+ text = text.slice(0, MAX_CHARS - 1) + "…";
64
+ const payload = {
65
+ hookSpecificOutput: {
66
+ hookEventName: "SessionStart",
67
+ additionalContext: text,
68
+ },
69
+ };
70
+ process.stdout.write(JSON.stringify(payload));
71
+ (0, log_1.appendLog)("session_start.inject", { project, hits: scoped.length, chars: text.length });
72
+ }
73
+ finally {
74
+ db.close();
75
+ }
76
+ }
77
+ catch (err) {
78
+ (0, log_1.appendLog)("session_start.miss", { cwd: input.cwd, reason: "error", err: err.message });
79
+ process.stderr.write(`zuun session-start hook: ${err.message}\n`);
80
+ }
81
+ }
package/dist/lib/db.js ADDED
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.SCHEMA_VERSION = void 0;
40
+ exports.openDb = openDb;
41
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
42
+ const sqliteVec = __importStar(require("sqlite-vec"));
43
+ const fs = __importStar(require("fs"));
44
+ const paths_1 = require("./paths");
45
+ exports.SCHEMA_VERSION = 2;
46
+ const DDL_STATEMENTS = [
47
+ `CREATE TABLE IF NOT EXISTS meta (
48
+ key TEXT PRIMARY KEY,
49
+ value TEXT NOT NULL
50
+ )`,
51
+ `CREATE TABLE IF NOT EXISTS entries (
52
+ id TEXT PRIMARY KEY,
53
+ body TEXT NOT NULL,
54
+ kind TEXT NOT NULL,
55
+ source TEXT NOT NULL,
56
+ created TEXT NOT NULL,
57
+ stance TEXT,
58
+ origin TEXT,
59
+ project TEXT,
60
+ tags TEXT NOT NULL DEFAULT '[]',
61
+ related TEXT NOT NULL DEFAULT '[]',
62
+ confidence TEXT
63
+ )`,
64
+ `CREATE VIRTUAL TABLE IF NOT EXISTS entries_fts USING fts5(
65
+ id UNINDEXED,
66
+ body,
67
+ tokenize = "unicode61 tokenchars '_-'"
68
+ )`,
69
+ `CREATE VIRTUAL TABLE IF NOT EXISTS entries_vec USING vec0(
70
+ id TEXT PRIMARY KEY,
71
+ embedding FLOAT[768]
72
+ )`,
73
+ ];
74
+ function openDb() {
75
+ fs.mkdirSync((0, paths_1.storeRoot)(), { recursive: true });
76
+ const db = new better_sqlite3_1.default((0, paths_1.dbPath)());
77
+ sqliteVec.load(db);
78
+ db.pragma("journal_mode = WAL");
79
+ db.pragma("foreign_keys = ON");
80
+ for (const sql of DDL_STATEMENTS)
81
+ db.prepare(sql).run();
82
+ const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
83
+ if (!row) {
84
+ db.prepare("INSERT INTO meta (key, value) VALUES ('schema_version', ?)").run(String(exports.SCHEMA_VERSION));
85
+ return db;
86
+ }
87
+ if (row.value !== String(exports.SCHEMA_VERSION)) {
88
+ db.close();
89
+ throw new Error(`zuun: index schema_version is ${row.value}, code expects ${exports.SCHEMA_VERSION}. ` +
90
+ `Run 'zuun reindex' to rebuild the index from your markdown files.`);
91
+ }
92
+ return db;
93
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.bodyHash = bodyHash;
37
+ exports.findRecentDuplicate = findRecentDuplicate;
38
+ const crypto = __importStar(require("crypto"));
39
+ function bodyHash(body) {
40
+ return crypto.createHash("sha256").update(body.trim()).digest("hex");
41
+ }
42
+ function findRecentDuplicate(db, body, now, windowMinutes = 10) {
43
+ const cutoff = new Date(now.getTime() - windowMinutes * 60_000).toISOString();
44
+ const target = bodyHash(body);
45
+ const rows = db
46
+ .prepare("SELECT id, body FROM entries WHERE created >= ? ORDER BY created DESC")
47
+ .all(cutoff);
48
+ for (const row of rows) {
49
+ if (bodyHash(row.body) === target)
50
+ return row.id;
51
+ }
52
+ return null;
53
+ }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDoctor = runDoctor;
4
+ const db_1 = require("./db");
5
+ const entry_io_1 = require("./entry-io");
6
+ const store_1 = require("./store");
7
+ const embed_provider_1 = require("./embed-provider");
8
+ const log_1 = require("./log");
9
+ async function runDoctor() {
10
+ const db = (0, db_1.openDb)();
11
+ const lines = [];
12
+ let healthy = true;
13
+ try {
14
+ const onDisk = (0, entry_io_1.listEntryIds)();
15
+ const inDb = (0, store_1.listEntries)(db);
16
+ lines.push(`schema_version: ${db_1.SCHEMA_VERSION}`);
17
+ lines.push(`entries on disk: ${onDisk.length}`);
18
+ lines.push(`entries in db: ${inDb.length}`);
19
+ if (onDisk.length !== inDb.length) {
20
+ healthy = false;
21
+ lines.push("WARN: drift between disk and db — run 'zuun reindex'");
22
+ }
23
+ const ids = new Set(inDb.map((e) => e.id));
24
+ let broken = 0;
25
+ for (const e of inDb) {
26
+ for (const r of e.related) {
27
+ if (!ids.has(r))
28
+ broken++;
29
+ }
30
+ }
31
+ lines.push(`broken related refs: ${broken}`);
32
+ const vec = await embed_provider_1.defaultProvider.embed("doctor-check");
33
+ lines.push(`ollama: ${vec ? "up" : "down"}`);
34
+ if (!vec)
35
+ lines.push(" note: embeddings are optional; FTS still works");
36
+ const tail = (0, log_1.tailLog)(5);
37
+ if (tail.length > 0) {
38
+ lines.push("recent log:");
39
+ for (const l of tail)
40
+ lines.push(` ${l.at} ${l.event} ${JSON.stringify(l.payload)}`);
41
+ }
42
+ }
43
+ finally {
44
+ db.close();
45
+ }
46
+ return { healthy, text: lines.join("\n") + "\n" };
47
+ }