@yurtsever/capsa 0.1.0-alpha.0 → 0.1.0-alpha.3
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 +154 -38
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10053,6 +10053,7 @@ function loadSqlite() {
|
|
|
10053
10053
|
}
|
|
10054
10054
|
var DB_DIR = ".capsa";
|
|
10055
10055
|
var DB_FILE = "index.db";
|
|
10056
|
+
var SCHEMA_VERSION = 3;
|
|
10056
10057
|
function dbPath(projectRoot) {
|
|
10057
10058
|
return join6(projectRoot, DB_DIR, DB_FILE);
|
|
10058
10059
|
}
|
|
@@ -10069,6 +10070,27 @@ var Store = class {
|
|
|
10069
10070
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
10070
10071
|
this.migrate();
|
|
10071
10072
|
}
|
|
10073
|
+
/** True when this index was written by an older schema and must be rebuilt. */
|
|
10074
|
+
needsRebuild() {
|
|
10075
|
+
const row = this.db.prepare("SELECT value FROM meta WHERE key = 'schema'").get();
|
|
10076
|
+
return row?.value !== String(SCHEMA_VERSION);
|
|
10077
|
+
}
|
|
10078
|
+
/**
|
|
10079
|
+
* Drop all indexed content (not the log, not decisions) and mark the schema
|
|
10080
|
+
* current. The virtual tables are dropped and recreated, not emptied:
|
|
10081
|
+
* `CREATE VIRTUAL TABLE IF NOT EXISTS` would keep an old tokenizer alive
|
|
10082
|
+
* through a rebuild — which once left FTS without stemming after v2.
|
|
10083
|
+
*/
|
|
10084
|
+
reset() {
|
|
10085
|
+
this.db.exec(`
|
|
10086
|
+
DROP TABLE IF EXISTS chunks_fts;
|
|
10087
|
+
DROP TABLE IF EXISTS chunks_vec;
|
|
10088
|
+
DELETE FROM chunks;
|
|
10089
|
+
DELETE FROM items;
|
|
10090
|
+
`);
|
|
10091
|
+
this.migrate();
|
|
10092
|
+
this.db.prepare("INSERT OR REPLACE INTO meta(key, value) VALUES ('schema', ?)").run(String(SCHEMA_VERSION));
|
|
10093
|
+
}
|
|
10072
10094
|
migrate() {
|
|
10073
10095
|
this.db.exec(`
|
|
10074
10096
|
CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
|
@@ -10097,7 +10119,10 @@ var Store = class {
|
|
|
10097
10119
|
);
|
|
10098
10120
|
CREATE INDEX IF NOT EXISTS chunks_item ON chunks(item_id);
|
|
10099
10121
|
|
|
10100
|
-
|
|
10122
|
+
-- porter: "enforcement" must find "Enforce". The indexed text is the
|
|
10123
|
+
-- chunk plus its provenance header (title, heading trail) so a section
|
|
10124
|
+
-- called "Acceptance criteria" still knows which ticket it belongs to.
|
|
10125
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(text, tokenize = 'porter unicode61');
|
|
10101
10126
|
|
|
10102
10127
|
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec USING vec0(
|
|
10103
10128
|
chunk_id INTEGER PRIMARY KEY,
|
|
@@ -10151,10 +10176,14 @@ var Store = class {
|
|
|
10151
10176
|
status = excluded.status, updated_at = excluded.updated_at, tags = excluded.tags,
|
|
10152
10177
|
indexed_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')`).run(row.id, row.format, row.kind, row.name, row.path, row.rel_path, row.content_hash, row.status, row.updated_at, row.tags);
|
|
10153
10178
|
}
|
|
10154
|
-
|
|
10179
|
+
/**
|
|
10180
|
+
* `text` is what the user sees; `searchText` (chunk + provenance header)
|
|
10181
|
+
* is what FTS and the embedder see. Falls back to `text` when omitted.
|
|
10182
|
+
*/
|
|
10183
|
+
insertChunk(itemId, idx, headingPath, text, embedding, searchText = text) {
|
|
10155
10184
|
const res = this.db.prepare("INSERT INTO chunks (item_id, idx, heading_path, text) VALUES (?, ?, ?, ?)").run(itemId, idx, JSON.stringify(headingPath), text);
|
|
10156
10185
|
const id = Number(res.lastInsertRowid);
|
|
10157
|
-
this.db.prepare("INSERT INTO chunks_fts (rowid, text) VALUES (?, ?)").run(id,
|
|
10186
|
+
this.db.prepare("INSERT INTO chunks_fts (rowid, text) VALUES (?, ?)").run(id, searchText);
|
|
10158
10187
|
this.db.prepare("INSERT INTO chunks_vec (chunk_id, embedding) VALUES (?, ?)").run(BigInt(id), new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength));
|
|
10159
10188
|
return id;
|
|
10160
10189
|
}
|
|
@@ -10221,7 +10250,7 @@ import { relative as relative5 } from "path";
|
|
|
10221
10250
|
import { readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
10222
10251
|
import { basename as basename4, dirname as dirname7, relative as relative3, sep as sep4 } from "path";
|
|
10223
10252
|
var DOC_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "runbooks", "runbook", "wiki", "adr", "decisions"]);
|
|
10224
|
-
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "plans", "node_modules"]);
|
|
10253
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "todo", "backlog", "plans", "plan", "roadmap", "milestones", "milestone", "node_modules"]);
|
|
10225
10254
|
function segments(relPath) {
|
|
10226
10255
|
return relPath.split(/[\\/]/).filter(Boolean);
|
|
10227
10256
|
}
|
|
@@ -10294,7 +10323,7 @@ function firstHeading(raw2) {
|
|
|
10294
10323
|
import { readFile as readFile6, stat as stat4 } from "fs/promises";
|
|
10295
10324
|
import { basename as basename5, dirname as dirname8, relative as relative4, sep as sep5 } from "path";
|
|
10296
10325
|
var TICKET_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "todo", "backlog"]);
|
|
10297
|
-
var PLAN_DIRS = /* @__PURE__ */ new Set(["plans", "plan", "roadmap"]);
|
|
10326
|
+
var PLAN_DIRS = /* @__PURE__ */ new Set(["plans", "plan", "roadmap", "milestones", "milestone"]);
|
|
10298
10327
|
var CHECKBOX = /^\s*[-*+]\s+\[( |x|X)\]\s+/gm;
|
|
10299
10328
|
function segments2(relPath) {
|
|
10300
10329
|
return relPath.split(/[\\/]/).filter(Boolean);
|
|
@@ -10414,6 +10443,10 @@ async function indexProject(projectRoot, embedder, onProgress) {
|
|
|
10414
10443
|
const started = Date.now();
|
|
10415
10444
|
const store = openStore(projectRoot, embedder);
|
|
10416
10445
|
try {
|
|
10446
|
+
if (store.needsRebuild()) {
|
|
10447
|
+
onProgress?.({ phase: "rebuild" });
|
|
10448
|
+
store.reset();
|
|
10449
|
+
}
|
|
10417
10450
|
onProgress?.({ phase: "scan" });
|
|
10418
10451
|
const result = await scan(knowledgeAdapters(), { roots: [projectRoot] });
|
|
10419
10452
|
const items = result.items.filter((i) => i.scope === "project" && isInside(projectRoot, i.path));
|
|
@@ -10431,8 +10464,8 @@ async function indexProject(projectRoot, embedder, onProgress) {
|
|
|
10431
10464
|
if (existing)
|
|
10432
10465
|
store.deleteItem(existing.id);
|
|
10433
10466
|
const chunks = chunkMarkdown(item.content);
|
|
10434
|
-
const
|
|
10435
|
-
const vectors =
|
|
10467
|
+
const searchTexts = chunks.map((c) => withContext(item, c.headingPath, c.text));
|
|
10468
|
+
const vectors = searchTexts.length ? await embedder.embed(searchTexts) : [];
|
|
10436
10469
|
const k = item.knowledge;
|
|
10437
10470
|
store.upsertItem({
|
|
10438
10471
|
id: item.id,
|
|
@@ -10446,7 +10479,7 @@ async function indexProject(projectRoot, embedder, onProgress) {
|
|
|
10446
10479
|
updated_at: k?.updatedAt ?? null,
|
|
10447
10480
|
tags: k?.tags ? JSON.stringify(k.tags) : null
|
|
10448
10481
|
});
|
|
10449
|
-
chunks.forEach((c, i) => store.insertChunk(item.id, c.index, c.headingPath, c.text, vectors[i]));
|
|
10482
|
+
chunks.forEach((c, i) => store.insertChunk(item.id, c.index, c.headingPath, c.text, vectors[i], searchTexts[i]));
|
|
10450
10483
|
indexed++;
|
|
10451
10484
|
onProgress?.({ phase: "embed", scanned: items.length, indexed, skipped });
|
|
10452
10485
|
}
|
|
@@ -10503,6 +10536,7 @@ var DEFAULT_WEIGHTS = {
|
|
|
10503
10536
|
plan: 1.05,
|
|
10504
10537
|
ticket: 1,
|
|
10505
10538
|
runbook: 1,
|
|
10539
|
+
doc: 0.95,
|
|
10506
10540
|
wiki: 0.95,
|
|
10507
10541
|
commit: 0.85
|
|
10508
10542
|
}
|
|
@@ -10518,7 +10552,9 @@ function recencyFactor(updatedAt, w, now = Date.now()) {
|
|
|
10518
10552
|
return w.recencyFloor + (1 - w.recencyFloor) * decay;
|
|
10519
10553
|
}
|
|
10520
10554
|
function signalFactor(s, w = DEFAULT_WEIGHTS, now = Date.now()) {
|
|
10521
|
-
|
|
10555
|
+
const status = w.status[s.status ?? "unknown"] ?? 1;
|
|
10556
|
+
const kind = w.kind[s.kind] ?? 1;
|
|
10557
|
+
return recencyFactor(s.updatedAt, w, now) * status * kind;
|
|
10522
10558
|
}
|
|
10523
10559
|
function rrf(lists, k = DEFAULT_WEIGHTS.rrfK) {
|
|
10524
10560
|
const scores = /* @__PURE__ */ new Map();
|
|
@@ -10536,27 +10572,54 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10536
10572
|
const maxTokens = options.maxTokens ?? 1500;
|
|
10537
10573
|
const candidates = options.candidates ?? 30;
|
|
10538
10574
|
const weights = options.weights ?? DEFAULT_WEIGHTS;
|
|
10575
|
+
const minRelative = options.minRelativeScore ?? 0.5;
|
|
10539
10576
|
const [qv] = await embedder.embed([query]);
|
|
10540
10577
|
const vec = qv ? store.vectorSearch(qv, candidates).map((r) => r.chunkId) : [];
|
|
10541
10578
|
const fts = store.textSearch(query, candidates).map((r) => r.chunkId);
|
|
10542
10579
|
const fused = rrf([vec, fts], weights.rrfK);
|
|
10543
10580
|
const rows = store.chunksWithItems([...fused.keys()]);
|
|
10544
10581
|
const now = Date.now();
|
|
10545
|
-
const scored = rows.map((r) =>
|
|
10546
|
-
|
|
10547
|
-
|
|
10548
|
-
|
|
10582
|
+
const scored = rows.map((r) => {
|
|
10583
|
+
const fusedScore = fused.get(r.id) ?? 0;
|
|
10584
|
+
const factor = signalFactor({ kind: r.item.kind, status: r.item.status, updatedAt: r.item.updated_at }, weights, now);
|
|
10585
|
+
return { row: r, fusedScore, factor, score: fusedScore * factor };
|
|
10586
|
+
}).sort((a, b) => b.score - a.score);
|
|
10549
10587
|
const perItem = /* @__PURE__ */ new Map();
|
|
10550
10588
|
const chunks = [];
|
|
10589
|
+
const debug = [];
|
|
10551
10590
|
let tokens = 0;
|
|
10552
|
-
|
|
10553
|
-
|
|
10554
|
-
|
|
10591
|
+
const best = scored[0]?.score ?? 0;
|
|
10592
|
+
let cut = false;
|
|
10593
|
+
for (const { row, fusedScore, factor, score } of scored) {
|
|
10594
|
+
let why = "kept";
|
|
10595
|
+
if (cut || chunks.length > 0 && score < best * minRelative) {
|
|
10596
|
+
cut = true;
|
|
10597
|
+
why = "cutoff";
|
|
10598
|
+
} else if ((perItem.get(row.item_id) ?? 0) >= 2) {
|
|
10599
|
+
why = "per-item cap";
|
|
10600
|
+
} else if (tokens + estimateTokens(row.text) > maxTokens && chunks.length > 0) {
|
|
10601
|
+
why = "budget";
|
|
10602
|
+
}
|
|
10603
|
+
if (options.debug) {
|
|
10604
|
+
debug.push({
|
|
10605
|
+
chunkId: row.id,
|
|
10606
|
+
relPath: row.item.rel_path,
|
|
10607
|
+
headingPath: JSON.parse(row.heading_path),
|
|
10608
|
+
kind: row.item.kind,
|
|
10609
|
+
status: row.item.status,
|
|
10610
|
+
vecRank: rankIn(vec, row.id),
|
|
10611
|
+
ftsRank: rankIn(fts, row.id),
|
|
10612
|
+
fused: fusedScore,
|
|
10613
|
+
factor,
|
|
10614
|
+
score,
|
|
10615
|
+
kept: why === "kept",
|
|
10616
|
+
why
|
|
10617
|
+
});
|
|
10618
|
+
}
|
|
10619
|
+
if (why !== "kept")
|
|
10555
10620
|
continue;
|
|
10556
10621
|
const t = estimateTokens(row.text);
|
|
10557
|
-
|
|
10558
|
-
continue;
|
|
10559
|
-
perItem.set(row.item_id, used + 1);
|
|
10622
|
+
perItem.set(row.item_id, (perItem.get(row.item_id) ?? 0) + 1);
|
|
10560
10623
|
tokens += t;
|
|
10561
10624
|
chunks.push({
|
|
10562
10625
|
chunkId: row.id,
|
|
@@ -10570,7 +10633,7 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10570
10633
|
tokens: t
|
|
10571
10634
|
});
|
|
10572
10635
|
if (tokens >= maxTokens)
|
|
10573
|
-
|
|
10636
|
+
cut = true;
|
|
10574
10637
|
}
|
|
10575
10638
|
const durationMs = Date.now() - started;
|
|
10576
10639
|
store.logContext({
|
|
@@ -10580,7 +10643,28 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10580
10643
|
tokensEst: tokens,
|
|
10581
10644
|
durationMs
|
|
10582
10645
|
});
|
|
10583
|
-
return { query, chunks, tokensEst: tokens, durationMs };
|
|
10646
|
+
return { query, chunks, tokensEst: tokens, durationMs, ...options.debug ? { debug } : {} };
|
|
10647
|
+
}
|
|
10648
|
+
function rankIn(list, id) {
|
|
10649
|
+
const i = list.indexOf(id);
|
|
10650
|
+
return i === -1 ? null : i + 1;
|
|
10651
|
+
}
|
|
10652
|
+
function formatDebug(result) {
|
|
10653
|
+
if (!result.debug)
|
|
10654
|
+
return "";
|
|
10655
|
+
const lines = [" vec fts factor score keep where"];
|
|
10656
|
+
for (const d of result.debug) {
|
|
10657
|
+
const where = [d.relPath, ...d.headingPath].join(" \u203A ").slice(0, 70);
|
|
10658
|
+
lines.push([
|
|
10659
|
+
String(d.vecRank ?? "-").padStart(5),
|
|
10660
|
+
String(d.ftsRank ?? "-").padStart(4),
|
|
10661
|
+
d.factor.toFixed(2).padStart(7),
|
|
10662
|
+
d.score.toFixed(4).padStart(8),
|
|
10663
|
+
(d.kept ? "yes" : d.why).padEnd(12),
|
|
10664
|
+
where
|
|
10665
|
+
].join(" "));
|
|
10666
|
+
}
|
|
10667
|
+
return lines.join("\n");
|
|
10584
10668
|
}
|
|
10585
10669
|
function formatContext(result) {
|
|
10586
10670
|
if (result.chunks.length === 0)
|
|
@@ -10602,7 +10686,8 @@ function projectState(store) {
|
|
|
10602
10686
|
for (const i of items)
|
|
10603
10687
|
counts[i.kind] = (counts[i.kind] ?? 0) + 1;
|
|
10604
10688
|
const live = (i) => (i.kind === "ticket" || i.kind === "plan") && (i.status === "open" || i.status === "in-progress");
|
|
10605
|
-
const
|
|
10689
|
+
const rank = (i) => i.status === "in-progress" ? 0 : 1;
|
|
10690
|
+
const open2 = items.filter(live).sort((a, b) => rank(a) - rank(b) || a.rel_path.localeCompare(b.rel_path)).map((i) => ({ relPath: i.rel_path, name: i.name, status: i.status ?? "unknown", updatedAt: i.updated_at }));
|
|
10606
10691
|
const recentlyChanged = [...items].sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? "")).slice(0, 10).map((i) => ({ relPath: i.rel_path, kind: i.kind, updatedAt: i.updated_at }));
|
|
10607
10692
|
const decisions = store.db.prepare("SELECT id, created_at, title FROM decisions ORDER BY id DESC LIMIT 10").all().map((d) => ({ id: d.id, createdAt: d.created_at, title: d.title }));
|
|
10608
10693
|
return { counts, open: open2, recentlyChanged, decisions };
|
|
@@ -39971,19 +40056,40 @@ async function guarded(run) {
|
|
|
39971
40056
|
process.exit(1);
|
|
39972
40057
|
}
|
|
39973
40058
|
}
|
|
39974
|
-
|
|
39975
|
-
|
|
39976
|
-
|
|
40059
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["--model", "--tokens"]);
|
|
40060
|
+
function parseArgs(args) {
|
|
40061
|
+
const positional = [];
|
|
40062
|
+
const flags = /* @__PURE__ */ new Map();
|
|
40063
|
+
for (let i = 0; i < args.length; i++) {
|
|
40064
|
+
const arg = args[i];
|
|
40065
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
40066
|
+
const value = args[++i];
|
|
40067
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
40068
|
+
throw new Error(`${arg} expects a value`);
|
|
40069
|
+
}
|
|
40070
|
+
flags.set(arg, value);
|
|
40071
|
+
} else if (arg.startsWith("--")) {
|
|
40072
|
+
flags.set(arg, "true");
|
|
40073
|
+
} else {
|
|
40074
|
+
positional.push(arg);
|
|
40075
|
+
}
|
|
40076
|
+
}
|
|
40077
|
+
return { positional, flags };
|
|
39977
40078
|
}
|
|
39978
|
-
function
|
|
39979
|
-
const
|
|
39980
|
-
|
|
40079
|
+
function tokensFlag(flags) {
|
|
40080
|
+
const raw2 = flags.get("--tokens");
|
|
40081
|
+
if (raw2 === void 0) return void 0;
|
|
40082
|
+
const n = Number(raw2);
|
|
40083
|
+
if (!Number.isInteger(n) || n < 200) throw new Error(`--tokens expects an integer \u2265 200, got "${raw2}"`);
|
|
40084
|
+
return n;
|
|
39981
40085
|
}
|
|
39982
40086
|
async function runIndexCommand(args) {
|
|
39983
|
-
const
|
|
39984
|
-
const
|
|
40087
|
+
const { positional, flags } = parseArgs(args);
|
|
40088
|
+
const root = resolve3(positional[0] ?? process.cwd());
|
|
40089
|
+
const embedder = ollamaEmbedder({ model: flags.get("--model") });
|
|
39985
40090
|
console.error(`capsa: indexing ${root} with ${embedder.model} \u2026`);
|
|
39986
40091
|
const res = await indexProject(root, embedder, (p) => {
|
|
40092
|
+
if (p.phase === "rebuild") console.error("capsa: index schema changed, rebuilding from scratch \u2026");
|
|
39987
40093
|
if (p.phase === "embed") process.stderr.write(`\r embedded ${p.indexed} / ${p.scanned} (skipped ${p.skipped}) `);
|
|
39988
40094
|
});
|
|
39989
40095
|
process.stderr.write("\n");
|
|
@@ -39993,19 +40099,25 @@ async function runIndexCommand(args) {
|
|
|
39993
40099
|
for (const i of res.issues.slice(0, 10)) console.error(` ! ${i.path}: ${i.message}`);
|
|
39994
40100
|
}
|
|
39995
40101
|
async function runSearchCommand(args) {
|
|
39996
|
-
const positional
|
|
40102
|
+
const { positional, flags } = parseArgs(args);
|
|
39997
40103
|
const query = positional[0];
|
|
39998
40104
|
if (!query) {
|
|
39999
|
-
console.error("capsa search <query> [path]");
|
|
40105
|
+
console.error("capsa search <query> [path] [--tokens n] [--model name] [--debug]");
|
|
40000
40106
|
process.exit(1);
|
|
40001
40107
|
}
|
|
40002
40108
|
const root = resolve3(positional[1] ?? process.cwd());
|
|
40003
|
-
const embedder = ollamaEmbedder({ model:
|
|
40004
|
-
const maxTokens =
|
|
40109
|
+
const embedder = ollamaEmbedder({ model: flags.get("--model") });
|
|
40110
|
+
const maxTokens = tokensFlag(flags);
|
|
40005
40111
|
const store = openStore(root, embedder);
|
|
40006
40112
|
try {
|
|
40007
|
-
const
|
|
40008
|
-
|
|
40113
|
+
const debug = flags.get("--debug") === "true";
|
|
40114
|
+
const res = await retrieve(store, embedder, query, { maxTokens, source: "cli:search", debug });
|
|
40115
|
+
if (debug) {
|
|
40116
|
+
console.log(formatDebug(res));
|
|
40117
|
+
console.log("");
|
|
40118
|
+
} else {
|
|
40119
|
+
console.log(formatContext(res));
|
|
40120
|
+
}
|
|
40009
40121
|
console.error(`
|
|
40010
40122
|
${res.chunks.length} chunks, ~${res.tokensEst} tokens, ${res.durationMs} ms`);
|
|
40011
40123
|
} finally {
|
|
@@ -40013,7 +40125,7 @@ ${res.chunks.length} chunks, ~${res.tokensEst} tokens, ${res.durationMs} ms`);
|
|
|
40013
40125
|
}
|
|
40014
40126
|
}
|
|
40015
40127
|
async function runStateCommand(args) {
|
|
40016
|
-
const root =
|
|
40128
|
+
const root = resolve3(parseArgs(args).positional[0] ?? process.cwd());
|
|
40017
40129
|
const store = openStore(root, ollamaEmbedder());
|
|
40018
40130
|
try {
|
|
40019
40131
|
console.log(formatState(projectState(store)));
|
|
@@ -40022,7 +40134,11 @@ async function runStateCommand(args) {
|
|
|
40022
40134
|
}
|
|
40023
40135
|
}
|
|
40024
40136
|
async function runMcpCommand(args) {
|
|
40025
|
-
|
|
40137
|
+
const { positional, flags } = parseArgs(args);
|
|
40138
|
+
await serveStdio({
|
|
40139
|
+
projectRoot: resolve3(positional[0] ?? process.cwd()),
|
|
40140
|
+
embedder: ollamaEmbedder({ model: flags.get("--model") })
|
|
40141
|
+
});
|
|
40026
40142
|
}
|
|
40027
40143
|
|
|
40028
40144
|
// src/index.ts
|
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.3",
|
|
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",
|