@hraness/wordcell 0.21.3 → 0.22.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 +170 -64
- package/dist/{authority-4sp9d552.js → authority-1pgftmwq.js} +55 -10
- package/dist/benchmark.js +3 -3
- package/dist/capture.js +6 -5
- package/dist/cli.js +37 -31
- package/dist/clip/acquire.js +3 -2
- package/dist/clip/cli.js +6 -5
- package/dist/clip/doctor.js +4 -3
- package/dist/clip/jobs.js +3 -3
- package/dist/clip/refresh.js +1 -1
- package/dist/evaluation-builder.js +18 -17
- package/dist/evaluation-kb.js +14 -13
- package/dist/graph-authority.js +3 -3
- package/dist/graph-percolation.js +5 -5
- package/dist/index-0dqfnrz9.js +1985 -0
- package/dist/{index-0k2x4nn9.js → index-1tm7bgx7.js} +16 -4
- package/dist/{index-vdw5yjfa.js → index-2hf27mws.js} +108 -18
- package/dist/index-3agn8scn.js +306 -0
- package/dist/{index-0rfcqab6.js → index-4ag64rc1.js} +555 -59
- package/dist/index-4knsp9qj.js +34 -0
- package/dist/index-66pshdtx.js +341 -0
- package/dist/index-9rf81m0p.js +368 -0
- package/dist/{index-59ssmqqm.js → index-a6kbv718.js} +1 -1
- package/dist/{index-51bap0mq.js → index-avbce90h.js} +9 -9
- package/dist/{index-9zcpdbjg.js → index-de2w8crk.js} +13 -13
- package/dist/{index-pbr6rv47.js → index-e6p9e926.js} +6 -6
- package/dist/{index-bcfn9xah.js → index-famy7fhs.js} +5 -5
- package/dist/{index-van48n3g.js → index-fc4dr114.js} +4 -2
- package/dist/{index-jzcws0zn.js → index-g5vsqmdy.js} +27 -57
- package/dist/{index-115b07ap.js → index-hcw140eb.js} +5 -5
- package/dist/index-j70m75wd.js +217 -0
- package/dist/{index-3ehtt59c.js → index-qwgsmtsz.js} +1 -1
- package/dist/{index-bgfzwt4h.js → index-t2bs9xdr.js} +4 -4
- package/dist/index.js +285 -93
- package/dist/portfolio.js +14 -13
- package/dist/publish-model.js +36 -0
- package/dist/publish-reader/reader.css +1042 -0
- package/dist/publish-reader/reader.js +1534 -0
- package/dist/publish-reader/theme.js +92 -0
- package/dist/publish-search.js +52 -0
- package/dist/publish.js +36 -0
- package/dist/rerank-typesafe.js +13 -0
- package/dist/rerank.js +14 -0
- package/dist/sdk.js +14 -11
- package/dist/search.js +2 -2
- package/dist/semantic.js +4 -4
- package/dist/{support-vm4mkw0k.js → support-mywv1em0.js} +38 -32
- package/dist/workflows/decision-context.js +13 -12
- package/dist/workflows/index.js +13 -12
- package/package.json +63 -5
- package/skills/wordcell/AGENTS.md +1 -0
- package/skills/wordcell/SKILL.md +2 -1
- package/skills/wordcell/agents/openai.yaml +1 -1
- package/skills/wordcell/references/publish.md +93 -0
- package/skills/wordcell/references/query.md +22 -0
- package/src/cli-program.ts +347 -9
- package/src/index.ts +4 -0
- package/src/oh/authority.ts +14 -5
- package/src/oh/canonical-rust.ts +127 -0
- package/src/oh/projection-rust.ts +63 -0
- package/src/oh-adoption.ts +7 -1
- package/src/publish-graph.ts +0 -0
- package/src/publish-index.ts +195 -0
- package/src/publish-markdown.ts +617 -0
- package/src/publish-model.ts +599 -0
- package/src/publish-nav.ts +204 -0
- package/src/publish-pages.ts +382 -0
- package/src/publish-reader/reader.css +1042 -0
- package/src/publish-reader/reader.ts +1017 -0
- package/src/publish-reader/theme.ts +97 -0
- package/src/publish-search.ts +418 -0
- package/src/publish-select.ts +419 -0
- package/src/publish-theme.ts +106 -0
- package/src/publish.ts +904 -0
- package/src/rerank-credentials.ts +56 -0
- package/src/rerank-typesafe.ts +472 -0
- package/src/rerank.ts +389 -0
- package/src/sdk.ts +157 -6
- package/src/serve.ts +180 -0
- package/src/vault.ts +23 -9
- package/dist/{index-why54q5p.js → index-gm9t95d9.js} +3 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Publish selected knowledge as a static site
|
|
2
|
+
|
|
3
|
+
Use `wordcell publish` to turn selected Markdown notes into read-only pages,
|
|
4
|
+
backlinks, a graph, and browser-local search. It builds files locally with no
|
|
5
|
+
model call. Publishing here means generating the artifact; uploading it to a
|
|
6
|
+
host is a separate action.
|
|
7
|
+
|
|
8
|
+
## Select and preview
|
|
9
|
+
|
|
10
|
+
Use the scope in the user's request. Ask only when the selection or destination
|
|
11
|
+
is materially ambiguous. Resolve `KB_ROOT` before running commands, and choose
|
|
12
|
+
an output directory outside the vault that does not contain it.
|
|
13
|
+
|
|
14
|
+
Start with a compact, read-only preview:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
wordcell publish --root "$KB_ROOT" --out ./public-notes \
|
|
18
|
+
--include notes/decision --include plans/implementation \
|
|
19
|
+
--dry-run --list-limit 20 --json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The report contains counts, at most 20 selected note IDs, and a digest of the
|
|
23
|
+
selected Markdown. It does not print note bodies. Inspect `selection.truncated` before
|
|
24
|
+
treating the preview as a complete list. Raise `--list-limit` up to 1000 when
|
|
25
|
+
individual IDs matter, or use zero for counts and a digest alone. Open only
|
|
26
|
+
selected notes whose contents need review; do not load or summarize the whole
|
|
27
|
+
vault to choose a known slice.
|
|
28
|
+
|
|
29
|
+
Selectors are repeatable:
|
|
30
|
+
|
|
31
|
+
- `--include <id-or-directory>` selects exact notes or directory prefixes.
|
|
32
|
+
- `--include-glob 'notes/**'` and `--exclude-glob '**/draft-*'` select by pattern.
|
|
33
|
+
Quote globs so the shell does not expand them. `*` matches within one segment,
|
|
34
|
+
`?` matches one character, and `**` matches whole path segments.
|
|
35
|
+
- `--where type=concept`, `--has relations`, `--tag public`, and
|
|
36
|
+
`--scope packages/parser` select by authored metadata.
|
|
37
|
+
- `--from notes/topic --depth 2 --direction both` selects a bounded neighborhood
|
|
38
|
+
of explicit links and relationships. Oversized neighborhoods fail rather
|
|
39
|
+
than silently publish an incomplete selection.
|
|
40
|
+
|
|
41
|
+
Positive path, glob, metadata, and graph selectors form a **union**. Metadata
|
|
42
|
+
predicates within their group must all match. `--include notes --tag public`
|
|
43
|
+
therefore includes all of `notes`, not only its public-tagged notes. Use a
|
|
44
|
+
metadata conjunction or explicit note IDs when an intersection is required.
|
|
45
|
+
Excludes and `publish: false` always win.
|
|
46
|
+
|
|
47
|
+
Publication is not prose redaction. Review selected text and referenced assets
|
|
48
|
+
for anything unsuitable for the intended audience. A selected note can mention
|
|
49
|
+
excluded material in its own text. Private flags exclude a note's page, search
|
|
50
|
+
record, and graph membership; they cannot remove information copied into another
|
|
51
|
+
note or attachment. `--noindex` is a crawler preference, not access control.
|
|
52
|
+
|
|
53
|
+
## Build the reviewed slice
|
|
54
|
+
|
|
55
|
+
Repeat the preview command without `--dry-run`. Keep the same selectors and
|
|
56
|
+
check the returned digest and counts; if the Markdown changed, review it again.
|
|
57
|
+
The digest covers selected Markdown, not attachment bytes. Review referenced
|
|
58
|
+
attachments separately before sharing.
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
wordcell publish --root "$KB_ROOT" --out ./public-notes \
|
|
62
|
+
--include notes/decision --include plans/implementation \
|
|
63
|
+
--title "Project decisions" --deterministic --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Use `--force` only to replace a known generated output directory. Set
|
|
67
|
+
`--base-path /handbook/` if the host serves the files at a subpath, and add
|
|
68
|
+
`--base-url https://docs.example.com` when a sitemap is useful. Use
|
|
69
|
+
`--no-index-content` for field-only search. None of these flags changes the
|
|
70
|
+
source vault.
|
|
71
|
+
|
|
72
|
+
Read dropped-link, skipped-asset, and search-truncation counts. Run
|
|
73
|
+
`wordcell check --root "$KB_ROOT"` when missing links or attachments need
|
|
74
|
+
diagnosis. Do not report missing content as complete.
|
|
75
|
+
|
|
76
|
+
## Preview and host
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
wordcell serve --root ./public-notes --port 8080
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Open the loopback URL. Check a note, its navigation, the Search overlay, and the
|
|
83
|
+
Graph page. HTML can be read without JavaScript; browser search and graph need
|
|
84
|
+
HTTP hosting rather than `file://`. The preview server is not a production
|
|
85
|
+
service.
|
|
86
|
+
|
|
87
|
+
Upload the generated directory to the user's selected static host only when
|
|
88
|
+
hosting is in the request. Confirm the exact destination and preserve unrelated
|
|
89
|
+
files. Verify the public URL and selected content after upload. A successful
|
|
90
|
+
local build alone is not a live deployment.
|
|
91
|
+
|
|
92
|
+
The full artifact contract and hosting options are in
|
|
93
|
+
[the publishing guide](https://github.com/hraness/wordcell/blob/main/docs/publish.md).
|
|
@@ -155,6 +155,28 @@ form. Graph neighbors and Git history remain separate from primary
|
|
|
155
155
|
text rank. They explain and expand candidates without becoming authored facts,
|
|
156
156
|
links, or recency boosts.
|
|
157
157
|
|
|
158
|
+
Reranking is opt-in through `--rerank typesafe`. It uses `jev-1.13.0` and sends
|
|
159
|
+
the query plus each candidate's identifier, title, vault-relative path, and at
|
|
160
|
+
most 512 UTF-8 bytes of snippet text to TypeSafe. Enable it only for vaults
|
|
161
|
+
approved for external processing and provider input-token charges. Use
|
|
162
|
+
`--rerank-limit 25` to bound the window (2–25 candidates); four requests run
|
|
163
|
+
concurrently under one eight-second deadline with no retries. Exact identities
|
|
164
|
+
remain first. Results retain their original scores and retrieval evidence.
|
|
165
|
+
|
|
166
|
+
The CLI reads `TYPESAFE_API_KEY`, an explicit `TYPESAFE_API_KEY_FILE`, or the
|
|
167
|
+
owner-only file `~/.config/wordcell/typesafe-api-key` (honoring
|
|
168
|
+
`XDG_CONFIG_HOME`). Keep credentials outside the repository. A missing key or
|
|
169
|
+
provider failure retains baseline ordering and marks the rerank lane
|
|
170
|
+
unavailable or degraded. Inspect its structured `rerank` receipt for attempted
|
|
171
|
+
requests, known usage, elapsed time, and whether usage is complete; an unknown
|
|
172
|
+
charge is never reported as zero. A successful exit alone does not establish
|
|
173
|
+
that reranking occurred. Omit `--rerank` for local-only retrieval.
|
|
174
|
+
|
|
175
|
+
For a repository's ordinary KB searches, prefer its approved, pinned
|
|
176
|
+
`kb:search` script when present. That script declares the vault and processing
|
|
177
|
+
choice. Read returned notes before acting; model probabilities are ranking
|
|
178
|
+
signals, not proof of truth. Explicit priority rules still run last.
|
|
179
|
+
|
|
158
180
|
`wordcell history <note>` returns the bounded commit history already associated with
|
|
159
181
|
one resolved note. `wordcell history search <query-or-path>` searches the bounded Git
|
|
160
182
|
projection directly and retains hashes, subjects, matched paths, co-change
|
package/src/cli-program.ts
CHANGED
|
@@ -96,6 +96,14 @@ import {
|
|
|
96
96
|
openKnowledgePortfolio,
|
|
97
97
|
type PortfolioSearchResult,
|
|
98
98
|
} from "./portfolio.js";
|
|
99
|
+
import {
|
|
100
|
+
DEFAULT_PUBLISH_LIST_LIMIT,
|
|
101
|
+
MAX_PUBLISH_LIST_LIMIT,
|
|
102
|
+
publishVault,
|
|
103
|
+
renderPublishReportText,
|
|
104
|
+
type PublishSelectionInput,
|
|
105
|
+
} from "./publish.js";
|
|
106
|
+
import { serveSite } from "./serve.js";
|
|
99
107
|
import {
|
|
100
108
|
MAX_AUTHORIZED_VAULTS,
|
|
101
109
|
loadPortfolioRegistry,
|
|
@@ -140,11 +148,14 @@ import {
|
|
|
140
148
|
MAX_SEARCH_RELATED_SEEDS,
|
|
141
149
|
MAX_SEARCH_RESULTS,
|
|
142
150
|
openKnowledgeBase,
|
|
151
|
+
searchEvidenceRank,
|
|
143
152
|
type KnowledgeBaseGraphOptions,
|
|
144
153
|
type KnowledgeBaseSearchMode,
|
|
145
154
|
type KnowledgeBaseSearchOrdering,
|
|
146
155
|
type KnowledgeBaseSearchResult,
|
|
147
156
|
} from "./sdk.js";
|
|
157
|
+
import { MAX_RERANK_CANDIDATES, type SearchReranker } from "./rerank.js";
|
|
158
|
+
import { createCliTypeSafeReranker } from "./rerank-credentials.js";
|
|
148
159
|
import {
|
|
149
160
|
refreshVault,
|
|
150
161
|
scanVault,
|
|
@@ -215,7 +226,16 @@ async function loadSearchRulesFile(path: string): Promise<SearchRulesV1> {
|
|
|
215
226
|
return parseSearchRules(input);
|
|
216
227
|
}
|
|
217
228
|
|
|
218
|
-
export const usage = `wordcell —
|
|
229
|
+
export const usage = `wordcell — a local knowledge base for coding agents
|
|
230
|
+
|
|
231
|
+
Start here (no account or model needed):
|
|
232
|
+
wordcell init kb
|
|
233
|
+
wordcell note create notes/decision --title "A decision" --body "Keep retries bounded." --root kb
|
|
234
|
+
wordcell search "retries" --root kb --mode exact
|
|
235
|
+
|
|
236
|
+
Already have Markdown? Search it with --root <your-notes-directory>.
|
|
237
|
+
Use --mode exact for model-free search; optional hybrid search needs a local index.
|
|
238
|
+
Quick start and examples: https://wordcell.io/docs
|
|
219
239
|
|
|
220
240
|
Usage:
|
|
221
241
|
wordcell init [directory] [--json]
|
|
@@ -243,12 +263,14 @@ Usage:
|
|
|
243
263
|
wordcell percolate [note] [--proofs] [--root <directory>] [--min-support <count>] [--limit <count>] [--json]
|
|
244
264
|
wordcell list [--root <directory>] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--sort <field>] [--order <asc|desc>] [--limit <count>] [--json]
|
|
245
265
|
wordcell index [--root <directory>] [--database <path>] [--force] [--json]
|
|
246
|
-
wordcell search <query> [--root <directory>] [--repo <repository>] [--database <path>] [--mode <hybrid|exact|keyword|semantic>] [--rules <file>] [--priority] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--related <note>] [--graph-depth <1|2>] [--no-graph] [--history | --no-history | --require-history] [--limit <count>] [--candidate-limit <count>] [--min-score <score>] [--json]
|
|
266
|
+
wordcell search <query> [--root <directory>] [--repo <repository>] [--database <path>] [--mode <hybrid|exact|keyword|semantic>] [--rules <file>] [--priority] [--where <path=value>] [--has <path>] [--tag <tag>] [--scope <repository-path>] [--related <note>] [--graph-depth <1|2>] [--no-graph] [--history | --no-history | --require-history] [--limit <count>] [--candidate-limit <count>] [--min-score <score>] [--rerank <typesafe>] [--rerank-limit <2..25>] [--json]
|
|
247
267
|
wordcell history <note> [--root <directory>] [--repo <repository>] [--limit <count>] [--cochanged-limit <count>] [--json]
|
|
248
268
|
wordcell history search <query-or-path> [--root <directory>] [--repo <repository>] [--limit <count>] [--commit-limit <count>] [--cochanged-limit <count>] [--json]
|
|
249
269
|
wordcell evaluate <manifest.json> [--root <directory>] [--repo <repository>] [--database <path>] [--retriever <id>] [--split <development|test|all>] [--limit <count>] [--cutoff <count>] [--timeout <milliseconds>] [--baseline <id>] [--model-file <path>] [--cache-state <cold|mixed|warm>] [--json]
|
|
250
270
|
wordcell portfolio search <query> --registry <file> --workspace <directory> (--shared | --vault <owner/id>...) [--mode <hybrid|exact|keyword|semantic>] [--rules <file>] [--priority] [--limit <count>] [--require-all] [--json]
|
|
251
271
|
wordcell portfolio audit --registry <file> --workspace <directory> (--all | --shared | --vault <owner/id>...) [--strict] [--json]
|
|
272
|
+
wordcell publish --out <directory> [--root <directory>] [--index <path>] [--include <path>]... [--exclude <path>]... [--include-glob <pattern>]... [--exclude-glob <pattern>]... [--where <path=value>]... [--has <path>]... [--tag <tag>]... [--scope <repository-path>]... [--from <note> [--depth <count>] [--direction <in|out|both>]] [--title <title>] [--description <text>] [--base-path <path>] [--base-url <url>] [--noindex] [--no-index-content] [--deterministic] [--dry-run] [--list-limit <0-1000>] [--force] [--json]
|
|
273
|
+
wordcell serve --root <directory> [--host <host>] [--port <port>] [--json]
|
|
252
274
|
wordcell inbox [--root <directory>] [--source-prefix <directory>] [--limit <count>] [--json]
|
|
253
275
|
wordcell context <repository-path> [--root <vault>] [--repo <repository>] [--kind <auto|file|directory>] [--json]
|
|
254
276
|
wordcell agents identity <repository-scope> [--json]
|
|
@@ -359,6 +381,8 @@ type ParsedCommand =
|
|
|
359
381
|
readonly limit?: number;
|
|
360
382
|
readonly candidateLimit?: number;
|
|
361
383
|
readonly minScore?: number;
|
|
384
|
+
readonly rerank?: "typesafe";
|
|
385
|
+
readonly rerankLimit?: number;
|
|
362
386
|
readonly query: string;
|
|
363
387
|
readonly json: boolean;
|
|
364
388
|
}
|
|
@@ -446,6 +470,31 @@ type ParsedCommand =
|
|
|
446
470
|
readonly minSupport: number;
|
|
447
471
|
readonly limit: number;
|
|
448
472
|
readonly json: boolean;
|
|
473
|
+
}
|
|
474
|
+
| {
|
|
475
|
+
readonly kind: "publish";
|
|
476
|
+
readonly root: string;
|
|
477
|
+
readonly out: string;
|
|
478
|
+
readonly index?: string;
|
|
479
|
+
readonly title?: string;
|
|
480
|
+
readonly description?: string;
|
|
481
|
+
readonly basePath?: string;
|
|
482
|
+
readonly baseUrl?: string;
|
|
483
|
+
readonly noindex: boolean;
|
|
484
|
+
readonly indexContent: boolean;
|
|
485
|
+
readonly deterministic: boolean;
|
|
486
|
+
readonly dryRun: boolean;
|
|
487
|
+
readonly listLimit: number;
|
|
488
|
+
readonly force: boolean;
|
|
489
|
+
readonly selection: PublishSelectionInput;
|
|
490
|
+
readonly json: boolean;
|
|
491
|
+
}
|
|
492
|
+
| {
|
|
493
|
+
readonly kind: "serve";
|
|
494
|
+
readonly root: string;
|
|
495
|
+
readonly host: string;
|
|
496
|
+
readonly port: number;
|
|
497
|
+
readonly json: boolean;
|
|
449
498
|
};
|
|
450
499
|
|
|
451
500
|
type ParseResult =
|
|
@@ -468,6 +517,8 @@ type CliDependencies = GraphCliDependencies & {
|
|
|
468
517
|
readonly indexSemanticVault?: typeof indexSemanticVault;
|
|
469
518
|
readonly openKnowledgeBase?: typeof openKnowledgeBase;
|
|
470
519
|
readonly openKnowledgeBaseEvaluation?: typeof openKnowledgeBaseEvaluation;
|
|
520
|
+
/** Test seam; production wiring builds the TypeSafe reranker lazily per flag. */
|
|
521
|
+
readonly rerankers?: readonly SearchReranker[];
|
|
471
522
|
readonly digestEvaluationModel?: (path: string) => Promise<string>;
|
|
472
523
|
readonly evaluationNow?: () => Date;
|
|
473
524
|
readonly createNote?: typeof createNote;
|
|
@@ -479,6 +530,8 @@ type CliDependencies = GraphCliDependencies & {
|
|
|
479
530
|
readonly buildRepositoryMemoryContext?: typeof buildRepositoryMemoryContext;
|
|
480
531
|
readonly auditAgentGuideRepository?: typeof auditAgentGuideRepository;
|
|
481
532
|
readonly validateMarkdownAttachments?: typeof validateMarkdownAttachments;
|
|
533
|
+
readonly publishVault?: typeof publishVault;
|
|
534
|
+
readonly serveSite?: typeof serveSite;
|
|
482
535
|
};
|
|
483
536
|
|
|
484
537
|
function safe(value: string): string {
|
|
@@ -866,6 +919,195 @@ function parseListCommand(arguments_: readonly string[]): ParseResult {
|
|
|
866
919
|
};
|
|
867
920
|
}
|
|
868
921
|
|
|
922
|
+
function parsePublishCommand(arguments_: readonly string[]): ParseResult {
|
|
923
|
+
let root = ".";
|
|
924
|
+
let out: string | undefined;
|
|
925
|
+
let index: string | undefined;
|
|
926
|
+
let title: string | undefined;
|
|
927
|
+
let description: string | undefined;
|
|
928
|
+
let basePath: string | undefined;
|
|
929
|
+
let baseUrl: string | undefined;
|
|
930
|
+
let noindex = false;
|
|
931
|
+
let indexContent = true;
|
|
932
|
+
let deterministic = false;
|
|
933
|
+
let dryRun = false;
|
|
934
|
+
let listLimit = DEFAULT_PUBLISH_LIST_LIMIT;
|
|
935
|
+
let force = false;
|
|
936
|
+
let json = false;
|
|
937
|
+
let from: string | undefined;
|
|
938
|
+
let depth = 1;
|
|
939
|
+
let direction: LinkDirection = "both";
|
|
940
|
+
const includes: string[] = [];
|
|
941
|
+
const excludes: string[] = [];
|
|
942
|
+
const includeGlobs: string[] = [];
|
|
943
|
+
const excludeGlobs: string[] = [];
|
|
944
|
+
const filters: MetadataFilter[] = [];
|
|
945
|
+
const tags: string[] = [];
|
|
946
|
+
const repositoryScopes: string[] = [];
|
|
947
|
+
|
|
948
|
+
for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
|
|
949
|
+
const argument = arguments_[cursor];
|
|
950
|
+
if (argument === undefined) continue;
|
|
951
|
+
if (argument === "--json") { json = true; continue; }
|
|
952
|
+
if (argument === "--noindex") { noindex = true; continue; }
|
|
953
|
+
if (argument === "--no-index-content") { indexContent = false; continue; }
|
|
954
|
+
if (argument === "--deterministic") { deterministic = true; continue; }
|
|
955
|
+
if (argument === "--dry-run") { dryRun = true; continue; }
|
|
956
|
+
if (argument === "--force") { force = true; continue; }
|
|
957
|
+
if (
|
|
958
|
+
argument === "--root" || argument === "--out" || argument === "--index"
|
|
959
|
+
|| argument === "--title" || argument === "--description"
|
|
960
|
+
|| argument === "--base-path" || argument === "--base-url"
|
|
961
|
+
|| argument === "--include" || argument === "--exclude"
|
|
962
|
+
|| argument === "--include-glob" || argument === "--exclude-glob"
|
|
963
|
+
|| argument === "--list-limit"
|
|
964
|
+
|| argument === "--where" || argument === "--has" || argument === "--tag"
|
|
965
|
+
|| argument === "--scope" || argument === "--repository-scope"
|
|
966
|
+
|| argument === "--from" || argument === "--depth" || argument === "--direction"
|
|
967
|
+
) {
|
|
968
|
+
const value = readValue(arguments_, cursor);
|
|
969
|
+
if (value === null) return { ok: false, message: `${argument} requires a value` };
|
|
970
|
+
if (argument === "--root") root = value;
|
|
971
|
+
else if (argument === "--out") out = value;
|
|
972
|
+
else if (argument === "--index") index = value;
|
|
973
|
+
else if (argument === "--title") title = value;
|
|
974
|
+
else if (argument === "--description") description = value;
|
|
975
|
+
else if (argument === "--base-path") basePath = value;
|
|
976
|
+
else if (argument === "--base-url") baseUrl = value;
|
|
977
|
+
else if (argument === "--include") includes.push(value);
|
|
978
|
+
else if (argument === "--exclude") excludes.push(value);
|
|
979
|
+
else if (argument === "--include-glob") includeGlobs.push(value);
|
|
980
|
+
else if (argument === "--exclude-glob") excludeGlobs.push(value);
|
|
981
|
+
else if (argument === "--list-limit") {
|
|
982
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(value) || Number(value) > MAX_PUBLISH_LIST_LIMIT) {
|
|
983
|
+
return { ok: false, message: `--list-limit must be an integer from 0 through ${MAX_PUBLISH_LIST_LIMIT}` };
|
|
984
|
+
}
|
|
985
|
+
listLimit = Number(value);
|
|
986
|
+
}
|
|
987
|
+
else if (argument === "--from") from = value;
|
|
988
|
+
else if (argument === "--depth") {
|
|
989
|
+
const parsed = Number(value);
|
|
990
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 10) {
|
|
991
|
+
return { ok: false, message: "--depth must be an integer from 1 through 10" };
|
|
992
|
+
}
|
|
993
|
+
depth = parsed;
|
|
994
|
+
} else if (argument === "--direction") {
|
|
995
|
+
if (value !== "in" && value !== "out" && value !== "both") {
|
|
996
|
+
return { ok: false, message: "--direction must be in, out, or both" };
|
|
997
|
+
}
|
|
998
|
+
direction = value;
|
|
999
|
+
} else if (argument === "--tag") {
|
|
1000
|
+
if (tags.length >= MAX_QUERY_TAGS) {
|
|
1001
|
+
return { ok: false, message: `Query tags may contain at most ${MAX_QUERY_TAGS} entries.` };
|
|
1002
|
+
}
|
|
1003
|
+
tags.push(value);
|
|
1004
|
+
} else if (argument === "--scope" || argument === "--repository-scope") {
|
|
1005
|
+
if (repositoryScopes.length >= MAX_REPOSITORY_SCOPES) {
|
|
1006
|
+
return { ok: false, message: `Repository scope filters may contain at most ${MAX_REPOSITORY_SCOPES} entries.` };
|
|
1007
|
+
}
|
|
1008
|
+
repositoryScopes.push(value);
|
|
1009
|
+
} else if (argument === "--has") {
|
|
1010
|
+
if (value.trim() === "") return { ok: false, message: "--has requires a metadata path" };
|
|
1011
|
+
if (filters.length >= MAX_QUERY_FILTERS) {
|
|
1012
|
+
return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
|
|
1013
|
+
}
|
|
1014
|
+
filters.push({ kind: "exists", path: value });
|
|
1015
|
+
} else {
|
|
1016
|
+
const equals = value.indexOf("=");
|
|
1017
|
+
const path = equals === -1 ? "" : value.slice(0, equals).trim();
|
|
1018
|
+
if (path === "") return { ok: false, message: "--where requires path=value" };
|
|
1019
|
+
const scalar = metadataScalar(value.slice(equals + 1));
|
|
1020
|
+
if (!scalar.ok) return scalar;
|
|
1021
|
+
if (filters.length >= MAX_QUERY_FILTERS) {
|
|
1022
|
+
return { ok: false, message: `Query filters may contain at most ${MAX_QUERY_FILTERS} entries.` };
|
|
1023
|
+
}
|
|
1024
|
+
filters.push({ kind: "equals", path, value: scalar.value });
|
|
1025
|
+
}
|
|
1026
|
+
cursor += 1;
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
ok: false,
|
|
1031
|
+
message: argument.startsWith("--")
|
|
1032
|
+
? "unknown publish option"
|
|
1033
|
+
: "publish does not accept positional arguments",
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
if (out === undefined) return { ok: false, message: "publish requires --out <directory>" };
|
|
1038
|
+
try {
|
|
1039
|
+
validateQueryOptions({ filters, tags, repositoryScopes });
|
|
1040
|
+
} catch (error: unknown) {
|
|
1041
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
1042
|
+
}
|
|
1043
|
+
const selection: PublishSelectionInput = {
|
|
1044
|
+
includes,
|
|
1045
|
+
excludes,
|
|
1046
|
+
includeGlobs,
|
|
1047
|
+
excludeGlobs,
|
|
1048
|
+
filters,
|
|
1049
|
+
tags,
|
|
1050
|
+
repositoryScopes,
|
|
1051
|
+
...(from === undefined ? {} : { from: { note: from, depth, direction } }),
|
|
1052
|
+
};
|
|
1053
|
+
return {
|
|
1054
|
+
ok: true,
|
|
1055
|
+
value: {
|
|
1056
|
+
kind: "publish",
|
|
1057
|
+
root,
|
|
1058
|
+
out,
|
|
1059
|
+
noindex,
|
|
1060
|
+
indexContent,
|
|
1061
|
+
deterministic,
|
|
1062
|
+
dryRun,
|
|
1063
|
+
listLimit,
|
|
1064
|
+
force,
|
|
1065
|
+
selection,
|
|
1066
|
+
json,
|
|
1067
|
+
...(index === undefined ? {} : { index }),
|
|
1068
|
+
...(title === undefined ? {} : { title }),
|
|
1069
|
+
...(description === undefined ? {} : { description }),
|
|
1070
|
+
...(basePath === undefined ? {} : { basePath }),
|
|
1071
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
1072
|
+
},
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function parseServeCommand(arguments_: readonly string[]): ParseResult {
|
|
1077
|
+
let root: string | undefined;
|
|
1078
|
+
let host = "127.0.0.1";
|
|
1079
|
+
let port = 8080;
|
|
1080
|
+
let json = false;
|
|
1081
|
+
for (let cursor = 0; cursor < arguments_.length; cursor += 1) {
|
|
1082
|
+
const argument = arguments_[cursor];
|
|
1083
|
+
if (argument === undefined) continue;
|
|
1084
|
+
if (argument === "--json") { json = true; continue; }
|
|
1085
|
+
if (argument === "--root" || argument === "--host" || argument === "--port") {
|
|
1086
|
+
const value = readValue(arguments_, cursor);
|
|
1087
|
+
if (value === null) return { ok: false, message: `${argument} requires a value` };
|
|
1088
|
+
if (argument === "--root") root = value;
|
|
1089
|
+
else if (argument === "--host") host = value;
|
|
1090
|
+
else {
|
|
1091
|
+
const parsed = Number(value);
|
|
1092
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0 || parsed > 65_535) {
|
|
1093
|
+
return { ok: false, message: "--port must be an integer from 0 through 65535" };
|
|
1094
|
+
}
|
|
1095
|
+
port = parsed;
|
|
1096
|
+
}
|
|
1097
|
+
cursor += 1;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
ok: false,
|
|
1102
|
+
message: argument.startsWith("--")
|
|
1103
|
+
? "unknown serve option"
|
|
1104
|
+
: "serve does not accept positional arguments",
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
if (root === undefined) return { ok: false, message: "serve requires --root <directory>" };
|
|
1108
|
+
return { ok: true, value: { kind: "serve", root, host, port, json } };
|
|
1109
|
+
}
|
|
1110
|
+
|
|
869
1111
|
function parseInboxCommand(arguments_: readonly string[]): ParseResult {
|
|
870
1112
|
let root = ".";
|
|
871
1113
|
let index: string | undefined;
|
|
@@ -942,6 +1184,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
|
|
|
942
1184
|
let limit: number | undefined;
|
|
943
1185
|
let candidateLimit: number | undefined;
|
|
944
1186
|
let minScore: number | undefined;
|
|
1187
|
+
let rerank: "typesafe" | undefined;
|
|
1188
|
+
let rerankLimit: number | undefined;
|
|
945
1189
|
let graphDepth: number | undefined;
|
|
946
1190
|
let noGraph = false;
|
|
947
1191
|
let history = false;
|
|
@@ -1003,6 +1247,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
|
|
|
1003
1247
|
|| argument === "--limit"
|
|
1004
1248
|
|| argument === "--candidate-limit"
|
|
1005
1249
|
|| argument === "--min-score"
|
|
1250
|
+
|| argument === "--rerank"
|
|
1251
|
+
|| argument === "--rerank-limit"
|
|
1006
1252
|
|| argument === "--where"
|
|
1007
1253
|
|| argument === "--has"
|
|
1008
1254
|
|| argument === "--tag"
|
|
@@ -1018,6 +1264,17 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
|
|
|
1018
1264
|
return { ok: false, message: "--mode must be hybrid, exact, keyword, or semantic" };
|
|
1019
1265
|
}
|
|
1020
1266
|
mode = value;
|
|
1267
|
+
} else if (argument === "--rerank") {
|
|
1268
|
+
if (value !== "typesafe") {
|
|
1269
|
+
return { ok: false, message: "--rerank must be typesafe" };
|
|
1270
|
+
}
|
|
1271
|
+
rerank = value;
|
|
1272
|
+
} else if (argument === "--rerank-limit") {
|
|
1273
|
+
const parsed = Number(value);
|
|
1274
|
+
if (!Number.isSafeInteger(parsed) || parsed < 2 || parsed > MAX_RERANK_CANDIDATES) {
|
|
1275
|
+
return { ok: false, message: `--rerank-limit must be an integer from 2 through ${MAX_RERANK_CANDIDATES}` };
|
|
1276
|
+
}
|
|
1277
|
+
rerankLimit = parsed;
|
|
1021
1278
|
} else if (argument === "--where") {
|
|
1022
1279
|
const equals = value.indexOf("=");
|
|
1023
1280
|
const path = equals === -1 ? "" : value.slice(0, equals).trim();
|
|
@@ -1147,6 +1404,9 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
|
|
|
1147
1404
|
message: "Search minimum score applies only to hybrid, keyword, or semantic mode.",
|
|
1148
1405
|
};
|
|
1149
1406
|
}
|
|
1407
|
+
if (rerankLimit !== undefined && rerank === undefined) {
|
|
1408
|
+
return { ok: false, message: "--rerank-limit requires --rerank typesafe" };
|
|
1409
|
+
}
|
|
1150
1410
|
if (priority && rulesPath === undefined) {
|
|
1151
1411
|
return { ok: false, message: "--priority requires --rules" };
|
|
1152
1412
|
}
|
|
@@ -1185,6 +1445,8 @@ function parseSemanticCommand(command: "index" | "search", arguments_: readonly
|
|
|
1185
1445
|
...(limit === undefined ? {} : { limit }),
|
|
1186
1446
|
...(candidateLimit === undefined ? {} : { candidateLimit }),
|
|
1187
1447
|
...(minScore === undefined ? {} : { minScore }),
|
|
1448
|
+
...(rerank === undefined ? {} : { rerank }),
|
|
1449
|
+
...(rerankLimit === undefined ? {} : { rerankLimit }),
|
|
1188
1450
|
query,
|
|
1189
1451
|
json,
|
|
1190
1452
|
},
|
|
@@ -1938,6 +2200,8 @@ export function parseArguments(arguments_: readonly string[]): ParseResult {
|
|
|
1938
2200
|
if (command === "note") return parseNoteCommand(arguments_.slice(1));
|
|
1939
2201
|
if (command === "relation") return parseRelationCommand(arguments_.slice(1));
|
|
1940
2202
|
if (command === "percolate") return parsePercolateCommand(arguments_.slice(1));
|
|
2203
|
+
if (command === "publish") return parsePublishCommand(arguments_.slice(1));
|
|
2204
|
+
if (command === "serve") return parseServeCommand(arguments_.slice(1));
|
|
1941
2205
|
return { ok: false, message: "unknown command" };
|
|
1942
2206
|
}
|
|
1943
2207
|
|
|
@@ -1960,10 +2224,28 @@ function renderKnowledgeBaseSearch(result: KnowledgeBaseSearchResult): string {
|
|
|
1960
2224
|
const lines = [
|
|
1961
2225
|
`${result.mode[0]?.toLocaleUpperCase("en-US") ?? ""}${result.mode.slice(1)} results for “${safe(result.query)}” (${result.results.length})${result.partial ? " [partial]" : ""}`,
|
|
1962
2226
|
];
|
|
2227
|
+
const rerankLane = result.diagnostics.lanes.find(({ lane }) => lane === "rerank");
|
|
2228
|
+
if (rerankLane !== undefined) {
|
|
2229
|
+
lines.push(
|
|
2230
|
+
` Rerank: typesafe over ${rerankLane.results} candidates (${rerankLane.status})`
|
|
2231
|
+
+ (rerankLane.message === undefined ? "" : ` — ${safe(rerankLane.message)}`),
|
|
2232
|
+
);
|
|
2233
|
+
const receipt = rerankLane.rerank;
|
|
2234
|
+
if (receipt?.accounting !== undefined) {
|
|
2235
|
+
const { attempted, candidates, completed, elapsedMs, usageComplete } = receipt.accounting;
|
|
2236
|
+
lines.push(` Rerank requests: ${attempted}/${candidates} attempted, ${completed} settled; ${elapsedMs.toFixed(0)} ms`
|
|
2237
|
+
+ (usageComplete ? "; usage complete" : "; usage incomplete, additional charges may be unknown"));
|
|
2238
|
+
if (rerankLane.status !== "ready" && receipt.usage !== undefined) {
|
|
2239
|
+
lines.push(` Known rerank usage: ${receipt.usage.inputTokens ?? 0} input tokens, ${receipt.usage.outputTokens ?? 0} output tokens`);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
1963
2243
|
if (result.results.length === 0) lines.push(" None.");
|
|
1964
2244
|
for (const hit of result.results) {
|
|
1965
2245
|
const location = `${safe(hit.path)}${hit.line === undefined ? "" : `:${hit.line}`}`;
|
|
1966
|
-
const evidence = hit.evidence
|
|
2246
|
+
const evidence = hit.evidence
|
|
2247
|
+
.map((item) => `${item.kind}#${searchEvidenceRank(item)}`)
|
|
2248
|
+
.join(", ");
|
|
1967
2249
|
lines.push(` ${hit.rank}. ${hit.score.toFixed(3)} ${location} — ${safe(hit.title)} [${safe(evidence)}]`);
|
|
1968
2250
|
if (hit.snippet !== "") lines.push(` ${safe(hit.snippet)}`);
|
|
1969
2251
|
}
|
|
@@ -1999,12 +2281,17 @@ async function runSemantic(
|
|
|
1999
2281
|
const searchRules = command.rulesPath === undefined
|
|
2000
2282
|
? undefined
|
|
2001
2283
|
: await loadSearchRulesFile(command.rulesPath);
|
|
2002
|
-
const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)(
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2284
|
+
const kb = await (dependencies.openKnowledgeBase ?? openKnowledgeBase)(
|
|
2285
|
+
{
|
|
2286
|
+
root: command.root,
|
|
2287
|
+
repository: command.repository,
|
|
2288
|
+
...(command.database === undefined ? {} : { database: command.database }),
|
|
2289
|
+
...(searchRules === undefined ? {} : { searchRules }),
|
|
2290
|
+
},
|
|
2291
|
+
...(command.rerank === undefined
|
|
2292
|
+
? []
|
|
2293
|
+
: [{ rerankers: dependencies.rerankers ?? [await createCliTypeSafeReranker()] }]),
|
|
2294
|
+
);
|
|
2008
2295
|
try {
|
|
2009
2296
|
const result = await kb.search({
|
|
2010
2297
|
query: command.query,
|
|
@@ -2020,6 +2307,12 @@ async function runSemantic(
|
|
|
2020
2307
|
? {}
|
|
2021
2308
|
: { candidateLimit: command.candidateLimit }),
|
|
2022
2309
|
...(command.minScore === undefined ? {} : { minScore: command.minScore }),
|
|
2310
|
+
...(command.rerank === undefined ? {} : {
|
|
2311
|
+
rerank: {
|
|
2312
|
+
engine: command.rerank,
|
|
2313
|
+
...(command.rerankLimit === undefined ? {} : { limit: command.rerankLimit }),
|
|
2314
|
+
},
|
|
2315
|
+
}),
|
|
2023
2316
|
});
|
|
2024
2317
|
output.stdout(command.json
|
|
2025
2318
|
? terminalSafeJson(result)
|
|
@@ -2834,6 +3127,49 @@ async function runList(
|
|
|
2834
3127
|
return 0;
|
|
2835
3128
|
}
|
|
2836
3129
|
|
|
3130
|
+
async function runPublish(
|
|
3131
|
+
command: Extract<ParsedCommand, { readonly kind: "publish" }>,
|
|
3132
|
+
output: Output,
|
|
3133
|
+
dependencies: CliDependencies,
|
|
3134
|
+
): Promise<number> {
|
|
3135
|
+
const result = await (dependencies.publishVault ?? publishVault)({
|
|
3136
|
+
root: command.root,
|
|
3137
|
+
out: command.out,
|
|
3138
|
+
noindex: command.noindex,
|
|
3139
|
+
indexContent: command.indexContent,
|
|
3140
|
+
deterministic: command.deterministic,
|
|
3141
|
+
dryRun: command.dryRun,
|
|
3142
|
+
listLimit: command.listLimit,
|
|
3143
|
+
force: command.force,
|
|
3144
|
+
selection: command.selection,
|
|
3145
|
+
...(command.index === undefined ? {} : { index: command.index }),
|
|
3146
|
+
...(command.title === undefined ? {} : { title: command.title }),
|
|
3147
|
+
...(command.description === undefined ? {} : { description: command.description }),
|
|
3148
|
+
...(command.basePath === undefined ? {} : { basePath: command.basePath }),
|
|
3149
|
+
...(command.baseUrl === undefined ? {} : { baseUrl: command.baseUrl }),
|
|
3150
|
+
});
|
|
3151
|
+
output.stdout(command.json
|
|
3152
|
+
? terminalSafeJson(result.report)
|
|
3153
|
+
: sanitizeTerminalText(renderPublishReportText(result.report, command.dryRun)));
|
|
3154
|
+
return 0;
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
async function runServe(
|
|
3158
|
+
command: Extract<ParsedCommand, { readonly kind: "serve" }>,
|
|
3159
|
+
output: Output,
|
|
3160
|
+
dependencies: CliDependencies,
|
|
3161
|
+
): Promise<number> {
|
|
3162
|
+
const site = await (dependencies.serveSite ?? serveSite)({
|
|
3163
|
+
root: command.root,
|
|
3164
|
+
host: command.host,
|
|
3165
|
+
port: command.port,
|
|
3166
|
+
});
|
|
3167
|
+
output.stdout(command.json
|
|
3168
|
+
? terminalSafeJson({ root: site.root, host: site.host, port: site.port, url: site.url })
|
|
3169
|
+
: `Serving ${safe(site.root)} at ${safe(site.url)}\n`);
|
|
3170
|
+
return 0;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
2837
3173
|
async function runCatalog(
|
|
2838
3174
|
command: Extract<ParsedCommand, { readonly kind: "catalog" }>,
|
|
2839
3175
|
output: Output,
|
|
@@ -3580,6 +3916,8 @@ export async function main(
|
|
|
3580
3916
|
}
|
|
3581
3917
|
if (command.kind === "percolate") return await runPercolate(command, output, dependencies);
|
|
3582
3918
|
if (command.kind === "list") return await runList(command, output, dependencies);
|
|
3919
|
+
if (command.kind === "publish") return await runPublish(command, output, dependencies);
|
|
3920
|
+
if (command.kind === "serve") return await runServe(command, output, dependencies);
|
|
3583
3921
|
if (command.kind === "inbox") return await runInbox(command, output, dependencies);
|
|
3584
3922
|
if (command.kind === "catalog") return await runCatalog(command, output, dependencies);
|
|
3585
3923
|
return await runVault(command, output, dependencies);
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,11 @@ export * from "./init.js";
|
|
|
11
11
|
export * from "./navigation.js";
|
|
12
12
|
export * from "./oh-adoption.js";
|
|
13
13
|
export * from "./percolate.js";
|
|
14
|
+
export * from "./publish.js";
|
|
15
|
+
export * from "./publish-model.js";
|
|
14
16
|
export * from "./query.js";
|
|
17
|
+
export * from "./rerank.js";
|
|
18
|
+
export * from "./rerank-typesafe.js";
|
|
15
19
|
export * from "./repository-memory.js";
|
|
16
20
|
export * from "./search.js";
|
|
17
21
|
export * from "./semantic.js";
|