@hraness/wordcell 0.23.0 → 0.24.0
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 +19 -8
- package/dist/cli.js +5 -5
- package/dist/evaluation-builder.js +2 -2
- package/dist/evaluation-kb.js +2 -2
- package/dist/{index-p70t8j13.js → index-1w0sp1q7.js} +1 -1
- package/dist/{index-vtwbapjr.js → index-44kw9tr8.js} +61 -7
- package/dist/{index-tvd2sqrx.js → index-5d1rd5v9.js} +437 -2
- package/dist/{index-r4qs6vq4.js → index-f184qyh1.js} +1 -1
- package/dist/{index-mnq2wy34.js → index-mvs6f7ne.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/portfolio.js +2 -2
- package/dist/sdk.js +1 -1
- package/dist/{support-c8hy7qfa.js → support-1yhv6vm6.js} +4 -4
- package/dist/workflows/decision-context.js +2 -2
- package/dist/workflows/index.js +2 -2
- package/package.json +4 -3
- package/skills/wordcell/SKILL.md +1 -1
- package/skills/wordcell/references/query.md +17 -3
- package/src/cli-help.ts +1 -0
- package/src/cli-program.ts +41 -3
- package/src/mcp-tools.ts +17 -1
- package/src/sdk.ts +149 -1
- package/src/selected-passage.ts +409 -0
package/README.md
CHANGED
|
@@ -52,14 +52,14 @@ Wordcell keeps the record in Markdown files you own, rebuilds every index from t
|
|
|
52
52
|
The CLI and TypeScript SDK run with Bun. Install the versioned GitHub archive:
|
|
53
53
|
|
|
54
54
|
```sh
|
|
55
|
-
bun add --global --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.
|
|
55
|
+
bun add --global --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz
|
|
56
56
|
wordcell --help
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Prefer npm? The same release is [mirrored there](https://www.npmjs.com/package/@hraness/wordcell):
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
|
-
npm install --global --ignore-scripts @hraness/wordcell@0.
|
|
62
|
+
npm install --global --ignore-scripts @hraness/wordcell@0.24.0
|
|
63
63
|
wordcell --help
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -101,7 +101,7 @@ After trying the CLI, install the public Agent Skill into a compatible agent,
|
|
|
101
101
|
such as Claude Code, Codex, Cursor, or GitHub Copilot:
|
|
102
102
|
|
|
103
103
|
```sh
|
|
104
|
-
bunx skills add hraness/wordcell#v0.
|
|
104
|
+
bunx skills add hraness/wordcell#v0.24.0 --skill wordcell
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
Then ask:
|
|
@@ -247,6 +247,14 @@ keeps one vault current on several machines.
|
|
|
247
247
|
|
|
248
248
|
## Evidence and comparisons
|
|
249
249
|
|
|
250
|
+
On eight questions sealed before the passage selector was finished,
|
|
251
|
+
`wordcell search --selected-passage` returned an excerpt holding the labeled
|
|
252
|
+
answer for **6 of 8**, versus **1 of 8** for the older search snippets, at the
|
|
253
|
+
same 512-byte limit and from the same retrieved notes. It runs locally with no
|
|
254
|
+
model. The corpus is small and consists of Wordcell's own notes; containing the
|
|
255
|
+
answer is not the same as an agent answering correctly.
|
|
256
|
+
[Method, raw results, and reproduction](https://github.com/hraness/wordcell/blob/main/docs/evidence.md#measure-whether-excerpts-contain-the-answer).
|
|
257
|
+
|
|
250
258
|
In a four-query example over a seven-note public vault, packed search snippets
|
|
251
259
|
used **80% fewer UTF-8 bytes** than passing the same matching notes in full:
|
|
252
260
|
12,126 versus 60,584 bytes. This measures context payload size, not tokenizer
|
|
@@ -269,10 +277,13 @@ covers Markdown alone, QMD, Basic Memory, Obsidian, static publishing tools,
|
|
|
269
277
|
Supermemory, Mem0, and Zep using their own documentation. Choose the smallest
|
|
270
278
|
workflow that meets your needs.
|
|
271
279
|
|
|
272
|
-
The [benchmarks page](https://wordcell.io/benchmarks) shows the payload
|
|
273
|
-
SciFact results beside the published results of Oh, the embedded memory
|
|
274
|
-
framework, each with its source data and limits.
|
|
275
|
-
|
|
280
|
+
The [benchmarks page](https://wordcell.io/benchmarks) shows the excerpt, payload,
|
|
281
|
+
and SciFact results beside the published results of Oh, the embedded memory
|
|
282
|
+
framework, each with its source data and limits. On all 500 LongMemEval-S
|
|
283
|
+
questions, Oh semantic retrieval scored 88.87% and BM25 86.13% with the same
|
|
284
|
+
reader and budget; on the measure Oh named before the run, its interval does
|
|
285
|
+
not rule out a tie. Oh's scores measure its own memory-retrieval path, not a
|
|
286
|
+
Wordcell vault.
|
|
276
287
|
|
|
277
288
|
## How the files fit together
|
|
278
289
|
|
|
@@ -321,7 +332,7 @@ exist. [Graph queries and proof limits](https://github.com/hraness/wordcell/blob
|
|
|
321
332
|
Add the same immutable release to a Bun project:
|
|
322
333
|
|
|
323
334
|
```sh
|
|
324
|
-
bun add --exact --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.
|
|
335
|
+
bun add --exact --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz
|
|
325
336
|
```
|
|
326
337
|
|
|
327
338
|
The SDK provides read-only vault sessions, metadata queries, search, graph
|
package/dist/cli.js
CHANGED
|
@@ -6,10 +6,10 @@ import {
|
|
|
6
6
|
parseArguments,
|
|
7
7
|
runExecutable,
|
|
8
8
|
usage
|
|
9
|
-
} from "./index-
|
|
9
|
+
} from "./index-44kw9tr8.js";
|
|
10
10
|
import"./index-0eacgvpv.js";
|
|
11
11
|
import"./index-qfqjfxaa.js";
|
|
12
|
-
import"./index-
|
|
12
|
+
import"./index-f184qyh1.js";
|
|
13
13
|
import"./index-j4zgmzjr.js";
|
|
14
14
|
import"./index-2fmnttca.js";
|
|
15
15
|
import"./index-6jcz0m1c.js";
|
|
@@ -28,8 +28,8 @@ import"./index-mxxxytys.js";
|
|
|
28
28
|
import"./index-23z4zxgg.js";
|
|
29
29
|
import"./index-pj501bh1.js";
|
|
30
30
|
import"./index-9rf81m0p.js";
|
|
31
|
-
import"./index-
|
|
32
|
-
import"./index-
|
|
31
|
+
import"./index-mvs6f7ne.js";
|
|
32
|
+
import"./index-5d1rd5v9.js";
|
|
33
33
|
import"./index-adx6khj5.js";
|
|
34
34
|
import"./index-fp732bgg.js";
|
|
35
35
|
import"./index-4j3tt0c3.js";
|
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
|
|
63
63
|
// src/cli.ts
|
|
64
64
|
if (import.meta.main) {
|
|
65
|
-
const { standaloneSupportEnvironment, isUsefulSupportResult, runProductSupportCommand, showProductSupportInvitation } = await import("./support-
|
|
65
|
+
const { standaloneSupportEnvironment, isUsefulSupportResult, runProductSupportCommand, showProductSupportInvitation } = await import("./support-1yhv6vm6.js");
|
|
66
66
|
const env = standaloneSupportEnvironment();
|
|
67
67
|
const args = process.argv.slice(2);
|
|
68
68
|
if (args[0] === "support") {
|
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
knowledgeBaseEvaluationRetrieverIds,
|
|
5
5
|
openKnowledgeBaseEvaluation,
|
|
6
6
|
verifyFrozenEvaluationSnapshot
|
|
7
|
-
} from "./index-
|
|
8
|
-
import"./index-
|
|
7
|
+
} from "./index-mvs6f7ne.js";
|
|
8
|
+
import"./index-5d1rd5v9.js";
|
|
9
9
|
import"./index-adx6khj5.js";
|
|
10
10
|
import {
|
|
11
11
|
indexSemanticVault,
|
package/dist/evaluation-kb.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
knowledgeBaseEvaluationRetrieverIds,
|
|
5
5
|
openKnowledgeBaseEvaluation,
|
|
6
6
|
verifyFrozenEvaluationSnapshot
|
|
7
|
-
} from "./index-
|
|
8
|
-
import"./index-
|
|
7
|
+
} from "./index-mvs6f7ne.js";
|
|
8
|
+
import"./index-5d1rd5v9.js";
|
|
9
9
|
import"./index-adx6khj5.js";
|
|
10
10
|
import"./index-fp732bgg.js";
|
|
11
11
|
import"./index-4j3tt0c3.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
loadPortfolioRegistry,
|
|
14
14
|
openKnowledgePortfolio,
|
|
15
15
|
snapshotPortfolioRegistry
|
|
16
|
-
} from "./index-
|
|
16
|
+
} from "./index-f184qyh1.js";
|
|
17
17
|
import {
|
|
18
18
|
diffCaptureBundle
|
|
19
19
|
} from "./index-j4zgmzjr.js";
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
import {
|
|
52
52
|
knowledgeBaseEvaluationRetrieverIds,
|
|
53
53
|
openKnowledgeBaseEvaluation
|
|
54
|
-
} from "./index-
|
|
54
|
+
} from "./index-mvs6f7ne.js";
|
|
55
55
|
import {
|
|
56
56
|
DEFAULT_SEARCH_RESULTS,
|
|
57
57
|
MAX_SEARCH_CANDIDATES,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
MAX_SEARCH_RESULTS,
|
|
61
61
|
openKnowledgeBase,
|
|
62
62
|
searchEvidenceRank
|
|
63
|
-
} from "./index-
|
|
63
|
+
} from "./index-5d1rd5v9.js";
|
|
64
64
|
import {
|
|
65
65
|
MAX_SEARCH_RULE_CONFIG_BYTES,
|
|
66
66
|
parseSearchRules
|
|
@@ -932,6 +932,7 @@ var commandHelp = [
|
|
|
932
932
|
["--min-score <score>", "Drop results scoring below this (0 to 1)"],
|
|
933
933
|
["--rerank <typesafe>", "Rerank results with the hosted TypeSafe service"],
|
|
934
934
|
["--rerank-limit <2..25>", "How many results the reranker sees"],
|
|
935
|
+
["--selected-passage", "Add a local source excerpt to each result"],
|
|
935
936
|
JSON_OPTION
|
|
936
937
|
],
|
|
937
938
|
examples: ['wordcell search "retries" --root kb --mode exact', 'wordcell search "retry policy" --root kb --limit 5']
|
|
@@ -3624,6 +3625,15 @@ function requiredText(arguments_, key, maxBytes) {
|
|
|
3624
3625
|
function optionalText(arguments_, key, maxBytes) {
|
|
3625
3626
|
return has(arguments_, key) ? checkedText(arguments_[key], key, maxBytes) : undefined;
|
|
3626
3627
|
}
|
|
3628
|
+
function optionalBoolean(arguments_, key) {
|
|
3629
|
+
if (!has(arguments_, key))
|
|
3630
|
+
return;
|
|
3631
|
+
const value = arguments_[key];
|
|
3632
|
+
if (typeof value !== "boolean") {
|
|
3633
|
+
throw new ToolArgumentError(`Invalid argument "${key}": expected a boolean.`);
|
|
3634
|
+
}
|
|
3635
|
+
return value;
|
|
3636
|
+
}
|
|
3627
3637
|
function optionalInteger(arguments_, key, minimum, maximum) {
|
|
3628
3638
|
if (!has(arguments_, key))
|
|
3629
3639
|
return;
|
|
@@ -3923,6 +3933,11 @@ function readTools(root, sessions) {
|
|
|
3923
3933
|
properties: {
|
|
3924
3934
|
query: { type: "string", minLength: 1, maxLength: MAX_QUERY_TEXT_UTF8_BYTES },
|
|
3925
3935
|
mode: { type: "string", enum: SEARCH_MODES, default: "hybrid" },
|
|
3936
|
+
selectedPassage: {
|
|
3937
|
+
type: "boolean",
|
|
3938
|
+
default: false,
|
|
3939
|
+
description: "Include a local source excerpt with line references and section headings. Ranking is unchanged."
|
|
3940
|
+
},
|
|
3926
3941
|
limit: { type: "integer", minimum: 1, maximum: MAX_SEARCH_RESULTS, default: DEFAULT_SEARCH_RESULTS },
|
|
3927
3942
|
...filterProperties
|
|
3928
3943
|
}
|
|
@@ -3930,10 +3945,11 @@ function readTools(root, sessions) {
|
|
|
3930
3945
|
annotations: { title: "Search notes", ...READ_ONLY }
|
|
3931
3946
|
},
|
|
3932
3947
|
handler: async (arguments_) => {
|
|
3933
|
-
allowOnly(arguments_, ["query", "mode", "limit", "tags", "where", "has", "scope"]);
|
|
3948
|
+
allowOnly(arguments_, ["query", "mode", "limit", "tags", "where", "has", "scope", "selectedPassage"]);
|
|
3934
3949
|
const query = requiredText(arguments_, "query", MAX_QUERY_TEXT_UTF8_BYTES);
|
|
3935
3950
|
const mode = optionalChoice(arguments_, "mode", SEARCH_MODES) ?? "hybrid";
|
|
3936
3951
|
const limit = optionalInteger(arguments_, "limit", 1, MAX_SEARCH_RESULTS) ?? DEFAULT_SEARCH_RESULTS;
|
|
3952
|
+
const selectedPassage = optionalBoolean(arguments_, "selectedPassage");
|
|
3937
3953
|
const filters = metadataFilters(arguments_);
|
|
3938
3954
|
const tags = optionalTextList(arguments_, "tags", MAX_QUERY_TAGS, MAX_QUERY_TEXT_UTF8_BYTES);
|
|
3939
3955
|
const scopes = optionalTextList(arguments_, "scope", MAX_REPOSITORY_SCOPES, MAX_QUERY_TEXT_UTF8_BYTES);
|
|
@@ -3942,6 +3958,7 @@ function readTools(root, sessions) {
|
|
|
3942
3958
|
query,
|
|
3943
3959
|
mode,
|
|
3944
3960
|
limit,
|
|
3961
|
+
...selectedPassage === undefined ? {} : { selectedPassage },
|
|
3945
3962
|
...filters.length === 0 ? {} : { filters },
|
|
3946
3963
|
...tags === undefined ? {} : { tags },
|
|
3947
3964
|
...scopes === undefined ? {} : { repositoryScopes: scopes }
|
|
@@ -5151,6 +5168,7 @@ function parseSemanticCommand(command, arguments_) {
|
|
|
5151
5168
|
let minScore;
|
|
5152
5169
|
let rerank;
|
|
5153
5170
|
let rerankLimit;
|
|
5171
|
+
let selectedPassage = false;
|
|
5154
5172
|
let graphDepth;
|
|
5155
5173
|
let noGraph = false;
|
|
5156
5174
|
let history = false;
|
|
@@ -5177,6 +5195,10 @@ function parseSemanticCommand(command, arguments_) {
|
|
|
5177
5195
|
noGraph = true;
|
|
5178
5196
|
continue;
|
|
5179
5197
|
}
|
|
5198
|
+
if (argument === "--selected-passage" && command === "search") {
|
|
5199
|
+
selectedPassage = true;
|
|
5200
|
+
continue;
|
|
5201
|
+
}
|
|
5180
5202
|
if (argument === "--no-history" && command === "search") {
|
|
5181
5203
|
noHistory = true;
|
|
5182
5204
|
continue;
|
|
@@ -5402,6 +5424,7 @@ function parseSemanticCommand(command, arguments_) {
|
|
|
5402
5424
|
...minScore === undefined ? {} : { minScore },
|
|
5403
5425
|
...rerank === undefined ? {} : { rerank },
|
|
5404
5426
|
...rerankLimit === undefined ? {} : { rerankLimit },
|
|
5427
|
+
...selectedPassage ? { selectedPassage: true } : {},
|
|
5405
5428
|
query,
|
|
5406
5429
|
json
|
|
5407
5430
|
}
|
|
@@ -6293,6 +6316,22 @@ function renderSemanticIndex(result) {
|
|
|
6293
6316
|
].join(`
|
|
6294
6317
|
`);
|
|
6295
6318
|
}
|
|
6319
|
+
function searchForDisplay(result) {
|
|
6320
|
+
return {
|
|
6321
|
+
...result,
|
|
6322
|
+
results: result.results.map((hit) => {
|
|
6323
|
+
const passage = hit.selectedPassage;
|
|
6324
|
+
if (passage?.status !== "selected")
|
|
6325
|
+
return hit;
|
|
6326
|
+
const texts = [passage.text, ...passage.headings.map(({ text }) => text)];
|
|
6327
|
+
if (texts.every((text) => sanitizeTerminalText(redactSensitiveText(text)) === text.replaceAll(`\r
|
|
6328
|
+
`, `
|
|
6329
|
+
`).replaceAll("\t", " ")))
|
|
6330
|
+
return hit;
|
|
6331
|
+
return { ...hit, selectedPassage: { status: "unavailable", reason: "display-sanitized" } };
|
|
6332
|
+
})
|
|
6333
|
+
};
|
|
6334
|
+
}
|
|
6296
6335
|
function renderKnowledgeBaseSearch(result) {
|
|
6297
6336
|
const lines = [
|
|
6298
6337
|
`${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for \u201C${safe(result.query)}\u201D (${result.results.length})${result.partial ? " [partial]" : ""}`
|
|
@@ -6315,8 +6354,21 @@ function renderKnowledgeBaseSearch(result) {
|
|
|
6315
6354
|
const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
|
|
6316
6355
|
const evidence = hit.evidence.map((item) => `${item.kind}#${searchEvidenceRank(item)}`).join(", ");
|
|
6317
6356
|
lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} \u2014 ${safe(hit.title)} [${safe(evidence)}]`);
|
|
6318
|
-
|
|
6319
|
-
|
|
6357
|
+
const passage = hit.selectedPassage;
|
|
6358
|
+
if (passage?.status === "selected") {
|
|
6359
|
+
lines.push(` Selected passage: ${safe(hit.path)}:${passage.startLine}-${passage.endLine}` + (passage.clippedStart || passage.clippedEnd ? " (clipped excerpt)" : ""));
|
|
6360
|
+
for (const heading of passage.headings) {
|
|
6361
|
+
lines.push(` Section: ${safe(heading.text)}${heading.truncated ? " (clipped)" : ""}`);
|
|
6362
|
+
}
|
|
6363
|
+
for (const line of passage.text.split(/\r?\n/u))
|
|
6364
|
+
lines.push(` ${safe(line)}`);
|
|
6365
|
+
} else {
|
|
6366
|
+
if (hit.snippet !== "")
|
|
6367
|
+
lines.push(` ${safe(hit.snippet)}`);
|
|
6368
|
+
if (passage !== undefined) {
|
|
6369
|
+
lines.push(` Selected passage: ${passage.status} (${safe(passage.reason)}); showing the search snippet.`);
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6320
6372
|
}
|
|
6321
6373
|
if ((result.graph?.related.length ?? 0) > 0) {
|
|
6322
6374
|
lines.push(` Related graph context: ${result.graph?.related.length ?? 0}`);
|
|
@@ -6369,6 +6421,7 @@ async function runSemantic(command, output, dependencies) {
|
|
|
6369
6421
|
...command.limit === undefined ? {} : { limit: command.limit },
|
|
6370
6422
|
...command.candidateLimit === undefined ? {} : { candidateLimit: command.candidateLimit },
|
|
6371
6423
|
...command.minScore === undefined ? {} : { minScore: command.minScore },
|
|
6424
|
+
...command.selectedPassage === undefined ? {} : { selectedPassage: command.selectedPassage },
|
|
6372
6425
|
...command.rerank === undefined ? {} : {
|
|
6373
6426
|
rerank: {
|
|
6374
6427
|
engine: command.rerank,
|
|
@@ -6376,7 +6429,8 @@ async function runSemantic(command, output, dependencies) {
|
|
|
6376
6429
|
}
|
|
6377
6430
|
}
|
|
6378
6431
|
});
|
|
6379
|
-
|
|
6432
|
+
const display = searchForDisplay(result);
|
|
6433
|
+
output.stdout(command.json ? terminalSafeJson(display) : sanitizeTerminalText(renderKnowledgeBaseSearch(display)));
|
|
6380
6434
|
if (withoutIndex && person) {
|
|
6381
6435
|
output.stderr("Searched without an index, so only exact words matched. " + `For meaning-based search, run wordcell index --root ${safe(shellWord(command.root))} ` + `(downloads about 300 MB once).
|
|
6382
6436
|
`);
|