@hraness/wordcell 0.21.3 → 0.22.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.
- package/README.md +170 -64
- package/dist/authoring.js +5 -3
- package/dist/{authority-4sp9d552.js → authority-06eqhqam.js} +57 -12
- package/dist/benchmark.js +4 -4
- package/dist/capture.js +6 -5
- package/dist/cli.js +39 -33
- 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 +20 -19
- package/dist/evaluation-kb.js +16 -15
- package/dist/graph-authority.js +5 -5
- package/dist/graph-percolation.js +7 -7
- package/dist/graph.js +1 -1
- package/dist/{index-pbr6rv47.js → index-1k595z6v.js} +6 -6
- package/dist/{index-0k2x4nn9.js → index-233z9wmn.js} +17 -5
- package/dist/index-3agn8scn.js +306 -0
- package/dist/index-4knsp9qj.js +34 -0
- package/dist/index-66pshdtx.js +341 -0
- package/dist/{index-59ssmqqm.js → index-76dbn6pe.js} +1 -1
- package/dist/{index-0rfcqab6.js → index-810nfzdj.js} +617 -66
- package/dist/index-9rf81m0p.js +368 -0
- package/dist/{index-51bap0mq.js → index-bqtqeak3.js} +10 -10
- package/dist/{index-9zcpdbjg.js → index-de2w8crk.js} +13 -13
- package/dist/{index-bgfzwt4h.js → index-djrcc1yf.js} +6 -6
- package/dist/{index-6sw24nvv.js → index-ecqpgr5y.js} +53 -2
- package/dist/{index-van48n3g.js → index-fc4dr114.js} +4 -2
- package/dist/{index-jzcws0zn.js → index-g5vsqmdy.js} +27 -57
- package/dist/index-hmw17zaa.js +1985 -0
- package/dist/index-j70m75wd.js +217 -0
- package/dist/{index-nd6nynv2.js → index-jk1sgx36.js} +1 -1
- package/dist/{index-3ehtt59c.js → index-n386evxj.js} +1 -1
- package/dist/{index-vdw5yjfa.js → index-q7tbg07z.js} +110 -20
- package/dist/{index-ekpwvbra.js → index-qbssx940.js} +1 -2
- package/dist/{index-115b07ap.js → index-rr9kzq4n.js} +5 -5
- package/dist/{index-11621h23.js → index-tf4wzjew.js} +1 -1
- package/dist/{index-why54q5p.js → index-vnybgywh.js} +4 -4
- package/dist/{index-bcfn9xah.js → index-xv80vzfp.js} +7 -7
- package/dist/index.js +289 -95
- package/dist/percolate.js +2 -2
- package/dist/portfolio.js +16 -15
- 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 +16 -13
- package/dist/search.js +3 -3
- package/dist/semantic.js +5 -5
- package/dist/{support-vm4mkw0k.js → support-1rh2d7mt.js} +38 -32
- package/dist/workflows/decision-context.js +15 -14
- package/dist/workflows/index.js +15 -14
- package/package.json +65 -6
- 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 +127 -0
- package/skills/wordcell/references/query.md +22 -0
- package/skills/wordcell/references/refresh.md +14 -0
- package/src/authoring-model.ts +29 -0
- package/src/authoring-program.ts +38 -3
- package/src/authoring.ts +17 -3
- package/src/cli-program.ts +441 -12
- package/src/graph.ts +1 -1
- 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
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Wordcell"
|
|
3
3
|
short_description: "Set up, query, capture, and maintain Markdown memory"
|
|
4
|
-
default_prompt: "Use $wordcell to set up or work with this hraness/wordcell knowledge base, routing the request before any runtime preparation and loading only the references needed. Follow its optional support protocol once at an eligible human-facing task closeout."
|
|
4
|
+
default_prompt: "Use $wordcell to set up or work with this hraness/wordcell knowledge base, routing the request before any runtime preparation and loading only the references needed. Prefer the repository's approved pinned kb:search script for search and inspect rerank diagnostics when it enables hosted processing. Follow its optional support protocol once at an eligible human-facing task closeout."
|
|
@@ -0,0 +1,127 @@
|
|
|
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).
|
|
94
|
+
|
|
95
|
+
## Publish through wordcell.io when the CLI cannot run
|
|
96
|
+
|
|
97
|
+
Agents on platforms without a filesystem or Bun runtime can publish the same
|
|
98
|
+
`hraness.wordcell.site.v1` artifact through the hosted surface. It runs the
|
|
99
|
+
identical projection server-side; published sites are public.
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
# Self-serve capability token (IP-limited; shown once — store it).
|
|
103
|
+
TOKEN=$(curl -sf -X POST https://wordcell.io/api/v1/tokens \
|
|
104
|
+
-H 'content-type: application/json' -d '{"label":"my-agent"}' | jq -r .token)
|
|
105
|
+
|
|
106
|
+
# Publish a vault as a files map: path -> utf8 string, {"base64":"..."}, or
|
|
107
|
+
# {"upload":"<id>"} from POST /api/v1/uploads for larger assets.
|
|
108
|
+
curl -sf -X PUT "https://wordcell.io/api/v1/sites/my-notes" \
|
|
109
|
+
-H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
|
|
110
|
+
-d '{"files":{"index.md":"# Notes\n","a.md":"# A\n"},"title":"My notes"}'
|
|
111
|
+
# -> {"site":{"url":"https://wordcell.io/p/<key8>/my-notes/",...},"idempotent":false}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`GET /api/v1/sites` lists the token's sites and `DELETE /api/v1/sites/{slug}`
|
|
115
|
+
unpublishes. Identical bytes republish idempotently without a revision bump;
|
|
116
|
+
changed bytes sweep the superseded artifact. Deletes and republishes can take
|
|
117
|
+
up to 60 seconds to propagate at the edge. MCP clients can instead point at the
|
|
118
|
+
streamable-HTTP endpoint `POST https://wordcell.io/api/v1/mcp`, which exposes
|
|
119
|
+
`create_token`, `publish_site`, `list_sites`, and `delete_site` tools over the
|
|
120
|
+
same routes — `create_token` is unauthenticated so an MCP-only client can
|
|
121
|
+
onboard itself. `GET /api/v1/openapi.json` describes the REST surface.
|
|
122
|
+
|
|
123
|
+
The hosted surface carries the same selection semantics (`selection` mirrors
|
|
124
|
+
the CLI selectors) and the same redaction warning: `publish: false` excludes a
|
|
125
|
+
note, it does not erase text quoted elsewhere. Bounded inputs apply — at most
|
|
126
|
+
256 files and 4 MiB inline per publish, 32 MiB per uploaded asset, 50 live
|
|
127
|
+
sites per token.
|
|
@@ -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
|
|
@@ -126,6 +126,20 @@ After any note or link edit, run the refresh command again so derived state and
|
|
|
126
126
|
wordcell check --root "$KB_ROOT"
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
+
When the vault lives inside a repository and `KB_REPO` is set, add the advisory
|
|
130
|
+
repository-state pass:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
wordcell check --root "$KB_ROOT" --repo "$KB_REPO"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
This compares every authored `repository_scopes` declaration with the current
|
|
137
|
+
working tree and reports each current record whose declared path is now absent
|
|
138
|
+
or unusable. It is advisory: the exit code does not change, and a terminal plan
|
|
139
|
+
that names a removed path stays silent because that is valid authored history.
|
|
140
|
+
Treat an absent-scope advisory as a note to reread against the code, not as a
|
|
141
|
+
proven error; confirm the path in the repository before editing the note.
|
|
142
|
+
|
|
129
143
|
Finish only when the graph check and any required agent-context check succeed,
|
|
130
144
|
the configured catalog mode is satisfied, and broken or ambiguous links,
|
|
131
145
|
relationships, and local attachments are resolved. Summarize deliberate
|
package/src/authoring-model.ts
CHANGED
|
@@ -89,6 +89,11 @@ export interface AuthoringOptions {
|
|
|
89
89
|
readonly dependencies?: Partial<AuthoringDependencies>;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
export interface UpdateNoteBodyOptions extends AuthoringOptions {
|
|
93
|
+
/** Exact revision read before preparing the replacement body. */
|
|
94
|
+
readonly expectedRevision: NoteRevision;
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
export class InvalidCanonicalNoteIdError extends TypeError {
|
|
93
98
|
readonly noteId: string;
|
|
94
99
|
|
|
@@ -692,6 +697,30 @@ export function normalizedRequestedBody(body: string): string {
|
|
|
692
697
|
return body.endsWith("\n") ? body : `${body}\n`;
|
|
693
698
|
}
|
|
694
699
|
|
|
700
|
+
/** Replace prose without serializing or modifying the authored frontmatter. */
|
|
701
|
+
export function renderUpdatedNoteBody(
|
|
702
|
+
snapshot: Pick<NoteSnapshot, "content" | "relativePath">,
|
|
703
|
+
parts: FrontmatterParts,
|
|
704
|
+
body: string,
|
|
705
|
+
): string {
|
|
706
|
+
if (typeof body !== "string") throw new TypeError("a note body must be a string");
|
|
707
|
+
if (Buffer.byteLength(body, "utf8") > MAX_NOTE_BYTES) {
|
|
708
|
+
throw new RangeError("the note body is too large for bounded authoring");
|
|
709
|
+
}
|
|
710
|
+
if (Buffer.from(body, "utf8").toString("utf8") !== body) {
|
|
711
|
+
throw new TypeError("a note body must contain well-formed Unicode");
|
|
712
|
+
}
|
|
713
|
+
const normalized = normalizedRequestedBody(body);
|
|
714
|
+
if (!parts.hadFrontmatter) {
|
|
715
|
+
if (frontmatter(normalized, snapshot.relativePath).hadFrontmatter) {
|
|
716
|
+
throw new TypeError("a body update cannot introduce frontmatter into a note");
|
|
717
|
+
}
|
|
718
|
+
return normalized;
|
|
719
|
+
}
|
|
720
|
+
const header = snapshot.content.slice(0, snapshot.content.length - parts.bodySuffix.length);
|
|
721
|
+
return `${header}${parts.newline}${parts.newline}${normalized}`;
|
|
722
|
+
}
|
|
723
|
+
|
|
695
724
|
export function renderCreatedNote(input: CreateNoteInput, documentId: string): string {
|
|
696
725
|
const title = validateTitle(input.title);
|
|
697
726
|
const type = validateType(input.type);
|
package/src/authoring-program.ts
CHANGED
|
@@ -3,13 +3,13 @@ import { Effect, Exit } from "effect";
|
|
|
3
3
|
import {
|
|
4
4
|
MAX_NOTE_BYTES, NoteRevisionConflictError, addRelationToParts, assertCompatibleCreate,
|
|
5
5
|
assertExpected, canonicalNoteId, canonicalRelationTarget, checkedExpectedRevision,
|
|
6
|
-
frontmatter, isErrno, normalizeRelationPredicate, noteResult, pathFor, relationsFromParts,
|
|
7
|
-
removeRelationFromParts, renderCreatedNote, renderFrontmatter, revisionFor,
|
|
6
|
+
existingDocumentId, frontmatter, isErrno, normalizeRelationPredicate, noteResult, pathFor, relationsFromParts,
|
|
7
|
+
removeRelationFromParts, renderCreatedNote, renderFrontmatter, renderUpdatedNoteBody, requireRevision, revisionFor,
|
|
8
8
|
sameQuarantinedSnapshot, sameSnapshot, withRecoveryPath,
|
|
9
9
|
} from "./authoring-model.js";
|
|
10
10
|
import type {
|
|
11
11
|
AuthoringDependencies, AuthoringInstallContext, AuthoringOptions, CreateNoteInput,
|
|
12
|
-
DirectoryIdentity, NoteAuthoringResult, NoteRevision, NoteSnapshot, RecoveryLocation, Vault,
|
|
12
|
+
DirectoryIdentity, NoteAuthoringResult, NoteRevision, NoteSnapshot, RecoveryLocation, UpdateNoteBodyOptions, Vault,
|
|
13
13
|
} from "./authoring-model.js";
|
|
14
14
|
import type { NoteLock } from "./note-lock.js";
|
|
15
15
|
import { parseDocumentId } from "./portfolio-identity.js";
|
|
@@ -276,3 +276,38 @@ export function editNoteRelationProgram(
|
|
|
276
276
|
);
|
|
277
277
|
});
|
|
278
278
|
}
|
|
279
|
+
|
|
280
|
+
export function updateNoteBodyProgram(
|
|
281
|
+
platform: AuthoringPlatform, root: string, idInput: string, body: string,
|
|
282
|
+
options: UpdateNoteBodyOptions,
|
|
283
|
+
): Effect.Effect<NoteAuthoringResult, AuthoringFailure> {
|
|
284
|
+
return Effect.gen(function*() {
|
|
285
|
+
const id = yield* authoringSync(() => canonicalNoteId(idInput));
|
|
286
|
+
const expected = yield* authoringSync(() => {
|
|
287
|
+
if (typeof options?.expectedRevision !== "string") {
|
|
288
|
+
throw new TypeError("expectedRevision is required for a note body update");
|
|
289
|
+
}
|
|
290
|
+
return requireRevision(options.expectedRevision);
|
|
291
|
+
});
|
|
292
|
+
const vault = yield* authoringNative(() => platform.resolveVault(root));
|
|
293
|
+
const dependencies = yield* authoringSync(() => platform.dependenciesFor(options.dependencies));
|
|
294
|
+
return yield* authoringResource(
|
|
295
|
+
authoringNative(() => platform.acquireNoteLock(vault.root, id, options.lock)),
|
|
296
|
+
(lock) => Effect.gen(function*() {
|
|
297
|
+
yield* authoringNative(() => platform.recoverInterruptedAuthoring(vault, id, lock));
|
|
298
|
+
const source = yield* authoringNative(() => platform.readSnapshot(vault, id));
|
|
299
|
+
yield* authoringSync(() => assertExpected(source, expected));
|
|
300
|
+
const parts = yield* authoringSync(() => frontmatter(source.content, source.relativePath));
|
|
301
|
+
const identity = yield* authoringSync(() => existingDocumentId(parts));
|
|
302
|
+
if (identity.kind === "invalid") return yield* fail(new TypeError("the note has an invalid document_id"));
|
|
303
|
+
const documentId = identity.kind === "valid" ? identity.documentId : undefined;
|
|
304
|
+
const relations = yield* authoringSync(() => relationsFromParts(parts, source.relativePath));
|
|
305
|
+
const content = yield* authoringSync(() => renderUpdatedNoteBody(source, parts, body));
|
|
306
|
+
if (content === source.content) return yield* authoringSync(() => noteResult(source, relations, false, documentId));
|
|
307
|
+
const revision = yield* installNote(platform, vault, id, content, source, lock, dependencies);
|
|
308
|
+
return yield* authoringSync(() => noteResult({ relativePath: source.relativePath, revision }, relations, true, documentId));
|
|
309
|
+
}),
|
|
310
|
+
(lock) => authoringNative(() => lock.release()),
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
}
|
package/src/authoring.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AuthoringOptions, CreateConceptNoteInput, CreateNoteInput, NoteAuthoringResult,
|
|
3
|
-
NoteRelation, NoteRevision,
|
|
3
|
+
NoteRelation, NoteRevision, UpdateNoteBodyOptions,
|
|
4
4
|
} from "./authoring-model.js";
|
|
5
5
|
import { canonicalNoteId, frontmatter, relationsFromParts } from "./authoring-model.js";
|
|
6
6
|
import {
|
|
7
7
|
assertNoInterruptedRecovery, nativeAuthoringPlatform, readSnapshot, resolveVault,
|
|
8
8
|
} from "./authoring-platform.js";
|
|
9
|
-
import { createNoteProgram, editNoteRelationProgram } from "./authoring-program.js";
|
|
9
|
+
import { createNoteProgram, editNoteRelationProgram, updateNoteBodyProgram } from "./authoring-program.js";
|
|
10
10
|
import { runAuthoring } from "./authoring-runtime.js";
|
|
11
11
|
|
|
12
12
|
export {
|
|
@@ -15,7 +15,7 @@ export {
|
|
|
15
15
|
} from "./authoring-model.js";
|
|
16
16
|
export type {
|
|
17
17
|
AuthoringDependencies, AuthoringInstallContext, AuthoringOptions,
|
|
18
|
-
CreateConceptNoteInput, CreateNoteInput, NoteAuthoringResult, NoteRelation, NoteRevision,
|
|
18
|
+
CreateConceptNoteInput, CreateNoteInput, NoteAuthoringResult, NoteRelation, NoteRevision, UpdateNoteBodyOptions,
|
|
19
19
|
} from "./authoring-model.js";
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -60,6 +60,20 @@ export async function createConceptNote(
|
|
|
60
60
|
return createNote(root, { ...input, type: "concept" }, options);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Replace one existing note's body at an exact revision. Frontmatter bytes,
|
|
65
|
+
* stable identity and relations remain unchanged. Like createNote, this adds a
|
|
66
|
+
* final newline and separates frontmatter from the body with one blank line.
|
|
67
|
+
*
|
|
68
|
+
* A stale revision fails even if the requested body already matches. Callers
|
|
69
|
+
* must read back an interrupted operation before deciding whether to retry.
|
|
70
|
+
*/
|
|
71
|
+
export async function updateNoteBody(
|
|
72
|
+
root: string, id: string, body: string, options: UpdateNoteBodyOptions,
|
|
73
|
+
): Promise<NoteAuthoringResult> {
|
|
74
|
+
return runAuthoring(updateNoteBodyProgram(nativeAuthoringPlatform, root, id, body, options));
|
|
75
|
+
}
|
|
76
|
+
|
|
63
77
|
/** Add one exact outbound typed relation, idempotently. */
|
|
64
78
|
export async function addNoteRelation(
|
|
65
79
|
root: string, sourceId: string, predicate: string, targetId: string, options: AuthoringOptions = {},
|