@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.
- package/LICENSE +21 -0
- package/README.md +331 -0
- package/bin/zuun.js +34 -0
- package/dist/capture.js +84 -0
- package/dist/cli.js +205 -0
- package/dist/commands/capture-commit.js +75 -0
- package/dist/commands/edit.js +83 -0
- package/dist/commands/explain.js +31 -0
- package/dist/commands/forget.js +72 -0
- package/dist/commands/install-git-hook.js +117 -0
- package/dist/hook-scripts/session-start.js +81 -0
- package/dist/lib/db.js +93 -0
- package/dist/lib/dedup.js +53 -0
- package/dist/lib/doctor.js +47 -0
- package/dist/lib/embed-provider.js +42 -0
- package/dist/lib/embed.js +36 -0
- package/dist/lib/entry-io.js +82 -0
- package/dist/lib/entry.js +58 -0
- package/dist/lib/id.js +49 -0
- package/dist/lib/log.js +66 -0
- package/dist/lib/paths.js +53 -0
- package/dist/lib/project.js +67 -0
- package/dist/lib/search.js +121 -0
- package/dist/lib/store.js +64 -0
- package/dist/lib/tags.js +19 -0
- package/dist/mcp.js +231 -0
- package/dist/scripts/reindex.js +71 -0
- package/package.json +61 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultProvider = exports.OllamaProvider = exports.EMBED_DIM = void 0;
|
|
4
|
+
const log_1 = require("./log");
|
|
5
|
+
exports.EMBED_DIM = 768;
|
|
6
|
+
const MAX_INPUT_CHARS = 8000;
|
|
7
|
+
function l2Normalize(v) {
|
|
8
|
+
const norm = Math.sqrt(v.reduce((s, x) => s + x * x, 0));
|
|
9
|
+
return norm === 0 ? v : v.map((x) => x / norm);
|
|
10
|
+
}
|
|
11
|
+
class OllamaProvider {
|
|
12
|
+
url;
|
|
13
|
+
model;
|
|
14
|
+
constructor(url = process.env.OLLAMA_URL ?? "http://127.0.0.1:11434", model = process.env.ZUUN_EMBED_MODEL ?? "nomic-embed-text") {
|
|
15
|
+
this.url = url;
|
|
16
|
+
this.model = model;
|
|
17
|
+
}
|
|
18
|
+
async embed(text) {
|
|
19
|
+
const prompt = text.length > MAX_INPUT_CHARS ? text.slice(0, MAX_INPUT_CHARS) : text;
|
|
20
|
+
if (prompt.length < text.length) {
|
|
21
|
+
(0, log_1.appendLog)("embed.truncate", { from: text.length, to: prompt.length });
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(`${this.url}/api/embeddings`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "content-type": "application/json" },
|
|
27
|
+
body: JSON.stringify({ model: this.model, prompt }),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return null;
|
|
31
|
+
const data = (await res.json());
|
|
32
|
+
if (!data.embedding || data.embedding.length !== exports.EMBED_DIM)
|
|
33
|
+
return null;
|
|
34
|
+
return l2Normalize(data.embedding);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.OllamaProvider = OllamaProvider;
|
|
42
|
+
exports.defaultProvider = new OllamaProvider();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setEmbedding = setEmbedding;
|
|
4
|
+
exports.hasEmbedding = hasEmbedding;
|
|
5
|
+
exports.embedMissing = embedMissing;
|
|
6
|
+
const embed_provider_1 = require("./embed-provider");
|
|
7
|
+
function setEmbedding(db, id, vec) {
|
|
8
|
+
if (vec.length !== embed_provider_1.EMBED_DIM) {
|
|
9
|
+
throw new Error(`embed: expected ${embed_provider_1.EMBED_DIM}-dim, got ${vec.length}`);
|
|
10
|
+
}
|
|
11
|
+
const tx = db.transaction(() => {
|
|
12
|
+
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(id);
|
|
13
|
+
db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)").run(id, new Float32Array(vec));
|
|
14
|
+
});
|
|
15
|
+
tx();
|
|
16
|
+
}
|
|
17
|
+
function hasEmbedding(db, id) {
|
|
18
|
+
return db.prepare("SELECT 1 FROM entries_vec WHERE id = ?").get(id) !== undefined;
|
|
19
|
+
}
|
|
20
|
+
async function embedMissing(db, provider) {
|
|
21
|
+
const rows = db
|
|
22
|
+
.prepare("SELECT e.id, e.body FROM entries e LEFT JOIN entries_vec v ON v.id = e.id WHERE v.id IS NULL")
|
|
23
|
+
.all();
|
|
24
|
+
let embedded = 0;
|
|
25
|
+
let skipped = 0;
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
const vec = await provider.embed(row.body);
|
|
28
|
+
if (vec === null) {
|
|
29
|
+
skipped++;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
setEmbedding(db, row.id, vec);
|
|
33
|
+
embedded++;
|
|
34
|
+
}
|
|
35
|
+
return { embedded, skipped };
|
|
36
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
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.writeEntry = writeEntry;
|
|
40
|
+
exports.readEntry = readEntry;
|
|
41
|
+
exports.listEntryIds = listEntryIds;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const gray_matter_1 = __importDefault(require("gray-matter"));
|
|
45
|
+
const entry_1 = require("./entry");
|
|
46
|
+
const paths_1 = require("./paths");
|
|
47
|
+
function entryPath(id) {
|
|
48
|
+
return path.join((0, paths_1.entriesDir)(), `${id}.md`);
|
|
49
|
+
}
|
|
50
|
+
function writeEntry(entry) {
|
|
51
|
+
entry_1.EntrySchema.parse(entry);
|
|
52
|
+
fs.mkdirSync((0, paths_1.entriesDir)(), { recursive: true });
|
|
53
|
+
const { body, ...rest } = entry;
|
|
54
|
+
// js-yaml cannot serialize `undefined` — strip optional fields that weren't set.
|
|
55
|
+
// Keeping `undefined` keys in frontmatter (e.g., stance: undefined from a caller
|
|
56
|
+
// that explicitly sets it) would throw "unacceptable kind of an object to dump".
|
|
57
|
+
const frontmatter = {};
|
|
58
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
59
|
+
if (value !== undefined)
|
|
60
|
+
frontmatter[key] = value;
|
|
61
|
+
}
|
|
62
|
+
const file = gray_matter_1.default.stringify(body, frontmatter);
|
|
63
|
+
const final = entryPath(entry.id);
|
|
64
|
+
const tmp = `${final}.${process.pid}.tmp`;
|
|
65
|
+
fs.writeFileSync(tmp, file);
|
|
66
|
+
fs.renameSync(tmp, final);
|
|
67
|
+
}
|
|
68
|
+
function readEntry(id) {
|
|
69
|
+
const raw = fs.readFileSync(entryPath(id), "utf8");
|
|
70
|
+
const parsed = (0, gray_matter_1.default)(raw);
|
|
71
|
+
// Do NOT .trim() — body is canonical as written (with trailing newline).
|
|
72
|
+
return entry_1.EntrySchema.parse({ ...parsed.data, body: parsed.content });
|
|
73
|
+
}
|
|
74
|
+
function listEntryIds() {
|
|
75
|
+
const dir = (0, paths_1.entriesDir)();
|
|
76
|
+
if (!fs.existsSync(dir))
|
|
77
|
+
return [];
|
|
78
|
+
return fs
|
|
79
|
+
.readdirSync(dir)
|
|
80
|
+
.filter((f) => !f.startsWith(".") && f.endsWith(".md"))
|
|
81
|
+
.map((f) => f.slice(0, -3));
|
|
82
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EntrySchema = exports.Confidence = exports.EntrySource = exports.EntryKind = exports.ENTRY_ID_REGEX = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* An Entry is anything worth remembering from your work.
|
|
7
|
+
*
|
|
8
|
+
* This schema is load-bearing. It sets:
|
|
9
|
+
* - the ceiling on retrieval quality (what fields exist to rank/filter by),
|
|
10
|
+
* - the floor on capture success (what fields a capture must produce).
|
|
11
|
+
*
|
|
12
|
+
* Design bias: small required set, rich optional set. Auto-capture from
|
|
13
|
+
* Claude Code sessions cannot reliably produce a stance, so stance is
|
|
14
|
+
* optional. But capture can always produce body, kind, and source.
|
|
15
|
+
*
|
|
16
|
+
* If required vs. optional is wrong, it's easy to migrate early. It gets
|
|
17
|
+
* hard once there are thousands of entries. Think hard before changing.
|
|
18
|
+
*/
|
|
19
|
+
// ID format: ENT-YYMMDD-XXXX where XXXX is 4 hex chars.
|
|
20
|
+
exports.ENTRY_ID_REGEX = /^ENT-\d{6}-[A-F0-9]{4}$/;
|
|
21
|
+
exports.EntryKind = zod_1.z.enum([
|
|
22
|
+
"decision", // a choice made, with reasoning
|
|
23
|
+
"observation", // something noticed about how the world works
|
|
24
|
+
"pattern", // a reusable approach or shape
|
|
25
|
+
"commitment", // a promise made to future-self
|
|
26
|
+
"reference", // a piece of context worth preserving (snippet, link, spec)
|
|
27
|
+
]);
|
|
28
|
+
exports.EntrySource = zod_1.z.enum([
|
|
29
|
+
"claude-code", // auto-captured from a Claude Code session
|
|
30
|
+
"cursor", // auto-captured from Cursor
|
|
31
|
+
"git", // captured from a commit / PR / diff
|
|
32
|
+
"manual", // user typed it in via remember() or CLI
|
|
33
|
+
"import", // bulk-imported from another system
|
|
34
|
+
]);
|
|
35
|
+
exports.Confidence = zod_1.z.preprocess(
|
|
36
|
+
// case-insensitive accept; avoids the silent-drop bug from Zuhn's review
|
|
37
|
+
(v) => (typeof v === "string" ? v.toLowerCase() : v), zod_1.z.enum(["low", "medium", "high"]));
|
|
38
|
+
exports.EntrySchema = zod_1.z.object({
|
|
39
|
+
// --- required ---
|
|
40
|
+
id: zod_1.z.string().regex(exports.ENTRY_ID_REGEX),
|
|
41
|
+
created: zod_1.z.string().datetime(),
|
|
42
|
+
body: zod_1.z.string().min(1),
|
|
43
|
+
kind: exports.EntryKind,
|
|
44
|
+
source: exports.EntrySource,
|
|
45
|
+
// --- optional, but the retrieval layer leans on these ---
|
|
46
|
+
/** One-line directional claim, assertable as true or false. */
|
|
47
|
+
stance: zod_1.z.string().optional(),
|
|
48
|
+
/** Freeform tags. No ontology, no hierarchy. Retrieval handles grouping. */
|
|
49
|
+
tags: zod_1.z.array(zod_1.z.string()).default([]),
|
|
50
|
+
/** Advisory links to other entry IDs. Not validated; broken refs are fine. */
|
|
51
|
+
related: zod_1.z.array(zod_1.z.string()).default([]),
|
|
52
|
+
/** Self-assessed confidence in the entry's claim. */
|
|
53
|
+
confidence: exports.Confidence.optional(),
|
|
54
|
+
/** Where this came from — git sha, session id, URL, file path, etc. */
|
|
55
|
+
origin: zod_1.z.string().optional(),
|
|
56
|
+
/** Absolute path to the project/repo this entry came from. Used by SessionStart + context_for for scoped retrieval. */
|
|
57
|
+
project: zod_1.z.string().optional(),
|
|
58
|
+
});
|
package/dist/lib/id.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
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.newEntryId = newEntryId;
|
|
37
|
+
const crypto = __importStar(require("crypto"));
|
|
38
|
+
function newEntryId(body, at = new Date()) {
|
|
39
|
+
const yy = String(at.getUTCFullYear()).slice(-2);
|
|
40
|
+
const mm = String(at.getUTCMonth() + 1).padStart(2, "0");
|
|
41
|
+
const dd = String(at.getUTCDate()).padStart(2, "0");
|
|
42
|
+
const hash = crypto
|
|
43
|
+
.createHash("sha256")
|
|
44
|
+
.update(`${body}|${at.getTime()}`)
|
|
45
|
+
.digest("hex")
|
|
46
|
+
.slice(0, 4)
|
|
47
|
+
.toUpperCase();
|
|
48
|
+
return `ENT-${yy}${mm}${dd}-${hash}`;
|
|
49
|
+
}
|
package/dist/lib/log.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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.appendLog = appendLog;
|
|
37
|
+
exports.tailLog = tailLog;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const paths_1 = require("./paths");
|
|
40
|
+
function appendLog(event, payload) {
|
|
41
|
+
try {
|
|
42
|
+
fs.mkdirSync((0, paths_1.storeRoot)(), { recursive: true });
|
|
43
|
+
const line = { at: new Date().toISOString(), event, payload };
|
|
44
|
+
fs.appendFileSync((0, paths_1.logPath)(), JSON.stringify(line) + "\n");
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Logging must never break the caller. Swallow.
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function tailLog(n) {
|
|
51
|
+
const p = (0, paths_1.logPath)();
|
|
52
|
+
if (!fs.existsSync(p))
|
|
53
|
+
return [];
|
|
54
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
55
|
+
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
56
|
+
return lines
|
|
57
|
+
.slice(-n)
|
|
58
|
+
.flatMap((l) => {
|
|
59
|
+
try {
|
|
60
|
+
return [JSON.parse(l)];
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -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.storeRoot = storeRoot;
|
|
37
|
+
exports.entriesDir = entriesDir;
|
|
38
|
+
exports.dbPath = dbPath;
|
|
39
|
+
exports.logPath = logPath;
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
function storeRoot() {
|
|
43
|
+
return process.env.ZUUN_HOME ?? path.join(os.homedir(), ".zuun");
|
|
44
|
+
}
|
|
45
|
+
function entriesDir() {
|
|
46
|
+
return path.join(storeRoot(), "entries");
|
|
47
|
+
}
|
|
48
|
+
function dbPath() {
|
|
49
|
+
return path.join(storeRoot(), "index.db");
|
|
50
|
+
}
|
|
51
|
+
function logPath() {
|
|
52
|
+
return path.join(storeRoot(), "log.jsonl");
|
|
53
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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.resolveProject = resolveProject;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
/**
|
|
40
|
+
* Resolve an absolute project path for the given working directory.
|
|
41
|
+
*
|
|
42
|
+
* Returns the enclosing git repo's toplevel if cwd is inside a git repo.
|
|
43
|
+
* Returns `undefined` if the cwd is NOT inside a git repo — treated as a
|
|
44
|
+
* "global" capture in zuun's semantics (applies to every project).
|
|
45
|
+
*
|
|
46
|
+
* Also returns undefined if the cwd doesn't exist.
|
|
47
|
+
*/
|
|
48
|
+
function resolveProject(cwd = process.cwd()) {
|
|
49
|
+
try {
|
|
50
|
+
const real = fs.realpathSync(cwd);
|
|
51
|
+
try {
|
|
52
|
+
const root = (0, child_process_1.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
53
|
+
cwd: real,
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
56
|
+
}).trim();
|
|
57
|
+
return fs.realpathSync(root);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Not inside a git repo — treat as global (return undefined).
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseWeights = parseWeights;
|
|
4
|
+
exports.search = search;
|
|
5
|
+
const store_1 = require("./store");
|
|
6
|
+
function parseWeights(raw) {
|
|
7
|
+
const def = { fts: 0.45, vec: 0.45, recency: 0.1 };
|
|
8
|
+
if (!raw)
|
|
9
|
+
return def;
|
|
10
|
+
const parts = raw.split(",").map((s) => Number(s.trim()));
|
|
11
|
+
if (parts.length !== 3 || parts.some((n) => !Number.isFinite(n)))
|
|
12
|
+
return def;
|
|
13
|
+
const sum = parts[0] + parts[1] + parts[2];
|
|
14
|
+
if (sum <= 0)
|
|
15
|
+
return def;
|
|
16
|
+
return { fts: parts[0] / sum, vec: parts[1] / sum, recency: parts[2] / sum };
|
|
17
|
+
}
|
|
18
|
+
function sanitizeFts(q) {
|
|
19
|
+
return q
|
|
20
|
+
.replace(/["*]/g, " ")
|
|
21
|
+
.split(/\s+/)
|
|
22
|
+
.filter((t) => t.length > 0)
|
|
23
|
+
.map((t) => `"${t.replace(/"/g, "")}"`)
|
|
24
|
+
.join(" OR ");
|
|
25
|
+
}
|
|
26
|
+
function minMax(scores) {
|
|
27
|
+
if (scores.size === 0)
|
|
28
|
+
return scores;
|
|
29
|
+
const values = [...scores.values()];
|
|
30
|
+
const min = Math.min(...values);
|
|
31
|
+
const max = Math.max(...values);
|
|
32
|
+
if (max === min) {
|
|
33
|
+
const uniform = new Map();
|
|
34
|
+
for (const k of scores.keys())
|
|
35
|
+
uniform.set(k, 1);
|
|
36
|
+
return uniform;
|
|
37
|
+
}
|
|
38
|
+
const out = new Map();
|
|
39
|
+
for (const [k, v] of scores)
|
|
40
|
+
out.set(k, (v - min) / (max - min));
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function recencyScore(createdIso, now) {
|
|
44
|
+
const ageMs = now - new Date(createdIso).getTime();
|
|
45
|
+
const ageDays = Math.max(0, ageMs / 86_400_000);
|
|
46
|
+
return Math.exp(-ageDays / 30);
|
|
47
|
+
}
|
|
48
|
+
function passesFilters(entry, opts) {
|
|
49
|
+
if (opts.kind && entry.kind !== opts.kind)
|
|
50
|
+
return false;
|
|
51
|
+
if (opts.since && entry.created < opts.since)
|
|
52
|
+
return false;
|
|
53
|
+
if (opts.tags && opts.tags.length > 0) {
|
|
54
|
+
const have = new Set(entry.tags);
|
|
55
|
+
for (const t of opts.tags)
|
|
56
|
+
if (!have.has(t))
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
if (opts.project) {
|
|
60
|
+
const p = entry.project;
|
|
61
|
+
if (p) {
|
|
62
|
+
if (!(p === opts.project || p.startsWith(opts.project + "/") || opts.project.startsWith(p + "/"))) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function search(db, opts) {
|
|
70
|
+
const limit = opts.limit ?? 10;
|
|
71
|
+
if (opts.query.trim().length === 0)
|
|
72
|
+
return [];
|
|
73
|
+
const envWeights = parseWeights(process.env.ZUUN_SEARCH_BLEND);
|
|
74
|
+
const weights = opts.queryVec
|
|
75
|
+
? envWeights
|
|
76
|
+
: { fts: envWeights.fts + envWeights.vec, vec: 0, recency: envWeights.recency };
|
|
77
|
+
const ftsQuery = sanitizeFts(opts.query);
|
|
78
|
+
const ftsScores = new Map();
|
|
79
|
+
if (ftsQuery.length > 0) {
|
|
80
|
+
try {
|
|
81
|
+
const rows = db
|
|
82
|
+
.prepare("SELECT id, bm25(entries_fts) AS score FROM entries_fts WHERE entries_fts MATCH ? ORDER BY score LIMIT ?")
|
|
83
|
+
.all(ftsQuery, limit * 8);
|
|
84
|
+
for (const r of rows)
|
|
85
|
+
ftsScores.set(r.id, -r.score);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// fall through
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const vecScores = new Map();
|
|
92
|
+
if (opts.queryVec) {
|
|
93
|
+
const rows = db
|
|
94
|
+
.prepare("SELECT id, distance FROM entries_vec WHERE embedding MATCH ? AND k = ? ORDER BY distance")
|
|
95
|
+
.all(new Float32Array(opts.queryVec), limit * 8);
|
|
96
|
+
for (const r of rows)
|
|
97
|
+
vecScores.set(r.id, 1 - r.distance);
|
|
98
|
+
}
|
|
99
|
+
const normFts = minMax(ftsScores);
|
|
100
|
+
const normVec = minMax(vecScores);
|
|
101
|
+
const candidateIds = new Set([...normFts.keys(), ...normVec.keys()]);
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
const scored = [];
|
|
104
|
+
for (const id of candidateIds) {
|
|
105
|
+
const entry = (0, store_1.getEntry)(db, id);
|
|
106
|
+
if (!entry || !passesFilters(entry, opts))
|
|
107
|
+
continue;
|
|
108
|
+
const f = normFts.get(id) ?? 0;
|
|
109
|
+
const v = normVec.get(id) ?? 0;
|
|
110
|
+
const r = recencyScore(entry.created, now);
|
|
111
|
+
const score = weights.fts * f + weights.vec * v + weights.recency * r;
|
|
112
|
+
scored.push({ id, score, parts: { fts: f, vec: v, recency: r }, entry });
|
|
113
|
+
}
|
|
114
|
+
scored.sort((x, y) => y.score - x.score);
|
|
115
|
+
return scored.slice(0, limit).map((s) => ({
|
|
116
|
+
id: s.id,
|
|
117
|
+
score: s.score,
|
|
118
|
+
entry: s.entry,
|
|
119
|
+
parts: s.parts,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertEntry = upsertEntry;
|
|
4
|
+
exports.deleteEntry = deleteEntry;
|
|
5
|
+
exports.getEntry = getEntry;
|
|
6
|
+
exports.listEntries = listEntries;
|
|
7
|
+
const entry_1 = require("./entry");
|
|
8
|
+
function rowToEntry(row) {
|
|
9
|
+
return entry_1.EntrySchema.parse({
|
|
10
|
+
...row,
|
|
11
|
+
tags: JSON.parse(row.tags),
|
|
12
|
+
related: JSON.parse(row.related),
|
|
13
|
+
stance: row.stance ?? undefined,
|
|
14
|
+
origin: row.origin ?? undefined,
|
|
15
|
+
project: row.project ?? undefined,
|
|
16
|
+
confidence: row.confidence ?? undefined,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function upsertEntry(db, entry) {
|
|
20
|
+
entry_1.EntrySchema.parse(entry);
|
|
21
|
+
const tx = db.transaction(() => {
|
|
22
|
+
db.prepare(`INSERT INTO entries (id, body, kind, source, created, stance, origin, project, tags, related, confidence)
|
|
23
|
+
VALUES (@id, @body, @kind, @source, @created, @stance, @origin, @project, @tags, @related, @confidence)
|
|
24
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
25
|
+
body=excluded.body, kind=excluded.kind, source=excluded.source,
|
|
26
|
+
created=excluded.created, stance=excluded.stance, origin=excluded.origin,
|
|
27
|
+
project=excluded.project,
|
|
28
|
+
tags=excluded.tags, related=excluded.related, confidence=excluded.confidence`).run({
|
|
29
|
+
id: entry.id,
|
|
30
|
+
body: entry.body,
|
|
31
|
+
kind: entry.kind,
|
|
32
|
+
source: entry.source,
|
|
33
|
+
created: entry.created,
|
|
34
|
+
stance: entry.stance ?? null,
|
|
35
|
+
origin: entry.origin ?? null,
|
|
36
|
+
project: entry.project ?? null,
|
|
37
|
+
tags: JSON.stringify(entry.tags),
|
|
38
|
+
related: JSON.stringify(entry.related),
|
|
39
|
+
confidence: entry.confidence ?? null,
|
|
40
|
+
});
|
|
41
|
+
// Regular-content FTS5 supports plain DELETE + INSERT.
|
|
42
|
+
db.prepare("DELETE FROM entries_fts WHERE id = ?").run(entry.id);
|
|
43
|
+
db.prepare("INSERT INTO entries_fts (id, body) VALUES (?, ?)").run(entry.id, entry.body);
|
|
44
|
+
});
|
|
45
|
+
tx();
|
|
46
|
+
}
|
|
47
|
+
function deleteEntry(db, id) {
|
|
48
|
+
const tx = db.transaction(() => {
|
|
49
|
+
db.prepare("DELETE FROM entries WHERE id = ?").run(id);
|
|
50
|
+
db.prepare("DELETE FROM entries_fts WHERE id = ?").run(id);
|
|
51
|
+
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(id);
|
|
52
|
+
});
|
|
53
|
+
tx();
|
|
54
|
+
}
|
|
55
|
+
function getEntry(db, id) {
|
|
56
|
+
const row = db.prepare("SELECT * FROM entries WHERE id = ?").get(id);
|
|
57
|
+
return row ? rowToEntry(row) : null;
|
|
58
|
+
}
|
|
59
|
+
function listEntries(db) {
|
|
60
|
+
const rows = db
|
|
61
|
+
.prepare("SELECT * FROM entries ORDER BY created DESC")
|
|
62
|
+
.all();
|
|
63
|
+
return rows.map(rowToEntry);
|
|
64
|
+
}
|
package/dist/lib/tags.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeTag = normalizeTag;
|
|
4
|
+
exports.normalizeTags = normalizeTags;
|
|
5
|
+
function normalizeTag(raw) {
|
|
6
|
+
return raw.trim().toLowerCase().replace(/\s+/g, "-");
|
|
7
|
+
}
|
|
8
|
+
function normalizeTags(raws) {
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
const out = [];
|
|
11
|
+
for (const raw of raws) {
|
|
12
|
+
const t = normalizeTag(raw);
|
|
13
|
+
if (t.length === 0 || seen.has(t))
|
|
14
|
+
continue;
|
|
15
|
+
seen.add(t);
|
|
16
|
+
out.push(t);
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|