@yurtsever/capsa 0.1.0-alpha.3 → 0.1.0-alpha.4
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/index.js +93 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10227,6 +10227,10 @@ var Store = class {
|
|
|
10227
10227
|
}
|
|
10228
10228
|
}));
|
|
10229
10229
|
}
|
|
10230
|
+
/** All chunks of one item, in document order. */
|
|
10231
|
+
chunksForItem(itemId) {
|
|
10232
|
+
return this.db.prepare("SELECT id, item_id, idx, heading_path, text FROM chunks WHERE item_id = ? ORDER BY idx").all(itemId);
|
|
10233
|
+
}
|
|
10230
10234
|
listItems() {
|
|
10231
10235
|
return this.db.prepare("SELECT * FROM items ORDER BY kind, rel_path").all();
|
|
10232
10236
|
}
|
|
@@ -10566,6 +10570,40 @@ function rrf(lists, k = DEFAULT_WEIGHTS.rrfK) {
|
|
|
10566
10570
|
return scores;
|
|
10567
10571
|
}
|
|
10568
10572
|
|
|
10573
|
+
// ../index/dist/pin.js
|
|
10574
|
+
var LEADING_ID = /^([A-Za-z]{1,8}-?\d{1,5}(?:-\d{1,5})?|\d{2,6})(?=[-_.\s]|$)/;
|
|
10575
|
+
function leadingId(relPath) {
|
|
10576
|
+
const base = relPath.split(/[\\/]/).pop() ?? "";
|
|
10577
|
+
return LEADING_ID.exec(base)?.[1];
|
|
10578
|
+
}
|
|
10579
|
+
function pinnedItems(query, items) {
|
|
10580
|
+
const q = query.toLowerCase();
|
|
10581
|
+
const idCount = /* @__PURE__ */ new Map();
|
|
10582
|
+
for (const item of items) {
|
|
10583
|
+
const id = leadingId(item.rel_path)?.toLowerCase();
|
|
10584
|
+
if (id)
|
|
10585
|
+
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
10586
|
+
}
|
|
10587
|
+
const out = [];
|
|
10588
|
+
for (const item of items) {
|
|
10589
|
+
const rel = item.rel_path.toLowerCase();
|
|
10590
|
+
if (q.includes(rel)) {
|
|
10591
|
+
out.push(item);
|
|
10592
|
+
continue;
|
|
10593
|
+
}
|
|
10594
|
+
const id = leadingId(item.rel_path);
|
|
10595
|
+
if (!id || (idCount.get(id.toLowerCase()) ?? 0) > 1)
|
|
10596
|
+
continue;
|
|
10597
|
+
const re = new RegExp(`(^|[^\\w-])${escape2(id)}(?=$|[^\\w-])`, "i");
|
|
10598
|
+
if (re.test(query))
|
|
10599
|
+
out.push(item);
|
|
10600
|
+
}
|
|
10601
|
+
return out;
|
|
10602
|
+
}
|
|
10603
|
+
function escape2(s) {
|
|
10604
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10605
|
+
}
|
|
10606
|
+
|
|
10569
10607
|
// ../index/dist/retrieve.js
|
|
10570
10608
|
async function retrieve(store, embedder, query, options = {}) {
|
|
10571
10609
|
const started = Date.now();
|
|
@@ -10573,31 +10611,73 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10573
10611
|
const candidates = options.candidates ?? 30;
|
|
10574
10612
|
const weights = options.weights ?? DEFAULT_WEIGHTS;
|
|
10575
10613
|
const minRelative = options.minRelativeScore ?? 0.5;
|
|
10614
|
+
const perItem = /* @__PURE__ */ new Map();
|
|
10615
|
+
const chunks = [];
|
|
10616
|
+
const debug = [];
|
|
10617
|
+
let tokens = 0;
|
|
10618
|
+
const pinned = pinnedItems(query, store.listItems());
|
|
10619
|
+
for (const item of pinned) {
|
|
10620
|
+
for (const c of store.chunksForItem(item.id)) {
|
|
10621
|
+
const t = estimateTokens(c.text);
|
|
10622
|
+
const fits = tokens + t <= maxTokens || chunks.length === 0;
|
|
10623
|
+
if (options.debug) {
|
|
10624
|
+
debug.push({
|
|
10625
|
+
chunkId: c.id,
|
|
10626
|
+
relPath: item.rel_path,
|
|
10627
|
+
headingPath: JSON.parse(c.heading_path),
|
|
10628
|
+
kind: item.kind,
|
|
10629
|
+
status: item.status,
|
|
10630
|
+
vecRank: null,
|
|
10631
|
+
ftsRank: null,
|
|
10632
|
+
fused: 0,
|
|
10633
|
+
factor: 1,
|
|
10634
|
+
score: Number.POSITIVE_INFINITY,
|
|
10635
|
+
kept: fits,
|
|
10636
|
+
why: fits ? "pinned" : "budget"
|
|
10637
|
+
});
|
|
10638
|
+
}
|
|
10639
|
+
if (!fits)
|
|
10640
|
+
continue;
|
|
10641
|
+
perItem.set(item.id, (perItem.get(item.id) ?? 0) + 1);
|
|
10642
|
+
tokens += t;
|
|
10643
|
+
chunks.push({
|
|
10644
|
+
chunkId: c.id,
|
|
10645
|
+
score: Number.POSITIVE_INFINITY,
|
|
10646
|
+
kind: item.kind,
|
|
10647
|
+
status: item.status,
|
|
10648
|
+
updatedAt: item.updated_at,
|
|
10649
|
+
relPath: item.rel_path,
|
|
10650
|
+
headingPath: JSON.parse(c.heading_path),
|
|
10651
|
+
text: c.text,
|
|
10652
|
+
tokens: t
|
|
10653
|
+
});
|
|
10654
|
+
}
|
|
10655
|
+
}
|
|
10656
|
+
const pinnedIds = new Set(pinned.map((i) => i.id));
|
|
10657
|
+
const neighbourCutoff = pinned.length ? Math.max(minRelative, 0.75) : minRelative;
|
|
10658
|
+
const neighbourBudget = pinned.length ? Math.min(maxTokens, tokens + Math.floor(maxTokens / 3)) : maxTokens;
|
|
10576
10659
|
const [qv] = await embedder.embed([query]);
|
|
10577
10660
|
const vec = qv ? store.vectorSearch(qv, candidates).map((r) => r.chunkId) : [];
|
|
10578
10661
|
const fts = store.textSearch(query, candidates).map((r) => r.chunkId);
|
|
10579
10662
|
const fused = rrf([vec, fts], weights.rrfK);
|
|
10580
|
-
const rows = store.chunksWithItems([...fused.keys()]);
|
|
10663
|
+
const rows = store.chunksWithItems([...fused.keys()]).filter((r) => !pinnedIds.has(r.item_id));
|
|
10581
10664
|
const now = Date.now();
|
|
10582
10665
|
const scored = rows.map((r) => {
|
|
10583
10666
|
const fusedScore = fused.get(r.id) ?? 0;
|
|
10584
10667
|
const factor = signalFactor({ kind: r.item.kind, status: r.item.status, updatedAt: r.item.updated_at }, weights, now);
|
|
10585
10668
|
return { row: r, fusedScore, factor, score: fusedScore * factor };
|
|
10586
10669
|
}).sort((a, b) => b.score - a.score);
|
|
10587
|
-
const perItem = /* @__PURE__ */ new Map();
|
|
10588
|
-
const chunks = [];
|
|
10589
|
-
const debug = [];
|
|
10590
|
-
let tokens = 0;
|
|
10591
10670
|
const best = scored[0]?.score ?? 0;
|
|
10592
|
-
let cut =
|
|
10671
|
+
let cut = tokens >= maxTokens;
|
|
10672
|
+
let kept = 0;
|
|
10593
10673
|
for (const { row, fusedScore, factor, score } of scored) {
|
|
10594
10674
|
let why = "kept";
|
|
10595
|
-
if (cut ||
|
|
10675
|
+
if (cut || kept > 0 && score < best * neighbourCutoff) {
|
|
10596
10676
|
cut = true;
|
|
10597
10677
|
why = "cutoff";
|
|
10598
10678
|
} else if ((perItem.get(row.item_id) ?? 0) >= 2) {
|
|
10599
10679
|
why = "per-item cap";
|
|
10600
|
-
} else if (tokens + estimateTokens(row.text) >
|
|
10680
|
+
} else if (tokens + estimateTokens(row.text) > neighbourBudget && chunks.length > 0) {
|
|
10601
10681
|
why = "budget";
|
|
10602
10682
|
}
|
|
10603
10683
|
if (options.debug) {
|
|
@@ -10621,6 +10701,7 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10621
10701
|
const t = estimateTokens(row.text);
|
|
10622
10702
|
perItem.set(row.item_id, (perItem.get(row.item_id) ?? 0) + 1);
|
|
10623
10703
|
tokens += t;
|
|
10704
|
+
kept++;
|
|
10624
10705
|
chunks.push({
|
|
10625
10706
|
chunkId: row.id,
|
|
10626
10707
|
score,
|
|
@@ -10632,7 +10713,7 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10632
10713
|
text: row.text,
|
|
10633
10714
|
tokens: t
|
|
10634
10715
|
});
|
|
10635
|
-
if (tokens >=
|
|
10716
|
+
if (tokens >= neighbourBudget)
|
|
10636
10717
|
cut = true;
|
|
10637
10718
|
}
|
|
10638
10719
|
const durationMs = Date.now() - started;
|
|
@@ -10652,15 +10733,15 @@ function rankIn(list, id) {
|
|
|
10652
10733
|
function formatDebug(result) {
|
|
10653
10734
|
if (!result.debug)
|
|
10654
10735
|
return "";
|
|
10655
|
-
const lines = [" vec fts factor score keep
|
|
10736
|
+
const lines = [" vec fts factor score keep where"];
|
|
10656
10737
|
for (const d of result.debug) {
|
|
10657
10738
|
const where = [d.relPath, ...d.headingPath].join(" \u203A ").slice(0, 70);
|
|
10658
10739
|
lines.push([
|
|
10659
10740
|
String(d.vecRank ?? "-").padStart(5),
|
|
10660
10741
|
String(d.ftsRank ?? "-").padStart(4),
|
|
10661
10742
|
d.factor.toFixed(2).padStart(7),
|
|
10662
|
-
d.score.toFixed(4).padStart(8),
|
|
10663
|
-
(d.kept ? "yes" : d.why).padEnd(12),
|
|
10743
|
+
(Number.isFinite(d.score) ? d.score.toFixed(4) : "pin").padStart(8),
|
|
10744
|
+
(d.kept ? d.why === "pinned" ? "pinned" : "yes" : d.why).padEnd(12),
|
|
10664
10745
|
where
|
|
10665
10746
|
].join(" "));
|
|
10666
10747
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yurtsever/capsa",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "Your project's knowledge in one capsule. Local-first project memory and cockpit for AI coding agents: indexes runbooks, tickets, plans and instruction files, serves the smallest relevant context over MCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|