@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 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.23.0/hraness-wordcell-0.23.0.tgz
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.23.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.23.0 --skill wordcell
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 and
273
- SciFact results beside the published results of Oh, the embedded memory
274
- framework, each with its source data and limits. Oh's scores measure its own
275
- memory-retrieval path, not a Wordcell vault.
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.23.0/hraness-wordcell-0.23.0.tgz
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-vtwbapjr.js";
9
+ } from "./index-44kw9tr8.js";
10
10
  import"./index-0eacgvpv.js";
11
11
  import"./index-qfqjfxaa.js";
12
- import"./index-r4qs6vq4.js";
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-mnq2wy34.js";
32
- import"./index-tvd2sqrx.js";
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-c8hy7qfa.js");
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-mnq2wy34.js";
8
- import"./index-tvd2sqrx.js";
7
+ } from "./index-mvs6f7ne.js";
8
+ import"./index-5d1rd5v9.js";
9
9
  import"./index-adx6khj5.js";
10
10
  import {
11
11
  indexSemanticVault,
@@ -4,8 +4,8 @@ import {
4
4
  knowledgeBaseEvaluationRetrieverIds,
5
5
  openKnowledgeBaseEvaluation,
6
6
  verifyFrozenEvaluationSnapshot
7
- } from "./index-mnq2wy34.js";
8
- import"./index-tvd2sqrx.js";
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";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-h170byqw.js";
5
5
  import {
6
6
  packUntrustedSearchContext
7
- } from "./index-tvd2sqrx.js";
7
+ } from "./index-5d1rd5v9.js";
8
8
 
9
9
  // src/workflows/decision-context.ts
10
10
  var decisionContextWorkflow = defineWorkflow({
@@ -13,7 +13,7 @@ import {
13
13
  loadPortfolioRegistry,
14
14
  openKnowledgePortfolio,
15
15
  snapshotPortfolioRegistry
16
- } from "./index-r4qs6vq4.js";
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-mnq2wy34.js";
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-tvd2sqrx.js";
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
- if (hit.snippet !== "")
6319
- lines.push(` ${safe(hit.snippet)}`);
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
- output.stdout(command.json ? terminalSafeJson(result) : sanitizeTerminalText(renderKnowledgeBaseSearch(result)));
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
  `);