@hraness/wordcell 0.23.0 → 0.24.1

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.
Files changed (45) hide show
  1. package/README.md +29 -16
  2. package/dist/authoring.js +2 -1
  3. package/dist/cli.js +10 -9
  4. package/dist/clip/jobs.js +5 -1
  5. package/dist/evaluation-builder.js +5 -4
  6. package/dist/evaluation-kb.js +5 -4
  7. package/dist/graph-authority.js +2 -1
  8. package/dist/graph-percolation.js +3 -2
  9. package/dist/{index-1er88ckw.js → index-3gc2yk4k.js} +10 -5
  10. package/dist/{index-mnq2wy34.js → index-59bcxqee.js} +1 -1
  11. package/dist/{index-p70t8j13.js → index-7drercgk.js} +1 -1
  12. package/dist/{index-tcaq1c7f.js → index-e26mdwxz.js} +1 -1
  13. package/dist/{index-r4qs6vq4.js → index-hz2wyggk.js} +1 -1
  14. package/dist/{index-vtwbapjr.js → index-jqkkb4fr.js} +72 -12
  15. package/dist/{index-tvd2sqrx.js → index-mcpppwbq.js} +439 -4
  16. package/dist/index-qkesh4c6.js +7 -0
  17. package/dist/{index-b0khj0h5.js → index-qt2zwza8.js} +6 -1
  18. package/dist/{index-0eacgvpv.js → index-t0fh8a1q.js} +7 -3
  19. package/dist/index.js +6 -5
  20. package/dist/portfolio.js +5 -4
  21. package/dist/sdk.js +4 -3
  22. package/dist/{support-c8hy7qfa.js → support-07jpt1z4.js} +9 -8
  23. package/dist/url-intelligence.js +2 -1
  24. package/dist/workflows/decision-context.js +5 -4
  25. package/dist/workflows/index.js +5 -4
  26. package/package.json +12 -8
  27. package/skills/wordcell/SKILL.md +1 -1
  28. package/skills/wordcell/references/publish.md +2 -1
  29. package/skills/wordcell/references/query.md +17 -3
  30. package/src/authoring-platform.ts +3 -0
  31. package/src/cli-help.ts +1 -0
  32. package/src/cli-program.ts +41 -3
  33. package/src/clip/jobs.ts +2 -1
  34. package/src/clip/metadata-search-tool/Cargo.lock +2 -0
  35. package/src/clip/metadata-search-tool/Cargo.toml +2 -0
  36. package/src/clip/metadata-search-tool/runner.ts +4 -1
  37. package/src/clip/metadata-search-tool/src/duckduckgo.rs +534 -0
  38. package/src/clip/metadata-search-tool/src/main.rs +31 -18
  39. package/src/clip/metadata-search.ts +4 -2
  40. package/src/clip/url-metadata.ts +2 -1
  41. package/src/directory-sync.ts +9 -0
  42. package/src/graph-authority.ts +5 -2
  43. package/src/mcp-tools.ts +17 -1
  44. package/src/sdk.ts +149 -1
  45. package/src/selected-passage.ts +409 -0
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  parseArguments as parseArguments2,
4
4
  parseUrlMetadataArguments
5
- } from "./index-vtwbapjr.js";
6
- import"./index-0eacgvpv.js";
5
+ } from "./index-jqkkb4fr.js";
6
+ import"./index-t0fh8a1q.js";
7
7
  import {
8
8
  parsePdfArguments
9
9
  } from "./index-qfqjfxaa.js";
10
- import"./index-r4qs6vq4.js";
10
+ import"./index-hz2wyggk.js";
11
11
  import"./index-j4zgmzjr.js";
12
12
  import"./index-2fmnttca.js";
13
13
  import"./index-6jcz0m1c.js";
@@ -28,16 +28,16 @@ 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-59bcxqee.js";
32
+ import"./index-mcpppwbq.js";
33
33
  import"./index-adx6khj5.js";
34
34
  import"./index-fp732bgg.js";
35
35
  import"./index-4j3tt0c3.js";
36
36
  import"./index-j70m75wd.js";
37
37
  import"./index-b88v3vtm.js";
38
- import"./index-tcaq1c7f.js";
38
+ import"./index-e26mdwxz.js";
39
39
  import"./index-bdwcjvr4.js";
40
- import"./index-1er88ckw.js";
40
+ import"./index-3gc2yk4k.js";
41
41
  import"./index-pgtm2nhf.js";
42
42
  import"./index-1gwbassd.js";
43
43
  import"./index-ahyhryb8.js";
@@ -53,8 +53,9 @@ import"./index-23m6bbjt.js";
53
53
  import"./index-hya40gb2.js";
54
54
  import"./index-5vwpzb5a.js";
55
55
  import"./index-x3fthpsc.js";
56
- import"./index-b0khj0h5.js";
56
+ import"./index-qt2zwza8.js";
57
57
  import"./index-3rm7cz6h.js";
58
+ import"./index-qkesh4c6.js";
58
59
  import"./index-jvb7w0gg.js";
59
60
  import"./index-z1w83f81.js";
60
61
 
@@ -10,7 +10,7 @@ import {
10
10
  readUrlMetadataDocument,
11
11
  renderUrlMetadataDocument,
12
12
  writeUrlMetadataDocument
13
- } from "./index-0eacgvpv.js";
13
+ } from "./index-t0fh8a1q.js";
14
14
  import {
15
15
  ARCHIVE_TODAY_HOSTS,
16
16
  ArchiveTodayFailure,
@@ -28,6 +28,7 @@ import"./index-e5fbsywq.js";
28
28
  import"./index-gh719d91.js";
29
29
  import"./index-mxxxytys.js";
30
30
  import"./index-1xxnjn0d.js";
31
+ import"./index-qkesh4c6.js";
31
32
  import"./index-z1w83f81.js";
32
33
 
33
34
  // src/url-intelligence.ts
@@ -1,16 +1,16 @@
1
1
  // @bun
2
2
  import {
3
3
  decisionContextWorkflow
4
- } from "../index-p70t8j13.js";
4
+ } from "../index-7drercgk.js";
5
5
  import"../index-h170byqw.js";
6
- import"../index-tvd2sqrx.js";
6
+ import"../index-mcpppwbq.js";
7
7
  import"../index-adx6khj5.js";
8
8
  import"../index-fp732bgg.js";
9
9
  import"../index-4j3tt0c3.js";
10
10
  import"../index-j70m75wd.js";
11
- import"../index-tcaq1c7f.js";
11
+ import"../index-e26mdwxz.js";
12
12
  import"../index-bdwcjvr4.js";
13
- import"../index-1er88ckw.js";
13
+ import"../index-3gc2yk4k.js";
14
14
  import"../index-pgtm2nhf.js";
15
15
  import"../index-1gwbassd.js";
16
16
  import"../index-ahyhryb8.js";
@@ -21,6 +21,7 @@ import"../index-48pz4jpc.js";
21
21
  import"../index-06c9ctr6.js";
22
22
  import"../index-5vwpzb5a.js";
23
23
  import"../index-3rm7cz6h.js";
24
+ import"../index-qkesh4c6.js";
24
25
  import"../index-jvb7w0gg.js";
25
26
  import"../index-z1w83f81.js";
26
27
  export {
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  decisionContextWorkflow
4
- } from "../index-p70t8j13.js";
4
+ } from "../index-7drercgk.js";
5
5
  import {
6
6
  explainChangeWorkflow
7
7
  } from "../index-e4fagfjz.js";
@@ -9,14 +9,14 @@ import {
9
9
  planRadarWorkflow
10
10
  } from "../index-rdd5f7jj.js";
11
11
  import"../index-h170byqw.js";
12
- import"../index-tvd2sqrx.js";
12
+ import"../index-mcpppwbq.js";
13
13
  import"../index-adx6khj5.js";
14
14
  import"../index-fp732bgg.js";
15
15
  import"../index-4j3tt0c3.js";
16
16
  import"../index-j70m75wd.js";
17
- import"../index-tcaq1c7f.js";
17
+ import"../index-e26mdwxz.js";
18
18
  import"../index-bdwcjvr4.js";
19
- import"../index-1er88ckw.js";
19
+ import"../index-3gc2yk4k.js";
20
20
  import"../index-pgtm2nhf.js";
21
21
  import"../index-1gwbassd.js";
22
22
  import"../index-ahyhryb8.js";
@@ -27,6 +27,7 @@ import"../index-48pz4jpc.js";
27
27
  import"../index-06c9ctr6.js";
28
28
  import"../index-5vwpzb5a.js";
29
29
  import"../index-3rm7cz6h.js";
30
+ import"../index-qkesh4c6.js";
30
31
  import"../index-jvb7w0gg.js";
31
32
  import"../index-z1w83f81.js";
32
33
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hraness/wordcell",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Wordcell keeps decisions, plans, and sources as Markdown beside your code, so coding agents can find them from the file they are about to change.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -368,6 +368,7 @@
368
368
  "src/clip/metadata-search-tool/Cargo.lock",
369
369
  "src/clip/metadata-search-tool/runner.ts",
370
370
  "src/clip/metadata-search-tool/src/main.rs",
371
+ "src/clip/metadata-search-tool/src/duckduckgo.rs",
371
372
  "src/git.ts",
372
373
  "src/evaluation-analysis-v2.ts",
373
374
  "src/evaluation-builder.ts",
@@ -437,6 +438,7 @@
437
438
  "src/semantic.ts",
438
439
  "src/serve.ts",
439
440
  "src/sdk.ts",
441
+ "src/selected-passage.ts",
440
442
  "src/source-inbox.ts",
441
443
  "src/untrusted-content.ts",
442
444
  "src/vault.ts",
@@ -467,13 +469,14 @@
467
469
  "src/oh/validation.ts",
468
470
  "assets/agent-skill.svg",
469
471
  "src/support.ts",
470
- "src/support-profile.ts"
472
+ "src/support-profile.ts",
473
+ "src/directory-sync.ts"
471
474
  ],
472
475
  "scripts": {
473
- "kb:refresh": "bunx --bun github:hraness/kb#v0.15.1 refresh --root kb",
474
- "kb:check:lane": "bunx --bun github:hraness/kb#v0.15.1 check --root kb --no-catalog",
475
- "kb:check": "bunx --bun github:hraness/kb#v0.15.1 check --root kb && bunx --bun github:hraness/kb#v0.15.1 agents check --root kb --repo .",
476
- "kb:catalog": "bunx --bun github:hraness/kb#v0.15.1 catalog --root kb",
476
+ "kb:refresh": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell refresh --root kb",
477
+ "kb:check:lane": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell check --root kb --no-catalog",
478
+ "kb:check": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell check --root kb && bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell agents check --root kb --repo .",
479
+ "kb:catalog": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell catalog --root kb",
477
480
  "build": "bun -e 'await (await import(\"node:fs/promises\")).rm(\"./dist\", { recursive: true, force: true })' && bun build ./src/index.ts ./src/publish.ts ./src/publish-model.ts ./src/publish-search.ts ./src/agent-context.ts ./src/agent-guide-audit.ts ./src/attachments.ts ./src/authoring.ts ./src/benchmark.ts ./src/clip/browser-profiles.ts ./src/capture.ts ./src/cli.ts ./src/clip/acquire.ts ./src/clip/args.ts ./src/clip/bounded-byte-buffer.ts ./src/clip/bundle-reader.ts ./src/clip/cli.ts ./src/clip/cookies.ts ./src/clip/doctor.ts ./src/clip/jobs.ts ./src/clip/network.ts ./src/clip/network-proxy.ts ./src/clip/persist.ts ./src/clip/refresh.ts ./src/clip/terminal.ts ./src/evaluation.ts ./src/evaluation-builder.ts ./src/evaluation-kb.ts ./src/git.ts ./src/graph.ts ./src/graph-percolation.ts ./src/graph-authority.ts ./src/navigation.ts ./src/pdf.ts ./src/percolate.ts ./src/portfolio.ts ./src/query.ts ./src/rerank.ts ./src/rerank-typesafe.ts ./src/repository-memory.ts ./src/sdk.ts ./src/search.ts ./src/search-rules.ts ./src/semantic.ts ./src/source-inbox.ts ./src/untrusted-content.ts ./src/url-intelligence.ts ./src/workflow.ts ./src/workflows/index.ts ./src/workflows/decision-context.ts ./src/workflows/explain-change.ts ./src/workflows/plan-radar.ts ./src/clip/defuddle-worker.ts --outdir ./dist --root ./src --target bun --format esm --splitting --packages external && bun build ./src/publish-reader/reader.ts --outfile ./dist/publish-reader/reader.js --target browser --format esm && bun build ./src/publish-reader/theme.ts --outfile ./dist/publish-reader/theme.js --target browser --format iife && cp ./src/publish-reader/reader.css ./dist/publish-reader/reader.css",
478
481
  "check:workflows": "bun ./scripts/check-workflow-yaml.ts",
479
482
  "check:installed-command-docs": "bun ./scripts/check-installed-command-docs.ts",
@@ -484,11 +487,12 @@
484
487
  "url-metadata:tool:build": "bun ./dist/cli.js url-metadata tool build",
485
488
  "url-metadata:tool:check": "bun ./dist/cli.js url-metadata tool check",
486
489
  "test:package": "bun run ./scripts/package-smoke.ts",
490
+ "check:fast": "bun run check:worker && bun run check:effect && bun run check:workflows && bun run check:portfolio-inventory && bun run check:installed-command-docs && bun run typecheck && bun run build && bun run test",
487
491
  "check": "bun run check:worker && bun run check:effect && bun run check:workflows && bun run check:portfolio-inventory && bun run check:installed-command-docs && bun run typecheck && bun run build && bun run test:package && bun run test && bun run url-metadata:tool:check",
488
492
  "prepack": "bun run check",
489
493
  "check:effect": "bun run scripts/check-effect-policy.ts",
490
- "kb:search": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.23.0/hraness-wordcell-0.23.0.tgz wordcell search --root kb --mode exact --rerank typesafe --rerank-limit 25",
491
- "kb:search:local": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.23.0/hraness-wordcell-0.23.0.tgz wordcell search --root kb --mode exact",
494
+ "kb:search": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell search --root kb --mode exact --selected-passage --rerank typesafe --rerank-limit 25",
495
+ "kb:search:local": "bunx --bun --package https://github.com/hraness/wordcell/releases/download/v0.24.0/hraness-wordcell-0.24.0.tgz wordcell search --root kb --mode exact --selected-passage",
492
496
  "check:worker": "tsc -p worker/tsconfig.json --noEmit"
493
497
  },
494
498
  "dependencies": {
@@ -58,7 +58,7 @@ missing:
58
58
  ```sh
59
59
  command -v wordcell >/dev/null 2>&1 || {
60
60
  command -v bun >/dev/null 2>&1 || exit 1
61
- bun add --global --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.23.0/hraness-wordcell-0.23.0.tgz
61
+ bun add --global --ignore-scripts https://github.com/hraness/wordcell/releases/download/v0.24.1/hraness-wordcell-0.24.1.tgz
62
62
  }
63
63
  wordcell --help
64
64
  ```
@@ -96,7 +96,8 @@ The full artifact contract and hosting options are in
96
96
 
97
97
  Agents on platforms without a filesystem or Bun runtime can publish the same
98
98
  `hraness.wordcell.site.v1` artifact through the hosted surface. It runs the
99
- identical projection server-side; published sites are public.
99
+ identical projection server-side; published sites are public, and search
100
+ engines may index them unless the publish request sets `"noindex": true`.
100
101
 
101
102
  ```sh
102
103
  # Self-serve capability token (IP-limited; shown once — store it).
@@ -54,7 +54,7 @@ returned:
54
54
  ```sh
55
55
  wordcell context packages/parser/src/index.ts --root "$KB_ROOT" --repo "$KB_REPO"
56
56
  wordcell search "why parser retries stop" --root "$KB_ROOT" --mode exact \
57
- --history --repo "$KB_REPO" --json
57
+ --selected-passage --history --repo "$KB_REPO" --json
58
58
  wordcell backlinks notes/parser-contract --root "$KB_ROOT" --json
59
59
  wordcell history notes/parser-contract --root "$KB_ROOT" \
60
60
  --repo "$KB_REPO" --json
@@ -80,7 +80,7 @@ read them with the commands under
80
80
  resolve note identities before returning authored relationships.
81
81
  - A whole-vault structural question or relationship audit: use `wordcell graph --json`,
82
82
  then inspect the smallest relevant portion of its canonical output.
83
- - A phrase, identity, or concept expressed with different vocabulary: use `wordcell search`, whose default hybrid result preserves exact and QMD evidence separately.
83
+ - A phrase, identity, or concept expressed with different vocabulary: use `wordcell search`, whose default hybrid result preserves exact and QMD evidence separately. Add `--selected-passage` when you will read, quote, or act on results: each hit then carries the note's best-matching excerpt with its line range and section headings.
84
84
  - Direct provenance for one note or repository path: use `wordcell history` or
85
85
  `wordcell history search` without changing authored metadata or links.
86
86
  - Recent captures awaiting maintained disposition: use the advisory `wordcell inbox` view.
@@ -96,7 +96,7 @@ wordcell backlinks "Plan title or path" --root "$KB_ROOT" --json
96
96
  wordcell links "Plan title or path" --root "$KB_ROOT" --direction both --depth 1 --limit 25 --json
97
97
  wordcell relation list "Plan title or path" --root "$KB_ROOT" --json
98
98
  wordcell graph --root "$KB_ROOT" --json
99
- wordcell search "why browser capture uses the current tab" --root "$KB_ROOT" --json
99
+ wordcell search "why browser capture uses the current tab" --root "$KB_ROOT" --selected-passage --json
100
100
  wordcell search "accepted ingestion plans" --root "$KB_ROOT" --where type=plan --where status=accepted --tag ingestion --json
101
101
  wordcell search "notes/write-path" --root "$KB_ROOT" --mode exact --no-history --json
102
102
  wordcell history "notes/write-path" --root "$KB_ROOT" --repo "$KB_REPO" --json
@@ -211,6 +211,20 @@ For a repository's ordinary KB searches, prefer its approved, pinned
211
211
  choice. Read returned notes before acting; model probabilities are ranking
212
212
  signals, not proof of truth. Explicit priority rules still run last.
213
213
 
214
+ Add `--selected-passage` to attach one source excerpt to each returned note.
215
+ Wordcell chooses it locally, without model calls: the paragraph, or window of
216
+ at most 512 UTF-8 bytes, that contains the most distinct query words, outside
217
+ frontmatter. The excerpt is unedited note text with its byte offsets, line
218
+ range, the SHA-256 of the note text, and the enclosing Markdown headings as
219
+ separate spans. Offsets count from the start of the note text, after any
220
+ byte-order mark. Ranking, scores, the `snippet` field, and reranker input do not
221
+ change. A note without matching body text reports `status: "none"`; a note
222
+ past a size limit reports `status: "unavailable"` with a reason. Both keep
223
+ their snippet. Terminal output shows CRLF as LF and tabs as four spaces, and
224
+ reports `display-sanitized` instead of an excerpt that credential redaction
225
+ would alter. An excerpt shows where query words appear; it is not a verified
226
+ answer. SDK and MCP `search` callers pass `selectedPassage: true`.
227
+
214
228
  `wordcell history <note>` returns the bounded commit history already associated with
215
229
  one resolved note. `wordcell history search <query-or-path>` searches the bounded Git
216
230
  projection directly and retains hashes, subjects, matched paths, co-change
@@ -1,4 +1,5 @@
1
1
  import { acquireNoteLock } from "./note-lock.js";
2
+ import { syncsDirectories } from "./directory-sync.js";
2
3
  import { rename } from "node:fs/promises";
3
4
  import { randomUUID } from "node:crypto";
4
5
  import { constants } from "node:fs";
@@ -202,7 +203,9 @@ export async function cleanupTemporary(
202
203
  export async function fsyncDirectory(
203
204
  path: string,
204
205
  openDirectory: typeof open = open,
206
+ platform: NodeJS.Platform = process.platform,
205
207
  ): Promise<void> {
208
+ if (!syncsDirectories(platform)) return;
206
209
  const handle = await openDirectory(path, constants.O_RDONLY);
207
210
  try {
208
211
  await handle.sync();
package/src/cli-help.ts CHANGED
@@ -114,6 +114,7 @@ export const commandHelp: readonly CommandHelp[] = [
114
114
  ["--min-score <score>", "Drop results scoring below this (0 to 1)"],
115
115
  ["--rerank <typesafe>", "Rerank results with the hosted TypeSafe service"],
116
116
  ["--rerank-limit <2..25>", "How many results the reranker sees"],
117
+ ["--selected-passage", "Add a local source excerpt to each result"],
117
118
  JSON_OPTION,
118
119
  ],
119
120
  examples: ['wordcell search "retries" --root kb --mode exact', 'wordcell search "retry policy" --root kb --limit 5'],
@@ -385,6 +385,7 @@ type ParsedCommand =
385
385
  readonly minScore?: number;
386
386
  readonly rerank?: "typesafe";
387
387
  readonly rerankLimit?: number;
388
+ readonly selectedPassage?: boolean;
388
389
  readonly query: string;
389
390
  readonly json: boolean;
390
391
  }
@@ -1274,6 +1275,7 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1274
1275
  let minScore: number | undefined;
1275
1276
  let rerank: "typesafe" | undefined;
1276
1277
  let rerankLimit: number | undefined;
1278
+ let selectedPassage = false;
1277
1279
  let graphDepth: number | undefined;
1278
1280
  let noGraph = false;
1279
1281
  let history = false;
@@ -1300,6 +1302,10 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1300
1302
  noGraph = true;
1301
1303
  continue;
1302
1304
  }
1305
+ if (argument === "--selected-passage" && command === "search") {
1306
+ selectedPassage = true;
1307
+ continue;
1308
+ }
1303
1309
  if (argument === "--no-history" && command === "search") {
1304
1310
  noHistory = true;
1305
1311
  continue;
@@ -1535,6 +1541,7 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
1535
1541
  ...(minScore === undefined ? {} : { minScore }),
1536
1542
  ...(rerank === undefined ? {} : { rerank }),
1537
1543
  ...(rerankLimit === undefined ? {} : { rerankLimit }),
1544
+ ...(selectedPassage ? { selectedPassage: true } : {}),
1538
1545
  query,
1539
1546
  json,
1540
1547
  },
@@ -2418,6 +2425,22 @@ function renderSemanticIndex(result: SemanticIndexResult): string {
2418
2425
  ].join("\n");
2419
2426
  }
2420
2427
 
2428
+ function searchForDisplay(result: KnowledgeBaseSearchResult): KnowledgeBaseSearchResult {
2429
+ return {
2430
+ ...result,
2431
+ results: result.results.map((hit) => {
2432
+ const passage = hit.selectedPassage;
2433
+ if (passage?.status !== "selected") return hit;
2434
+ const texts = [passage.text, ...passage.headings.map(({ text }) => text)];
2435
+ // Terminal output always renders CRLF as LF and tabs as four spaces; only other changes lose content.
2436
+ if (texts.every((text) => sanitizeTerminalText(redactSensitiveText(text))
2437
+ === text.replaceAll("\r\n", "\n").replaceAll("\t", " "))) return hit;
2438
+ // Redacted display text cannot retain a claim of exact source bytes.
2439
+ return { ...hit, selectedPassage: { status: "unavailable", reason: "display-sanitized" } as const };
2440
+ }),
2441
+ };
2442
+ }
2443
+
2421
2444
  function renderKnowledgeBaseSearch(result: KnowledgeBaseSearchResult): string {
2422
2445
  const lines = [
2423
2446
  `${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for “${safe(result.query)}” (${result.results.length})${result.partial ? " [partial]" : ""}`,
@@ -2445,7 +2468,20 @@ function renderKnowledgeBaseSearch(result: KnowledgeBaseSearchResult): string {
2445
2468
  .map((item) => `${item.kind}#${searchEvidenceRank(item)}`)
2446
2469
  .join(", ");
2447
2470
  lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} — ${safe(hit.title)} [${safe(evidence)}]`);
2448
- if (hit.snippet !== "") lines.push(` ${safe(hit.snippet)}`);
2471
+ const passage = hit.selectedPassage;
2472
+ if (passage?.status === "selected") {
2473
+ lines.push(` Selected passage: ${safe(hit.path)}:${passage.startLine}-${passage.endLine}`
2474
+ + (passage.clippedStart || passage.clippedEnd ? " (clipped excerpt)" : ""));
2475
+ for (const heading of passage.headings) {
2476
+ lines.push(` Section: ${safe(heading.text)}${heading.truncated ? " (clipped)" : ""}`);
2477
+ }
2478
+ for (const line of passage.text.split(/\r?\n/u)) lines.push(` ${safe(line)}`);
2479
+ } else {
2480
+ if (hit.snippet !== "") lines.push(` ${safe(hit.snippet)}`);
2481
+ if (passage !== undefined) {
2482
+ lines.push(` Selected passage: ${passage.status} (${safe(passage.reason)}); showing the search snippet.`);
2483
+ }
2484
+ }
2449
2485
  }
2450
2486
  if ((result.graph?.related.length ?? 0) > 0) {
2451
2487
  lines.push(` Related graph context: ${result.graph?.related.length ?? 0}`);
@@ -2523,6 +2559,7 @@ async function runSemantic(
2523
2559
  ? {}
2524
2560
  : { candidateLimit: command.candidateLimit }),
2525
2561
  ...(command.minScore === undefined ? {} : { minScore: command.minScore }),
2562
+ ...(command.selectedPassage === undefined ? {} : { selectedPassage: command.selectedPassage }),
2526
2563
  ...(command.rerank === undefined ? {} : {
2527
2564
  rerank: {
2528
2565
  engine: command.rerank,
@@ -2530,9 +2567,10 @@ async function runSemantic(
2530
2567
  },
2531
2568
  }),
2532
2569
  });
2570
+ const display = searchForDisplay(result);
2533
2571
  output.stdout(command.json
2534
- ? terminalSafeJson(result)
2535
- : sanitizeTerminalText(renderKnowledgeBaseSearch(result)));
2572
+ ? terminalSafeJson(display)
2573
+ : sanitizeTerminalText(renderKnowledgeBaseSearch(display)));
2536
2574
  if (withoutIndex && person) {
2537
2575
  output.stderr("Searched without an index, so only exact words matched. "
2538
2576
  + `For meaning-based search, run wordcell index --root ${safe(shellWord(command.root))} `
package/src/clip/jobs.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ import { syncsDirectories } from "../directory-sync.js";
2
3
  import { randomUUID } from "node:crypto";
3
4
  import { constants } from "node:fs";
4
5
  import {
@@ -500,7 +501,7 @@ async function fsyncStore(store: CaptureJobStore): Promise<void> {
500
501
  if (!stat.isDirectory() || !sameIdentity(stat, expected)) {
501
502
  throw new CaptureJobSafetyError("Capture job store changed before synchronization.");
502
503
  }
503
- await handle.sync();
504
+ if (syncsDirectories()) await handle.sync();
504
505
  } finally {
505
506
  await handle.close();
506
507
  }
@@ -897,9 +897,11 @@ dependencies = [
897
897
  "libc",
898
898
  "metadata-search-engine-rs",
899
899
  "reqwest",
900
+ "scraper",
900
901
  "serde",
901
902
  "serde_json",
902
903
  "tokio",
904
+ "url",
903
905
  ]
904
906
 
905
907
  [[package]]
@@ -8,6 +8,8 @@ publish = false
8
8
  metadata-search-engine-rs = { git = "https://github.com/MikeLuu99/searxng-rust", rev = "f40a00ea67a857ee996e1caba1ebab3ee7a14a47" }
9
9
  libc = "=0.2.189"
10
10
  reqwest = { version = "=0.12.28", features = ["brotli", "cookies", "gzip"] }
11
+ scraper = "=0.20.0"
12
+ url = "=2.5.8"
11
13
  serde = { version = "1", features = ["derive"] }
12
14
  serde_json = "1"
13
15
  tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
@@ -1,4 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
+ import { syncsDirectories } from "../../directory-sync.js";
2
3
  import {
3
4
  closeSync,
4
5
  constants,
@@ -387,7 +388,9 @@ export function installMetadataSearchExecutable(input: {
387
388
  const stagingPath = join(toolDirectory, stagingName);
388
389
  const backupPath = join(toolDirectory, backupName);
389
390
  const syncDirectory = input.syncDirectory
390
- ?? ((descriptor: number) => fsyncSync(descriptor));
391
+ ?? ((descriptor: number) => {
392
+ if (syncsDirectories()) fsyncSync(descriptor);
393
+ });
391
394
 
392
395
  const toolBinding = openBoundDirectory(toolDirectory, "metadata-search tool directory");
393
396
  let targetBinding: DirectoryBinding | null = null;