@yurtsever/capsa 0.1.0-alpha.6 → 0.1.0-alpha.8
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/README.md +3 -2
- package/dist/index.js +745 -276
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2807,8 +2807,8 @@ var require_validate = __commonJS({
|
|
|
2807
2807
|
return data;
|
|
2808
2808
|
}
|
|
2809
2809
|
let expr = data;
|
|
2810
|
-
const
|
|
2811
|
-
for (const segment of
|
|
2810
|
+
const segments4 = jsonPointer.split("/");
|
|
2811
|
+
for (const segment of segments4) {
|
|
2812
2812
|
if (segment) {
|
|
2813
2813
|
data = (0, codegen_1._)`${data}${(0, codegen_1.getProperty)((0, util_1.unescapeJsonPointer)(segment))}`;
|
|
2814
2814
|
expr = (0, codegen_1._)`${expr} && ${data}`;
|
|
@@ -3870,49 +3870,49 @@ var require_fast_uri = __commonJS({
|
|
|
3870
3870
|
schemelessOptions.skipEscape = true;
|
|
3871
3871
|
return serialize(resolved, schemelessOptions);
|
|
3872
3872
|
}
|
|
3873
|
-
function resolveComponent(base,
|
|
3873
|
+
function resolveComponent(base, relative7, options, skipNormalization) {
|
|
3874
3874
|
const target = {};
|
|
3875
3875
|
if (!skipNormalization) {
|
|
3876
3876
|
base = parse3(serialize(base, options), options);
|
|
3877
|
-
|
|
3877
|
+
relative7 = parse3(serialize(relative7, options), options);
|
|
3878
3878
|
}
|
|
3879
3879
|
options = options || {};
|
|
3880
|
-
if (!options.tolerant &&
|
|
3881
|
-
target.scheme =
|
|
3882
|
-
target.userinfo =
|
|
3883
|
-
target.host =
|
|
3884
|
-
target.port =
|
|
3885
|
-
target.path = removeDotSegments(
|
|
3886
|
-
target.query =
|
|
3880
|
+
if (!options.tolerant && relative7.scheme) {
|
|
3881
|
+
target.scheme = relative7.scheme;
|
|
3882
|
+
target.userinfo = relative7.userinfo;
|
|
3883
|
+
target.host = relative7.host;
|
|
3884
|
+
target.port = relative7.port;
|
|
3885
|
+
target.path = removeDotSegments(relative7.path || "");
|
|
3886
|
+
target.query = relative7.query;
|
|
3887
3887
|
} else {
|
|
3888
|
-
if (
|
|
3889
|
-
target.userinfo =
|
|
3890
|
-
target.host =
|
|
3891
|
-
target.port =
|
|
3892
|
-
target.path = removeDotSegments(
|
|
3893
|
-
target.query =
|
|
3888
|
+
if (relative7.userinfo !== void 0 || relative7.host !== void 0 || relative7.port !== void 0) {
|
|
3889
|
+
target.userinfo = relative7.userinfo;
|
|
3890
|
+
target.host = relative7.host;
|
|
3891
|
+
target.port = relative7.port;
|
|
3892
|
+
target.path = removeDotSegments(relative7.path || "");
|
|
3893
|
+
target.query = relative7.query;
|
|
3894
3894
|
} else {
|
|
3895
|
-
if (!
|
|
3895
|
+
if (!relative7.path) {
|
|
3896
3896
|
target.path = base.path;
|
|
3897
|
-
if (
|
|
3898
|
-
target.query =
|
|
3897
|
+
if (relative7.query !== void 0) {
|
|
3898
|
+
target.query = relative7.query;
|
|
3899
3899
|
} else {
|
|
3900
3900
|
target.query = base.query;
|
|
3901
3901
|
}
|
|
3902
3902
|
} else {
|
|
3903
|
-
if (
|
|
3904
|
-
target.path = removeDotSegments(
|
|
3903
|
+
if (relative7.path[0] === "/") {
|
|
3904
|
+
target.path = removeDotSegments(relative7.path);
|
|
3905
3905
|
} else {
|
|
3906
3906
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3907
|
-
target.path = "/" +
|
|
3907
|
+
target.path = "/" + relative7.path;
|
|
3908
3908
|
} else if (!base.path) {
|
|
3909
|
-
target.path =
|
|
3909
|
+
target.path = relative7.path;
|
|
3910
3910
|
} else {
|
|
3911
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3911
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative7.path;
|
|
3912
3912
|
}
|
|
3913
3913
|
target.path = removeDotSegments(target.path);
|
|
3914
3914
|
}
|
|
3915
|
-
target.query =
|
|
3915
|
+
target.query = relative7.query;
|
|
3916
3916
|
}
|
|
3917
3917
|
target.userinfo = base.userinfo;
|
|
3918
3918
|
target.host = base.host;
|
|
@@ -3920,7 +3920,7 @@ var require_fast_uri = __commonJS({
|
|
|
3920
3920
|
}
|
|
3921
3921
|
target.scheme = base.scheme;
|
|
3922
3922
|
}
|
|
3923
|
-
target.fragment =
|
|
3923
|
+
target.fragment = relative7.fragment;
|
|
3924
3924
|
return target;
|
|
3925
3925
|
}
|
|
3926
3926
|
function equal(uriA, uriB, options) {
|
|
@@ -4621,15 +4621,15 @@ var require_core = __commonJS({
|
|
|
4621
4621
|
errorsText(errors = this.errors, { separator = ", ", dataVar = "data" } = {}) {
|
|
4622
4622
|
if (!errors || errors.length === 0)
|
|
4623
4623
|
return "No errors";
|
|
4624
|
-
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((
|
|
4624
|
+
return errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).reduce((text2, msg) => text2 + separator + msg);
|
|
4625
4625
|
}
|
|
4626
4626
|
$dataMetaSchema(metaSchema, keywordsJsonPointers) {
|
|
4627
4627
|
const rules = this.RULES.all;
|
|
4628
4628
|
metaSchema = JSON.parse(JSON.stringify(metaSchema));
|
|
4629
4629
|
for (const jsonPointer of keywordsJsonPointers) {
|
|
4630
|
-
const
|
|
4630
|
+
const segments4 = jsonPointer.split("/").slice(1);
|
|
4631
4631
|
let keywords = metaSchema;
|
|
4632
|
-
for (const seg of
|
|
4632
|
+
for (const seg of segments4)
|
|
4633
4633
|
keywords = keywords[seg];
|
|
4634
4634
|
for (const key in rules) {
|
|
4635
4635
|
const rule = rules[key];
|
|
@@ -7196,8 +7196,8 @@ var require_dist = __commonJS({
|
|
|
7196
7196
|
});
|
|
7197
7197
|
|
|
7198
7198
|
// src/index.ts
|
|
7199
|
-
import { readFile as
|
|
7200
|
-
import { dirname as
|
|
7199
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
7200
|
+
import { dirname as dirname10, resolve as resolve4 } from "path";
|
|
7201
7201
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7202
7202
|
|
|
7203
7203
|
// ../core/dist/scanner.js
|
|
@@ -7247,7 +7247,7 @@ async function scan(adapters, options = {}) {
|
|
|
7247
7247
|
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
7248
7248
|
const issues = [];
|
|
7249
7249
|
const items = [];
|
|
7250
|
-
const seenItemPaths = /* @__PURE__ */ new
|
|
7250
|
+
const seenItemPaths = /* @__PURE__ */ new Map();
|
|
7251
7251
|
const scannedRoots = [];
|
|
7252
7252
|
const formatAdapters = adapters.filter((a) => !isSourceAdapter(a));
|
|
7253
7253
|
const sourceAdapters = adapters.filter(isSourceAdapter);
|
|
@@ -7259,10 +7259,19 @@ async function scan(adapters, options = {}) {
|
|
|
7259
7259
|
rootSet.add(resolve(r));
|
|
7260
7260
|
}
|
|
7261
7261
|
const roots = [...rootSet].filter((r) => ![...rootSet].some((other) => other !== r && r.startsWith(other + "/")));
|
|
7262
|
-
const addItem = (item) => {
|
|
7263
|
-
|
|
7262
|
+
const addItem = (item, adapterId) => {
|
|
7263
|
+
const owner = seenItemPaths.get(item.path);
|
|
7264
|
+
if (owner !== void 0) {
|
|
7265
|
+
if (owner !== adapterId) {
|
|
7266
|
+
issues.push({
|
|
7267
|
+
path: item.path,
|
|
7268
|
+
adapter: adapterId,
|
|
7269
|
+
message: `claimed by both "${owner}" and "${adapterId}"; kept "${owner}" \u2014 two adapters' conventions overlap here`
|
|
7270
|
+
});
|
|
7271
|
+
}
|
|
7264
7272
|
return;
|
|
7265
|
-
|
|
7273
|
+
}
|
|
7274
|
+
seenItemPaths.set(item.path, adapterId);
|
|
7266
7275
|
items.push(item);
|
|
7267
7276
|
};
|
|
7268
7277
|
const handleEntry = async (entry) => {
|
|
@@ -7281,8 +7290,13 @@ async function scan(adapters, options = {}) {
|
|
|
7281
7290
|
if (!matched)
|
|
7282
7291
|
continue;
|
|
7283
7292
|
try {
|
|
7284
|
-
|
|
7285
|
-
|
|
7293
|
+
const parsed = await adapter.parse(entry, env);
|
|
7294
|
+
const result = Array.isArray(parsed) ? { items: parsed } : parsed;
|
|
7295
|
+
for (const item of result.items)
|
|
7296
|
+
addItem(item, adapter.id);
|
|
7297
|
+
for (const issue2 of result.issues ?? []) {
|
|
7298
|
+
issues.push({ adapter: adapter.id, ...issue2 });
|
|
7299
|
+
}
|
|
7286
7300
|
} catch (err) {
|
|
7287
7301
|
issues.push({
|
|
7288
7302
|
path: entry.absPath,
|
|
@@ -7356,7 +7370,7 @@ async function scan(adapters, options = {}) {
|
|
|
7356
7370
|
for (const adapter of sourceAdapters) {
|
|
7357
7371
|
try {
|
|
7358
7372
|
for (const item of await adapter.collect(root, env))
|
|
7359
|
-
addItem(item);
|
|
7373
|
+
addItem(item, adapter.id);
|
|
7360
7374
|
} catch (err) {
|
|
7361
7375
|
issues.push({
|
|
7362
7376
|
path: root,
|
|
@@ -7414,20 +7428,24 @@ function findProjectRoot(startDir, homeDir) {
|
|
|
7414
7428
|
|
|
7415
7429
|
// ../core/dist/chunk.js
|
|
7416
7430
|
var DEFAULT_MAX_CHARS = 2e3;
|
|
7417
|
-
|
|
7431
|
+
var DEFAULT_MAX_HEADING_LEVEL = 6;
|
|
7432
|
+
function chunkMarkdown(markdown, options = {}) {
|
|
7433
|
+
const maxChars = options.maxChars ?? DEFAULT_MAX_CHARS;
|
|
7434
|
+
const maxHeadingLevel = options.maxHeadingLevel ?? DEFAULT_MAX_HEADING_LEVEL;
|
|
7418
7435
|
const body = parseFrontmatter(markdown).body;
|
|
7419
7436
|
const chunks = [];
|
|
7420
7437
|
const trail = [];
|
|
7421
7438
|
let buffer = [];
|
|
7422
7439
|
let inFence = false;
|
|
7440
|
+
let openPath = [];
|
|
7441
|
+
const path = () => trail.filter((s) => Boolean(s));
|
|
7423
7442
|
const flush = () => {
|
|
7424
|
-
const
|
|
7443
|
+
const text2 = buffer.join("\n").trim();
|
|
7425
7444
|
buffer = [];
|
|
7426
|
-
if (!
|
|
7445
|
+
if (!text2)
|
|
7427
7446
|
return;
|
|
7428
|
-
const
|
|
7429
|
-
|
|
7430
|
-
chunks.push({ index: chunks.length, headingPath, text: part });
|
|
7447
|
+
for (const part of splitLong(text2, maxChars)) {
|
|
7448
|
+
chunks.push({ index: chunks.length, headingPath: openPath, text: part });
|
|
7431
7449
|
}
|
|
7432
7450
|
};
|
|
7433
7451
|
for (const line of body.split(/\r?\n/)) {
|
|
@@ -7435,10 +7453,13 @@ function chunkMarkdown(markdown, maxChars = DEFAULT_MAX_CHARS) {
|
|
|
7435
7453
|
inFence = !inFence;
|
|
7436
7454
|
const heading = !inFence && /^(#{1,6})\s+(.*\S)\s*$/.exec(line);
|
|
7437
7455
|
if (heading) {
|
|
7438
|
-
flush();
|
|
7439
7456
|
const level = heading[1].length;
|
|
7457
|
+
if (level <= maxHeadingLevel)
|
|
7458
|
+
flush();
|
|
7440
7459
|
trail.splice(level - 1);
|
|
7441
7460
|
trail[level - 1] = heading[2];
|
|
7461
|
+
if (buffer.length === 0)
|
|
7462
|
+
openPath = path();
|
|
7442
7463
|
buffer.push(line);
|
|
7443
7464
|
continue;
|
|
7444
7465
|
}
|
|
@@ -7447,25 +7468,58 @@ function chunkMarkdown(markdown, maxChars = DEFAULT_MAX_CHARS) {
|
|
|
7447
7468
|
flush();
|
|
7448
7469
|
return chunks;
|
|
7449
7470
|
}
|
|
7450
|
-
function splitLong(
|
|
7451
|
-
if (
|
|
7452
|
-
return [
|
|
7471
|
+
function splitLong(text2, maxChars) {
|
|
7472
|
+
if (text2.length <= maxChars)
|
|
7473
|
+
return [text2];
|
|
7453
7474
|
const out = [];
|
|
7454
7475
|
let current = "";
|
|
7455
|
-
for (const
|
|
7456
|
-
if (current && current.length +
|
|
7476
|
+
for (const { sep: sep7, piece } of pieces(text2, maxChars)) {
|
|
7477
|
+
if (current && current.length + sep7.length + piece.length > maxChars) {
|
|
7457
7478
|
out.push(current);
|
|
7458
|
-
current =
|
|
7479
|
+
current = piece;
|
|
7459
7480
|
} else {
|
|
7460
|
-
current = current ?
|
|
7461
|
-
|
|
7462
|
-
${para}` : para;
|
|
7481
|
+
current = current ? current + sep7 + piece : piece;
|
|
7463
7482
|
}
|
|
7464
7483
|
}
|
|
7465
7484
|
if (current)
|
|
7466
7485
|
out.push(current);
|
|
7467
7486
|
return out;
|
|
7468
7487
|
}
|
|
7488
|
+
var SENTENCE_BREAK = /(?<=[.!?…]["'’”»)\]]*)(\s+)/;
|
|
7489
|
+
function* pieces(text2, maxChars) {
|
|
7490
|
+
let sep7 = "";
|
|
7491
|
+
for (const para of text2.split(/\n{2,}/)) {
|
|
7492
|
+
if (para.length <= maxChars) {
|
|
7493
|
+
yield { sep: sep7, piece: para };
|
|
7494
|
+
} else {
|
|
7495
|
+
const parts = para.split(SENTENCE_BREAK);
|
|
7496
|
+
for (let i = 0; i < parts.length; i += 2) {
|
|
7497
|
+
yield* cut(parts[i], sep7, maxChars);
|
|
7498
|
+
sep7 = parts[i + 1] ?? "";
|
|
7499
|
+
}
|
|
7500
|
+
}
|
|
7501
|
+
sep7 = "\n\n";
|
|
7502
|
+
}
|
|
7503
|
+
}
|
|
7504
|
+
function* cut(sentence, sep7, maxChars) {
|
|
7505
|
+
let rest = sentence;
|
|
7506
|
+
while (rest.length > maxChars) {
|
|
7507
|
+
const window = rest.slice(0, maxChars + 1);
|
|
7508
|
+
let at = window.lastIndexOf("\n");
|
|
7509
|
+
if (at < maxChars / 2)
|
|
7510
|
+
at = Math.max(at, window.lastIndexOf(" "));
|
|
7511
|
+
if (at <= 0)
|
|
7512
|
+
at = Math.max(maxChars, 1);
|
|
7513
|
+
const piece = rest.slice(0, at).trimEnd();
|
|
7514
|
+
const gap = /^\s*/.exec(rest.slice(piece.length))[0];
|
|
7515
|
+
if (piece)
|
|
7516
|
+
yield { sep: sep7, piece };
|
|
7517
|
+
sep7 = piece ? gap : sep7 + gap;
|
|
7518
|
+
rest = rest.slice(piece.length + gap.length);
|
|
7519
|
+
}
|
|
7520
|
+
if (rest)
|
|
7521
|
+
yield { sep: sep7, piece: rest };
|
|
7522
|
+
}
|
|
7469
7523
|
|
|
7470
7524
|
// ../adapter-claude-skills/dist/index.js
|
|
7471
7525
|
import { readFile } from "fs/promises";
|
|
@@ -7516,10 +7570,10 @@ import { readFile as readFile2 } from "fs/promises";
|
|
|
7516
7570
|
import { basename as basename2, dirname as dirname3, join as join4, relative as relative2 } from "path";
|
|
7517
7571
|
var INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]);
|
|
7518
7572
|
var MAX_DESCRIPTION_LENGTH = 120;
|
|
7519
|
-
function truncate(
|
|
7520
|
-
if (
|
|
7521
|
-
return
|
|
7522
|
-
return
|
|
7573
|
+
function truncate(text2) {
|
|
7574
|
+
if (text2.length <= MAX_DESCRIPTION_LENGTH)
|
|
7575
|
+
return text2;
|
|
7576
|
+
return text2.slice(0, MAX_DESCRIPTION_LENGTH - 1).trimEnd() + "\u2026";
|
|
7523
7577
|
}
|
|
7524
7578
|
function deriveDescription(raw2) {
|
|
7525
7579
|
let firstNonEmpty;
|
|
@@ -7583,8 +7637,8 @@ var LEGACY_FILE = ".cursorrules";
|
|
|
7583
7637
|
var MDC_EXTENSION = ".mdc";
|
|
7584
7638
|
var RULES_MARKER = sep2 + join5(".cursor", "rules") + sep2;
|
|
7585
7639
|
var MAX_DESCRIPTION_LENGTH2 = 120;
|
|
7586
|
-
function firstLine(
|
|
7587
|
-
for (const line of
|
|
7640
|
+
function firstLine(text2) {
|
|
7641
|
+
for (const line of text2.split(/\r?\n/)) {
|
|
7588
7642
|
const trimmed = line.trim();
|
|
7589
7643
|
if (!trimmed)
|
|
7590
7644
|
continue;
|
|
@@ -7958,10 +8012,10 @@ var checkOptionalParameter = (path) => {
|
|
|
7958
8012
|
if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":")) {
|
|
7959
8013
|
return null;
|
|
7960
8014
|
}
|
|
7961
|
-
const
|
|
8015
|
+
const segments4 = path.split("/");
|
|
7962
8016
|
const results = [];
|
|
7963
8017
|
let basePath = "";
|
|
7964
|
-
|
|
8018
|
+
segments4.forEach((segment) => {
|
|
7965
8019
|
if (segment !== "" && !/\:/.test(segment)) {
|
|
7966
8020
|
basePath += "/" + segment;
|
|
7967
8021
|
} else if (/\:/.test(segment)) {
|
|
@@ -8178,7 +8232,7 @@ var HonoRequest = class {
|
|
|
8178
8232
|
* ```
|
|
8179
8233
|
*/
|
|
8180
8234
|
json() {
|
|
8181
|
-
return this.#cachedBody("text").then((
|
|
8235
|
+
return this.#cachedBody("text").then((text2) => JSON.parse(text2));
|
|
8182
8236
|
}
|
|
8183
8237
|
/**
|
|
8184
8238
|
* `.text()` can parse Request body of type `text/plain`
|
|
@@ -8744,9 +8798,9 @@ var Context = class {
|
|
|
8744
8798
|
* })
|
|
8745
8799
|
* ```
|
|
8746
8800
|
*/
|
|
8747
|
-
text = (
|
|
8748
|
-
return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(
|
|
8749
|
-
|
|
8801
|
+
text = (text2, arg, headers) => {
|
|
8802
|
+
return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text2) : this.#newResponse(
|
|
8803
|
+
text2,
|
|
8750
8804
|
arg,
|
|
8751
8805
|
setDefaultContentType(TEXT_PLAIN, headers)
|
|
8752
8806
|
);
|
|
@@ -10050,8 +10104,8 @@ function ollamaEmbedder(options = {}) {
|
|
|
10050
10104
|
}
|
|
10051
10105
|
};
|
|
10052
10106
|
}
|
|
10053
|
-
function estimateTokens(
|
|
10054
|
-
return Math.ceil(
|
|
10107
|
+
function estimateTokens(text2) {
|
|
10108
|
+
return Math.ceil(text2.length / 4);
|
|
10055
10109
|
}
|
|
10056
10110
|
|
|
10057
10111
|
// ../index/dist/store.js
|
|
@@ -10074,7 +10128,10 @@ function loadSqlite() {
|
|
|
10074
10128
|
}
|
|
10075
10129
|
var DB_DIR = ".capsa";
|
|
10076
10130
|
var DB_FILE = "index.db";
|
|
10077
|
-
var SCHEMA_VERSION =
|
|
10131
|
+
var SCHEMA_VERSION = 11;
|
|
10132
|
+
var META_INDEXED_AT = "indexed_at";
|
|
10133
|
+
var META_INDEX_FAILED_AT = "index_failed_at";
|
|
10134
|
+
var META_INDEX_ERROR = "index_error";
|
|
10078
10135
|
var CONTEXT_LOG_DDL = `
|
|
10079
10136
|
-- Every context delivery. This is the measurement for the MVP gate and
|
|
10080
10137
|
-- the seed of the organisation tier's audit log. Alone among these
|
|
@@ -10088,6 +10145,12 @@ var CONTEXT_LOG_DDL = `
|
|
|
10088
10145
|
query TEXT NOT NULL,
|
|
10089
10146
|
delivered TEXT NOT NULL,
|
|
10090
10147
|
tokens_est INTEGER NOT NULL,
|
|
10148
|
+
-- The ceiling this call ran under. Without it a row says what was
|
|
10149
|
+
-- spent but not what it was allowed to spend, and two deliveries are
|
|
10150
|
+
-- only comparable when that number matches. NULL on rows written
|
|
10151
|
+
-- before v6: they ran under a budget nobody recorded, and filling in
|
|
10152
|
+
-- today's default would be a guess wearing the costume of data.
|
|
10153
|
+
tokens_budget INTEGER,
|
|
10091
10154
|
duration_ms INTEGER NOT NULL
|
|
10092
10155
|
);
|
|
10093
10156
|
`;
|
|
@@ -10109,24 +10172,44 @@ var Store = class {
|
|
|
10109
10172
|
}
|
|
10110
10173
|
/** True when this index was written by an older schema and must be rebuilt. */
|
|
10111
10174
|
needsRebuild() {
|
|
10112
|
-
|
|
10113
|
-
|
|
10175
|
+
return this.getMeta("schema") !== String(SCHEMA_VERSION);
|
|
10176
|
+
}
|
|
10177
|
+
getMeta(key) {
|
|
10178
|
+
const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
10179
|
+
return row?.value;
|
|
10180
|
+
}
|
|
10181
|
+
setMeta(key, value) {
|
|
10182
|
+
this.db.prepare("INSERT OR REPLACE INTO meta(key, value) VALUES (?, ?)").run(key, value);
|
|
10183
|
+
}
|
|
10184
|
+
deleteMeta(key) {
|
|
10185
|
+
this.db.prepare("DELETE FROM meta WHERE key = ?").run(key);
|
|
10114
10186
|
}
|
|
10115
10187
|
/**
|
|
10116
10188
|
* Drop all indexed content (not the log, not decisions) and mark the schema
|
|
10117
|
-
* current.
|
|
10118
|
-
* `CREATE
|
|
10119
|
-
*
|
|
10189
|
+
* current. Every one of these tables is dropped and recreated, not emptied,
|
|
10190
|
+
* because `CREATE TABLE IF NOT EXISTS` in migrate() is a no-op against a
|
|
10191
|
+
* table that already exists under its old shape. For the virtual tables that
|
|
10192
|
+
* once left FTS without stemming after v2; for `items` it meant a schema
|
|
10193
|
+
* bump could not add a column at all — v7 added `title` and the rebuild it
|
|
10194
|
+
* asked for failed with "table items has no column named title", because
|
|
10195
|
+
* DELETE had emptied the v6 table and left it standing.
|
|
10196
|
+
*
|
|
10197
|
+
* Nothing here is a source of truth: items and chunks are derived from files
|
|
10198
|
+
* on disk and from `decisions`, which is one of the two tables a reset
|
|
10199
|
+
* spares.
|
|
10120
10200
|
*/
|
|
10121
10201
|
reset() {
|
|
10122
10202
|
this.db.exec(`
|
|
10123
10203
|
DROP TABLE IF EXISTS chunks_fts;
|
|
10124
10204
|
DROP TABLE IF EXISTS chunks_vec;
|
|
10125
|
-
|
|
10126
|
-
|
|
10205
|
+
DROP TABLE IF EXISTS chunks;
|
|
10206
|
+
DROP TABLE IF EXISTS items;
|
|
10127
10207
|
`);
|
|
10128
10208
|
this.migrate();
|
|
10129
|
-
this.
|
|
10209
|
+
this.setMeta("schema", String(SCHEMA_VERSION));
|
|
10210
|
+
this.deleteMeta(META_INDEXED_AT);
|
|
10211
|
+
this.deleteMeta(META_INDEX_FAILED_AT);
|
|
10212
|
+
this.deleteMeta(META_INDEX_ERROR);
|
|
10130
10213
|
}
|
|
10131
10214
|
migrate() {
|
|
10132
10215
|
this.db.exec(`
|
|
@@ -10137,12 +10220,14 @@ var Store = class {
|
|
|
10137
10220
|
format TEXT NOT NULL,
|
|
10138
10221
|
kind TEXT NOT NULL,
|
|
10139
10222
|
name TEXT NOT NULL,
|
|
10223
|
+
title TEXT NOT NULL,
|
|
10140
10224
|
path TEXT NOT NULL UNIQUE,
|
|
10141
10225
|
rel_path TEXT NOT NULL,
|
|
10142
10226
|
content_hash TEXT NOT NULL,
|
|
10143
10227
|
status TEXT,
|
|
10144
10228
|
updated_at TEXT,
|
|
10145
10229
|
tags TEXT,
|
|
10230
|
+
metadata TEXT,
|
|
10146
10231
|
origin TEXT NOT NULL DEFAULT 'local',
|
|
10147
10232
|
indexed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
|
|
10148
10233
|
);
|
|
@@ -10179,27 +10264,40 @@ var Store = class {
|
|
|
10179
10264
|
${CONTEXT_LOG_DDL}
|
|
10180
10265
|
`);
|
|
10181
10266
|
this.migrateContextLog();
|
|
10182
|
-
this.
|
|
10267
|
+
this.setMeta("dimensions", String(this.dimensions));
|
|
10183
10268
|
}
|
|
10184
10269
|
/**
|
|
10185
10270
|
* `context_log` outlives every rebuild, so it is the one table that has to
|
|
10186
|
-
* be migrated in place instead of recreated
|
|
10187
|
-
*
|
|
10188
|
-
*
|
|
10189
|
-
*
|
|
10190
|
-
*
|
|
10191
|
-
*
|
|
10271
|
+
* be migrated in place instead of recreated — twice now.
|
|
10272
|
+
*
|
|
10273
|
+
* v5 replaced `chunk_ids` with `delivered`: a chunk id is a rowid the next
|
|
10274
|
+
* `capsa index` reassigns, so an older row ended up naming whatever text
|
|
10275
|
+
* inherited its ids — the measurement quietly decayed. Rows written before
|
|
10276
|
+
* v5 keep everything that still means something (when, who asked, what for,
|
|
10277
|
+
* at what cost) and carry an empty delivery, because their ids can no longer
|
|
10278
|
+
* be resolved honestly.
|
|
10279
|
+
*
|
|
10280
|
+
* v6 added `tokens_budget`. A delivery of 1727 tokens against one of 758
|
|
10281
|
+
* reads like a saving until you learn the first ran on a larger ceiling;
|
|
10282
|
+
* that comparison was attempted on this repo's own log and could not be
|
|
10283
|
+
* settled, because the number was never written down. Old rows get NULL
|
|
10284
|
+
* rather than a backfilled default — an unknown budget is a fact about the
|
|
10285
|
+
* measurement, and inventing one would hide exactly the gap this closes.
|
|
10286
|
+
*
|
|
10287
|
+
* Keyed on the newest column, so a v4 database reaches v6 in one pass.
|
|
10192
10288
|
*/
|
|
10193
10289
|
migrateContextLog() {
|
|
10194
10290
|
const columns = this.db.prepare("PRAGMA table_info(context_log)").all();
|
|
10195
|
-
|
|
10291
|
+
const names = new Set(columns.map((c) => c.name));
|
|
10292
|
+
if (names.has("tokens_budget"))
|
|
10196
10293
|
return;
|
|
10294
|
+
const delivered = names.has("delivered") ? "delivered" : "'[]'";
|
|
10197
10295
|
this.db.exec(`
|
|
10198
|
-
ALTER TABLE context_log RENAME TO
|
|
10296
|
+
ALTER TABLE context_log RENAME TO context_log_old;
|
|
10199
10297
|
${CONTEXT_LOG_DDL}
|
|
10200
|
-
INSERT INTO context_log (id, created_at, source, query, delivered, tokens_est, duration_ms)
|
|
10201
|
-
SELECT id, created_at, source, query,
|
|
10202
|
-
DROP TABLE
|
|
10298
|
+
INSERT INTO context_log (id, created_at, source, query, delivered, tokens_est, tokens_budget, duration_ms)
|
|
10299
|
+
SELECT id, created_at, source, query, ${delivered}, tokens_est, NULL, duration_ms FROM context_log_old;
|
|
10300
|
+
DROP TABLE context_log_old;
|
|
10203
10301
|
`);
|
|
10204
10302
|
}
|
|
10205
10303
|
getItem(path) {
|
|
@@ -10217,20 +10315,22 @@ var Store = class {
|
|
|
10217
10315
|
this.db.prepare("DELETE FROM items WHERE id = ?").run(id);
|
|
10218
10316
|
}
|
|
10219
10317
|
upsertItem(row) {
|
|
10220
|
-
this.db.prepare(`INSERT INTO items (id, format, kind, name, path, rel_path, content_hash, status, updated_at, tags)
|
|
10221
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10318
|
+
this.db.prepare(`INSERT INTO items (id, format, kind, name, title, path, rel_path, content_hash, status, updated_at, tags, metadata)
|
|
10319
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10222
10320
|
ON CONFLICT(id) DO UPDATE SET
|
|
10223
10321
|
format = excluded.format, kind = excluded.kind, name = excluded.name,
|
|
10322
|
+
title = excluded.title,
|
|
10224
10323
|
rel_path = excluded.rel_path, content_hash = excluded.content_hash,
|
|
10225
10324
|
status = excluded.status, updated_at = excluded.updated_at, tags = excluded.tags,
|
|
10226
|
-
|
|
10325
|
+
metadata = excluded.metadata,
|
|
10326
|
+
indexed_at = strftime('%Y-%m-%dT%H:%M:%fZ','now')`).run(row.id, row.format, row.kind, row.name, row.title, row.path, row.rel_path, row.content_hash, row.status, row.updated_at, row.tags, row.metadata);
|
|
10227
10327
|
}
|
|
10228
10328
|
/**
|
|
10229
10329
|
* `text` is what the user sees; `searchText` (chunk + provenance header)
|
|
10230
10330
|
* is what FTS and the embedder see. Falls back to `text` when omitted.
|
|
10231
10331
|
*/
|
|
10232
|
-
insertChunk(itemId, idx, headingPath,
|
|
10233
|
-
const res = this.db.prepare("INSERT INTO chunks (item_id, idx, heading_path, text) VALUES (?, ?, ?, ?)").run(itemId, idx, JSON.stringify(headingPath),
|
|
10332
|
+
insertChunk(itemId, idx, headingPath, text2, embedding, searchText = text2) {
|
|
10333
|
+
const res = this.db.prepare("INSERT INTO chunks (item_id, idx, heading_path, text) VALUES (?, ?, ?, ?)").run(itemId, idx, JSON.stringify(headingPath), text2);
|
|
10234
10334
|
const id = Number(res.lastInsertRowid);
|
|
10235
10335
|
this.db.prepare("INSERT INTO chunks_fts (rowid, text) VALUES (?, ?)").run(id, searchText);
|
|
10236
10336
|
this.db.prepare("INSERT INTO chunks_vec (chunk_id, embedding) VALUES (?, ?)").run(BigInt(id), new Uint8Array(embedding.buffer, embedding.byteOffset, embedding.byteLength));
|
|
@@ -10254,7 +10354,7 @@ var Store = class {
|
|
|
10254
10354
|
return [];
|
|
10255
10355
|
const placeholders = ids.map(() => "?").join(",");
|
|
10256
10356
|
const rows = this.db.prepare(`SELECT c.id, c.item_id, c.idx, c.heading_path, c.text,
|
|
10257
|
-
i.id AS i_id, i.format, i.kind, i.name, i.path, i.rel_path, i.content_hash, i.status, i.updated_at, i.tags
|
|
10357
|
+
i.id AS i_id, i.format, i.kind, i.name, i.title, i.path, i.rel_path, i.content_hash, i.status, i.updated_at, i.tags, i.metadata
|
|
10258
10358
|
FROM chunks c JOIN items i ON i.id = c.item_id WHERE c.id IN (${placeholders})`).all(...ids);
|
|
10259
10359
|
return rows.map((r) => ({
|
|
10260
10360
|
id: r["id"],
|
|
@@ -10267,12 +10367,14 @@ var Store = class {
|
|
|
10267
10367
|
format: r["format"],
|
|
10268
10368
|
kind: r["kind"],
|
|
10269
10369
|
name: r["name"],
|
|
10370
|
+
title: r["title"],
|
|
10270
10371
|
path: r["path"],
|
|
10271
10372
|
rel_path: r["rel_path"],
|
|
10272
10373
|
content_hash: r["content_hash"],
|
|
10273
10374
|
status: r["status"],
|
|
10274
10375
|
updated_at: r["updated_at"],
|
|
10275
|
-
tags: r["tags"]
|
|
10376
|
+
tags: r["tags"],
|
|
10377
|
+
metadata: r["metadata"]
|
|
10276
10378
|
}
|
|
10277
10379
|
}));
|
|
10278
10380
|
}
|
|
@@ -10284,7 +10386,7 @@ var Store = class {
|
|
|
10284
10386
|
return this.db.prepare("SELECT * FROM items ORDER BY kind, rel_path").all();
|
|
10285
10387
|
}
|
|
10286
10388
|
logContext(entry) {
|
|
10287
|
-
this.db.prepare("INSERT INTO context_log (source, query, delivered, tokens_est, duration_ms) VALUES (?, ?, ?, ?, ?)").run(entry.source, entry.query, JSON.stringify(entry.delivered), entry.tokensEst, entry.durationMs);
|
|
10389
|
+
this.db.prepare("INSERT INTO context_log (source, query, delivered, tokens_est, tokens_budget, duration_ms) VALUES (?, ?, ?, ?, ?, ?)").run(entry.source, entry.query, JSON.stringify(entry.delivered), entry.tokensEst, entry.tokensBudget, entry.durationMs);
|
|
10288
10390
|
}
|
|
10289
10391
|
/**
|
|
10290
10392
|
* Returns the stored row, not just its id: `created_at` is SQL's to write,
|
|
@@ -10308,12 +10410,116 @@ var Store = class {
|
|
|
10308
10410
|
|
|
10309
10411
|
// ../index/dist/indexer.js
|
|
10310
10412
|
import { createHash as createHash2 } from "crypto";
|
|
10311
|
-
import { relative as
|
|
10413
|
+
import { relative as relative6 } from "path";
|
|
10414
|
+
|
|
10415
|
+
// ../adapter-documents/dist/index.js
|
|
10416
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
10417
|
+
import { basename as basename4, dirname as dirname7, relative as relative3, sep as sep4 } from "path";
|
|
10418
|
+
var BOOK_DIR = "books";
|
|
10419
|
+
var OUTLINE_FILE = "index.md";
|
|
10420
|
+
var CHAPTER_HEADING_LEVEL = 3;
|
|
10421
|
+
var CHAPTER_MAX_CHARS = 1e3;
|
|
10422
|
+
function segments(relPath) {
|
|
10423
|
+
return relPath.split(/[\\/]/).filter(Boolean);
|
|
10424
|
+
}
|
|
10425
|
+
function documentsAdapter() {
|
|
10426
|
+
return {
|
|
10427
|
+
id: "documents",
|
|
10428
|
+
displayName: "Document",
|
|
10429
|
+
matches(entry) {
|
|
10430
|
+
if (!entry.isFile || !entry.name.toLowerCase().endsWith(".md"))
|
|
10431
|
+
return false;
|
|
10432
|
+
return segments(entry.relPath).slice(0, -1).some((p) => p.toLowerCase() === BOOK_DIR);
|
|
10433
|
+
},
|
|
10434
|
+
async parse(entry, env) {
|
|
10435
|
+
let raw2;
|
|
10436
|
+
try {
|
|
10437
|
+
raw2 = await readFile5(entry.absPath, "utf8");
|
|
10438
|
+
} catch {
|
|
10439
|
+
return { items: [] };
|
|
10440
|
+
}
|
|
10441
|
+
const fm = parseFrontmatter(raw2);
|
|
10442
|
+
const book = fm.data["book"];
|
|
10443
|
+
if (!book) {
|
|
10444
|
+
return {
|
|
10445
|
+
items: [],
|
|
10446
|
+
issues: [
|
|
10447
|
+
{
|
|
10448
|
+
path: entry.absPath,
|
|
10449
|
+
message: `no \`book:\` in frontmatter \u2014 a markdown file under ${BOOK_DIR}/ is a chapter of a document or it is nothing`
|
|
10450
|
+
}
|
|
10451
|
+
]
|
|
10452
|
+
};
|
|
10453
|
+
}
|
|
10454
|
+
const projectRoot = findProjectRoot(dirname7(entry.absPath), env.homeDir);
|
|
10455
|
+
const relPathInProject = relative3(projectRoot, entry.absPath);
|
|
10456
|
+
const outline = entry.name.toLowerCase() === OUTLINE_FILE;
|
|
10457
|
+
const title = fm.data["title"] || firstHeading(fm.body) || basename4(entry.name, ".md");
|
|
10458
|
+
const chapter = fm.data["chapter"];
|
|
10459
|
+
const metadata = {
|
|
10460
|
+
relPathInProject,
|
|
10461
|
+
book,
|
|
10462
|
+
title,
|
|
10463
|
+
...fm.data["author"] ? { author: fm.data["author"] } : {},
|
|
10464
|
+
...chapter ? { chapter: number(chapter) } : {},
|
|
10465
|
+
...pages("printed_pages", fm.data["printed_pages"]),
|
|
10466
|
+
...pages("pdf_pages", fm.data["pdf_pages"])
|
|
10467
|
+
};
|
|
10468
|
+
return {
|
|
10469
|
+
items: [
|
|
10470
|
+
{
|
|
10471
|
+
id: stableId(entry.absPath),
|
|
10472
|
+
format: "documents",
|
|
10473
|
+
formatLabel: outline ? "Outline" : "Document",
|
|
10474
|
+
name: `${basename4(projectRoot)}${sep4}${relPathInProject}`,
|
|
10475
|
+
// What the item calls itself, and what a pinned item's
|
|
10476
|
+
// neighbourhood is searched with: a chapter title alone
|
|
10477
|
+
// ("Conclusion") belongs to nine books at once.
|
|
10478
|
+
description: outline ? `${book} \u2014 Contents` : `${book} \u2014 ${chapterLabel(chapter, title)}`,
|
|
10479
|
+
path: entry.absPath,
|
|
10480
|
+
scope: "project",
|
|
10481
|
+
projectRoot,
|
|
10482
|
+
content: raw2,
|
|
10483
|
+
metadata,
|
|
10484
|
+
knowledge: { kind: outline ? "outline" : "document" },
|
|
10485
|
+
// An outline is delivered whole or not at all — a table of
|
|
10486
|
+
// contents retrieved three lines at a time is noise. Empty, so
|
|
10487
|
+
// the indexer stores it and embeds nothing of it.
|
|
10488
|
+
chunks: outline ? [] : chapterChunks(raw2)
|
|
10489
|
+
}
|
|
10490
|
+
]
|
|
10491
|
+
};
|
|
10492
|
+
}
|
|
10493
|
+
};
|
|
10494
|
+
}
|
|
10495
|
+
function chapterLabel(chapter, title) {
|
|
10496
|
+
return chapter === void 0 || chapter === "" ? title : `Ch. ${chapter} ${title}`;
|
|
10497
|
+
}
|
|
10498
|
+
function chapterChunks(raw2) {
|
|
10499
|
+
return chunkMarkdown(raw2, { maxHeadingLevel: CHAPTER_HEADING_LEVEL, maxChars: CHAPTER_MAX_CHARS });
|
|
10500
|
+
}
|
|
10501
|
+
function pages(key, value) {
|
|
10502
|
+
if (!value)
|
|
10503
|
+
return {};
|
|
10504
|
+
const parts = value.replace(/^\[|\]$/g, "").split(",").map((p) => p.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
10505
|
+
return parts.length ? { [key]: parts.map(number) } : {};
|
|
10506
|
+
}
|
|
10507
|
+
function number(value) {
|
|
10508
|
+
return /^\d+$/.test(value) ? Number(value) : value;
|
|
10509
|
+
}
|
|
10510
|
+
function firstHeading(body) {
|
|
10511
|
+
for (const line of body.split(/\r?\n/)) {
|
|
10512
|
+
const m = /^#{1,6}\s+(.*\S)\s*$/.exec(line.trim());
|
|
10513
|
+
if (m)
|
|
10514
|
+
return m[1];
|
|
10515
|
+
}
|
|
10516
|
+
return void 0;
|
|
10517
|
+
}
|
|
10312
10518
|
|
|
10313
10519
|
// ../adapter-git-log/dist/index.js
|
|
10314
10520
|
import { execFile } from "child_process";
|
|
10315
10521
|
import { existsSync as existsSync2 } from "fs";
|
|
10316
|
-
import { basename as
|
|
10522
|
+
import { basename as basename5, join as join7 } from "path";
|
|
10317
10523
|
import { promisify } from "util";
|
|
10318
10524
|
var execFileAsync = promisify(execFile);
|
|
10319
10525
|
var DEFAULTS = {
|
|
@@ -10416,7 +10622,7 @@ function toItem(root, c, opts) {
|
|
|
10416
10622
|
author: c.author,
|
|
10417
10623
|
subject,
|
|
10418
10624
|
files: c.files,
|
|
10419
|
-
repo:
|
|
10625
|
+
repo: basename5(root)
|
|
10420
10626
|
},
|
|
10421
10627
|
knowledge: {
|
|
10422
10628
|
kind: "commit",
|
|
@@ -10461,18 +10667,27 @@ function conventionalTags(subject) {
|
|
|
10461
10667
|
const tags = [m[1], ...m[2] ? [m[2]] : []];
|
|
10462
10668
|
return tags;
|
|
10463
10669
|
}
|
|
10464
|
-
function firstLine2(
|
|
10465
|
-
return (
|
|
10670
|
+
function firstLine2(text2) {
|
|
10671
|
+
return (text2 ?? "").split("\n").find((l) => l.trim()) ?? "";
|
|
10466
10672
|
}
|
|
10467
10673
|
|
|
10468
10674
|
// ../adapter-runbooks/dist/index.js
|
|
10469
|
-
import { readFile as
|
|
10470
|
-
import { basename as
|
|
10675
|
+
import { readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
10676
|
+
import { basename as basename6, dirname as dirname8, relative as relative4, sep as sep5 } from "path";
|
|
10471
10677
|
var DOC_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "runbooks", "runbook", "wiki", "adr", "decisions"]);
|
|
10472
|
-
var
|
|
10473
|
-
|
|
10678
|
+
var CLAIMED_ELSEWHERE = /* @__PURE__ */ new Set(["skill.md"]);
|
|
10679
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["books", "tickets", "issues", "todo", "backlog", "plans", "plan", "roadmap", "milestones", "milestone", "node_modules"]);
|
|
10680
|
+
function segments2(relPath) {
|
|
10474
10681
|
return relPath.split(/[\\/]/).filter(Boolean);
|
|
10475
10682
|
}
|
|
10683
|
+
function kindOf(relPathInProject) {
|
|
10684
|
+
const parts = segments2(relPathInProject).map((p) => p.toLowerCase());
|
|
10685
|
+
if (parts.some((p) => p === "adr" || p === "decisions"))
|
|
10686
|
+
return "decision";
|
|
10687
|
+
if (parts.some((p) => p === "runbooks" || p === "runbook"))
|
|
10688
|
+
return "runbook";
|
|
10689
|
+
return "doc";
|
|
10690
|
+
}
|
|
10476
10691
|
function runbooksAdapter() {
|
|
10477
10692
|
return {
|
|
10478
10693
|
id: "runbooks",
|
|
@@ -10480,7 +10695,9 @@ function runbooksAdapter() {
|
|
|
10480
10695
|
matches(entry) {
|
|
10481
10696
|
if (!entry.isFile || !entry.name.toLowerCase().endsWith(".md"))
|
|
10482
10697
|
return false;
|
|
10483
|
-
|
|
10698
|
+
if (CLAIMED_ELSEWHERE.has(entry.name.toLowerCase()))
|
|
10699
|
+
return false;
|
|
10700
|
+
const parts = segments2(entry.relPath).slice(0, -1);
|
|
10484
10701
|
if (!parts.some((p) => DOC_DIRS.has(p.toLowerCase())))
|
|
10485
10702
|
return false;
|
|
10486
10703
|
return !parts.some((p) => EXCLUDED_DIRS.has(p.toLowerCase()));
|
|
@@ -10489,24 +10706,23 @@ function runbooksAdapter() {
|
|
|
10489
10706
|
let raw2;
|
|
10490
10707
|
let mtime;
|
|
10491
10708
|
try {
|
|
10492
|
-
raw2 = await
|
|
10709
|
+
raw2 = await readFile6(entry.absPath, "utf8");
|
|
10493
10710
|
mtime = (await stat3(entry.absPath)).mtime;
|
|
10494
10711
|
} catch {
|
|
10495
10712
|
return [];
|
|
10496
10713
|
}
|
|
10497
|
-
const projectRoot = findProjectRoot(
|
|
10498
|
-
const relPathInProject =
|
|
10714
|
+
const projectRoot = findProjectRoot(dirname8(entry.absPath), env.homeDir);
|
|
10715
|
+
const relPathInProject = relative4(projectRoot, entry.absPath);
|
|
10499
10716
|
const fm = parseFrontmatter(raw2);
|
|
10500
10717
|
const data = fm.data;
|
|
10501
|
-
const title = data["title"] ||
|
|
10502
|
-
const kindFromPath = segments(relPathInProject).some((p) => ["adr", "decisions"].includes(p.toLowerCase())) ? "decision" : "runbook";
|
|
10718
|
+
const title = data["title"] || firstHeading2(raw2) || basename6(entry.name, ".md");
|
|
10503
10719
|
const tags = parseTags(data["tags"]);
|
|
10504
10720
|
return [
|
|
10505
10721
|
{
|
|
10506
10722
|
id: stableId(entry.absPath),
|
|
10507
10723
|
format: "runbooks",
|
|
10508
10724
|
formatLabel: "Runbook",
|
|
10509
|
-
name: `${
|
|
10725
|
+
name: `${basename6(projectRoot)}${sep5}${relPathInProject}`,
|
|
10510
10726
|
description: title,
|
|
10511
10727
|
path: entry.absPath,
|
|
10512
10728
|
scope: "project",
|
|
@@ -10514,7 +10730,7 @@ function runbooksAdapter() {
|
|
|
10514
10730
|
content: raw2,
|
|
10515
10731
|
metadata: { relPathInProject, title },
|
|
10516
10732
|
knowledge: {
|
|
10517
|
-
kind:
|
|
10733
|
+
kind: kindOf(relPathInProject),
|
|
10518
10734
|
updatedAt: mtime?.toISOString(),
|
|
10519
10735
|
tags
|
|
10520
10736
|
}
|
|
@@ -10529,7 +10745,7 @@ function parseTags(value) {
|
|
|
10529
10745
|
const tags = value.replace(/^\[|\]$/g, "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
10530
10746
|
return tags.length ? tags : void 0;
|
|
10531
10747
|
}
|
|
10532
|
-
function
|
|
10748
|
+
function firstHeading2(raw2) {
|
|
10533
10749
|
for (const line of raw2.split(/\r?\n/)) {
|
|
10534
10750
|
const m = /^#{1,6}\s+(.*\S)\s*$/.exec(line.trim());
|
|
10535
10751
|
if (m)
|
|
@@ -10539,12 +10755,12 @@ function firstHeading(raw2) {
|
|
|
10539
10755
|
}
|
|
10540
10756
|
|
|
10541
10757
|
// ../adapter-tickets/dist/index.js
|
|
10542
|
-
import { readFile as
|
|
10543
|
-
import { basename as
|
|
10758
|
+
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
10759
|
+
import { basename as basename7, dirname as dirname9, relative as relative5, sep as sep6 } from "path";
|
|
10544
10760
|
var TICKET_DIRS = /* @__PURE__ */ new Set(["tickets", "issues", "todo", "backlog"]);
|
|
10545
10761
|
var PLAN_DIRS = /* @__PURE__ */ new Set(["plans", "plan", "roadmap", "milestones", "milestone"]);
|
|
10546
10762
|
var CHECKBOX = /^\s*[-*+]\s+\[( |x|X)\]\s+/gm;
|
|
10547
|
-
function
|
|
10763
|
+
function segments3(relPath) {
|
|
10548
10764
|
return relPath.split(/[\\/]/).filter(Boolean);
|
|
10549
10765
|
}
|
|
10550
10766
|
function deriveStatus(raw2) {
|
|
@@ -10582,33 +10798,33 @@ function ticketsAdapter() {
|
|
|
10582
10798
|
matches(entry) {
|
|
10583
10799
|
if (!entry.isFile || !entry.name.toLowerCase().endsWith(".md"))
|
|
10584
10800
|
return false;
|
|
10585
|
-
const parts =
|
|
10801
|
+
const parts = segments3(entry.relPath).slice(0, -1).map((p) => p.toLowerCase());
|
|
10586
10802
|
return parts.some((p) => TICKET_DIRS.has(p) || PLAN_DIRS.has(p));
|
|
10587
10803
|
},
|
|
10588
10804
|
async parse(entry, env) {
|
|
10589
10805
|
let raw2;
|
|
10590
10806
|
let mtime;
|
|
10591
10807
|
try {
|
|
10592
|
-
raw2 = await
|
|
10808
|
+
raw2 = await readFile7(entry.absPath, "utf8");
|
|
10593
10809
|
mtime = (await stat4(entry.absPath)).mtime;
|
|
10594
10810
|
} catch {
|
|
10595
10811
|
return [];
|
|
10596
10812
|
}
|
|
10597
|
-
const projectRoot = findProjectRoot(
|
|
10598
|
-
const relPathInProject =
|
|
10599
|
-
const isPlan =
|
|
10813
|
+
const projectRoot = findProjectRoot(dirname9(entry.absPath), env.homeDir);
|
|
10814
|
+
const relPathInProject = relative5(projectRoot, entry.absPath);
|
|
10815
|
+
const isPlan = segments3(relPathInProject).slice(0, -1).some((p) => PLAN_DIRS.has(p.toLowerCase()));
|
|
10600
10816
|
const fm = parseFrontmatter(raw2);
|
|
10601
10817
|
const data = fm.data;
|
|
10602
10818
|
const derived2 = deriveStatus(raw2);
|
|
10603
10819
|
const status = normaliseStatus(data["status"]) ?? derived2.status;
|
|
10604
|
-
const title = data["title"] ||
|
|
10820
|
+
const title = data["title"] || firstHeading3(raw2) || basename7(entry.name, ".md");
|
|
10605
10821
|
const tags = parseTags2(data["tags"]);
|
|
10606
10822
|
return [
|
|
10607
10823
|
{
|
|
10608
10824
|
id: stableId(entry.absPath),
|
|
10609
10825
|
format: "tickets",
|
|
10610
10826
|
formatLabel: isPlan ? "Plan" : "Ticket",
|
|
10611
|
-
name: `${
|
|
10827
|
+
name: `${basename7(projectRoot)}${sep6}${relPathInProject}`,
|
|
10612
10828
|
description: title,
|
|
10613
10829
|
path: entry.absPath,
|
|
10614
10830
|
scope: "project",
|
|
@@ -10636,7 +10852,7 @@ function parseTags2(value) {
|
|
|
10636
10852
|
const tags = value.replace(/^\[|\]$/g, "").split(",").map((t) => t.trim()).filter(Boolean);
|
|
10637
10853
|
return tags.length ? tags : void 0;
|
|
10638
10854
|
}
|
|
10639
|
-
function
|
|
10855
|
+
function firstHeading3(raw2) {
|
|
10640
10856
|
for (const line of raw2.split(/\r?\n/)) {
|
|
10641
10857
|
const m = /^#{1,6}\s+(.*\S)\s*$/.exec(line.trim());
|
|
10642
10858
|
if (m)
|
|
@@ -10713,6 +10929,7 @@ function knowledgeAdapters() {
|
|
|
10713
10929
|
cursorRulesAdapter(),
|
|
10714
10930
|
runbooksAdapter(),
|
|
10715
10931
|
ticketsAdapter(),
|
|
10932
|
+
documentsAdapter(),
|
|
10716
10933
|
gitLogAdapter()
|
|
10717
10934
|
];
|
|
10718
10935
|
}
|
|
@@ -10720,76 +10937,116 @@ function openStore(projectRoot, embedder) {
|
|
|
10720
10937
|
return new Store(dbPath(projectRoot), embedder.dimensions);
|
|
10721
10938
|
}
|
|
10722
10939
|
async function indexProject(projectRoot, embedder, onProgress) {
|
|
10723
|
-
const started = Date.now();
|
|
10724
10940
|
const store = openStore(projectRoot, embedder);
|
|
10725
10941
|
try {
|
|
10726
|
-
|
|
10727
|
-
onProgress?.({ phase: "rebuild" });
|
|
10728
|
-
store.reset();
|
|
10729
|
-
}
|
|
10730
|
-
onProgress?.({ phase: "scan" });
|
|
10731
|
-
const result = await scan(knowledgeAdapters(), { roots: [projectRoot] });
|
|
10732
|
-
const items = result.items.filter((i) => i.scope === "project" && isInside(projectRoot, i.path));
|
|
10733
|
-
let indexed = 0;
|
|
10734
|
-
let skipped = 0;
|
|
10735
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10736
|
-
for (const item of items) {
|
|
10737
|
-
seen.add(item.path);
|
|
10738
|
-
const hash2 = sha1(item.content);
|
|
10739
|
-
const existing = store.getItem(item.path);
|
|
10740
|
-
if (existing && existing.content_hash === hash2) {
|
|
10741
|
-
skipped++;
|
|
10742
|
-
continue;
|
|
10743
|
-
}
|
|
10744
|
-
if (existing)
|
|
10745
|
-
store.deleteItem(existing.id);
|
|
10746
|
-
await indexItem(store, embedder, item, projectRoot, hash2);
|
|
10747
|
-
indexed++;
|
|
10748
|
-
onProgress?.({ phase: "embed", scanned: items.length, indexed, skipped });
|
|
10749
|
-
}
|
|
10750
|
-
const decisions = await indexDecisions(store, embedder, projectRoot);
|
|
10751
|
-
for (const path of decisions.paths)
|
|
10752
|
-
seen.add(path);
|
|
10753
|
-
indexed += decisions.indexed;
|
|
10754
|
-
skipped += decisions.skipped;
|
|
10755
|
-
let removed = 0;
|
|
10756
|
-
for (const row of store.listItems()) {
|
|
10757
|
-
if (!seen.has(row.path)) {
|
|
10758
|
-
store.deleteItem(row.id);
|
|
10759
|
-
removed++;
|
|
10760
|
-
}
|
|
10761
|
-
}
|
|
10762
|
-
const out = {
|
|
10763
|
-
scanned: items.length + decisions.paths.length,
|
|
10764
|
-
indexed,
|
|
10765
|
-
skipped,
|
|
10766
|
-
removed,
|
|
10767
|
-
issues: result.issues.map((i) => ({ path: i.path, message: i.message })),
|
|
10768
|
-
durationMs: Date.now() - started
|
|
10769
|
-
};
|
|
10770
|
-
onProgress?.({ phase: "done", ...out, issues: out.issues.length });
|
|
10771
|
-
return out;
|
|
10942
|
+
return await indexInto(store, projectRoot, embedder, { onProgress });
|
|
10772
10943
|
} finally {
|
|
10773
10944
|
store.close();
|
|
10774
10945
|
}
|
|
10775
10946
|
}
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10947
|
+
var TooManyChangesError = class extends Error {
|
|
10948
|
+
changed;
|
|
10949
|
+
limit;
|
|
10950
|
+
constructor(changed, limit) {
|
|
10951
|
+
super(`${changed} items changed since the last index \u2014 run \`capsa index\``);
|
|
10952
|
+
this.changed = changed;
|
|
10953
|
+
this.limit = limit;
|
|
10954
|
+
this.name = "TooManyChangesError";
|
|
10955
|
+
}
|
|
10956
|
+
};
|
|
10957
|
+
async function indexInto(store, projectRoot, embedder, options = {}) {
|
|
10958
|
+
const { onProgress, maxChanges } = options;
|
|
10959
|
+
const started = Date.now();
|
|
10960
|
+
if (store.needsRebuild()) {
|
|
10961
|
+
onProgress?.({ phase: "rebuild" });
|
|
10962
|
+
store.reset();
|
|
10963
|
+
}
|
|
10964
|
+
onProgress?.({ phase: "scan" });
|
|
10965
|
+
const result = await scan(knowledgeAdapters(), { roots: [projectRoot] });
|
|
10966
|
+
const items = result.items.filter((i) => i.scope === "project" && isInside(projectRoot, i.path));
|
|
10967
|
+
const all = [...items, ...decisionItems(store, projectRoot)];
|
|
10968
|
+
const { changed, skipped } = changedItems(store, all);
|
|
10969
|
+
if (maxChanges !== void 0 && changed.length > maxChanges) {
|
|
10970
|
+
throw new TooManyChangesError(changed.length, maxChanges);
|
|
10971
|
+
}
|
|
10972
|
+
let indexed = 0;
|
|
10973
|
+
for (const c of changed) {
|
|
10974
|
+
await indexItem(store, embedder, c.item, projectRoot, c.hash, c.previousId);
|
|
10975
|
+
indexed++;
|
|
10976
|
+
onProgress?.({ phase: "embed", scanned: all.length, indexed, skipped });
|
|
10977
|
+
}
|
|
10978
|
+
const seen = new Set(all.map((i) => i.path));
|
|
10979
|
+
let removed = 0;
|
|
10980
|
+
for (const row of store.listItems()) {
|
|
10981
|
+
if (!seen.has(row.path)) {
|
|
10982
|
+
store.deleteItem(row.id);
|
|
10983
|
+
removed++;
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
const out = {
|
|
10987
|
+
scanned: all.length,
|
|
10988
|
+
indexed,
|
|
10989
|
+
skipped,
|
|
10990
|
+
removed,
|
|
10991
|
+
issues: result.issues.map((i) => ({ path: i.path, message: i.message })),
|
|
10992
|
+
durationMs: Date.now() - started
|
|
10993
|
+
};
|
|
10994
|
+
store.setMeta(META_INDEXED_AT, new Date(started).toISOString());
|
|
10995
|
+
store.deleteMeta(META_INDEX_FAILED_AT);
|
|
10996
|
+
store.deleteMeta(META_INDEX_ERROR);
|
|
10997
|
+
onProgress?.({ phase: "done", ...out, issues: out.issues.length });
|
|
10998
|
+
return out;
|
|
10999
|
+
}
|
|
11000
|
+
var DEFAULT_MAX_INDEX_AGE_MS = 6e4;
|
|
11001
|
+
var DEFAULT_MAX_REFRESH_CHANGES = 20;
|
|
11002
|
+
async function freshenIndex(store, projectRoot, embedder, options = {}) {
|
|
11003
|
+
const maxAge = options.maxAgeMs ?? DEFAULT_MAX_INDEX_AGE_MS;
|
|
11004
|
+
const maxChanges = options.maxChanges ?? DEFAULT_MAX_REFRESH_CHANGES;
|
|
11005
|
+
const now = Date.now();
|
|
11006
|
+
if (ageMs(store.getMeta(META_INDEXED_AT), now) <= maxAge)
|
|
11007
|
+
return { refreshed: false, stale: false };
|
|
11008
|
+
if (store.needsRebuild()) {
|
|
11009
|
+
return {
|
|
11010
|
+
refreshed: false,
|
|
11011
|
+
stale: true,
|
|
11012
|
+
reason: "this index was never built or its schema changed \u2014 run `capsa index`"
|
|
11013
|
+
};
|
|
11014
|
+
}
|
|
11015
|
+
if (ageMs(store.getMeta(META_INDEX_FAILED_AT), now) <= maxAge) {
|
|
11016
|
+
return { refreshed: false, stale: true, reason: store.getMeta(META_INDEX_ERROR) ?? "the last index run failed" };
|
|
11017
|
+
}
|
|
11018
|
+
try {
|
|
11019
|
+
const res = await indexInto(store, projectRoot, embedder, { maxChanges });
|
|
11020
|
+
return { refreshed: true, stale: false, indexed: res.indexed, removed: res.removed };
|
|
11021
|
+
} catch (err) {
|
|
11022
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
11023
|
+
store.setMeta(META_INDEX_FAILED_AT, (/* @__PURE__ */ new Date()).toISOString());
|
|
11024
|
+
store.setMeta(META_INDEX_ERROR, reason);
|
|
11025
|
+
return { refreshed: false, stale: true, reason };
|
|
11026
|
+
}
|
|
11027
|
+
}
|
|
11028
|
+
function ageMs(iso, now) {
|
|
11029
|
+
if (!iso)
|
|
11030
|
+
return Number.POSITIVE_INFINITY;
|
|
11031
|
+
const t = Date.parse(iso);
|
|
11032
|
+
return Number.isNaN(t) ? Number.POSITIVE_INFINITY : now - t;
|
|
11033
|
+
}
|
|
11034
|
+
function changedItems(store, items) {
|
|
11035
|
+
const changed = [];
|
|
11036
|
+
let skipped = 0;
|
|
11037
|
+
for (const item of items) {
|
|
10781
11038
|
const hash2 = sha1(item.content);
|
|
10782
11039
|
const existing = store.getItem(item.path);
|
|
10783
11040
|
if (existing && existing.content_hash === hash2) {
|
|
10784
|
-
|
|
11041
|
+
skipped++;
|
|
10785
11042
|
continue;
|
|
10786
11043
|
}
|
|
10787
|
-
|
|
10788
|
-
store.deleteItem(existing.id);
|
|
10789
|
-
await indexItem(store, embedder, item, projectRoot, hash2);
|
|
10790
|
-
out.indexed++;
|
|
11044
|
+
changed.push({ item, hash: hash2, previousId: existing?.id });
|
|
10791
11045
|
}
|
|
10792
|
-
return
|
|
11046
|
+
return { changed, skipped };
|
|
11047
|
+
}
|
|
11048
|
+
function decisionItems(store, projectRoot) {
|
|
11049
|
+
return store.listDecisions().map((row) => decisionItem(projectRoot, row));
|
|
10793
11050
|
}
|
|
10794
11051
|
async function recordDecision(store, embedder, projectRoot, input2) {
|
|
10795
11052
|
const row = store.recordDecision(input2.title, input2.body, input2.related);
|
|
@@ -10801,37 +11058,44 @@ async function recordDecision(store, embedder, projectRoot, input2) {
|
|
|
10801
11058
|
return { id: row.id, indexed: false, reason: err.message };
|
|
10802
11059
|
}
|
|
10803
11060
|
}
|
|
10804
|
-
async function indexItem(store, embedder, item, projectRoot, hash2) {
|
|
10805
|
-
const chunks = chunkMarkdown(item.content);
|
|
11061
|
+
async function indexItem(store, embedder, item, projectRoot, hash2, previousId) {
|
|
11062
|
+
const chunks = item.chunks ?? chunkMarkdown(item.content);
|
|
10806
11063
|
const searchTexts = chunks.map((c) => withContext(item, c.headingPath, c.text));
|
|
10807
11064
|
const vectors = searchTexts.length ? await embedder.embed(searchTexts) : [];
|
|
11065
|
+
if (previousId)
|
|
11066
|
+
store.deleteItem(previousId);
|
|
10808
11067
|
const k = item.knowledge;
|
|
10809
11068
|
store.upsertItem({
|
|
10810
11069
|
id: item.id,
|
|
10811
11070
|
format: item.format,
|
|
10812
11071
|
kind: k?.kind ?? "instruction",
|
|
10813
11072
|
name: item.name,
|
|
11073
|
+
// The same expression the provenance header uses (see withContext): the
|
|
11074
|
+
// title an item claims for itself, falling back to its name when the
|
|
11075
|
+
// format has none to give.
|
|
11076
|
+
title: item.description ?? item.name,
|
|
10814
11077
|
path: item.path,
|
|
10815
|
-
rel_path:
|
|
11078
|
+
rel_path: relative6(projectRoot, item.path),
|
|
10816
11079
|
content_hash: hash2,
|
|
10817
11080
|
status: k?.status ?? null,
|
|
10818
11081
|
updated_at: k?.updatedAt ?? null,
|
|
10819
|
-
tags: k?.tags ? JSON.stringify(k.tags) : null
|
|
11082
|
+
tags: k?.tags ? JSON.stringify(k.tags) : null,
|
|
11083
|
+
metadata: item.metadata ? JSON.stringify(item.metadata) : null
|
|
10820
11084
|
});
|
|
10821
11085
|
chunks.forEach((c, i) => store.insertChunk(item.id, c.index, c.headingPath, c.text, vectors[i], searchTexts[i]));
|
|
10822
11086
|
}
|
|
10823
|
-
function withContext(item, headingPath,
|
|
11087
|
+
function withContext(item, headingPath, text2) {
|
|
10824
11088
|
const kind = item.knowledge?.kind ?? "instruction";
|
|
10825
11089
|
const head = [kind, item.description ?? item.name, ...headingPath].filter(Boolean).join(" \u203A ");
|
|
10826
11090
|
return `${head}
|
|
10827
11091
|
|
|
10828
|
-
${
|
|
11092
|
+
${text2}`;
|
|
10829
11093
|
}
|
|
10830
11094
|
function sha1(s) {
|
|
10831
11095
|
return createHash2("sha1").update(s).digest("hex");
|
|
10832
11096
|
}
|
|
10833
11097
|
function isInside(root, path) {
|
|
10834
|
-
const rel =
|
|
11098
|
+
const rel = relative6(root, path);
|
|
10835
11099
|
return !!rel && !rel.startsWith("..") && !rel.includes(":");
|
|
10836
11100
|
}
|
|
10837
11101
|
|
|
@@ -10852,6 +11116,13 @@ var DEFAULT_WEIGHTS = {
|
|
|
10852
11116
|
plan: 1.05,
|
|
10853
11117
|
ticket: 1,
|
|
10854
11118
|
runbook: 1,
|
|
11119
|
+
// A book is reference, not project knowledge: neutral until the context
|
|
11120
|
+
// log says otherwise. It carries no recency and no status either — see
|
|
11121
|
+
// the documents adapter on what it refuses to claim.
|
|
11122
|
+
document: 1,
|
|
11123
|
+
// Never ranked: an outline has no chunks to rank. The map is total, so
|
|
11124
|
+
// it needs a number; 1.0 is the one that says nothing.
|
|
11125
|
+
outline: 1,
|
|
10855
11126
|
doc: 0.95,
|
|
10856
11127
|
wiki: 0.95,
|
|
10857
11128
|
commit: 0.85
|
|
@@ -10884,15 +11155,19 @@ function rrf(lists, k = DEFAULT_WEIGHTS.rrfK) {
|
|
|
10884
11155
|
|
|
10885
11156
|
// ../index/dist/pin.js
|
|
10886
11157
|
var LEADING_ID = /^([A-Za-z]{1,8}-?\d{1,5}(?:-\d{1,5})?|\d{2,6})(?=[-_.\s]|$)/;
|
|
11158
|
+
var POSITIONAL_KINDS = /* @__PURE__ */ new Set(["document", "outline"]);
|
|
10887
11159
|
function leadingId(relPath) {
|
|
10888
11160
|
const base = relPath.split(/[\\/]/).pop() ?? "";
|
|
10889
11161
|
return LEADING_ID.exec(base)?.[1];
|
|
10890
11162
|
}
|
|
11163
|
+
function pinnableId(item) {
|
|
11164
|
+
return POSITIONAL_KINDS.has(item.kind) ? void 0 : leadingId(item.rel_path);
|
|
11165
|
+
}
|
|
10891
11166
|
function pinnedItems(query, items) {
|
|
10892
11167
|
const q = query.toLowerCase();
|
|
10893
11168
|
const idCount = /* @__PURE__ */ new Map();
|
|
10894
11169
|
for (const item of items) {
|
|
10895
|
-
const id =
|
|
11170
|
+
const id = pinnableId(item)?.toLowerCase();
|
|
10896
11171
|
if (id)
|
|
10897
11172
|
idCount.set(id, (idCount.get(id) ?? 0) + 1);
|
|
10898
11173
|
}
|
|
@@ -10903,7 +11178,7 @@ function pinnedItems(query, items) {
|
|
|
10903
11178
|
out.push(item);
|
|
10904
11179
|
continue;
|
|
10905
11180
|
}
|
|
10906
|
-
const id =
|
|
11181
|
+
const id = pinnableId(item);
|
|
10907
11182
|
if (!id || (idCount.get(id.toLowerCase()) ?? 0) > 1)
|
|
10908
11183
|
continue;
|
|
10909
11184
|
const re = new RegExp(`(^|[^\\w-])${escape2(id)}(?=$|[^\\w-])`, "i");
|
|
@@ -10923,6 +11198,7 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10923
11198
|
const candidates = options.candidates ?? 30;
|
|
10924
11199
|
const weights = options.weights ?? DEFAULT_WEIGHTS;
|
|
10925
11200
|
const minRelative = options.minRelativeScore ?? 0.5;
|
|
11201
|
+
const perItemCap = { ...DEFAULT_PER_ITEM_CAP, ...options.perItemCap };
|
|
10926
11202
|
const perItem = /* @__PURE__ */ new Map();
|
|
10927
11203
|
const chunks = [];
|
|
10928
11204
|
const debug = [];
|
|
@@ -10932,11 +11208,13 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10932
11208
|
for (const c of store.chunksForItem(item.id)) {
|
|
10933
11209
|
const t = estimateTokens(c.text);
|
|
10934
11210
|
const fits = tokens + t <= maxTokens || chunks.length === 0;
|
|
11211
|
+
const metadata = parseMetadata(item.metadata);
|
|
10935
11212
|
if (options.debug) {
|
|
10936
11213
|
debug.push({
|
|
10937
11214
|
chunkId: c.id,
|
|
10938
11215
|
relPath: item.rel_path,
|
|
10939
11216
|
headingPath: JSON.parse(c.heading_path),
|
|
11217
|
+
metadata,
|
|
10940
11218
|
kind: item.kind,
|
|
10941
11219
|
status: item.status,
|
|
10942
11220
|
vecRank: null,
|
|
@@ -10961,16 +11239,25 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10961
11239
|
relPath: item.rel_path,
|
|
10962
11240
|
headingPath: JSON.parse(c.heading_path),
|
|
10963
11241
|
text: c.text,
|
|
10964
|
-
tokens: t
|
|
11242
|
+
tokens: t,
|
|
11243
|
+
...metadata ? { metadata } : {}
|
|
10965
11244
|
});
|
|
10966
11245
|
}
|
|
10967
11246
|
}
|
|
10968
11247
|
const pinnedIds = new Set(pinned.map((i) => i.id));
|
|
10969
11248
|
const neighbourCutoff = pinned.length ? Math.max(minRelative, 0.75) : minRelative;
|
|
10970
11249
|
const neighbourBudget = pinned.length ? Math.min(maxTokens, tokens + Math.floor(maxTokens / 3)) : maxTokens;
|
|
10971
|
-
const
|
|
10972
|
-
|
|
10973
|
-
|
|
11250
|
+
const searchText = neighbourQuery(query, pinned);
|
|
11251
|
+
let vec = [];
|
|
11252
|
+
let degraded;
|
|
11253
|
+
try {
|
|
11254
|
+
const [qv] = await embedder.embed([searchText]);
|
|
11255
|
+
if (qv)
|
|
11256
|
+
vec = store.vectorSearch(qv, candidates).map((r) => r.chunkId);
|
|
11257
|
+
} catch (err) {
|
|
11258
|
+
degraded = err instanceof Error ? err.message : String(err);
|
|
11259
|
+
}
|
|
11260
|
+
const fts = store.textSearch(searchText, candidates).map((r) => r.chunkId);
|
|
10974
11261
|
const fused = rrf([vec, fts], weights.rrfK);
|
|
10975
11262
|
const rows = store.chunksWithItems([...fused.keys()]).filter((r) => !pinnedIds.has(r.item_id));
|
|
10976
11263
|
const now = Date.now();
|
|
@@ -10979,24 +11266,45 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
10979
11266
|
const factor = signalFactor({ kind: r.item.kind, status: r.item.status, updatedAt: r.item.updated_at }, weights, now);
|
|
10980
11267
|
return { row: r, fusedScore, factor, score: fusedScore * factor };
|
|
10981
11268
|
}).sort((a, b) => b.score - a.score);
|
|
10982
|
-
const
|
|
10983
|
-
|
|
10984
|
-
|
|
10985
|
-
|
|
10986
|
-
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
11269
|
+
const neighbourhood = [];
|
|
11270
|
+
for (const c of scored) {
|
|
11271
|
+
if (options.excludeRootInstructions && isRootInstruction(c.row.item.rel_path)) {
|
|
11272
|
+
if (options.debug) {
|
|
11273
|
+
debug.push({
|
|
11274
|
+
chunkId: c.row.id,
|
|
11275
|
+
relPath: c.row.item.rel_path,
|
|
11276
|
+
headingPath: JSON.parse(c.row.heading_path),
|
|
11277
|
+
metadata: parseMetadata(c.row.item.metadata),
|
|
11278
|
+
kind: c.row.item.kind,
|
|
11279
|
+
status: c.row.item.status,
|
|
11280
|
+
vecRank: rankIn(vec, c.row.id),
|
|
11281
|
+
ftsRank: rankIn(fts, c.row.id),
|
|
11282
|
+
fused: c.fusedScore,
|
|
11283
|
+
factor: c.factor,
|
|
11284
|
+
score: c.score,
|
|
11285
|
+
kept: false,
|
|
11286
|
+
why: "in prompt"
|
|
11287
|
+
});
|
|
11288
|
+
}
|
|
11289
|
+
continue;
|
|
10994
11290
|
}
|
|
11291
|
+
neighbourhood.push(c);
|
|
11292
|
+
}
|
|
11293
|
+
const verdicts = keep(neighbourhood.map((c) => ({
|
|
11294
|
+
itemId: c.row.item_id,
|
|
11295
|
+
kind: c.row.item.kind,
|
|
11296
|
+
score: c.score,
|
|
11297
|
+
tokens: estimateTokens(c.row.text)
|
|
11298
|
+
})), { budget: neighbourBudget, minRelativeScore: neighbourCutoff, perItemCap }, { tokens, chunks: chunks.length, perItem });
|
|
11299
|
+
for (const [i, { row, fusedScore, factor, score }] of neighbourhood.entries()) {
|
|
11300
|
+
const why = verdicts[i];
|
|
11301
|
+
const metadata = parseMetadata(row.item.metadata);
|
|
10995
11302
|
if (options.debug) {
|
|
10996
11303
|
debug.push({
|
|
10997
11304
|
chunkId: row.id,
|
|
10998
11305
|
relPath: row.item.rel_path,
|
|
10999
11306
|
headingPath: JSON.parse(row.heading_path),
|
|
11307
|
+
metadata,
|
|
11000
11308
|
kind: row.item.kind,
|
|
11001
11309
|
status: row.item.status,
|
|
11002
11310
|
vecRank: rankIn(vec, row.id),
|
|
@@ -11011,9 +11319,7 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
11011
11319
|
if (why !== "kept")
|
|
11012
11320
|
continue;
|
|
11013
11321
|
const t = estimateTokens(row.text);
|
|
11014
|
-
perItem.set(row.item_id, (perItem.get(row.item_id) ?? 0) + 1);
|
|
11015
11322
|
tokens += t;
|
|
11016
|
-
kept++;
|
|
11017
11323
|
chunks.push({
|
|
11018
11324
|
chunkId: row.id,
|
|
11019
11325
|
score,
|
|
@@ -11023,31 +11329,123 @@ async function retrieve(store, embedder, query, options = {}) {
|
|
|
11023
11329
|
relPath: row.item.rel_path,
|
|
11024
11330
|
headingPath: JSON.parse(row.heading_path),
|
|
11025
11331
|
text: row.text,
|
|
11026
|
-
tokens: t
|
|
11332
|
+
tokens: t,
|
|
11333
|
+
...metadata ? { metadata } : {}
|
|
11027
11334
|
});
|
|
11028
|
-
if (tokens >= neighbourBudget)
|
|
11029
|
-
cut = true;
|
|
11030
11335
|
}
|
|
11031
11336
|
const durationMs = Date.now() - started;
|
|
11032
11337
|
store.logContext({
|
|
11033
|
-
source
|
|
11338
|
+
// A keyword-only delivery is logged under its own source. The week-4
|
|
11339
|
+
// gate is read from this table, and a run with half the retriever
|
|
11340
|
+
// missing is not a sample of how the ranking performs — it should show
|
|
11341
|
+
// up as its own bucket in a `group by source`, not quietly average in.
|
|
11342
|
+
source: (options.source ?? "unknown") + (degraded ? ":degraded" : ""),
|
|
11034
11343
|
query,
|
|
11035
11344
|
delivered: chunks.map((c) => ({ rel_path: c.relPath, heading_path: c.headingPath, tokens: c.tokens })),
|
|
11036
11345
|
tokensEst: tokens,
|
|
11346
|
+
// The ceiling, not the spend: two rows are only comparable when this
|
|
11347
|
+
// matches, and retrieval stops at the relevance cliff well before it.
|
|
11348
|
+
tokensBudget: maxTokens,
|
|
11037
11349
|
durationMs
|
|
11038
11350
|
});
|
|
11039
|
-
return {
|
|
11351
|
+
return {
|
|
11352
|
+
query,
|
|
11353
|
+
searchText,
|
|
11354
|
+
chunks,
|
|
11355
|
+
tokensEst: tokens,
|
|
11356
|
+
durationMs,
|
|
11357
|
+
...degraded ? { degraded } : {},
|
|
11358
|
+
...options.debug ? { debug } : {}
|
|
11359
|
+
};
|
|
11360
|
+
}
|
|
11361
|
+
var PER_ITEM_CAP = 2;
|
|
11362
|
+
var DOCUMENT_PER_ITEM_CAP = 6;
|
|
11363
|
+
var DEFAULT_PER_ITEM_CAP = {
|
|
11364
|
+
instruction: PER_ITEM_CAP,
|
|
11365
|
+
doc: PER_ITEM_CAP,
|
|
11366
|
+
runbook: PER_ITEM_CAP,
|
|
11367
|
+
ticket: PER_ITEM_CAP,
|
|
11368
|
+
plan: PER_ITEM_CAP,
|
|
11369
|
+
decision: PER_ITEM_CAP,
|
|
11370
|
+
wiki: PER_ITEM_CAP,
|
|
11371
|
+
commit: PER_ITEM_CAP,
|
|
11372
|
+
document: DOCUMENT_PER_ITEM_CAP,
|
|
11373
|
+
// An outline has no chunks; the number is here because the map is total.
|
|
11374
|
+
outline: PER_ITEM_CAP
|
|
11375
|
+
};
|
|
11376
|
+
function keep(candidates, rules, start = { tokens: 0, chunks: 0, perItem: /* @__PURE__ */ new Map() }) {
|
|
11377
|
+
const best = candidates[0]?.score ?? 0;
|
|
11378
|
+
const perItem = new Map(start.perItem);
|
|
11379
|
+
let tokens = start.tokens;
|
|
11380
|
+
let chunks = start.chunks;
|
|
11381
|
+
let kept = 0;
|
|
11382
|
+
let end = tokens >= rules.budget ? "budget" : void 0;
|
|
11383
|
+
return candidates.map((c) => {
|
|
11384
|
+
if (end)
|
|
11385
|
+
return end;
|
|
11386
|
+
if (kept > 0 && c.score < best * rules.minRelativeScore)
|
|
11387
|
+
return end = "cutoff";
|
|
11388
|
+
const taken = perItem.get(c.itemId) ?? 0;
|
|
11389
|
+
if (taken >= (rules.perItemCap[c.kind] ?? PER_ITEM_CAP))
|
|
11390
|
+
return "per-item cap";
|
|
11391
|
+
if (chunks > 0 && tokens + c.tokens > rules.budget)
|
|
11392
|
+
return end = "budget";
|
|
11393
|
+
perItem.set(c.itemId, taken + 1);
|
|
11394
|
+
tokens += c.tokens;
|
|
11395
|
+
chunks++;
|
|
11396
|
+
kept++;
|
|
11397
|
+
if (tokens >= rules.budget)
|
|
11398
|
+
end = "budget";
|
|
11399
|
+
return "kept";
|
|
11400
|
+
});
|
|
11401
|
+
}
|
|
11402
|
+
function neighbourQuery(query, pinned) {
|
|
11403
|
+
if (pinned.length === 0)
|
|
11404
|
+
return query;
|
|
11405
|
+
return pinned.map((i) => i.title).join("; ");
|
|
11406
|
+
}
|
|
11407
|
+
var ROOT_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md", "AGENTS.md"]);
|
|
11408
|
+
function isRootInstruction(relPath) {
|
|
11409
|
+
return ROOT_INSTRUCTION_FILES.has(relPath);
|
|
11040
11410
|
}
|
|
11041
11411
|
function rankIn(list, id) {
|
|
11042
11412
|
const i = list.indexOf(id);
|
|
11043
11413
|
return i === -1 ? null : i + 1;
|
|
11044
11414
|
}
|
|
11415
|
+
function parseMetadata(json2) {
|
|
11416
|
+
if (!json2)
|
|
11417
|
+
return void 0;
|
|
11418
|
+
try {
|
|
11419
|
+
const parsed = JSON.parse(json2);
|
|
11420
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
11421
|
+
} catch {
|
|
11422
|
+
return void 0;
|
|
11423
|
+
}
|
|
11424
|
+
}
|
|
11425
|
+
function whereOf(c) {
|
|
11426
|
+
const book = text(c.metadata?.["book"]);
|
|
11427
|
+
if (c.kind !== "document" || !book)
|
|
11428
|
+
return [c.relPath, ...c.headingPath].join(" \u203A ");
|
|
11429
|
+
const title = text(c.metadata?.["title"]);
|
|
11430
|
+
const chapter = text(c.metadata?.["chapter"]);
|
|
11431
|
+
const head = [chapter ? `Ch. ${chapter}` : "", title].filter(Boolean).join(" ");
|
|
11432
|
+
const first = c.headingPath[0];
|
|
11433
|
+
const rest = title && first && first.toLowerCase().includes(title.toLowerCase()) ? c.headingPath.slice(1) : c.headingPath;
|
|
11434
|
+
return [book, head, ...rest].filter(Boolean).join(" \u203A ");
|
|
11435
|
+
}
|
|
11436
|
+
function text(value) {
|
|
11437
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
11438
|
+
}
|
|
11045
11439
|
function formatDebug(result) {
|
|
11046
11440
|
if (!result.debug)
|
|
11047
11441
|
return "";
|
|
11048
|
-
const lines = [
|
|
11442
|
+
const lines = [
|
|
11443
|
+
`stage 2 searched: ${JSON.stringify(result.searchText)}${result.searchText === result.query ? "" : " (from the pinned titles)"}`,
|
|
11444
|
+
"",
|
|
11445
|
+
" vec fts factor score keep where"
|
|
11446
|
+
];
|
|
11049
11447
|
for (const d of result.debug) {
|
|
11050
|
-
const where =
|
|
11448
|
+
const where = whereOf(d).slice(0, 100);
|
|
11051
11449
|
lines.push([
|
|
11052
11450
|
String(d.vecRank ?? "-").padStart(5),
|
|
11053
11451
|
String(d.ftsRank ?? "-").padStart(4),
|
|
@@ -11063,14 +11461,32 @@ function formatContext(result) {
|
|
|
11063
11461
|
if (result.chunks.length === 0)
|
|
11064
11462
|
return "(no relevant project knowledge found)";
|
|
11065
11463
|
return result.chunks.map((c) => {
|
|
11066
|
-
const
|
|
11067
|
-
|
|
11068
|
-
return `### ${where}
|
|
11464
|
+
const meta3 = citation(c) ?? [c.kind, c.status, c.updatedAt?.slice(0, 10)].filter(Boolean).join(", ");
|
|
11465
|
+
return `### ${whereOf(c)}
|
|
11069
11466
|
_${meta3}_
|
|
11070
11467
|
|
|
11071
11468
|
${c.text.trim()}`;
|
|
11072
11469
|
}).join("\n\n");
|
|
11073
11470
|
}
|
|
11471
|
+
function citation(c) {
|
|
11472
|
+
const book = text(c.metadata?.["book"]);
|
|
11473
|
+
if (c.kind !== "document" || !book)
|
|
11474
|
+
return void 0;
|
|
11475
|
+
const title = text(c.metadata?.["title"]);
|
|
11476
|
+
const chapter = text(c.metadata?.["chapter"]);
|
|
11477
|
+
const head = [chapter ? `Ch. ${chapter}` : "", title ? `"${title}"` : ""].filter(Boolean).join(" ");
|
|
11478
|
+
const pp = pageRange(c.metadata?.["printed_pages"]);
|
|
11479
|
+
return [book, text(c.metadata?.["author"]), head, pp ? `pp. ${pp}` : ""].filter(Boolean).join(", ");
|
|
11480
|
+
}
|
|
11481
|
+
function pageRange(value) {
|
|
11482
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
11483
|
+
return void 0;
|
|
11484
|
+
const from = text(value[0]);
|
|
11485
|
+
const to = text(value[value.length - 1]);
|
|
11486
|
+
if (!from)
|
|
11487
|
+
return void 0;
|
|
11488
|
+
return !to || to === from ? from : `${from}\u2013${to}`;
|
|
11489
|
+
}
|
|
11074
11490
|
|
|
11075
11491
|
// ../index/dist/state.js
|
|
11076
11492
|
function projectState(store) {
|
|
@@ -11081,7 +11497,8 @@ function projectState(store) {
|
|
|
11081
11497
|
const live = (i) => (i.kind === "ticket" || i.kind === "plan") && (i.status === "open" || i.status === "in-progress");
|
|
11082
11498
|
const rank = (i) => i.status === "in-progress" ? 0 : 1;
|
|
11083
11499
|
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 }));
|
|
11084
|
-
const
|
|
11500
|
+
const STILL = /* @__PURE__ */ new Set(["commit", "document", "outline"]);
|
|
11501
|
+
const recentlyChanged = items.filter((i) => !STILL.has(i.kind)).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 }));
|
|
11085
11502
|
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 }));
|
|
11086
11503
|
return { counts, open: open2, recentlyChanged, decisions };
|
|
11087
11504
|
}
|
|
@@ -16673,7 +17090,7 @@ __export(regexes_exports, {
|
|
|
16673
17090
|
nanoid: () => nanoid,
|
|
16674
17091
|
nanoidOfLength: () => nanoidOfLength,
|
|
16675
17092
|
null: () => _null,
|
|
16676
|
-
number: () =>
|
|
17093
|
+
number: () => number2,
|
|
16677
17094
|
rfc5322Email: () => rfc5322Email,
|
|
16678
17095
|
sha1_base64: () => sha1_base64,
|
|
16679
17096
|
sha1_base64url: () => sha1_base64url,
|
|
@@ -16774,7 +17191,7 @@ var string = (params) => {
|
|
|
16774
17191
|
};
|
|
16775
17192
|
var bigint = /^-?\d+n?$/;
|
|
16776
17193
|
var integer = /^-?\d+$/;
|
|
16777
|
-
var
|
|
17194
|
+
var number2 = /^-?\d+(?:\.\d+)?$/;
|
|
16778
17195
|
var boolean = /^(?:true|false)$/i;
|
|
16779
17196
|
var _null = /^null$/i;
|
|
16780
17197
|
var _undefined = /^undefined$/i;
|
|
@@ -17901,7 +18318,7 @@ var $ZodCustomStringFormat = /* @__PURE__ */ $constructor("$ZodCustomStringForma
|
|
|
17901
18318
|
});
|
|
17902
18319
|
var $ZodNumber = /* @__PURE__ */ $constructor("$ZodNumber", (inst, def) => {
|
|
17903
18320
|
$ZodType.init(inst, def);
|
|
17904
|
-
inst._zod.pattern =
|
|
18321
|
+
inst._zod.pattern = number2;
|
|
17905
18322
|
inst._zod.parse = (payload, _ctx) => {
|
|
17906
18323
|
if (def.coerce)
|
|
17907
18324
|
try {
|
|
@@ -18978,7 +19395,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
18978
19395
|
if (keyResult instanceof Promise) {
|
|
18979
19396
|
throw new Error("Async schemas not supported in object keys currently");
|
|
18980
19397
|
}
|
|
18981
|
-
const checkNumericKey = typeof key === "string" &&
|
|
19398
|
+
const checkNumericKey = typeof key === "string" && number2.test(key) && keyResult.issues.length;
|
|
18982
19399
|
if (checkNumericKey) {
|
|
18983
19400
|
const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
|
|
18984
19401
|
if (retryResult instanceof Promise) {
|
|
@@ -19545,7 +19962,7 @@ function leafPattern(schema) {
|
|
|
19545
19962
|
if (minimum !== void 0 || maximum !== void 0)
|
|
19546
19963
|
return string({ minimum, maximum }).source;
|
|
19547
19964
|
const own2 = schema._zod.pattern;
|
|
19548
|
-
return (isInt && own2 ===
|
|
19965
|
+
return (isInt && own2 === number2 ? integer : own2)?.source;
|
|
19549
19966
|
}
|
|
19550
19967
|
function partPattern(schema) {
|
|
19551
19968
|
const def = schema._zod.def;
|
|
@@ -24043,11 +24460,11 @@ function ko_default() {
|
|
|
24043
24460
|
}
|
|
24044
24461
|
|
|
24045
24462
|
// ../../node_modules/.bun/zod@4.6.5/node_modules/zod/v4/locales/lt.js
|
|
24046
|
-
var capitalizeFirstCharacter = (
|
|
24047
|
-
return
|
|
24463
|
+
var capitalizeFirstCharacter = (text2) => {
|
|
24464
|
+
return text2.charAt(0).toUpperCase() + text2.slice(1);
|
|
24048
24465
|
};
|
|
24049
|
-
function getUnitTypeFromNumber(
|
|
24050
|
-
const abs = Math.abs(
|
|
24466
|
+
function getUnitTypeFromNumber(number5) {
|
|
24467
|
+
const abs = Math.abs(number5);
|
|
24051
24468
|
const last = abs % 10;
|
|
24052
24469
|
const last2 = abs % 100;
|
|
24053
24470
|
if (last2 >= 11 && last2 <= 19 || last === 0)
|
|
@@ -29019,7 +29436,7 @@ function generateRecordCheck(doc, ctx, schema, accessor) {
|
|
|
29019
29436
|
if (keyFn.definite === false)
|
|
29020
29437
|
ctx.definite = false;
|
|
29021
29438
|
const keyFast = addConstant(ctx, keyFn);
|
|
29022
|
-
const numericConst = addConstant(ctx,
|
|
29439
|
+
const numericConst = addConstant(ctx, number2);
|
|
29023
29440
|
const outKeyVar = newVar(ctx);
|
|
29024
29441
|
emitOwnKeys(doc, ctx, accessor, kVar, (d) => {
|
|
29025
29442
|
d.write(`let ${outKeyVar} = ${keyFast}(${kVar});`);
|
|
@@ -31327,7 +31744,7 @@ function stringifyKeyNames(bySchema, json2, visited) {
|
|
|
31327
31744
|
return rest;
|
|
31328
31745
|
rest.type = "string";
|
|
31329
31746
|
if (!values)
|
|
31330
|
-
rest.pattern = (types.includes("number") ?
|
|
31747
|
+
rest.pattern = (types.includes("number") ? number2 : integer).source;
|
|
31331
31748
|
return rest;
|
|
31332
31749
|
}
|
|
31333
31750
|
var pendingRecords = /* @__PURE__ */ new WeakMap();
|
|
@@ -32229,7 +32646,7 @@ __export(external_exports, {
|
|
|
32229
32646
|
null: () => _null3,
|
|
32230
32647
|
nullable: () => nullable,
|
|
32231
32648
|
nullish: () => nullish2,
|
|
32232
|
-
number: () =>
|
|
32649
|
+
number: () => number3,
|
|
32233
32650
|
object: () => object2,
|
|
32234
32651
|
optional: () => optional,
|
|
32235
32652
|
output: () => output,
|
|
@@ -32442,7 +32859,7 @@ __export(schemas_exports2, {
|
|
|
32442
32859
|
null: () => _null3,
|
|
32443
32860
|
nullable: () => nullable,
|
|
32444
32861
|
nullish: () => nullish2,
|
|
32445
|
-
number: () =>
|
|
32862
|
+
number: () => number3,
|
|
32446
32863
|
object: () => object2,
|
|
32447
32864
|
optional: () => optional,
|
|
32448
32865
|
partialRecord: () => partialRecord,
|
|
@@ -33188,7 +33605,7 @@ var ZodNumber2 = /* @__PURE__ */ $constructor(
|
|
|
33188
33605
|
}
|
|
33189
33606
|
})
|
|
33190
33607
|
);
|
|
33191
|
-
function
|
|
33608
|
+
function number3(params) {
|
|
33192
33609
|
return _number(ZodNumber2, params);
|
|
33193
33610
|
}
|
|
33194
33611
|
var ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, def) => {
|
|
@@ -34028,7 +34445,7 @@ var stringbool = (...args) => _stringbool({
|
|
|
34028
34445
|
}, ...args);
|
|
34029
34446
|
function json(params) {
|
|
34030
34447
|
const jsonSchema = lazy(() => {
|
|
34031
|
-
return union([string2(params),
|
|
34448
|
+
return union([string2(params), number3(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
|
|
34032
34449
|
});
|
|
34033
34450
|
return jsonSchema;
|
|
34034
34451
|
}
|
|
@@ -34872,13 +35289,13 @@ __export(coerce_exports2, {
|
|
|
34872
35289
|
bigint: () => bigint3,
|
|
34873
35290
|
boolean: () => boolean3,
|
|
34874
35291
|
date: () => date4,
|
|
34875
|
-
number: () =>
|
|
35292
|
+
number: () => number4,
|
|
34876
35293
|
string: () => string3
|
|
34877
35294
|
});
|
|
34878
35295
|
function string3(params) {
|
|
34879
35296
|
return _coercedString(ZodString2, params);
|
|
34880
35297
|
}
|
|
34881
|
-
function
|
|
35298
|
+
function number4(params) {
|
|
34882
35299
|
return _coercedNumber(ZodNumber2, params);
|
|
34883
35300
|
}
|
|
34884
35301
|
function boolean3(params) {
|
|
@@ -34897,20 +35314,20 @@ var SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-
|
|
|
34897
35314
|
var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
|
|
34898
35315
|
var JSONRPC_VERSION = "2.0";
|
|
34899
35316
|
var AssertObjectSchema = custom((v) => v !== null && (typeof v === "object" || typeof v === "function"));
|
|
34900
|
-
var ProgressTokenSchema = union([string2(),
|
|
35317
|
+
var ProgressTokenSchema = union([string2(), number3().int()]);
|
|
34901
35318
|
var CursorSchema = string2();
|
|
34902
35319
|
var TaskCreationParamsSchema = looseObject({
|
|
34903
35320
|
/**
|
|
34904
35321
|
* Requested duration in milliseconds to retain task from creation.
|
|
34905
35322
|
*/
|
|
34906
|
-
ttl:
|
|
35323
|
+
ttl: number3().optional(),
|
|
34907
35324
|
/**
|
|
34908
35325
|
* Time in milliseconds to wait between task status requests.
|
|
34909
35326
|
*/
|
|
34910
|
-
pollInterval:
|
|
35327
|
+
pollInterval: number3().optional()
|
|
34911
35328
|
});
|
|
34912
35329
|
var TaskMetadataSchema = object2({
|
|
34913
|
-
ttl:
|
|
35330
|
+
ttl: number3().optional()
|
|
34914
35331
|
});
|
|
34915
35332
|
var RelatedTaskMetadataSchema = object2({
|
|
34916
35333
|
taskId: string2()
|
|
@@ -34965,7 +35382,7 @@ var ResultSchema = looseObject({
|
|
|
34965
35382
|
*/
|
|
34966
35383
|
_meta: RequestMetaSchema.optional()
|
|
34967
35384
|
});
|
|
34968
|
-
var RequestIdSchema = union([string2(),
|
|
35385
|
+
var RequestIdSchema = union([string2(), number3().int()]);
|
|
34969
35386
|
var JSONRPCRequestSchema = object2({
|
|
34970
35387
|
jsonrpc: literal(JSONRPC_VERSION),
|
|
34971
35388
|
id: RequestIdSchema,
|
|
@@ -35001,7 +35418,7 @@ var JSONRPCErrorResponseSchema = object2({
|
|
|
35001
35418
|
/**
|
|
35002
35419
|
* The error type that occurred.
|
|
35003
35420
|
*/
|
|
35004
|
-
code:
|
|
35421
|
+
code: number3().int(),
|
|
35005
35422
|
/**
|
|
35006
35423
|
* A short description of the error. The message SHOULD be limited to a concise single sentence.
|
|
35007
35424
|
*/
|
|
@@ -35300,11 +35717,11 @@ var ProgressSchema = object2({
|
|
|
35300
35717
|
/**
|
|
35301
35718
|
* The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
|
35302
35719
|
*/
|
|
35303
|
-
progress:
|
|
35720
|
+
progress: number3(),
|
|
35304
35721
|
/**
|
|
35305
35722
|
* Total number of items to process (or total progress required), if known.
|
|
35306
35723
|
*/
|
|
35307
|
-
total: optional(
|
|
35724
|
+
total: optional(number3()),
|
|
35308
35725
|
/**
|
|
35309
35726
|
* An optional message describing the current progress.
|
|
35310
35727
|
*/
|
|
@@ -35347,7 +35764,7 @@ var TaskSchema = object2({
|
|
|
35347
35764
|
* Time in milliseconds to keep task results available after completion.
|
|
35348
35765
|
* If null, the task has unlimited lifetime until manually cleaned up.
|
|
35349
35766
|
*/
|
|
35350
|
-
ttl: union([
|
|
35767
|
+
ttl: union([number3(), _null3()]),
|
|
35351
35768
|
/**
|
|
35352
35769
|
* ISO 8601 timestamp when the task was created.
|
|
35353
35770
|
*/
|
|
@@ -35356,7 +35773,7 @@ var TaskSchema = object2({
|
|
|
35356
35773
|
* ISO 8601 timestamp when the task was last updated.
|
|
35357
35774
|
*/
|
|
35358
35775
|
lastUpdatedAt: string2(),
|
|
35359
|
-
pollInterval: optional(
|
|
35776
|
+
pollInterval: optional(number3()),
|
|
35360
35777
|
/**
|
|
35361
35778
|
* Optional diagnostic message for failed tasks or other status information.
|
|
35362
35779
|
*/
|
|
@@ -35441,7 +35858,7 @@ var AnnotationsSchema = object2({
|
|
|
35441
35858
|
/**
|
|
35442
35859
|
* Importance hint for the resource, from 0 (least) to 1 (most).
|
|
35443
35860
|
*/
|
|
35444
|
-
priority:
|
|
35861
|
+
priority: number3().min(0).max(1).optional(),
|
|
35445
35862
|
/**
|
|
35446
35863
|
* ISO 8601 timestamp for the most recent modification.
|
|
35447
35864
|
*/
|
|
@@ -35469,7 +35886,7 @@ var ResourceSchema = object2({
|
|
|
35469
35886
|
*
|
|
35470
35887
|
* This can be used by Hosts to display file sizes and estimate context window usage.
|
|
35471
35888
|
*/
|
|
35472
|
-
size: optional(
|
|
35889
|
+
size: optional(number3()),
|
|
35473
35890
|
/**
|
|
35474
35891
|
* Optional annotations for the client.
|
|
35475
35892
|
*/
|
|
@@ -35892,7 +36309,7 @@ var ListChangedOptionsBaseSchema = object2({
|
|
|
35892
36309
|
*
|
|
35893
36310
|
* @default 300
|
|
35894
36311
|
*/
|
|
35895
|
-
debounceMs:
|
|
36312
|
+
debounceMs: number3().int().nonnegative().default(300)
|
|
35896
36313
|
});
|
|
35897
36314
|
var LoggingLevelSchema = _enum2(["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"]);
|
|
35898
36315
|
var SetLevelRequestParamsSchema = BaseRequestParamsSchema.extend({
|
|
@@ -35937,15 +36354,15 @@ var ModelPreferencesSchema = object2({
|
|
|
35937
36354
|
/**
|
|
35938
36355
|
* How much to prioritize cost when selecting a model.
|
|
35939
36356
|
*/
|
|
35940
|
-
costPriority:
|
|
36357
|
+
costPriority: number3().min(0).max(1).optional(),
|
|
35941
36358
|
/**
|
|
35942
36359
|
* How much to prioritize sampling speed (latency) when selecting a model.
|
|
35943
36360
|
*/
|
|
35944
|
-
speedPriority:
|
|
36361
|
+
speedPriority: number3().min(0).max(1).optional(),
|
|
35945
36362
|
/**
|
|
35946
36363
|
* How much to prioritize intelligence and capabilities when selecting a model.
|
|
35947
36364
|
*/
|
|
35948
|
-
intelligencePriority:
|
|
36365
|
+
intelligencePriority: number3().min(0).max(1).optional()
|
|
35949
36366
|
});
|
|
35950
36367
|
var ToolChoiceSchema = object2({
|
|
35951
36368
|
/**
|
|
@@ -36003,13 +36420,13 @@ var CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend({
|
|
|
36003
36420
|
* declares ClientCapabilities.sampling.context. These values may be removed in future spec releases.
|
|
36004
36421
|
*/
|
|
36005
36422
|
includeContext: _enum2(["none", "thisServer", "allServers"]).optional(),
|
|
36006
|
-
temperature:
|
|
36423
|
+
temperature: number3().optional(),
|
|
36007
36424
|
/**
|
|
36008
36425
|
* The requested maximum number of tokens to sample (to prevent runaway completions).
|
|
36009
36426
|
*
|
|
36010
36427
|
* The client MAY choose to sample fewer tokens than the requested maximum.
|
|
36011
36428
|
*/
|
|
36012
|
-
maxTokens:
|
|
36429
|
+
maxTokens: number3().int(),
|
|
36013
36430
|
stopSequences: array(string2()).optional(),
|
|
36014
36431
|
/**
|
|
36015
36432
|
* Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.
|
|
@@ -36086,8 +36503,8 @@ var StringSchemaSchema = object2({
|
|
|
36086
36503
|
type: literal("string"),
|
|
36087
36504
|
title: string2().optional(),
|
|
36088
36505
|
description: string2().optional(),
|
|
36089
|
-
minLength:
|
|
36090
|
-
maxLength:
|
|
36506
|
+
minLength: number3().optional(),
|
|
36507
|
+
maxLength: number3().optional(),
|
|
36091
36508
|
format: _enum2(["email", "uri", "date", "date-time"]).optional(),
|
|
36092
36509
|
default: string2().optional()
|
|
36093
36510
|
});
|
|
@@ -36095,9 +36512,9 @@ var NumberSchemaSchema = object2({
|
|
|
36095
36512
|
type: _enum2(["number", "integer"]),
|
|
36096
36513
|
title: string2().optional(),
|
|
36097
36514
|
description: string2().optional(),
|
|
36098
|
-
minimum:
|
|
36099
|
-
maximum:
|
|
36100
|
-
default:
|
|
36515
|
+
minimum: number3().optional(),
|
|
36516
|
+
maximum: number3().optional(),
|
|
36517
|
+
default: number3().optional()
|
|
36101
36518
|
});
|
|
36102
36519
|
var UntitledSingleSelectEnumSchemaSchema = object2({
|
|
36103
36520
|
type: literal("string"),
|
|
@@ -36129,8 +36546,8 @@ var UntitledMultiSelectEnumSchemaSchema = object2({
|
|
|
36129
36546
|
type: literal("array"),
|
|
36130
36547
|
title: string2().optional(),
|
|
36131
36548
|
description: string2().optional(),
|
|
36132
|
-
minItems:
|
|
36133
|
-
maxItems:
|
|
36549
|
+
minItems: number3().optional(),
|
|
36550
|
+
maxItems: number3().optional(),
|
|
36134
36551
|
items: object2({
|
|
36135
36552
|
type: literal("string"),
|
|
36136
36553
|
enum: array(string2())
|
|
@@ -36141,8 +36558,8 @@ var TitledMultiSelectEnumSchemaSchema = object2({
|
|
|
36141
36558
|
type: literal("array"),
|
|
36142
36559
|
title: string2().optional(),
|
|
36143
36560
|
description: string2().optional(),
|
|
36144
|
-
minItems:
|
|
36145
|
-
maxItems:
|
|
36561
|
+
minItems: number3().optional(),
|
|
36562
|
+
maxItems: number3().optional(),
|
|
36146
36563
|
items: object2({
|
|
36147
36564
|
anyOf: array(object2({
|
|
36148
36565
|
const: string2(),
|
|
@@ -36223,7 +36640,7 @@ var ElicitResultSchema = ResultSchema.extend({
|
|
|
36223
36640
|
* Per MCP spec, content is "typically omitted" for decline/cancel actions.
|
|
36224
36641
|
* We normalize null to undefined for leniency while maintaining type compatibility.
|
|
36225
36642
|
*/
|
|
36226
|
-
content: preprocess((val) => val === null ? void 0 : val, record(string2(), union([string2(),
|
|
36643
|
+
content: preprocess((val) => val === null ? void 0 : val, record(string2(), union([string2(), number3(), boolean2(), array(string2())])).optional())
|
|
36227
36644
|
});
|
|
36228
36645
|
var ResourceTemplateReferenceSchema = object2({
|
|
36229
36646
|
type: literal("ref/resource"),
|
|
@@ -36286,7 +36703,7 @@ var CompleteResultSchema = ResultSchema.extend({
|
|
|
36286
36703
|
/**
|
|
36287
36704
|
* The total number of completion options available. This can exceed the number of values actually sent in the response.
|
|
36288
36705
|
*/
|
|
36289
|
-
total: optional(
|
|
36706
|
+
total: optional(number3().int()),
|
|
36290
36707
|
/**
|
|
36291
36708
|
* Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
|
|
36292
36709
|
*/
|
|
@@ -40384,21 +40801,31 @@ var StdioServerTransport = class {
|
|
|
40384
40801
|
function createCapsaServer(options) {
|
|
40385
40802
|
const embedder = options.embedder ?? ollamaEmbedder();
|
|
40386
40803
|
const server = new McpServer({ name: "capsa", version: "0.0.1" });
|
|
40804
|
+
const ensureFresh = freshener(options, embedder);
|
|
40387
40805
|
server.registerTool("get_context", {
|
|
40388
40806
|
title: "Get project context",
|
|
40389
|
-
description:
|
|
40807
|
+
description: 'Return the smallest set of project knowledge (runbooks, tickets, plans, decisions, instruction files) relevant to a task. Call this before starting work on a task; pass the task as you understand it. Answers are markdown: each passage has a `###` heading saying where it is from and an italic line under it. For a chapter of an indexed book that line is its citation \u2014 `_<book>, <author>, Ch. <n> "<title>", pp. <from>\u2013<to>_` \u2014 so quote the book, the chapter and the printed pages when you use the passage, rather than the file it was read from.',
|
|
40390
40808
|
inputSchema: {
|
|
40391
40809
|
task: external_exports.string().describe("The task or question, in one or two sentences"),
|
|
40392
|
-
|
|
40810
|
+
// 3000 is a ceiling on a ceiling: retrieval stops at the relevance
|
|
40811
|
+
// cliff long before it fills a budget, so a larger number only ever
|
|
40812
|
+
// buys a longer tail of near-misses.
|
|
40813
|
+
maxTokens: external_exports.number().int().min(200).max(3e3).optional().describe("Token budget, default 1500. Raise it when this project is a library of reference documents rather than a codebase: a book section runs several hundred tokens, so 1500 is a handful of them.")
|
|
40393
40814
|
}
|
|
40394
40815
|
}, async ({ task, maxTokens }) => {
|
|
40816
|
+
const freshness = await ensureFresh();
|
|
40395
40817
|
const store = openStore(options.projectRoot, embedder);
|
|
40396
40818
|
try {
|
|
40397
|
-
const result = await retrieve(store, embedder, task, {
|
|
40819
|
+
const result = await retrieve(store, embedder, task, {
|
|
40820
|
+
maxTokens,
|
|
40821
|
+
source: "mcp:get_context",
|
|
40822
|
+
excludeRootInstructions: true
|
|
40823
|
+
});
|
|
40824
|
+
const caveat = caveats(freshness, result.degraded);
|
|
40398
40825
|
const footer = `
|
|
40399
40826
|
|
|
40400
40827
|
---
|
|
40401
|
-
_capsa: ${result.chunks.length} chunks, ~${result.tokensEst} tokens, ${result.durationMs}
|
|
40828
|
+
_capsa: ${result.chunks.length} chunks, ~${result.tokensEst} tokens, ${result.durationMs} ms${caveat ? ` \xB7 ${caveat}` : ""}_`;
|
|
40402
40829
|
return { content: [{ type: "text", text: formatContext(result) + footer }] };
|
|
40403
40830
|
} finally {
|
|
40404
40831
|
store.close();
|
|
@@ -40409,9 +40836,14 @@ _capsa: ${result.chunks.length} chunks, ~${result.tokensEst} tokens, ${result.du
|
|
|
40409
40836
|
description: "Where the project stands: open tickets and plans, recent decisions, recently changed knowledge.",
|
|
40410
40837
|
inputSchema: {}
|
|
40411
40838
|
}, async () => {
|
|
40839
|
+
const freshness = await ensureFresh();
|
|
40412
40840
|
const store = openStore(options.projectRoot, embedder);
|
|
40413
40841
|
try {
|
|
40414
|
-
|
|
40842
|
+
const caveat = caveats(freshness);
|
|
40843
|
+
const text2 = formatState(projectState(store)) + (caveat ? `
|
|
40844
|
+
|
|
40845
|
+
_${caveat}_` : "");
|
|
40846
|
+
return { content: [{ type: "text", text: text2 }] };
|
|
40415
40847
|
} finally {
|
|
40416
40848
|
store.close();
|
|
40417
40849
|
}
|
|
@@ -40440,6 +40872,41 @@ _capsa: ${result.chunks.length} chunks, ~${result.tokensEst} tokens, ${result.du
|
|
|
40440
40872
|
});
|
|
40441
40873
|
return server;
|
|
40442
40874
|
}
|
|
40875
|
+
function freshener(options, embedder) {
|
|
40876
|
+
const maxAgeMs = options.maxIndexAgeMs ?? DEFAULT_MAX_INDEX_AGE_MS;
|
|
40877
|
+
let inFlight = null;
|
|
40878
|
+
return () => {
|
|
40879
|
+
if (!inFlight) {
|
|
40880
|
+
const run = (async () => {
|
|
40881
|
+
let store;
|
|
40882
|
+
try {
|
|
40883
|
+
store = openStore(options.projectRoot, embedder);
|
|
40884
|
+
return await freshenIndex(store, options.projectRoot, embedder, { maxAgeMs });
|
|
40885
|
+
} catch (err) {
|
|
40886
|
+
return { refreshed: false, stale: true, reason: err instanceof Error ? err.message : String(err) };
|
|
40887
|
+
} finally {
|
|
40888
|
+
store?.close();
|
|
40889
|
+
}
|
|
40890
|
+
})();
|
|
40891
|
+
inFlight = run;
|
|
40892
|
+
void run.finally(() => {
|
|
40893
|
+
if (inFlight === run)
|
|
40894
|
+
inFlight = null;
|
|
40895
|
+
});
|
|
40896
|
+
}
|
|
40897
|
+
return inFlight;
|
|
40898
|
+
};
|
|
40899
|
+
}
|
|
40900
|
+
function caveats(freshness, degraded) {
|
|
40901
|
+
const parts = [];
|
|
40902
|
+
if (freshness.stale) {
|
|
40903
|
+
parts.push(`index may be out of date \u2014 ${freshness.reason ?? "the last index run did not finish"}`);
|
|
40904
|
+
}
|
|
40905
|
+
if (degraded) {
|
|
40906
|
+
parts.push(freshness.stale ? "keyword-only search (no embeddings)" : `keyword-only search \u2014 ${degraded}`);
|
|
40907
|
+
}
|
|
40908
|
+
return parts.join(" \xB7 ");
|
|
40909
|
+
}
|
|
40443
40910
|
async function serveStdio(options) {
|
|
40444
40911
|
const server = createCapsaServer(options);
|
|
40445
40912
|
await server.connect(new StdioServerTransport());
|
|
@@ -40516,6 +40983,8 @@ async function runSearchCommand(args) {
|
|
|
40516
40983
|
} else {
|
|
40517
40984
|
console.log(formatContext(res));
|
|
40518
40985
|
}
|
|
40986
|
+
if (res.degraded) console.error(`
|
|
40987
|
+
capsa: keyword-only search \u2014 ${res.degraded}`);
|
|
40519
40988
|
console.error(`
|
|
40520
40989
|
${res.chunks.length} chunks, ~${res.tokensEst} tokens, ${res.durationMs} ms`);
|
|
40521
40990
|
} finally {
|
|
@@ -40569,8 +41038,8 @@ agent explicitly records.
|
|
|
40569
41038
|
`;
|
|
40570
41039
|
async function readVersion() {
|
|
40571
41040
|
try {
|
|
40572
|
-
const pkgPath = resolve4(
|
|
40573
|
-
const pkg = JSON.parse(await
|
|
41041
|
+
const pkgPath = resolve4(dirname10(fileURLToPath2(import.meta.url)), "../package.json");
|
|
41042
|
+
const pkg = JSON.parse(await readFile8(pkgPath, "utf8"));
|
|
40574
41043
|
const version2 = pkg.version;
|
|
40575
41044
|
return typeof version2 === "string" ? version2 : "unknown";
|
|
40576
41045
|
} catch {
|